Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 28 Aug 2014 16:11:04 +0200
changeset 202190 f6beabe7cfb60c8a405199cff263b5e7ecd5cd93
parent 202189 12a5e1b45d1cade707483ac8a7574c26fedd0bdc (current diff)
parent 202127 47c9418fbc28608e6b9b2c0b1f1664b183bd5b30 (diff)
child 202191 8883019fca40023d4f5d6941491d03f3e339dd10
push idunknown
push userunknown
push dateunknown
milestone34.0a1
Merge mozilla-central to fx-team
content/media/test/can_play_type_mpeg.js
dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl
dom/mobilemessage/src/SmsFilter.cpp
dom/mobilemessage/src/SmsFilter.h
dom/mobilemessage/tests/mochitest/test_smsfilter.html
security/nss/lib/certhigh/certvfypkixprint.c
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1435,18 +1435,25 @@ HyperTextAccessible::SelectionBoundsAt(i
     nsINode* tempNode = startNode;
     startNode = endNode;
     endNode = tempNode;
     int32_t tempOffset = startOffset;
     startOffset = endOffset;
     endOffset = tempOffset;
   }
 
-  *aStartOffset = DOMPointToOffset(startNode, startOffset);
-  *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
+  if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
+    *aStartOffset = 0;
+  else
+    *aStartOffset = DOMPointToOffset(startNode, startOffset);
+
+  if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
+    *aEndOffset = CharacterCount();
+  else
+    *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
   return true;
 }
 
 bool
 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -115,32 +115,34 @@ this.ContentControl.prototype = {
         if (action === 'moveNext') {
           childAction = 'moveFirst';
         } else if (action === 'movePrevious') {
           childAction = 'moveLast';
         }
 
         // Attempt to forward move to a potential child cursor in our
         // new position.
-        this.sendToChild(vc, aMessage, { action: childAction});
+        this.sendToChild(vc, aMessage, { action: childAction }, true);
       }
     } else if (!this._childMessageSenders.has(aMessage.target)) {
       // We failed to move, and the message is not from a child, so forward
       // to parent.
       this.sendToParent(aMessage);
     }
   },
 
   handleEvent: function cc_handleEvent(aEvent) {
     if (aEvent.type === 'mousemove') {
       this.handleMoveToPoint(
         { json: { x: aEvent.screenX, y: aEvent.screenY, rule: 'Simple' } });
     }
     if (!Utils.getMessageManager(aEvent.target)) {
       aEvent.preventDefault();
+    } else {
+      aEvent.target.focus();
     }
   },
 
   handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
     let [x, y] = [aMessage.json.x, aMessage.json.y];
     let rule = TraversalRules[aMessage.json.rule];
 
     let dpr = this.window.devicePixelRatio;
@@ -148,16 +150,17 @@ this.ContentControl.prototype = {
   },
 
   handleClearCursor: function cc_handleClearCursor(aMessage) {
     let forwarded = this.sendToChild(this.vc, aMessage);
     this.vc.position = null;
     if (!forwarded) {
       this._contentScope.get().sendAsyncMessage('AccessFu:CursorCleared');
     }
+    this.document.activeElement.blur();
   },
 
   handleAutoMove: function cc_handleAutoMove(aMessage) {
     this.autoMove(null, aMessage.json);
   },
 
   handleActivate: function cc_handleActivate(aMessage) {
     let activateAccessible = (aAccessible) => {
@@ -243,17 +246,17 @@ this.ContentControl.prototype = {
           return activatable;
         }
       }
 
       return null;
     };
 
     let vc = this.vc;
-    if (!this.sendToChild(vc, aMessage)) {
+    if (!this.sendToChild(vc, aMessage, null, true)) {
       let position = vc.position;
       activateAccessible(getActivatableDescendant(position) || position);
     }
   },
 
   handleMoveByGranularity: function cc_handleMoveByGranularity(aMessage) {
     // XXX: Add sendToChild. Right now this is only used in Android, so no need.
     let direction = aMessage.json.direction;
@@ -342,22 +345,28 @@ this.ContentControl.prototype = {
       }
 
       return mm;
     }
 
     return null;
   },
 
-  sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer) {
-    let mm = this.getChildCursor(aVirtualCursor.position);
+  sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer,
+                                       aFocus) {
+    let position = aVirtualCursor.position;
+    let mm = this.getChildCursor(position);
     if (!mm) {
       return false;
     }
 
+    if (aFocus) {
+      position.takeFocus();
+    }
+
     // XXX: This is a silly way to make a deep copy
     let newJSON = JSON.parse(JSON.stringify(aMessage.json));
     newJSON.origin = 'parent';
     for (let attr in aReplacer) {
       newJSON[attr] = aReplacer[attr];
     }
 
     mm.sendAsyncMessage(aMessage.name, newJSON);
@@ -427,17 +436,17 @@ this.ContentControl.prototype = {
       let sentToChild = this.sendToChild(vc, {
         name: 'AccessFu:AutoMove',
         json: {
           moveMethod: aOptions.moveMethod,
           moveToFocused: aOptions.moveToFocused,
           noOpIfOnScreen: true,
           forcePresent: true
         }
-      });
+      }, null, true);
 
       if (!moved && !sentToChild) {
         forcePresentFunc();
       }
     };
 
     if (aOptions.delay) {
       this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -151,17 +151,18 @@ this.EventManager.prototype = {
         let position = pivot.position;
         if (position && position.role == Roles.INTERNAL_FRAME)
           break;
         let event = aEvent.
           QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
         let reason = event.reason;
         let oldAccessible = event.oldAccessible;
 
-        if (this.editState.editing) {
+        if (this.editState.editing &&
+            !Utils.getState(position).contains(States.FOCUSED)) {
           aEvent.accessibleDocument.takeFocus();
         }
         this.present(
           Presentation.pivotChanged(position, oldAccessible, reason,
                                     pivot.startOffset, pivot.endOffset,
                                     aEvent.isFromUserInput));
 
         break;
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -298,16 +298,22 @@ AccessFuContentTest.prototype = {
       }
 
       if (expected.android) {
         var checkFunc = SimpleTest[expected.android_checkFunc] || ok;
         checkFunc.apply(SimpleTest,
           this.lazyCompare(android, expected.android));
       }
 
+      if (expected.focused) {
+        var doc = currentTabDocument();
+        is(doc.activeElement, doc.querySelector(expected.focused),
+          'Correct element is focused');
+      }
+
       this.pump();
     }
 
   },
 
   lazyCompare: function lazyCompare(aReceived, aExpected) {
     var matches = true;
     var delta = [];
--- a/accessible/tests/mochitest/jsat/test_content_integration.html
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -25,20 +25,22 @@
       var doc = currentTabDocument();
       var iframe = doc.createElement('iframe');
       iframe.mozbrowser = true;
       iframe.addEventListener('mozbrowserloadend', function () {
       var contentTest = new AccessFuContentTest(
         [
           // Simple traversal forward
           [ContentMessages.simpleMoveNext, {
-            speak: ['Phone status bar', 'Traversal Rule test document']
+            speak: ['Phone status bar', 'Traversal Rule test document'],
+            focused: 'body'
           }],
           [ContentMessages.simpleMoveNext, {
-            speak: ['wow', {'string': 'headingLevel', 'args': [1]} ,'such app']
+            speak: ['wow', {'string': 'headingLevel', 'args': [1]} ,'such app'],
+            focused: 'iframe'
           }],
           [ContentMessages.simpleMoveNext, {
             speak: ['many option', {'string': 'stateNotChecked'},
               {'string': 'checkbutton'}, {'string': 'listStart'},
               {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}]
           }],
           // check checkbox
           [ContentMessages.activateCurrent(), {
--- a/accessible/tests/mochitest/textselection/test_general.html
+++ b/accessible/tests/mochitest/textselection/test_general.html
@@ -106,17 +106,17 @@
       this.getID = function removeSelection_getID()
       {
         return "nsIAccessibleText::removeSelection test for " + aID;
       }
     }
 
     function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
                                 aNodeID2, aNodeOffset2,
-                                aStartOffset, aEndOffset)
+                                aTests)
     {
       this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
 
       this.eventSeq = [
         new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
       ];
 
       this.invoke = function changeDOMSelection_invoke()
@@ -125,25 +125,28 @@
         var range = document.createRange();
         range.setStart(getNode(aNodeID1), aNodeOffset1);
         range.setEnd(getNode(aNodeID2), aNodeOffset2);
         sel.addRange(range);
       }
 
       this.finalCheck = function changeDOMSelection_finalCheck()
       {
-        is(this.hyperText.selectionCount, 1,
-           "setSelectionBounds: Wrong selection count for " + aID);
-        var startOffset = {}, endOffset = {};
-        this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+        for (var i = 0; i < aTests.length; i++) {
+          var text = getAccessible(aTests[i][0], nsIAccessibleText);
+          is(text.selectionCount, 1,
+             "setSelectionBounds: Wrong selection count for " + aID);
+          var startOffset = {}, endOffset = {};
+          text.getSelectionBounds(0, startOffset, endOffset);
 
-        is(startOffset.value, aStartOffset,
-           "setSelectionBounds: Wrong start offset for " + aID);
-        is(endOffset.value, aEndOffset,
-           "setSelectionBounds: Wrong end offset for " + aID);
+          is(startOffset.value, aTests[i][1],
+             "setSelectionBounds: Wrong start offset for " + aID);
+          is(endOffset.value, aTests[i][2],
+             "setSelectionBounds: Wrong end offset for " + aID);
+        }
       }
 
       this.getID = function changeDOMSelection_getID()
       {
         return "DOM selection change for " + aID;
       }
     }
 
@@ -174,17 +177,20 @@
       gQueue.push(new removeSelection("paragraph"));
 
       gQueue.push(new synthFocus("textbox", onfocusEventSeq("textbox")));
       gQueue.push(new changeSelection("textbox", 1, 3));
 
       gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
       gQueue.push(new changeSelection("textarea", 1, 3));
 
-      gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0, 2, 2));
+      gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
+                                         [["c1", 2, 2]]));
+      gQueue.push(new changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
+                                         [["c2", 0, 3], ["c2_div2", 0, 2]]));
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -204,11 +210,12 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <p id="paragraph">hello</p>
   <input id="textbox" value="hello"/>
   <textarea id="textarea">hello</textarea>
   <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
+  <div id="c2">hi<div id="c2_div2">hi</div></div>
 
 </body>
 </html>
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -20,16 +20,17 @@
 #include <string.h>
 
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
 #include "nsStringGlue.h"
 
 #ifdef XP_WIN
 // we want a wmain entry point
+#define XRE_DONT_SUPPORT_XPSP2 // See https://bugzil.la/1023941#c32
 #include "nsWindowsWMain.cpp"
 #define snprintf _snprintf
 #define strcasecmp _stricmp
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkDisplay.h"
 #endif
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="53a59364ce4f14068034c8d6fe01f4f6b9f78f23">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="7eef86294cd794ab9e6a53d218c238bfc63c3a6d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -125,15 +125,15 @@
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="30d0dfa566651fea8031551e86cec6018b7bbb12"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="57b16fcb790bdf0b53b3c6435a37ccc8ca90ed36"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="aad3e80dea67774aa51ed4e6c054856168dd180b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="53a59364ce4f14068034c8d6fe01f4f6b9f78f23">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
@@ -123,15 +123,15 @@
   <project name="platform/system/vold" path="system/vold" revision="d4455b8cf361f8353e8aebac15ffd64b4aedd2b9"/>
   <project name="platform/external/icu4c" path="external/icu4c" remote="aosp" revision="b4c6379528887dc25ca9991a535a8d92a61ad6b6"/>
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="614747e5e6755ffcdb36156ea82d8b5c1609a3af"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="9395eb5aa885cf6d305a202de6e9694a58a89717"/>
   <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="c0e0019a6ec1a6199a9c7bc4ace041259f3b8512"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5f184e4aa6ad784e20b4c5e6be24db4b9a848087"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="e4b7cd053711ece3cd5616cd4fb7f75c43bce9c0"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="694cecf256122d0cb3b6a1a4efb4b5c7401db223"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="97d63c256a047b491565d624aea1dd5f1f8593ea"/>
   <project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="0951179277915335251c5e11d242e4e1a8c2236f"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="7eef86294cd794ab9e6a53d218c238bfc63c3a6d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "d4afc0a7f72fd7793359b9575ea7c90cd54e2348", 
+    "revision": "6fa1c6e992e9d7169e8e6cd8c714d1983087a87c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="7eef86294cd794ab9e6a53d218c238bfc63c3a6d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -25,16 +25,21 @@ GENERATED_INCLUDES += [
 ]
 
 LOCAL_INCLUDES += [
     '/toolkit/xre',
     '/xpcom/base',
     '/xpcom/build',
 ]
 
+DELAYLOAD_DLLS += [
+    'mozglue.dll',
+]
+USE_STATIC_LIBS = True
+
 if CONFIG['_MSC_VER']:
     # Always enter a Windows program through wmain, whether or not we're
     # a console application.
     WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     RCINCLUDE = 'splash.rc'
     DEFINES['MOZ_PHOENIX'] = True
@@ -45,19 +50,25 @@ if CONFIG['OS_ARCH'] == 'WINNT':
 #
 # The default heap size is 1MB on Win32.
 # The heap will grow if need be.
 #
 # Set it to 256k.  See bug 127069.
 if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
     LDFLAGS += ['/HEAP:0x40000']
 
-USE_LIBS += [
-    'xpcomglue',
-]
+if CONFIG['OS_ARCH'] == 'WINNT':
+    USE_LIBS += [
+        'mozglue',
+        'xpcomglue_staticruntime',
+    ]
+else:
+    USE_LIBS += [
+        'xpcomglue',
+    ]
 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1663,16 +1663,22 @@
             <xul:hbox anonid="trackingContentProtectionDisabled" hidden="true"
                       class="popup-notification-footer" xbl:inherits="popupid">
               <xul:description class="popup-notification-item-message popup-notification-item-message-critical" xbl:inherits="popupid">
                 &trackingContentBlocked.disabled.message;
                 </xul:description>
             </xul:hbox>
           </xul:vbox>
         </xul:vbox>
+        <xul:vbox pack="start">
+          <xul:toolbarbutton anonid="closebutton"
+                             class="messageCloseButton popup-notification-closebutton tabbable close-icon"
+                             xbl:inherits="oncommand=closebuttoncommand"
+                             tooltiptext="&closeNotification.tooltip;"/>
+        </xul:vbox>
       </xul:hbox>
     </content>
     <resources>
       <stylesheet src="chrome://global/skin/notification.css"/>
     </resources>
     <implementation>
       <field name="_brandShortName">
         document.getElementById("bundle_brand").getString("brandShortName")
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2659,16 +2659,17 @@ richlistitem[type~="action"][actiontype=
 #historySwipeAnimationContainer {
   background: url("chrome://browser/skin/subtle-pattern.png") #B3B9C1;
 }
 
 /* ----- SIDEBAR ELEMENTS ----- */
 
 #sidebar,
 sidebarheader {
+  -moz-appearance: -moz-mac-vibrancy-light;
   background-color: #e2e7ed;
 }
 
 #sidebar:-moz-window-inactive,
 sidebarheader:-moz-window-inactive {
   background-color: #e8e8e8;
 }
 
--- a/content/base/src/ChildIterator.h
+++ b/content/base/src/ChildIterator.h
@@ -39,16 +39,30 @@ public:
     : mParent(aParent),
       mChild(nullptr),
       mDefaultChild(nullptr),
       mIndexInInserted(0),
       mIsFirst(aStartAtBeginning)
   {
   }
 
+  ExplicitChildIterator(const ExplicitChildIterator& aOther)
+    : mParent(aOther.mParent), mChild(aOther.mChild),
+      mDefaultChild(aOther.mDefaultChild),
+      mShadowIterator(aOther.mShadowIterator ?
+                      new ExplicitChildIterator(*aOther.mShadowIterator) :
+                      nullptr),
+      mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
+
+  ExplicitChildIterator(ExplicitChildIterator&& aOther)
+    : mParent(aOther.mParent), mChild(aOther.mChild),
+      mDefaultChild(aOther.mDefaultChild),
+      mShadowIterator(Move(aOther.mShadowIterator)),
+      mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
+
   nsIContent* GetNextChild();
 
   // Looks for aChildToFind respecting insertion points until aChildToFind
   // or aBound is found. If aBound is nullptr then the seek is unbounded. Returns
   // whether aChildToFind was found as an explicit child prior to encountering
   // aBound.
   bool Seek(nsIContent* aChildToFind, nsIContent* aBound = nullptr)
   {
@@ -113,25 +127,31 @@ class FlattenedChildIterator : public Ex
 {
 public:
   FlattenedChildIterator(nsIContent* aParent)
     : ExplicitChildIterator(aParent), mXBLInvolved(false)
   {
     Init(false);
   }
 
+  FlattenedChildIterator(FlattenedChildIterator&& aOther)
+    : ExplicitChildIterator(Move(aOther)), mXBLInvolved(aOther.mXBLInvolved) {}
+
+  FlattenedChildIterator(const FlattenedChildIterator& aOther)
+    : ExplicitChildIterator(aOther), mXBLInvolved(aOther.mXBLInvolved) {}
+
   bool XBLInvolved() { return mXBLInvolved; }
 
 protected:
   /**
    * This constructor is a hack to help AllChildrenIterator which sometimes
    * doesn't want to consider XBL.
    */
   FlattenedChildIterator(nsIContent* aParent, bool aIgnoreXBL)
-  : ExplicitChildIterator(aParent), mXBLInvolved(false)
+    : ExplicitChildIterator(aParent), mXBLInvolved(false)
   {
     Init(aIgnoreXBL);
   }
 
   void Init(bool aIgnoreXBL);
 
   // For certain optimizations, nsCSSFrameConstructor needs to know if the
   // child list of the element that we're iterating matches its .childNodes.
@@ -147,16 +167,26 @@ protected:
 class AllChildrenIterator : private FlattenedChildIterator
 {
 public:
   AllChildrenIterator(nsIContent* aNode, uint32_t aFlags) :
     FlattenedChildIterator(aNode, (aFlags & nsIContent::eAllButXBL)),
     mOriginalContent(aNode), mFlags(aFlags),
     mPhase(eNeedBeforeKid) {}
 
+  AllChildrenIterator(AllChildrenIterator&& aOther)
+    : FlattenedChildIterator(Move(aOther)),
+      mOriginalContent(aOther.mOriginalContent),
+      mAnonKids(Move(aOther.mAnonKids)), mFlags(aOther.mFlags),
+      mPhase(aOther.mPhase)
+#ifdef DEBUG
+      , mMutationGuard(aOther.mMutationGuard)
+#endif
+      {}
+
 #ifdef DEBUG
   ~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
 #endif
 
   nsIContent* GetNextChild();
 
 private:
   enum IteratorPhase
--- a/content/base/src/nsDOMAttributeMap.cpp
+++ b/content/base/src/nsDOMAttributeMap.cpp
@@ -1,9 +1,10 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- 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/. */
 
 /*
  * Implementation of the |attributes| property of DOM Core's Element object.
  */
 
@@ -45,23 +46,27 @@ RemoveMapRef(nsAttrHashKey::KeyType aKey
 {
   aData->SetMap(nullptr);
 
   return PL_DHASH_REMOVE;
 }
 
 nsDOMAttributeMap::~nsDOMAttributeMap()
 {
-  mAttributeCache.Enumerate(RemoveMapRef, nullptr);
+  if (mAttributeCache) {
+    mAttributeCache->Enumerate(RemoveMapRef, nullptr);
+  }
 }
 
 void
 nsDOMAttributeMap::DropReference()
 {
-  mAttributeCache.Enumerate(RemoveMapRef, nullptr);
+  if (mAttributeCache) {
+    mAttributeCache->Enumerate(RemoveMapRef, nullptr);
+  }
   mContent = nullptr;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttributeMap)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap)
   tmp->DropReference();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
@@ -77,17 +82,19 @@ TraverseMapEntry(nsAttrHashKey::KeyType 
     static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
 
   cb->NoteXPCOMChild(static_cast<nsINode*>(aData.get()));
 
   return PL_DHASH_NEXT;
 }
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap)
-  tmp->mAttributeCache.Enumerate(TraverseMapEntry, &cb);
+  if (tmp->mAttributeCache) {
+    tmp->mAttributeCache->Enumerate(TraverseMapEntry, &cb);
+  }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap)
   if (tmp->IsBlack()) {
@@ -131,76 +138,79 @@ SetOwnerDocumentFunc(nsAttrHashKey::KeyT
   nsresult rv = aData->SetOwnerDocument(static_cast<nsIDocument*>(aUserArg));
 
   return NS_FAILED(rv) ? PL_DHASH_STOP : PL_DHASH_NEXT;
 }
 
 nsresult
 nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument)
 {
-  uint32_t n = mAttributeCache.Enumerate(SetOwnerDocumentFunc, aDocument);
-  NS_ENSURE_TRUE(n == mAttributeCache.Count(), NS_ERROR_FAILURE);
-
+  if (mAttributeCache) {
+    uint32_t n = mAttributeCache->Enumerate(SetOwnerDocumentFunc, aDocument);
+    NS_ENSURE_TRUE(n == mAttributeCache->Count(), NS_ERROR_FAILURE);
+  }
   return NS_OK;
 }
 
 void
 nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, nsIAtom* aLocalName)
 {
   nsAttrKey attr(aNamespaceID, aLocalName);
-  Attr *node = mAttributeCache.GetWeak(attr);
-  if (node) {
-    // Break link to map
-    node->SetMap(nullptr);
+  if (mAttributeCache) {
+    Attr *node = mAttributeCache->GetWeak(attr);
+    if (node) {
+      // Break link to map
+      node->SetMap(nullptr);
 
-    // Remove from cache
-    mAttributeCache.Remove(attr);
+      // Remove from cache
+      mAttributeCache->Remove(attr);
+    }
   }
 }
 
 already_AddRefed<Attr>
 nsDOMAttributeMap::RemoveAttribute(mozilla::dom::NodeInfo* aNodeInfo)
 {
   NS_ASSERTION(aNodeInfo, "RemoveAttribute() called with aNodeInfo == nullptr!");
 
   nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
 
   nsRefPtr<Attr> node;
-  if (!mAttributeCache.Get(attr, getter_AddRefs(node))) {
+  if (mAttributeCache && mAttributeCache->Get(attr, getter_AddRefs(node))) {
+    // Break link to map
+    node->SetMap(nullptr);
+
+    // Remove from cache
+    mAttributeCache->Remove(attr);
+  } else {
     nsAutoString value;
     // As we are removing the attribute we need to set the current value in
     // the attribute node.
     mContent->GetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(), value);
     nsRefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
     node = new Attr(nullptr, ni.forget(), value, true);
   }
-  else {
-    // Break link to map
-    node->SetMap(nullptr);
-
-    // Remove from cache
-    mAttributeCache.Remove(attr);
-  }
 
   return node.forget();
 }
 
 Attr*
 nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo, bool aNsAware)
 {
   NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!");
 
   nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
 
-  Attr* node = mAttributeCache.GetWeak(attr);
+  EnsureAttributeCache();
+  Attr* node = mAttributeCache->GetWeak(attr);
   if (!node) {
     nsRefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
     nsRefPtr<Attr> newAttr =
       new Attr(this, ni.forget(), EmptyString(), aNsAware);
-    mAttributeCache.Put(attr, newAttr);
+    mAttributeCache->Put(attr, newAttr);
     node = newAttr;
   }
 
   return node;
 }
 
 Attr*
 nsDOMAttributeMap::NamedGetter(const nsAString& aAttrName, bool& aFound)
@@ -236,16 +246,24 @@ nsDOMAttributeMap::GetNamedItem(const ns
 {
   NS_ENSURE_ARG_POINTER(aAttribute);
 
   NS_IF_ADDREF(*aAttribute = GetNamedItem(aAttrName));
 
   return NS_OK;
 }
 
+void
+nsDOMAttributeMap::EnsureAttributeCache()
+{
+  if (!mAttributeCache) {
+    mAttributeCache = MakeUnique<AttrCache>();
+  }
+}
+
 NS_IMETHODIMP
 nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
 {
   Attr* attribute = static_cast<Attr*>(aAttr);
   NS_ENSURE_ARG(attribute);
 
   ErrorResult rv;
   *aReturn = SetNamedItem(*attribute, rv).take();
@@ -337,17 +355,18 @@ nsDOMAttributeMap::SetNamedItemInternal(
   }
 
   nsAutoString value;
   aAttr.GetValue(value);
 
   // Add the new attribute to the attribute map before updating
   // its value in the element. @see bug 364413.
   nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom());
-  mAttributeCache.Put(attrkey, &aAttr);
+  EnsureAttributeCache();
+  mAttributeCache->Put(attrkey, &aAttr);
   aAttr.SetMap(this);
 
   rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(),
                          ni->GetPrefixAtom(), value, true);
   if (NS_FAILED(rv)) {
     aError.Throw(rv);
     DropAttribute(ni->NamespaceID(), ni->NameAtom());
   }
@@ -523,41 +542,43 @@ nsDOMAttributeMap::RemoveNamedItemNS(con
   mContent->UnsetAttr(attrNi->NamespaceID(), attrNi->NameAtom(), true);
 
   return attr.forget();
 }
 
 uint32_t
 nsDOMAttributeMap::Count() const
 {
-  return mAttributeCache.Count();
+  return mAttributeCache ? mAttributeCache->Count() : 0;
 }
 
 uint32_t
 nsDOMAttributeMap::Enumerate(AttrCache::EnumReadFunction aFunc,
                              void *aUserArg) const
 {
-  return mAttributeCache.EnumerateRead(aFunc, aUserArg);
+  return mAttributeCache ? mAttributeCache->EnumerateRead(aFunc, aUserArg) : 0;
 }
 
 size_t
 AttrCacheSizeEnumerator(const nsAttrKey& aKey,
                         const nsRefPtr<Attr>& aValue,
                         MallocSizeOf aMallocSizeOf,
                         void* aUserArg)
 {
   return aMallocSizeOf(aValue.get());
 }
 
 size_t
 nsDOMAttributeMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
-  n += mAttributeCache.SizeOfExcludingThis(AttrCacheSizeEnumerator,
-                                           aMallocSizeOf);
+  n += mAttributeCache
+     ? mAttributeCache->SizeOfExcludingThis(AttrCacheSizeEnumerator,
+                                            aMallocSizeOf)
+     : 0;
 
   // NB: mContent is non-owning and thus not counted.
   return n;
 }
 
 /* virtual */ JSObject*
 nsDOMAttributeMap::WrapObject(JSContext* aCx)
 {
--- a/content/base/src/nsDOMAttributeMap.h
+++ b/content/base/src/nsDOMAttributeMap.h
@@ -6,16 +6,17 @@
 /*
  * Implementation of the |attributes| property of DOM Core's Element object.
  */
 
 #ifndef nsDOMAttributeMap_h
 #define nsDOMAttributeMap_h
 
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/dom/Attr.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDOMMozNamedAttrMap.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
@@ -178,19 +179,21 @@ public:
 
 protected:
   virtual ~nsDOMAttributeMap();
 
 private:
   nsCOMPtr<Element> mContent;
 
   /**
-   * Cache of Attrs.
+   * Cache of Attrs. It's usually empty, and thus initialized lazily.
    */
-  AttrCache mAttributeCache;
+  mozilla::UniquePtr<AttrCache> mAttributeCache;
+
+  void EnsureAttributeCache();
 
   /**
    * SetNamedItem() (aWithNS = false) and SetNamedItemNS() (aWithNS =
    * true) implementation.
    */
   already_AddRefed<Attr>
   SetNamedItemInternal(Attr& aNode, bool aWithNS, ErrorResult& aError);
 
--- a/content/media/DecoderTraits.cpp
+++ b/content/media/DecoderTraits.cpp
@@ -318,17 +318,17 @@ IsDirectShowSupportedType(const nsACStri
 }
 #endif
 
 #ifdef MOZ_FMP4
 static bool
 IsMP4SupportedType(const nsACString& aType)
 {
   return Preferences::GetBool("media.fragmented-mp4.exposed", false) &&
-         MP4Decoder::GetSupportedCodecs(aType, nullptr);
+         MP4Decoder::CanHandleMediaType(aType);
 }
 #endif
 
 #ifdef MOZ_APPLEMEDIA
 static const char * const gAppleMP3Types[] = {
   "audio/mp3",
   "audio/mpeg",
   nullptr,
@@ -401,18 +401,19 @@ DecoderTraits::CanHandleMediaType(const 
 #endif
 #if defined(MOZ_WEBM) && !defined(MOZ_OMX_WEBM_DECODER)
   if (IsWebMType(nsDependentCString(aMIMEType))) {
     codecList = gWebMCodecs;
     result = CANPLAY_MAYBE;
   }
 #endif
 #ifdef MOZ_FMP4
-  if (IsMP4SupportedType(nsDependentCString(aMIMEType))) {
-    result = aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
+  if (MP4Decoder::CanHandleMediaType(nsDependentCString(aMIMEType),
+                                     aRequestedCodecs)) {
+    return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
   }
 #endif
 #ifdef MOZ_GSTREAMER
   if (GStreamerDecoder::CanHandleMediaType(nsDependentCString(aMIMEType),
                                            aHaveRequestedCodecs ? &aRequestedCodecs : nullptr)) {
     if (aHaveRequestedCodecs)
       return CANPLAY_YES;
     return CANPLAY_MAYBE;
@@ -678,16 +679,24 @@ MediaDecoderReader* DecoderTraits::Creat
   if (false) {} // dummy if to take care of the dangling else
 
   return decoderReader;
 }
 
 /* static */
 bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
 {
+  // Forbid playing media in video documents if the user has opted
+  // not to, using either the legacy WMF specific pref, or the newer
+  // catch-all pref.
+  if (!Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true) ||
+      !Preferences::GetBool("media.play-stand-alone", true)) {
+    return false;
+  }
+
   return
     IsOggType(aType) ||
 #ifdef MOZ_OMX_DECODER
     // We support amr inside WebApps on firefoxOS but not in general web content.
     // Ensure we dont create a VideoDocument when accessing amr URLs directly.
     (IsOmxSupportedType(aType) && !aType.EqualsASCII("audio/amr")) ||
 #endif
 #ifdef MOZ_WEBM
@@ -698,18 +707,17 @@ bool DecoderTraits::IsSupportedInVideoDo
 #endif
 #ifdef MOZ_ANDROID_OMX
     (MediaDecoder::IsAndroidMediaEnabled() && IsAndroidMediaType(aType)) ||
 #endif
 #ifdef MOZ_FMP4
     IsMP4SupportedType(aType) ||
 #endif
 #ifdef MOZ_WMF
-    (IsWMFSupportedType(aType) &&
-     Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true)) ||
+    IsWMFSupportedType(aType) ||
 #endif
 #ifdef MOZ_DIRECTSHOW
     IsDirectShowSupportedType(aType) ||
 #endif
 #ifdef MOZ_APPLEMEDIA
     IsAppleMediaSupportedType(aType) ||
 #endif
 #ifdef NECKO_PROTOCOL_rtsp
--- a/content/media/VideoUtils.cpp
+++ b/content/media/VideoUtils.cpp
@@ -193,9 +193,41 @@ IsValidVideoRegion(const nsIntSize& aFra
 }
 
 TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool()
 {
   return SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"),
                                Preferences::GetUint("media.num-decode-threads", 25));
 }
 
+bool
+ExtractH264CodecDetails(const nsAString& aCodec,
+                        int16_t& aProfile,
+                        int16_t& aLevel)
+{
+  // H.264 codecs parameters have a type defined as avc1.PPCCLL, where
+  // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
+  // We ignore the constraint_set flags, as it's not clear from any
+  // documentation what constraints the platform decoders support.
+  // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
+  // for more details.
+  if (aCodec.Length() != strlen("avc1.PPCCLL")) {
+    return false;
+  }
+
+  // Verify the codec starts with "avc1.".
+  const nsAString& sample = Substring(aCodec, 0, 5);
+  if (!sample.EqualsASCII("avc1.")) {
+    return false;
+  }
+
+  // Extract the profile_idc, constrains, and level_idc.
+  nsresult rv = NS_OK;
+  aProfile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  aLevel = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
 } // end namespace mozilla
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -210,11 +210,50 @@ private:
 };
 
 class SharedThreadPool;
 
 // Returns the thread pool that is shared amongst all decoder state machines
 // for decoding streams.
 TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool();
 
+enum H264_PROFILE {
+  H264_PROFILE_UNKNOWN                     = 0,
+  H264_PROFILE_BASE                        = 0x42,
+  H264_PROFILE_MAIN                        = 0x4D,
+  H264_PROFILE_EXTENDED                    = 0x58,
+  H264_PROFILE_HIGH                        = 0x64,
+};
+
+enum H264_LEVEL {
+    H264_LEVEL_1         = 10,
+    H264_LEVEL_1_b       = 11,
+    H264_LEVEL_1_1       = 11,
+    H264_LEVEL_1_2       = 12,
+    H264_LEVEL_1_3       = 13,
+    H264_LEVEL_2         = 20,
+    H264_LEVEL_2_1       = 21,
+    H264_LEVEL_2_2       = 22,
+    H264_LEVEL_3         = 30,
+    H264_LEVEL_3_1       = 31,
+    H264_LEVEL_3_2       = 32,
+    H264_LEVEL_4         = 40,
+    H264_LEVEL_4_1       = 41,
+    H264_LEVEL_4_2       = 42,
+    H264_LEVEL_5         = 50,
+    H264_LEVEL_5_1       = 51,
+    H264_LEVEL_5_2       = 52
+};
+
+// Extracts the H.264/AVC profile and level from an H.264 codecs string.
+// H.264 codecs parameters have a type defined as avc1.PPCCLL, where
+// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
+// See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
+// for more details.
+// Returns false on failure.
+bool
+ExtractH264CodecDetails(const nsAString& aCodecs,
+                        int16_t& aProfile,
+                        int16_t& aLevel);
+
 } // end namespace mozilla
 
 #endif
--- a/content/media/fmp4/MP4Decoder.cpp
+++ b/content/media/fmp4/MP4Decoder.cpp
@@ -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/. */
 
 #include "MP4Decoder.h"
 #include "MP4Reader.h"
 #include "MediaDecoderStateMachine.h"
 #include "mozilla/Preferences.h"
+#include "nsCharSeparatedTokenizer.h"
 #ifdef MOZ_EME
 #include "mozilla/CDMProxy.h"
 #endif
 #include "prlog.h"
 
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
@@ -45,58 +46,87 @@ MP4Decoder::SetCDMProxy(CDMProxy* aProxy
     nsRefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoder::NotifyWaitingForResourcesStatusChanged));
     caps.CallOnMainThreadWhenCapsAvailable(task);
   }
   return NS_OK;
 }
 #endif
 
+static bool
+IsSupportedAudioCodec(const nsAString& aCodec)
+{
+  // AAC-LC, HE-AAC or MP3 in M4A.
+  return aCodec.EqualsASCII("mp4a.40.2") ||
+#ifndef MOZ_GONK_MEDIACODEC // B2G doesn't support MP3 in MP4 yet.
+         aCodec.EqualsASCII("mp3") ||
+#endif
+         aCodec.EqualsASCII("mp4a.40.5");
+}
+
+static bool
+IsSupportedH264Codec(const nsAString& aCodec)
+{
+  int16_t profile = 0, level = 0;
+
+  if (!ExtractH264CodecDetails(aCodec, profile, level)) {
+    return false;
+  }
+
+  // Just assume what we can play on all platforms the codecs/formats that
+  // WMF can play, since we don't have documentation about what other
+  // platforms can play... According to the WMF documentation:
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815%28v=vs.85%29.aspx
+  // "The Media Foundation H.264 video decoder is a Media Foundation Transform
+  // that supports decoding of Baseline, Main, and High profiles, up to level
+  // 5.1.". We also report that we can play Extended profile, as there are
+  // bitstreams that are Extended compliant that are also Baseline compliant.
+  return level >= H264_LEVEL_1 &&
+         level <= H264_LEVEL_5_1 &&
+         (profile == H264_PROFILE_BASE ||
+          profile == H264_PROFILE_MAIN ||
+          profile == H264_PROFILE_EXTENDED ||
+          profile == H264_PROFILE_HIGH);
+}
+
+/* static */
 bool
-MP4Decoder::GetSupportedCodecs(const nsACString& aType,
-                               char const *const ** aCodecList)
+MP4Decoder::CanHandleMediaType(const nsACString& aType,
+                               const nsAString& aCodecs)
 {
   if (!IsEnabled()) {
     return false;
   }
 
-  // AAC in M4A.
-  static char const *const aacAudioCodecs[] = {
-    "mp4a.40.2",    // AAC-LC
-    // TODO: AAC-HE ?
-    nullptr
-  };
-  if (aType.EqualsASCII("audio/mp4") ||
-      aType.EqualsASCII("audio/x-m4a")) {
-    if (aCodecList) {
-      *aCodecList = aacAudioCodecs;
-    }
-    return true;
+  if (aType.EqualsASCII("audio/mp4") || aType.EqualsASCII("audio/x-m4a")) {
+    return aCodecs.IsEmpty() || IsSupportedAudioCodec(aCodecs);
+  }
+
+  if (!aType.EqualsASCII("video/mp4")) {
+    return false;
   }
 
-  // H.264 + AAC in MP4.
-  static char const *const h264Codecs[] = {
-    "avc1.42E01E",  // H.264 Constrained Baseline Profile Level 3.0
-    "avc1.42001E",  // H.264 Baseline Profile Level 3.0
-    "avc1.58A01E",  // H.264 Extended Profile Level 3.0
-    "avc1.4D401E",  // H.264 Main Profile Level 3.0
-    "avc1.64001E",  // H.264 High Profile Level 3.0
-    "avc1.64001F",  // H.264 High Profile Level 3.1
-    "mp4a.40.2",    // AAC-LC
-    // TODO: There must be more profiles here?
-    nullptr
-  };
-  if (aType.EqualsASCII("video/mp4")) {
-    if (aCodecList) {
-      *aCodecList = h264Codecs;
+  // Verify that all the codecs specifed are ones that we expect that
+  // we can play.
+  nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
+  bool expectMoreTokens = false;
+  while (tokenizer.hasMoreTokens()) {
+    const nsSubstring& token = tokenizer.nextToken();
+    expectMoreTokens = tokenizer.separatorAfterCurrentToken();
+    if (IsSupportedAudioCodec(token) || IsSupportedH264Codec(token)) {
+      continue;
     }
-    return true;
+    return false;
   }
+  if (expectMoreTokens) {
+    // Last codec name was empty
+    return false;
+  }
+  return true;
 
-  return false;
 }
 
 static bool
 IsFFmpegAvailable()
 {
 #ifndef MOZ_FFMPEG
   return false;
 #else
@@ -146,23 +176,23 @@ HavePlatformMPEGDecoders()
 {
   return Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
 #ifdef XP_WIN
          // We have H.264/AAC platform decoders on Windows Vista and up.
          IsVistaOrLater() ||
 #endif
          IsFFmpegAvailable() ||
          IsAppleAvailable() ||
-	 IsGonkMP4DecoderAvailable() ||
+         IsGonkMP4DecoderAvailable() ||
          // TODO: Other platforms...
          false;
 }
 
 /* static */
 bool
 MP4Decoder::IsEnabled()
 {
-  return HavePlatformMPEGDecoders() &&
-         Preferences::GetBool("media.fragmented-mp4.enabled");
+  return Preferences::GetBool("media.fragmented-mp4.enabled") &&
+         HavePlatformMPEGDecoders();
 }
 
 } // namespace mozilla
 
--- a/content/media/fmp4/MP4Decoder.h
+++ b/content/media/fmp4/MP4Decoder.h
@@ -23,22 +23,21 @@ public:
   }
 
   virtual MediaDecoderStateMachine* CreateStateMachine();
 
 #ifdef MOZ_EME
   virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
 #endif
 
-  // Returns true if aType is a MIME type that we can render with the
-  // a MP4 platform decoder backend. If aCodecList is non null,
-  // it is filled with a (static const) null-terminated list of strings
-  // denoting the codecs we'll playback.
-  static bool GetSupportedCodecs(const nsACString& aType,
-                                 char const *const ** aCodecList);
+  // Returns true if aMIMEType is a type that we think we can render with the
+  // a MP4 platform decoder backend. If aCodecs is non emtpy, it is filled
+  // with a comma-delimited list of codecs to check support for.
+  static bool CanHandleMediaType(const nsACString& aMIMEType,
+                                 const nsAString& aCodecs = EmptyString());
 
   // Returns true if the MP4 backend is preffed on, and we're running on a
   // platform that is likely to have decoders for the contained formats.
   static bool IsEnabled();
 };
 
 } // namespace mozilla
 
--- a/content/media/fmp4/MP4Reader.cpp
+++ b/content/media/fmp4/MP4Reader.cpp
@@ -769,17 +769,20 @@ MP4Reader::Seek(int64_t aTime,
   return NS_OK;
 }
 
 void
 MP4Reader::NotifyDataArrived(const char* aBuffer, uint32_t aLength,
                              int64_t aOffset)
 {
   if (NS_IsMainThread()) {
-    GetTaskQueue()->Dispatch(NS_NewRunnableMethod(this, &MP4Reader::UpdateIndex));
+    if (GetTaskQueue()) {
+      GetTaskQueue()->Dispatch(
+        NS_NewRunnableMethod(this, &MP4Reader::UpdateIndex));
+    }
   } else {
     UpdateIndex();
   }
 }
 
 void
 MP4Reader::UpdateIndex()
 {
--- a/content/media/fmp4/apple/AppleVTDecoder.cpp
+++ b/content/media/fmp4/apple/AppleVTDecoder.cpp
@@ -140,25 +140,27 @@ AppleVTDecoder::Drain()
 
 //
 // Implementation details.
 //
 
 // Context object to hold a copy of sample metadata.
 class FrameRef {
 public:
-  Microseconds timestamp;
+  Microseconds decode_timestamp;
+  Microseconds composition_timestamp;
   Microseconds duration;
   int64_t byte_offset;
   bool is_sync_point;
 
   explicit FrameRef(mp4_demuxer::MP4Sample* aSample)
   {
     MOZ_ASSERT(aSample);
-    timestamp = aSample->composition_timestamp;
+    decode_timestamp = aSample->decode_timestamp;
+    composition_timestamp = aSample->composition_timestamp;
     duration = aSample->duration;
     byte_offset = aSample->byte_offset;
     is_sync_point = aSample->is_sync_point;
   }
 };
 
 // Callback passed to the VideoToolbox decoder for returning data.
 // This needs to be static because the API takes a C-style pair of
@@ -175,19 +177,20 @@ PlatformCallback(void* decompressionOutp
 {
   LOG("AppleVideoDecoder %s status %d flags %d", __func__, status, flags);
 
   AppleVTDecoder* decoder =
     static_cast<AppleVTDecoder*>(decompressionOutputRefCon);
   nsAutoPtr<FrameRef> frameRef =
     nsAutoPtr<FrameRef>(static_cast<FrameRef*>(sourceFrameRefCon));
 
-  LOG("mp4 output frame %lld pts %lld duration %lld us%s",
+  LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
     frameRef->byte_offset,
-    frameRef->timestamp,
+    frameRef->decode_timestamp,
+    frameRef->composition_timestamp,
     frameRef->duration,
     frameRef->is_sync_point ? " keyframe" : ""
   );
 
   // Validate our arguments.
   if (status != noErr || !image) {
     NS_WARNING("VideoToolbox decoder returned no data");
     return;
@@ -297,53 +300,64 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
 
   // Copy the image data into our own format.
   nsAutoPtr<VideoData> data;
   data =
     VideoData::Create(info,
                       mImageContainer,
                       nullptr,
                       aFrameRef->byte_offset,
-                      aFrameRef->timestamp,
+                      aFrameRef->composition_timestamp,
                       aFrameRef->duration,
                       buffer,
                       aFrameRef->is_sync_point,
-                      aFrameRef->timestamp,
+                      aFrameRef->decode_timestamp,
                       visible);
   // Unlock the returned image data.
   CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
 
   if (!data) {
     NS_ERROR("Couldn't create VideoData for frame");
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
   // Frames come out in DTS order but we need to output them
   // in composition order.
   mReorderQueue.Push(data.forget());
-  if (mReorderQueue.Length() > 2) {
+  // Assume a frame with a PTS <= current DTS is ready.
+  while (mReorderQueue.Length() > 0) {
     VideoData* readyData = mReorderQueue.Pop();
-    mCallback->Output(readyData);
+    if (readyData->mTime <= aFrameRef->decode_timestamp) {
+      LOG("returning queued frame with pts %lld", readyData->mTime);
+      mCallback->Output(readyData);
+    } else {
+      LOG("requeued frame with pts %lld > %lld",
+          readyData->mTime, aFrameRef->decode_timestamp);
+      mReorderQueue.Push(readyData);
+      break;
+    }
   }
+  LOG("%llu decoded frames queued",
+      static_cast<unsigned long long>(mReorderQueue.Length()));
 
   return NS_OK;
 }
 
 // Helper to fill in a timestamp structure.
 static CMSampleTimingInfo
 TimingInfoFromSample(mp4_demuxer::MP4Sample* aSample)
 {
   CMSampleTimingInfo timestamp;
 
   timestamp.duration = CMTimeMake(aSample->duration, USECS_PER_S);
   timestamp.presentationTimeStamp =
     CMTimeMake(aSample->composition_timestamp, USECS_PER_S);
-  // No DTS value available from libstagefright.
-  timestamp.decodeTimeStamp = CMTimeMake(0, USECS_PER_S);
+  timestamp.decodeTimeStamp =
+    CMTimeMake(aSample->decode_timestamp, USECS_PER_S);
 
   return timestamp;
 }
 
 nsresult
 AppleVTDecoder::SubmitFrame(mp4_demuxer::MP4Sample* aSample)
 {
   // For some reason this gives me a double-free error with stagefright.
--- a/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
+++ b/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
@@ -51,16 +51,17 @@ void
 FFmpegH264Decoder<LIBAV_VER>::DecodeFrame(mp4_demuxer::MP4Sample* aSample)
 {
   AVPacket packet;
   av_init_packet(&packet);
 
   aSample->Pad(FF_INPUT_BUFFER_PADDING_SIZE);
   packet.data = aSample->data;
   packet.size = aSample->size;
+  packet.dts = aSample->decode_timestamp;
   packet.pts = aSample->composition_timestamp;
   packet.flags = aSample->is_sync_point ? AV_PKT_FLAG_KEY : 0;
   packet.pos = aSample->byte_offset;
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg h264 decoder failed to allocate frame.");
     mCallback->Error();
     return;
deleted file mode 100644
--- a/content/media/test/can_play_type_mpeg.js
+++ /dev/null
@@ -1,51 +0,0 @@
-function check_mp4(v, enabled) {
-  function check(type, expected) {
-    var ex = enabled ? expected : "";
-    is(v.canPlayType(type), ex, type + "='" + ex + "'");
-  }
-
-  check("video/mp4", "maybe");
-  check("audio/mp4", "maybe");
-  check("audio/x-m4a", "maybe");
-
-  // Not the MIME type that other browsers respond to, so we won't either.
-  check("audio/m4a", "");
-  // Only Safari responds affirmatively to "audio/aac",
-  // so we'll let x-m4a cover aac support.
-  check("audio/aac", "");
-
-  check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably");
-  check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably");
-  check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably");
-  check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably");
-  check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably");
-  check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably");
-
-  check("video/mp4; codecs=\"avc1.42E01E\"", "probably");
-  check("video/mp4; codecs=\"avc1.42001E\"", "probably");
-  check("video/mp4; codecs=\"avc1.58A01E\"", "probably");
-  check("video/mp4; codecs=\"avc1.4D401E\"", "probably");
-  check("video/mp4; codecs=\"avc1.64001E\"", "probably");
-  check("video/mp4; codecs=\"avc1.64001F\"", "probably");
-
-  check("audio/mp4; codecs=\"mp4a.40.2\"", "probably");
-  check("audio/mp4; codecs=mp4a.40.2", "probably");
-  check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably");
-  check("audio/x-m4a; codecs=mp4a.40.2", "probably");
-}
-
-function check_mp3(v, enabled) {
-  function check(type, expected) {
-    var ex = enabled ? expected : "";
-    is(v.canPlayType(type), ex, type + "='" + ex + "'");
-  }
-
-  check("audio/mpeg", "maybe");
-  check("audio/mp3", "maybe");
-
-  check("audio/mpeg; codecs=\"mp3\"", "probably");
-  check("audio/mpeg; codecs=mp3", "probably");
-
-  check("audio/mp3; codecs=\"mp3\"", "probably");
-  check("audio/mp3; codecs=mp3", "probably");
-}
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -103,17 +103,16 @@ support-files =
   bug580982.webm
   bug580982.webm^headers^
   bug603918.webm
   bug603918.webm^headers^
   bug604067.webm
   bug604067.webm^headers^
   bug883173.vtt
   can_play_type_dash.js
-  can_play_type_mpeg.js
   can_play_type_ogg.js
   can_play_type_wave.js
   can_play_type_webm.js
   cancellable_request.sjs
   chain.ogg
   chain.ogg^headers^
   chain.ogv
   chain.ogv^headers^
--- a/content/media/test/test_can_play_type_mpeg.html
+++ b/content/media/test/test_can_play_type_mpeg.html
@@ -12,19 +12,92 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 
 <video id="v"></video>
 
 <pre id="test">
-<script src="can_play_type_mpeg.js"></script>
 <script>
 
+function check_mp4(v, enabled) {
+  function check(type, expected) {
+    var ex = enabled ? expected : "";
+    is(v.canPlayType(type), ex, type + "='" + ex + "'");
+  }
+
+  check("video/mp4", "maybe");
+  check("audio/mp4", "maybe");
+  check("audio/x-m4a", "maybe");
+
+  // Not the MIME type that other browsers respond to, so we won't either.
+  check("audio/m4a", "");
+  // Only Safari responds affirmatively to "audio/aac",
+  // so we'll let x-m4a cover aac support.
+  check("audio/aac", "");
+
+  // H.264 Constrained Baseline Profile Level 3.0, AAC-LC
+  check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably");
+
+  // H.264 Constrained Baseline Profile Level 3.0, mp3
+  check("video/mp4; codecs=\"avc1.42E01E, mp3\"", "probably");
+  
+  check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably");
+  check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably");
+  
+  const ProbablyIfNotLinux = !IsLinuxGStreamer() ? "probably" : "";
+  
+  // H.264 Main Profile Level 3.0, AAC-LC
+  check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably");
+  // H.264 Main Profile Level 3.1, AAC-LC
+  check("video/mp4; codecs=\"avc1.4D401F, mp4a.40.2\"", ProbablyIfNotLinux);
+  // H.264 Main Profile Level 4.0, AAC-LC
+  check("video/mp4; codecs=\"avc1.4D4028, mp4a.40.2\"", ProbablyIfNotLinux);
+  // H.264 High Profile Level 3.0, AAC-LC
+  check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably");
+  // H.264 High Profile Level 3.1, AAC-LC
+  check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably");
+
+  check("video/mp4; codecs=\"avc1.42E01E\"", "probably");
+  check("video/mp4; codecs=\"avc1.42001E\"", "probably");
+  check("video/mp4; codecs=\"avc1.58A01E\"", "probably");
+  check("video/mp4; codecs=\"avc1.4D401E\"", "probably");
+  check("video/mp4; codecs=\"avc1.64001F\"", "probably");
+
+  // AAC-LC
+  check("audio/mp4; codecs=\"mp4a.40.2\"", "probably");
+  check("audio/mp4; codecs=mp4a.40.2", "probably");
+  check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably");
+  check("audio/x-m4a; codecs=mp4a.40.2", "probably");
+
+  // HE-AAC v1
+  check("audio/mp4; codecs=\"mp4a.40.5\"", ProbablyIfNotLinux);
+  check("audio/mp4; codecs=mp4a.40.5", ProbablyIfNotLinux);
+  check("audio/x-m4a; codecs=\"mp4a.40.5\"", ProbablyIfNotLinux);
+  check("audio/x-m4a; codecs=mp4a.40.5", ProbablyIfNotLinux);
+  
+}
+
+function check_mp3(v, enabled) {
+  function check(type, expected) {
+    var ex = enabled ? expected : "";
+    is(v.canPlayType(type), ex, type + "='" + ex + "'");
+  }
+
+  check("audio/mpeg", "maybe");
+  check("audio/mp3", "maybe");
+
+  check("audio/mpeg; codecs=\"mp3\"", "probably");
+  check("audio/mpeg; codecs=mp3", "probably");
+
+  check("audio/mp3; codecs=\"mp3\"", "probably");
+  check("audio/mp3; codecs=mp3", "probably");
+}
+
 function IsWindowsVistaOrLater() {
   var re = /Windows NT (\d+\.\d)/;
   var winver = navigator.userAgent.match(re);
   return winver && winver.length == 2 && parseFloat(winver[1]) >= 6.0;
 }
 
 function IsMacOSLionOrLater() {
   var re = /Mac OS X (\d+)\.(\d+)/;
@@ -40,16 +113,21 @@ function IsMacOSLionOrLater() {
 function getPref(name) {
   var pref = false;
   try {
     pref = SpecialPowers.getBoolPref(name);
   } catch(ex) { }
   return pref;
 }
 
+function IsLinuxGStreamer() {
+  return /Linux/.test(navigator.userAgent) &&
+         getPref("media.gstreamer.enabled");
+}
+
 // Check whether we should expect the new MP4Reader-based support to work.
 function IsMP4ReaderAvailable() {
   var prefs = getPref("media.fragmented-mp4.enabled") &&
               getPref("media.fragmented-mp4.exposed");
   return prefs && (IsWindowsVistaOrLater() || IsMacOSLionOrLater());
 }
 
 var haveMp4 = (getPref("media.windows-media-foundation.enabled") && IsWindowsVistaOrLater()) ||
--- a/content/media/wmf/WMFDecoder.cpp
+++ b/content/media/wmf/WMFDecoder.cpp
@@ -49,48 +49,27 @@ IsSupportedH264Codec(const nsAString& aC
 {
   // According to the WMF documentation:
   // http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815%28v=vs.85%29.aspx
   // "The Media Foundation H.264 video decoder is a Media Foundation Transform
   // that supports decoding of Baseline, Main, and High profiles, up to level
   // 5.1.". We also report that we can play Extended profile, as there are
   // bitstreams that are Extended compliant that are also Baseline compliant.
 
-  // H.264 codecs parameters have a type defined as avc1.PPCCLL, where
-  // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
-  // We ignore the constraint_set flags, as it's not clear from the WMF
-  // documentation what constraints the WMF H.264 decoder supports.
-  // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
-  // for more details.
-  if (aCodec.Length() != strlen("avc1.PPCCLL")) {
-    return false;
-  }
-
-  // Verify the codec starts with "avc1.".
-  const nsAString& sample = Substring(aCodec, 0, 5);
-  if (!sample.EqualsASCII("avc1.")) {
+  int16_t profile = 0, level = 0;
+  if (!ExtractH264CodecDetails(aCodec, profile, level)) {
     return false;
   }
 
-  // Extract the profile_idc and level_idc. Note: the constraint_set flags
-  // are ignored, it's not clear from the WMF documentation if they make a
-  // difference.
-  nsresult rv = NS_OK;
-  const int32_t profile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  const int32_t level = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  return level >= eAVEncH264VLevel1 &&
-         level <= eAVEncH264VLevel5_1 &&
-         (profile == eAVEncH264VProfile_Base ||
-          profile == eAVEncH264VProfile_Main ||
-          profile == eAVEncH264VProfile_Extended ||
-          profile == eAVEncH264VProfile_High);
+  return level >= H264_LEVEL_1 &&
+         level <= H264_LEVEL_5_1 &&
+         (profile == H264_PROFILE_BASE ||
+          profile == H264_PROFILE_MAIN ||
+          profile == H264_PROFILE_EXTENDED ||
+          profile == H264_PROFILE_HIGH);
 }
 
 bool
 WMFDecoder::CanPlayType(const nsACString& aType,
                         const nsAString& aCodecs)
 {
   if (!MediaDecoder::IsWMFEnabled() ||
       NS_FAILED(LoadDLLs())) {
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1,17 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=78: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ArrayUtils.h"
-// On top because they include basictypes.h:
-#include "mozilla/dom/SmsFilter.h"
 
 #ifdef XP_WIN
 #undef GetClassName
 #endif
 
 // JavaScript includes
 #include "jsapi.h"
 #include "jsfriendapi.h"
@@ -120,17 +118,16 @@
 
 #include "mozilla/dom/TouchEvent.h"
 
 #include "nsWrapperCacheInlines.h"
 #include "mozilla/dom/HTMLCollectionBinding.h"
 
 #include "nsIDOMMozSmsMessage.h"
 #include "nsIDOMMozMmsMessage.h"
-#include "nsIDOMSmsFilter.h"
 #include "nsIDOMMozMobileMessageThread.h"
 
 #ifdef MOZ_B2G_FM
 #include "FMRadio.h"
 #endif
 
 #include "nsIDOMGlobalObjectConstructor.h"
 #include "nsDebug.h"
@@ -343,19 +340,16 @@ static nsDOMClassInfoData sClassInfoData
                            WINDOW_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsMessage, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozMmsMessage, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
-  NS_DEFINE_CLASSINFO_DATA(MozSmsFilter, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
   NS_DEFINE_CLASSINFO_DATA(MozMobileMessageThread, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(CSSFontFaceRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentFrameMessageManager, nsEventTargetSH,
                                        DOM_DEFAULT_SCRIPTABLE_FLAGS |
@@ -417,17 +411,16 @@ struct nsConstructorFuncMapData
 
 #define NS_DEFINE_CONSTRUCTOR_FUNC_DATA(_class, _func)                        \
   { eDOMClassInfo_##_class##_id, _func },
 
 static const nsConstructorFuncMapData kConstructorFuncMap[] =
 {
   NS_DEFINE_CONSTRUCTOR_FUNC_DATA(Blob, DOMMultipartFileImpl::NewBlob)
   NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, DOMMultipartFileImpl::NewFile)
-  NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozSmsFilter, SmsFilter::NewSmsFilter)
   NS_DEFINE_CONSTRUCTOR_FUNC_DATA(XSLTProcessor, XSLTProcessorCtor)
 };
 #undef NS_DEFINE_CONSTRUCTOR_FUNC_DATA
 
 nsIXPConnect *nsDOMClassInfo::sXPConnect = nullptr;
 bool nsDOMClassInfo::sIsInitialized = false;
 
 
@@ -908,20 +901,16 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN(MozSmsMessage, nsIDOMMozSmsMessage)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsMessage)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozMmsMessage, nsIDOMMozMmsMessage)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozMmsMessage)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(MozSmsFilter, nsIDOMMozSmsFilter)
-     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsFilter)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(MozMobileMessageThread, nsIDOMMozMobileMessageThread)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozMobileMessageThread)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CSSFontFaceRule, nsIDOMCSSFontFaceRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFaceRule)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -50,17 +50,16 @@ DOMCI_CLASS(XPathNSResolver)
 DOMCI_CLASS(Blob)
 DOMCI_CLASS(File)
 
 // DOM modal content window class, almost identical to Window
 DOMCI_CLASS(ModalContentWindow)
 
 DOMCI_CLASS(MozSmsMessage)
 DOMCI_CLASS(MozMmsMessage)
-DOMCI_CLASS(MozSmsFilter)
 DOMCI_CLASS(MozMobileMessageThread)
 
 // @font-face in CSS
 DOMCI_CLASS(CSSFontFaceRule)
 
 DOMCI_CLASS(ContentFrameMessageManager)
 DOMCI_CLASS(ChromeMessageBroadcaster)
 DOMCI_CLASS(ChromeMessageSender)
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2697,22 +2697,26 @@ nsDOMWindowUtils::SetAsyncScrollOffset(n
           layer = manager->GetRoot();
         }
       }
     }
     if (!layer) {
       return NS_ERROR_UNEXPECTED;
     }
   }
+  FrameMetrics::ViewID viewId;
+  if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
+    return NS_ERROR_UNEXPECTED;
+  }
   ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
   if (!forwarder || !forwarder->HasShadowManager()) {
     return NS_ERROR_UNEXPECTED;
   }
   forwarder->GetShadowManager()->SendSetAsyncScrollOffset(
-    layer->AsShadowableLayer()->GetShadow(), aX, aY);
+    layer->AsShadowableLayer()->GetShadow(), viewId, aX, aY);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
                                            const nsAString& aProperty,
                                            const nsAString& aValue1,
                                            const nsAString& aValue2,
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1985,17 +1985,16 @@ addExternalIface('MozFrameLoader', nativ
 addExternalIface('MozFrameRequestCallback', nativeType='nsIFrameRequestCallback',
                  notflattened=True)
 addExternalIface('MozIccInfo', headerFile='nsIDOMIccInfo.h')
 addExternalIface('MozMmsMessage')
 addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True)
 addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource',
                  notflattened=True)
 addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True)
-addExternalIface('MozSmsFilter', headerFile='nsIDOMSmsFilter.h')
 addExternalIface('MozSmsMessage')
 addExternalIface('MozTreeBoxObject', nativeType='nsITreeBoxObject',
                  notflattened=True)
 addExternalIface('MozTreeColumn', nativeType='nsITreeColumn',
                  headerFile='nsITreeColumns.h')
 addExternalIface('MozWakeLockListener', headerFile='nsIDOMWakeLockListener.h')
 addExternalIface('MozXULTemplateBuilder', nativeType='nsIXULTemplateBuilder')
 addExternalIface('nsIBrowserDOMWindow', nativeType='nsIBrowserDOMWindow',
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -24,18 +24,17 @@ namespace dom {
 jsid s_length_id = JSID_VOID;
 
 bool
 DefineStaticJSVals(JSContext* cx)
 {
   return InternJSString(cx, s_length_id, "length");
 }
 
-
-const char HandlerFamily = 0;
+const char DOMProxyHandler::family = 0;
 
 js::DOMProxyShadowsResult
 DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
 {
   JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
   if (v.isObject()) {
     bool hasOwn;
     Rooted<JSObject*> object(cx, &v.toObject());
@@ -55,17 +54,17 @@ DOMProxyShadows(JSContext* cx, JS::Handl
 
   return hasOwn ? js::Shadows : js::DoesntShadowUnique;
 }
 
 // Store the information for the specialized ICs.
 struct SetDOMProxyInformation
 {
   SetDOMProxyInformation() {
-    js::SetDOMProxyInformation((const void*) &HandlerFamily,
+    js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family,
                                js::PROXY_EXTRA_SLOT + JSPROXYSLOT_EXPANDO, DOMProxyShadows);
   }
 };
 
 SetDOMProxyInformation gSetDOMProxyInformation;
 
 // static
 JSObject*
@@ -358,10 +357,30 @@ IdToInt32(JSContext* cx, JS::Handle<jsid
 bool
 DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                            JS::MutableHandle<JS::Value> vp, bool *done) const
 {
   *done = false;
   return true;
 }
 
+//static
+JSObject *
+DOMProxyHandler::GetExpandoObject(JSObject *obj)
+{
+  MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
+  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  if (v.isObject()) {
+    return &v.toObject();
+  }
+
+  if (v.isUndefined()) {
+    return nullptr;
+  }
+
+  js::ExpandoAndGeneration* expandoAndGeneration =
+    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+  v = expandoAndGeneration->expando;
+  return v.isUndefined() ? nullptr : &v.toObject();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -19,27 +19,16 @@ namespace mozilla {
 namespace dom {
 
 enum {
   JSPROXYSLOT_EXPANDO = 0
 };
 
 template<typename T> struct Prefable;
 
-// This variable exists solely to provide a unique address for use as an identifier.
-extern const char HandlerFamily;
-inline const void* ProxyFamily() { return &HandlerFamily; }
-
-inline bool IsDOMProxy(JSObject *obj)
-{
-    const js::Class* clasp = js::GetObjectClass(obj);
-    return clasp->isProxy() &&
-           js::GetProxyHandler(obj)->family() == ProxyFamily();
-}
-
 class BaseDOMProxyHandler : public js::BaseProxyHandler
 {
 public:
   explicit BaseDOMProxyHandler(const void* aProxyFamily, bool aHasPrototype = false)
     : js::BaseProxyHandler(aProxyFamily, aHasPrototype)
   {}
 
   // Implementations of traps that can be implemented in terms of
@@ -84,17 +73,17 @@ protected:
                                     bool ignoreNamedProps,
                                     JS::MutableHandle<JSPropertyDescriptor> desc) const = 0;
 };
 
 class DOMProxyHandler : public BaseDOMProxyHandler
 {
 public:
   DOMProxyHandler()
-    : BaseDOMProxyHandler(ProxyFamily())
+    : BaseDOMProxyHandler(&family)
   {
   }
 
   bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy) const MOZ_OVERRIDE;
   bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                       JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE
   {
     bool unused;
@@ -116,39 +105,33 @@ public:
   /*
    * If assigning to proxy[id] hits a named setter with OverrideBuiltins or
    * an indexed setter, call it and set *done to true on success. Otherwise, set
    * *done to false.
    */
   virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                          JS::MutableHandle<JS::Value> vp, bool *done) const;
 
-  static JSObject* GetExpandoObject(JSObject* obj)
-  {
-    MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
-    JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
-    if (v.isObject()) {
-      return &v.toObject();
-    }
+  static JSObject* GetExpandoObject(JSObject* obj);
 
-    if (v.isUndefined()) {
-      return nullptr;
-    }
-
-    js::ExpandoAndGeneration* expandoAndGeneration =
-      static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
-    v = expandoAndGeneration->expando;
-    return v.isUndefined() ? nullptr : &v.toObject();
-  }
   /* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
+
+  static const char family;
 };
 
+inline bool IsDOMProxy(JSObject *obj)
+{
+    const js::Class* clasp = js::GetObjectClass(obj);
+    return clasp->isProxy() &&
+           js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
+}
+
 inline const DOMProxyHandler*
 GetDOMProxyHandler(JSObject* obj)
 {
   MOZ_ASSERT(IsDOMProxy(obj));
   return static_cast<const DOMProxyHandler*>(js::GetProxyHandler(obj));
 }
 
 extern jsid s_length_id;
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -67,16 +67,18 @@
 #include "mozilla/EnumeratedArrayCycleCollection.h"
 
 #include "Layers.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "mozilla/layers/ShadowLayers.h"
 #endif
 
+#include <queue>
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 using namespace mozilla::layers;
 
 WebGLObserver::WebGLObserver(WebGLContext* aContext)
     : mContext(aContext)
@@ -430,23 +432,21 @@ WebGLContext::SetContextOptions(JSContex
 
     WebGLContextOptions newOpts;
 
     newOpts.stencil = attributes.mStencil;
     newOpts.depth = attributes.mDepth;
     newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
     newOpts.antialias = attributes.mAntialias;
     newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
+
     if (attributes.mAlpha.WasPassed()) {
-      newOpts.alpha = attributes.mAlpha.Value();
+        newOpts.alpha = attributes.mAlpha.Value();
     }
 
-    // enforce that if stencil is specified, we also give back depth
-    newOpts.depth |= newOpts.stencil;
-
     // Don't do antialiasing if we've disabled MSAA.
     if (!gfxPrefs::MSAALevel()) {
       newOpts.antialias = false;
     }
 
 #if 0
     GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n",
                newOpts.antialias ? 1 : 0,
@@ -466,266 +466,467 @@ WebGLContext::SetContextOptions(JSContex
     mOptions = newOpts;
     return NS_OK;
 }
 
 #ifdef DEBUG
 int32_t
 WebGLContext::GetWidth() const
 {
-  return mWidth;
+    return mWidth;
 }
 
 int32_t
 WebGLContext::GetHeight() const
 {
-  return mHeight;
+    return mHeight;
 }
 #endif
 
+/* So there are a number of points of failure here. We might fail based
+ * on EGL vs. WGL, or we might fail to alloc a too-large size, or we
+ * might not be able to create a context with a certain combo of context
+ * creation attribs.
+ *
+ * We don't want to test the complete fallback matrix. (for now, at
+ * least) Instead, attempt creation in this order:
+ * 1. By platform API. (e.g. EGL vs. WGL)
+ * 2. By context creation attribs.
+ * 3. By size.
+ *
+ * That is, try to create headless contexts based on the platform API.
+ * Next, create dummy-sized backbuffers for the contexts with the right
+ * caps. Finally, resize the backbuffer to an acceptable size given the
+ * requested size.
+ */
+
+static bool
+IsFeatureInBlacklist(const nsCOMPtr<nsIGfxInfo>& gfxInfo, int32_t feature)
+{
+    int32_t status;
+    if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status)))
+        return false;
+
+    return status != nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+static already_AddRefed<GLContext>
+CreateHeadlessNativeGL(bool forceEnabled,
+                       const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                       WebGLContext* webgl)
+{
+    if (!forceEnabled &&
+        IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_OPENGL))
+    {
+        webgl->GenerateWarning("Refused to create native OpenGL context"
+                               " because of blacklisting.");
+        return nullptr;
+    }
+
+    nsRefPtr<GLContext> gl = gl::GLContextProvider::CreateHeadless();
+    if (!gl) {
+        webgl->GenerateWarning("Error during native OpenGL init.");
+        return nullptr;
+    }
+    MOZ_ASSERT(!gl->IsANGLE());
+
+    return gl.forget();
+}
+
+// Note that we have a separate call for ANGLE and EGL, even though
+// right now, we get ANGLE implicitly by using EGL on Windows.
+// Eventually, we want to be able to pick ANGLE-EGL or native EGL.
+static already_AddRefed<GLContext>
+CreateHeadlessANGLE(bool forceEnabled,
+                    const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                    WebGLContext* webgl)
+{
+    nsRefPtr<GLContext> gl;
+
+#ifdef XP_WIN
+    if (!forceEnabled &&
+        IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE))
+    {
+        webgl->GenerateWarning("Refused to create ANGLE OpenGL context"
+                               " because of blacklisting.");
+        return nullptr;
+    }
+
+    gl = gl::GLContextProviderEGL::CreateHeadless();
+    if (!gl) {
+        webgl->GenerateWarning("Error during ANGLE OpenGL init.");
+        return nullptr;
+    }
+    MOZ_ASSERT(gl->IsANGLE());
+#endif
+
+    return gl.forget();
+}
+
+static already_AddRefed<GLContext>
+CreateHeadlessEGL(bool forceEnabled,
+                  const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                  WebGLContext* webgl)
+{
+    nsRefPtr<GLContext> gl;
+
+#ifdef ANDROID
+    gl = gl::GLContextProviderEGL::CreateHeadless();
+    if (!gl) {
+        webgl->GenerateWarning("Error during EGL OpenGL init.");
+        return nullptr;
+    }
+    MOZ_ASSERT(!gl->IsANGLE());
+#endif
+
+    return gl.forget();
+}
+
+
+static already_AddRefed<GLContext>
+CreateHeadlessGL(bool forceEnabled,
+                 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                 WebGLContext* webgl)
+{
+    bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
+    bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false);
+
+    if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
+        disableANGLE = true;
+    }
+
+    nsRefPtr<GLContext> gl;
+
+    if (preferEGL)
+        gl = CreateHeadlessEGL(forceEnabled, gfxInfo, webgl);
+
+    if (!gl && !disableANGLE)
+        gl = CreateHeadlessANGLE(forceEnabled, gfxInfo, webgl);
+
+    if (!gl)
+        gl = CreateHeadlessNativeGL(forceEnabled, gfxInfo, webgl);
+
+    return gl.forget();
+}
+
+// Try to create a dummy offscreen with the given caps.
+static bool
+CreateOffscreenWithCaps(GLContext* gl, const SurfaceCaps& caps)
+{
+    gfx::IntSize dummySize(16, 16);
+    return gl->InitOffscreen(dummySize, caps);
+}
+
+static void
+PopulateCapFallbackQueue(const SurfaceCaps& baseCaps,
+                         std::queue<SurfaceCaps>* fallbackCaps)
+{
+    fallbackCaps->push(baseCaps);
+
+    // Dropping antialias drops our quality, but not our correctness.
+    // The user basically doesn't have to handle if this fails, they
+    // just get reduced quality.
+    if (baseCaps.antialias) {
+        SurfaceCaps nextCaps(baseCaps);
+        nextCaps.antialias = false;
+        PopulateCapFallbackQueue(nextCaps, fallbackCaps);
+    }
+
+    // If we have to drop one of depth or stencil, we'd prefer to keep
+    // depth. However, the client app will need to handle if this
+    // doesn't work.
+    if (baseCaps.stencil) {
+        SurfaceCaps nextCaps(baseCaps);
+        nextCaps.stencil = false;
+        PopulateCapFallbackQueue(nextCaps, fallbackCaps);
+    }
+
+    if (baseCaps.depth) {
+        SurfaceCaps nextCaps(baseCaps);
+        nextCaps.depth = false;
+        PopulateCapFallbackQueue(nextCaps, fallbackCaps);
+    }
+}
+
+static bool
+CreateOffscreen(GLContext* gl,
+                const WebGLContextOptions& options,
+                const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                WebGLContext* webgl,
+                layers::ISurfaceAllocator* surfAllocator)
+{
+    SurfaceCaps baseCaps;
+
+    baseCaps.color = true;
+    baseCaps.alpha = options.alpha;
+    baseCaps.antialias = options.antialias;
+    baseCaps.depth = options.depth;
+    baseCaps.preserve = options.preserveDrawingBuffer;
+    baseCaps.stencil = options.stencil;
+
+    // we should really have this behind a
+    // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
+    // for now it's just behind a pref for testing/evaluation.
+    baseCaps.bpp16 = Preferences::GetBool("webgl.prefer-16bpp", false);
+
+#ifdef MOZ_WIDGET_GONK
+    baseCaps.surfaceAllocator = surfAllocator;
+#endif
+
+    // Done with baseCaps construction.
+
+    bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false);
+    if (!forceAllowAA &&
+        IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA))
+    {
+        webgl->GenerateWarning("Disallowing antialiased backbuffers due"
+                               " to blacklisting.");
+        baseCaps.antialias = false;
+    }
+
+    std::queue<SurfaceCaps> fallbackCaps;
+    PopulateCapFallbackQueue(baseCaps, &fallbackCaps);
+
+    bool created = false;
+    while (!fallbackCaps.empty()) {
+        SurfaceCaps& caps = fallbackCaps.front();
+
+        created = CreateOffscreenWithCaps(gl, caps);
+        if (created)
+            break;
+
+        fallbackCaps.pop();
+    }
+
+    return created;
+}
+
+bool
+WebGLContext::CreateOffscreenGL(bool forceEnabled)
+{
+    nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+
+    layers::ISurfaceAllocator* surfAllocator = nullptr;
+#ifdef MOZ_WIDGET_GONK
+    nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
+    if (docWidget) {
+        layers::LayerManager* layerManager = docWidget->GetLayerManager();
+        if (layerManager) {
+            // XXX we really want "AsSurfaceAllocator" here for generality
+            layers::ShadowLayerForwarder* forwarder = layerManager->AsShadowForwarder();
+            if (forwarder) {
+                surfAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
+            }
+        }
+    }
+#endif
+
+    gl = CreateHeadlessGL(forceEnabled, gfxInfo, this);
+
+    do {
+        if (!gl)
+            break;
+
+        if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator))
+            break;
+
+        if (!InitAndValidateGL())
+            break;
+
+        return true;
+    } while (false);
+
+    gl = nullptr;
+    return false;
+}
+
+// Fallback for resizes:
+bool
+WebGLContext::ResizeBackbuffer(uint32_t requestedWidth, uint32_t requestedHeight)
+{
+    uint32_t width = requestedWidth;
+    uint32_t height = requestedHeight;
+
+    bool resized = false;
+    while (width || height) {
+      width = width ? width : 1;
+      height = height ? height : 1;
+
+      gfx::IntSize curSize(width, height);
+      if (gl->ResizeOffscreen(curSize)) {
+          resized = true;
+          break;
+      }
+
+      width /= 2;
+      height /= 2;
+    }
+
+    if (!resized)
+        return false;
+
+    mWidth = gl->OffscreenSize().width;
+    mHeight = gl->OffscreenSize().height;
+    MOZ_ASSERT((uint32_t)mWidth == width);
+    MOZ_ASSERT((uint32_t)mHeight == height);
+
+    if (width != requestedWidth ||
+        height != requestedHeight)
+    {
+        GenerateWarning("Requested size %dx%d was too large, but resize"
+                          " to %dx%d succeeded.",
+                        requestedWidth, requestedHeight,
+                        width, height);
+    }
+    return true;
+}
+
+
 NS_IMETHODIMP
-WebGLContext::SetDimensions(int32_t width, int32_t height)
+WebGLContext::SetDimensions(int32_t sWidth, int32_t sHeight)
 {
     // Early error return cases
+    if (!GetCanvas())
+        return NS_ERROR_FAILURE;
 
-    if (width < 0 || height < 0) {
+    if (sWidth < 0 || sHeight < 0) {
         GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    if (!GetCanvas())
-        return NS_ERROR_FAILURE;
+    uint32_t width = sWidth;
+    uint32_t height = sHeight;
 
     // Early success return cases
-
     GetCanvas()->InvalidateCanvas();
 
-    if (gl && mWidth == width && mHeight == height)
-        return NS_OK;
-
     // Zero-sized surfaces can cause problems.
     if (width == 0) {
         width = 1;
     }
     if (height == 0) {
         height = 1;
     }
 
     // If we already have a gl context, then we just need to resize it
     if (gl) {
+        if ((uint32_t)mWidth == width &&
+            (uint32_t)mHeight == height)
+        {
+            return NS_OK;
+        }
+
+        if (IsContextLost())
+            return NS_OK;
+
         MakeContextCurrent();
 
         // If we've already drawn, we should commit the current buffer.
         PresentScreenBuffer();
 
         // ResizeOffscreen scraps the current prod buffer before making a new one.
-        gl->ResizeOffscreen(gfx::IntSize(width, height)); // Doesn't matter if it succeeds (soft-fail)
-        // It's unlikely that we'll get a proper-sized context if we recreate if we didn't on resize
+        if (!ResizeBackbuffer(width, height)) {
+            GenerateWarning("WebGL context failed to resize.");
+            ForceLoseContext();
+            return NS_OK;
+        }
 
         // everything's good, we're done here
-        mWidth = gl->OffscreenSize().width;
-        mHeight = gl->OffscreenSize().height;
         mResetLayer = true;
-
         mBackbufferNeedsClear = true;
 
         return NS_OK;
     }
 
     // End of early return cases.
     // At this point we know that we're not just resizing an existing context,
     // we are initializing a new context.
 
     // if we exceeded either the global or the per-principal limit for WebGL contexts,
     // lose the oldest-used context now to free resources. Note that we can't do that
     // in the WebGLContext constructor as we don't have a canvas element yet there.
     // Here is the right place to do so, as we are about to create the OpenGL context
     // and that is what can fail if we already have too many.
     LoseOldestWebGLContextIfLimitExceeded();
 
-    // Get some prefs for some preferred/overriden things
-    NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
-
-#ifdef XP_WIN
-    bool preferEGL =
-        Preferences::GetBool("webgl.prefer-egl", false);
-    bool preferOpenGL =
-        Preferences::GetBool("webgl.prefer-native-gl", false);
-#endif
-    bool forceEnabled =
-        Preferences::GetBool("webgl.force-enabled", false);
-    bool disabled =
-        Preferences::GetBool("webgl.disabled", false);
-    bool prefer16bit =
-        Preferences::GetBool("webgl.prefer-16bpp", false);
-
-    ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
-
-    if (disabled)
-        return NS_ERROR_FAILURE;
-
     // We're going to create an entirely new context.  If our
     // generation is not 0 right now (that is, if this isn't the first
     // context we're creating), we may have to dispatch a context lost
     // event.
 
     // If incrementing the generation would cause overflow,
     // don't allow it.  Allowing this would allow us to use
     // resource handles created from older context generations.
-    if (!(mGeneration + 1).isValid())
+    if (!(mGeneration + 1).isValid()) {
+        GenerateWarning("Too many WebGL contexts created this run.");
         return NS_ERROR_FAILURE; // exit without changing the value of mGeneration
-
-    SurfaceCaps caps;
-
-    caps.color = true;
-    caps.alpha = mOptions.alpha;
-    caps.depth = mOptions.depth;
-    caps.stencil = mOptions.stencil;
-
-    // we should really have this behind a
-    // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
-    // for now it's just behind a pref for testing/evaluation.
-    caps.bpp16 = prefer16bit;
-
-    caps.preserve = mOptions.preserveDrawingBuffer;
-
-#ifdef MOZ_WIDGET_GONK
-    nsIWidget *docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
-    if (docWidget) {
-        layers::LayerManager *layerManager = docWidget->GetLayerManager();
-        if (layerManager) {
-            // XXX we really want "AsSurfaceAllocator" here for generality
-            layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
-            if (forwarder) {
-                caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
-            }
-        }
-    }
-#endif
-
-    bool forceMSAA =
-        Preferences::GetBool("webgl.msaa-force", false);
-
-    int32_t status;
-    nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
-    if (mOptions.antialias &&
-        gfxInfo &&
-        NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_MSAA, &status))) {
-        if (status == nsIGfxInfo::FEATURE_STATUS_OK || forceMSAA) {
-            caps.antialias = true;
-        }
     }
 
-#ifdef XP_WIN
-    if (PR_GetEnv("MOZ_WEBGL_PREFER_EGL")) {
-        preferEGL = true;
-    }
-#endif
-
-    // Ask GfxInfo about what we should use
-    bool useOpenGL = true;
-
-#ifdef XP_WIN
-    bool useANGLE = true;
-#endif
+    // Get some prefs for some preferred/overriden things
+    NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
 
-    if (gfxInfo && !forceEnabled) {
-        if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_OPENGL, &status))) {
-            if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
-                useOpenGL = false;
-            }
-        }
-#ifdef XP_WIN
-        if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &status))) {
-            if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
-                useANGLE = false;
-            }
-        }
-#endif
+    bool disabled = Preferences::GetBool("webgl.disabled", false);
+    if (disabled) {
+        GenerateWarning("WebGL creation is disabled, and so disallowed here.");
+        return NS_ERROR_FAILURE;
     }
 
-#ifdef XP_WIN
-    // allow forcing GL and not EGL/ANGLE
-    if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
-        preferEGL = false;
-        useANGLE = false;
-        useOpenGL = true;
-    }
-#endif
-
-    gfxIntSize size(width, height);
+    // Alright, now let's start trying.
+    bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false);
+    ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
 
-#ifdef XP_WIN
-    // if we want EGL, try it now
-    if (!gl && (preferEGL || useANGLE) && !preferOpenGL) {
-        gl = gl::GLContextProviderEGL::CreateOffscreen(size, caps);
-        if (!gl || !InitAndValidateGL()) {
-            GenerateWarning("Error during ANGLE OpenGL ES initialization");
-            return NS_ERROR_FAILURE;
-        }
+    if (!CreateOffscreenGL(forceEnabled)) {
+        GenerateWarning("WebGL creation failed.");
+        return NS_ERROR_FAILURE;
     }
-#endif
+    MOZ_ASSERT(gl);
 
-    // try the default provider, whatever that is
-    if (!gl && useOpenGL) {
-        gl = gl::GLContextProvider::CreateOffscreen(size, caps);
-        if (gl && !InitAndValidateGL()) {
-            GenerateWarning("Error during OpenGL initialization");
-            return NS_ERROR_FAILURE;
-        }
-    }
-
-    if (!gl) {
-        GenerateWarning("Can't get a usable WebGL context");
+    if (!ResizeBackbuffer(width, height)) {
+        GenerateWarning("Initializing WebGL backbuffer failed.");
         return NS_ERROR_FAILURE;
     }
 
 #ifdef DEBUG
     if (gl->DebugMode()) {
         printf_stderr("--- WebGL context created: %p\n", gl.get());
     }
 #endif
 
-    mWidth = width;
-    mHeight = height;
-    mViewportWidth = width;
-    mViewportHeight = height;
     mResetLayer = true;
     mOptionsFrozen = true;
 
     // increment the generation number
     ++mGeneration;
-#if 0
-    if (mGeneration > 0) {
-        // XXX dispatch context lost event
-    }
-#endif
 
     MakeContextCurrent();
 
+    gl->fViewport(0, 0, mWidth, mHeight);
+    mViewportWidth = mWidth;
+    mViewportHeight = 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
     // buffer.
     mBackbufferNeedsClear = true;
     ClearBackbufferIfNeeded();
 
     mShouldPresent = true;
 
-    MOZ_ASSERT(gl->Caps().color == caps.color);
-    MOZ_ASSERT(gl->Caps().alpha == caps.alpha);
-    MOZ_ASSERT(gl->Caps().depth == caps.depth || !gl->Caps().depth);
-    MOZ_ASSERT(gl->Caps().stencil == caps.stencil || !gl->Caps().stencil);
-    MOZ_ASSERT(gl->Caps().antialias == caps.antialias || !gl->Caps().antialias);
-    MOZ_ASSERT(gl->Caps().preserve == caps.preserve);
+    MOZ_ASSERT(gl->Caps().color);
+    MOZ_ASSERT(gl->Caps().alpha == mOptions.alpha);
+    MOZ_ASSERT(gl->Caps().depth == mOptions.depth || !gl->Caps().depth);
+    MOZ_ASSERT(gl->Caps().stencil == mOptions.stencil || !gl->Caps().stencil);
+    MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias || !gl->Caps().antialias);
+    MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
 
     AssertCachedBindings();
     AssertCachedState();
 
     reporter.SetSuccessful();
     return NS_OK;
 }
 
@@ -1206,17 +1407,17 @@ WebGLContext::PresentScreenBuffer()
 
     if (!mShouldPresent) {
         return false;
     }
 
     gl->MakeCurrent();
     MOZ_ASSERT(!mBackbufferNeedsClear);
     if (!gl->PublishFrame()) {
-        this->ForceLoseContext();
+        ForceLoseContext();
         return false;
     }
 
     if (!mOptions.preserveDrawingBuffer) {
         mBackbufferNeedsClear = true;
     }
 
     mShouldPresent = false;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1031,17 +1031,19 @@ protected:
 
     bool InitWebGL2();
 
 
     // -------------------------------------------------------------------------
     // Validation functions (implemented in WebGLContextValidate.cpp)
     GLenum BaseTexFormat(GLenum internalFormat) const;
 
+    bool CreateOffscreenGL(bool forceEnabled);
     bool InitAndValidateGL();
+    bool ResizeBackbuffer(uint32_t width, uint32_t height);
     bool ValidateBlendEquationEnum(GLenum cap, const char *info);
     bool ValidateBlendFuncDstEnum(GLenum mode, const char *info);
     bool ValidateBlendFuncSrcEnum(GLenum mode, const char *info);
     bool ValidateBlendFuncEnumsCompatibility(GLenum sfactor, GLenum dfactor, const char *info);
     bool ValidateTextureTargetEnum(GLenum target, const char *info);
     bool ValidateComparisonEnum(GLenum target, const char *info);
     bool ValidateStencilOpEnum(GLenum action, const char *info);
     bool ValidateFaceEnum(GLenum face, const char *info);
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -170,20 +170,20 @@ pref(webgl.force-layers-readback,true) r
 
 # Check that our experimental prefs still work:
 
 # 16bpp:
 skip-if(winWidget) pref(webgl.prefer-16bpp,true)                                        random-if(Android&&AndroidVersion<15)  == webgl-color-test.html?16bpp           wrapper.html?colors.png
 skip-if(winWidget) pref(webgl.prefer-16bpp,true) pref(webgl.force-layers-readback,true) random-if(Android&&AndroidVersion<15)  == webgl-color-test.html?16bpp&readback  wrapper.html?colors.png
 
 # Force native GL (Windows):
-skip-if(!winWidget) pref(webgl.prefer-native-gl,true)                                == webgl-clear-test.html?native-gl        wrapper.html?green.png
-skip-if(!winWidget) pref(webgl.prefer-native-gl,true)                                == webgl-orientation-test.html?native-gl  wrapper.html?white-top-left.png
-skip-if(!winWidget) pref(webgl.prefer-native-gl,true)                                == webgl-color-test.html?native-gl        wrapper.html?colors.png
-skip-if(!winWidget) pref(webgl.prefer-native-gl,true) pref(webgl.prefer-16bpp,true)  == webgl-color-test.html?native-gl&16bpp  wrapper.html?colors.png
+skip-if(!winWidget) pref(webgl.disable-angle,true)                                == webgl-clear-test.html?native-gl        wrapper.html?green.png
+skip-if(!winWidget) pref(webgl.disable-angle,true)                                == webgl-orientation-test.html?native-gl  wrapper.html?white-top-left.png
+skip-if(!winWidget) pref(webgl.disable-angle,true)                                == webgl-color-test.html?native-gl        wrapper.html?colors.png
+skip-if(!winWidget) pref(webgl.disable-angle,true) pref(webgl.prefer-16bpp,true)  == webgl-color-test.html?native-gl&16bpp  wrapper.html?colors.png
 
 
 # Non-WebGL Reftests!
 
 # Do we correctly handle multiple clip paths?
 != clip-multiple-paths.html clip-multiple-paths-badref.html
 
 # Bug 815648
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -2276,16 +2276,44 @@ PeerConnectionWrapper.prototype = {
       ok(numRemoteCandidates, "Have remotecandidate stat(s)");
     } else {
       is(numLocalCandidates, 0, "Have no localcandidate stats");
       is(numRemoteCandidates, 0, "Have no remotecandidate stats");
     }
   },
 
   /**
+   * Property-matching function for finding a certain stat in passed-in stats
+   *
+   * @param {object} stats
+   *        The stats to check from this PeerConnectionWrapper
+   * @param {object} props
+   *        The properties to look for
+   * @returns {boolean} Whether an entry containing all match-props was found.
+   */
+  hasStat : function PCW_hasStat(stats, props) {
+    for (var key in stats) {
+      if (stats.hasOwnProperty(key)) {
+        var res = stats[key];
+        var match = true;
+        for (var prop in props) {
+          if (res[prop] !== props[prop]) {
+            match = false;
+            break;
+          }
+        }
+        if (match) {
+          return true;
+        }
+      }
+    }
+    return false;
+  },
+
+  /**
    * Closes the connection
    */
   close : function PCW_close() {
     // It might be that a test has already closed the pc. In those cases
     // we should not fail.
     try {
       this._pc.close();
       info(this + ": Closed connection.");
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -408,16 +408,184 @@ var commandsPeerConnection = [
   [
     'PC_REMOTE_CHECK_STATS',
     function (test) {
       test.pcRemote.getStats(null, function(stats) {
         test.pcRemote.checkStats(stats);
         test.next();
       });
     }
+  ],
+  [
+    'PC_LOCAL_CHECK_GETSTATS_AUDIOTRACK_OUTBOUND',
+    function (test) {
+      var pc = test.pcLocal;
+      var stream = pc._pc.getLocalStreams()[0];
+      var track = stream && stream.getAudioTracks()[0];
+      if (track) {
+        var msg = "pcLocal.HasStat outbound audio rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"outboundrtp", isRemote:false, mediaType:"audio" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_LOCAL_CHECK_GETSTATS_VIDEOTRACK_OUTBOUND',
+    function (test) {
+      var pc = test.pcLocal;
+      var stream = pc._pc.getLocalStreams()[0];
+      var track = stream && stream.getVideoTracks()[0];
+      if (track) {
+        var msg = "pcLocal.HasStat outbound video rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"outboundrtp", isRemote:false, mediaType:"video" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_LOCAL_CHECK_GETSTATS_AUDIOTRACK_INBOUND',
+    function (test) {
+      var pc = test.pcLocal;
+      var stream = pc._pc.getRemoteStreams()[0];
+      var track = stream && stream.getAudioTracks()[0];
+      if (track) {
+        var msg = "pcLocal.HasStat inbound audio rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"inboundrtp", isRemote:false, mediaType:"audio" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_LOCAL_CHECK_GETSTATS_VIDEOTRACK_INBOUND',
+    function (test) {
+      var pc = test.pcLocal;
+      var stream = pc._pc.getRemoteStreams()[0];
+      var track = stream && stream.getVideoTracks()[0];
+      if (track) {
+        var msg = "pcLocal.HasStat inbound video rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"inboundrtp", isRemote:false, mediaType:"video" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_REMOTE_CHECK_GETSTATS_AUDIOTRACK_OUTBOUND',
+    function (test) {
+      var pc = test.pcRemote;
+      var stream = pc._pc.getLocalStreams()[0];
+      var track = stream && stream.getAudioTracks()[0];
+      if (track) {
+        var msg = "pcRemote.HasStat outbound audio rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"outboundrtp", isRemote:false, mediaType:"audio" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_REMOTE_CHECK_GETSTATS_VIDEOTRACK_OUTBOUND',
+    function (test) {
+      var pc = test.pcRemote;
+      var stream = pc._pc.getLocalStreams()[0];
+      var track = stream && stream.getVideoTracks()[0];
+      if (track) {
+        var msg = "pcRemote.HasStat outbound audio rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"outboundrtp", isRemote:false, mediaType:"video" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_REMOTE_CHECK_GETSTATS_AUDIOTRACK_INBOUND',
+    function (test) {
+      var pc = test.pcRemote;
+      var stream = pc._pc.getRemoteStreams()[0];
+      var track = stream && stream.getAudioTracks()[0];
+      if (track) {
+        var msg = "pcRemote.HasStat inbound audio rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"inboundrtp", isRemote:false, mediaType:"audio" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
+  ],
+  [
+    'PC_REMOTE_CHECK_GETSTATS_VIDEOTRACK_INBOUND',
+    function (test) {
+      var pc = test.pcRemote;
+      var stream = pc._pc.getRemoteStreams()[0];
+      var track = stream && stream.getVideoTracks()[0];
+      if (track) {
+        var msg = "pcRemote.HasStat inbound video rtp ";
+        pc.getStats(track, function(stats) {
+          ok(pc.hasStat(stats,
+                        { type:"inboundrtp", isRemote:false, mediaType:"video" }),
+             msg + "1");
+          ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
+          ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
+          test.next();
+        });
+      } else {
+        test.next();
+      }
+    }
   ]
 ];
 
 
 /**
  * Default list of commands to execute for a Datachannel test.
  */
 var commandsDataChannel = [
--- a/dom/mobilemessage/interfaces/moz.build
+++ b/dom/mobilemessage/interfaces/moz.build
@@ -4,17 +4,16 @@
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIDeletedMessageInfo.idl',
     'nsIDOMMozMmsMessage.idl',
     'nsIDOMMozMobileMessageThread.idl',
     'nsIDOMMozSmsMessage.idl',
-    'nsIDOMSmsFilter.idl',
     'nsIMmsService.idl',
     'nsIMobileMessageCallback.idl',
     'nsIMobileMessageCursorCallback.idl',
     'nsIMobileMessageDatabaseService.idl',
     'nsIMobileMessageService.idl',
     'nsISmsService.idl',
     'nsIWapPushApplication.idl',
 ]
deleted file mode 100644
--- a/dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl
+++ /dev/null
@@ -1,33 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-[scriptable, builtinclass, uuid(17890b60-0367-45c6-9729-62e5bf349b2b)]
-interface nsIDOMMozSmsFilter : nsISupports
-{
-  // A date that can return null.
-  [implicit_jscontext]
-  attribute jsval startDate;
-
-  // A date that can return null.
-  [implicit_jscontext]
-  attribute jsval endDate;
-
-  // An array of DOMString that can return null.
-  [implicit_jscontext]
-  attribute jsval numbers;
-
-  // A DOMString that can return and be set to "sent", "received" or null.
-  [Null(Empty)]
-  attribute DOMString delivery;
-
-  // A read flag that can return and be set to a boolean or null.
-  [implicit_jscontext]
-  attribute jsval read;
-
-  // A thread id that can return and be set to a numeric value or null.
-  [implicit_jscontext]
-  attribute jsval threadId;
-};
--- a/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
@@ -7,32 +7,40 @@
 %{C++
 #define MOBILE_MESSAGE_DATABASE_SERVICE_CID \
 { 0x0d84b9c2, 0x8f76, 0x4ba4,    \
 { 0xa5, 0xcd, 0xdb, 0xfb, 0x01, 0xdf, 0xda, 0x99 } }
 #define MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID "@mozilla.org/mobilemessage/mobilemessagedatabaseservice;1"
 %}
 
 interface nsICursorContinueCallback;
-interface nsIDOMMozSmsFilter;
 interface nsIMobileMessageCallback;
 interface nsIMobileMessageCursorCallback;
 
-[scriptable, uuid(8439916f-abc1-4c67-aa45-8a276a0a7855)]
+[scriptable, uuid(ead626bc-f5b4-47e1-921c-0b956c9298e0)]
 interface nsIMobileMessageDatabaseService : nsISupports
 {
   [binaryname(GetMessageMoz)]
   void getMessage(in long messageId,
                   in nsIMobileMessageCallback request);
 
   void deleteMessage([array, size_is(count)] in long messageIds,
                      in uint32_t count,
                      in nsIMobileMessageCallback request);
 
-  nsICursorContinueCallback createMessageCursor(in nsIDOMMozSmsFilter filter,
+  nsICursorContinueCallback createMessageCursor(in boolean hasStartDate,
+                                                in unsigned long long startDate,
+                                                in boolean hasEndDate,
+                                                in unsigned long long endDate,
+                                                [array, size_is(numbersCount)] in wstring numbers,
+                                                in uint32_t numbersCount,
+                                                [Null(Null), Undefined(Null)] in DOMString delivery,
+                                                in boolean hasRead,
+                                                in boolean read,
+                                                in unsigned long long threadId,
                                                 in boolean reverse,
                                                 in nsIMobileMessageCursorCallback callback);
 
   void markMessageRead(in long messageId,
                        in boolean value,
                        in boolean sendReadReport,
                        in nsIMobileMessageCallback request);
 
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -21,17 +21,16 @@
 #include "nsIDOMMozMmsMessage.h"
 #include "nsIDOMMozSmsMessage.h"
 #include "nsIMmsService.h"
 #include "nsIMobileMessageCallback.h"
 #include "nsIMobileMessageDatabaseService.h"
 #include "nsIObserverService.h"
 #include "nsISmsService.h"
 #include "nsServiceManagerUtils.h" // For do_GetService()
-#include "SmsFilter.h"
 
 #define RECEIVED_EVENT_NAME         NS_LITERAL_STRING("received")
 #define RETRIEVING_EVENT_NAME       NS_LITERAL_STRING("retrieving")
 #define SENDING_EVENT_NAME          NS_LITERAL_STRING("sending")
 #define SENT_EVENT_NAME             NS_LITERAL_STRING("sent")
 #define FAILED_EVENT_NAME           NS_LITERAL_STRING("failed")
 #define DELIVERY_SUCCESS_EVENT_NAME NS_LITERAL_STRING("deliverysuccess")
 #define DELIVERY_ERROR_EVENT_NAME   NS_LITERAL_STRING("deliveryerror")
@@ -362,37 +361,83 @@ MobileMessageManager::Delete(const Seque
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   return Delete(idArray.Elements(), size, aRv);
 }
 
 already_AddRefed<DOMCursor>
-MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
+MobileMessageManager::GetMessages(const MobileMessageFilter& aFilter,
                                   bool aReverse,
                                   ErrorResult& aRv)
 {
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
     do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
   if (!dbService) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  nsCOMPtr<nsIDOMMozSmsFilter> filter = aFilter;
-  if (!filter) {
-    filter = new SmsFilter();
+  bool hasStartDate = !aFilter.mStartDate.IsNull();
+  uint64_t startDate = 0;
+  if (hasStartDate) {
+    startDate = aFilter.mStartDate.Value();
+  }
+
+  bool hasEndDate = !aFilter.mEndDate.IsNull();
+  uint64_t endDate = 0;
+  if (hasEndDate) {
+    endDate = aFilter.mEndDate.Value();
+  }
+
+  nsAutoArrayPtr<const char16_t*> ptrNumbers;
+  uint32_t numbersCount = 0;
+  if (!aFilter.mNumbers.IsNull() &&
+      aFilter.mNumbers.Value().Length()) {
+    const FallibleTArray<nsString>& numbers = aFilter.mNumbers.Value();
+    uint32_t index;
+
+    numbersCount = numbers.Length();
+    ptrNumbers = new const char16_t* [numbersCount];
+    for (index = 0; index < numbersCount; index++) {
+      ptrNumbers[index] = numbers[index].get();
+    }
+  }
+
+  nsString delivery;
+  delivery.SetIsVoid(true);
+  if (!aFilter.mDelivery.IsNull()) {
+    const uint32_t index = static_cast<uint32_t>(aFilter.mDelivery.Value());
+    const EnumEntry& entry =
+      MobileMessageFilterDeliveryValues::strings[index];
+    delivery.AssignASCII(entry.value, entry.length);
+  }
+
+  bool hasRead = !aFilter.mRead.IsNull();
+  bool read = false;
+  if (hasRead) {
+    read = aFilter.mRead.Value();
+  }
+
+  uint64_t threadId = 0;
+  if (!aFilter.mThreadId.IsNull()) {
+    threadId = aFilter.mThreadId.Value();
   }
 
   nsRefPtr<MobileMessageCursorCallback> cursorCallback =
     new MobileMessageCursorCallback();
-
   nsCOMPtr<nsICursorContinueCallback> continueCallback;
-  nsresult rv = dbService->CreateMessageCursor(filter, aReverse, cursorCallback,
+  nsresult rv = dbService->CreateMessageCursor(hasStartDate, startDate,
+                                               hasEndDate, endDate,
+                                               ptrNumbers, numbersCount,
+                                               delivery,
+                                               hasRead, read,
+                                               threadId,
+                                               aReverse, cursorCallback,
                                                getter_AddRefs(continueCallback));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   cursorCallback->mDOMCursor = new DOMCursor(GetOwner(), continueCallback);
 
--- a/dom/mobilemessage/src/MobileMessageManager.h
+++ b/dom/mobilemessage/src/MobileMessageManager.h
@@ -9,25 +9,25 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsIObserver.h"
 
 class nsISmsService;
 class nsIDOMMozSmsMessage;
 class nsIDOMMozMmsMessage;
-class nsIDOMMozSmsFilter;
 
 namespace mozilla {
 namespace dom {
 
 class DOMRequest;
 class DOMCursor;
 struct MmsParameters;
 struct MmsSendParameters;
+struct MobileMessageFilter;
 struct SmsSendParameters;
 
 class MobileMessageManager MOZ_FINAL : public DOMEventTargetHelper
                                      , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIOBSERVER
@@ -85,17 +85,17 @@ public:
   Delete(nsIDOMMozMmsMessage* aMessage,
          ErrorResult& aRv);
 
   already_AddRefed<DOMRequest>
   Delete(const Sequence<OwningLongOrMozSmsMessageOrMozMmsMessage>& aParams,
          ErrorResult& aRv);
 
   already_AddRefed<DOMCursor>
-  GetMessages(nsIDOMMozSmsFilter* aFilter,
+  GetMessages(const MobileMessageFilter& aFilter,
               bool aReverse,
               ErrorResult& aRv);
 
   already_AddRefed<DOMRequest>
   MarkMessageRead(int32_t aId,
                   bool aRead,
                   bool aSendReadReport,
                   ErrorResult& aRv);
deleted file mode 100644
--- a/dom/mobilemessage/src/SmsFilter.cpp
+++ /dev/null
@@ -1,292 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "SmsFilter.h"
-#include "jsapi.h"
-#include "jsfriendapi.h" // For js_DateGetMsecSinceEpoch.
-#include "js/Utility.h"
-#include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
-#include "mozilla/dom/ToJSValue.h"
-#include "nsDOMString.h"
-#include "nsError.h"
-#include "nsIDOMClassInfo.h"
-#include "nsJSUtils.h"
-
-using namespace mozilla::dom::mobilemessage;
-
-DOMCI_DATA(MozSmsFilter, mozilla::dom::SmsFilter)
-
-namespace mozilla {
-namespace dom {
-
-NS_INTERFACE_MAP_BEGIN(SmsFilter)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsFilter)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsFilter)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(SmsFilter)
-NS_IMPL_RELEASE(SmsFilter)
-
-SmsFilter::SmsFilter()
-{
-  mData.startDate() = 0;
-  mData.endDate() = 0;
-  mData.delivery() = eDeliveryState_Unknown;
-  mData.read() = eReadState_Unknown;
-  mData.threadId() = 0;
-}
-
-SmsFilter::SmsFilter(const SmsFilterData& aData)
-  : mData(aData)
-{
-}
-
-/* static */ nsresult
-SmsFilter::NewSmsFilter(nsISupports** aSmsFilter)
-{
-  NS_ADDREF(*aSmsFilter = new SmsFilter());
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetStartDate(JSContext* aCx, JS::MutableHandle<JS::Value> aStartDate)
-{
-  if (mData.startDate() == 0) {
-    aStartDate.setNull();
-    return NS_OK;
-  }
-
-  aStartDate.setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.startDate()));
-  NS_ENSURE_TRUE(aStartDate.isObject(), NS_ERROR_FAILURE);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetStartDate(JSContext* aCx, JS::Handle<JS::Value> aStartDate)
-{
-  if (aStartDate.isNull()) {
-    mData.startDate() = 0;
-    return NS_OK;
-  }
-
-  if (!aStartDate.isObject()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  JS::Rooted<JSObject*> obj(aCx, &aStartDate.toObject());
-  if (!JS_ObjectIsDate(aCx, obj)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  mData.startDate() = js_DateGetMsecSinceEpoch(obj);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetEndDate(JSContext* aCx, JS::MutableHandle<JS::Value> aEndDate)
-{
-  if (mData.endDate() == 0) {
-    aEndDate.setNull();
-    return NS_OK;
-  }
-
-  aEndDate.setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.endDate()));
-  NS_ENSURE_TRUE(aEndDate.isObject(), NS_ERROR_FAILURE);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetEndDate(JSContext* aCx, JS::Handle<JS::Value> aEndDate)
-{
-  if (aEndDate.isNull()) {
-    mData.endDate() = 0;
-    return NS_OK;
-  }
-
-  if (!aEndDate.isObject()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  JS::Rooted<JSObject*> obj(aCx, &aEndDate.toObject());
-  if (!JS_ObjectIsDate(aCx, obj)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  mData.endDate() = js_DateGetMsecSinceEpoch(obj);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetNumbers(JSContext* aCx, JS::MutableHandle<JS::Value> aNumbers)
-{
-  uint32_t length = mData.numbers().Length();
-
-  if (length == 0) {
-    aNumbers.setNull();
-    return NS_OK;
-  }
-
-  if (!ToJSValue(aCx, mData.numbers(), aNumbers)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetNumbers(JSContext* aCx, JS::Handle<JS::Value> aNumbers)
-{
-  if (aNumbers.isNull()) {
-    mData.numbers().Clear();
-    return NS_OK;
-  }
-
-  if (!aNumbers.isObject()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  JS::Rooted<JSObject*> obj(aCx, &aNumbers.toObject());
-  if (!JS_IsArrayObject(aCx, obj)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  uint32_t size;
-  MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, obj, &size));
-
-  nsTArray<nsString> numbers;
-
-  for (uint32_t i=0; i<size; ++i) {
-    JS::Rooted<JS::Value> jsNumber(aCx);
-    if (!JS_GetElement(aCx, obj, i, &jsNumber)) {
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    if (!jsNumber.isString()) {
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    nsAutoJSString number;
-    if (!number.init(aCx, jsNumber.toString())) {
-      return NS_ERROR_FAILURE;
-    }
-
-    numbers.AppendElement(number);
-  }
-
-  mData.numbers().Clear();
-  mData.numbers().AppendElements(numbers);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetDelivery(nsAString& aDelivery)
-{
-  switch (mData.delivery()) {
-    case eDeliveryState_Received:
-      aDelivery = DELIVERY_RECEIVED;
-      break;
-    case eDeliveryState_Sent:
-      aDelivery = DELIVERY_SENT;
-      break;
-    case eDeliveryState_Unknown:
-      SetDOMStringToNull(aDelivery);
-      break;
-    default:
-      NS_ASSERTION(false, "We shouldn't get another delivery state!");
-      return NS_ERROR_UNEXPECTED;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetDelivery(const nsAString& aDelivery)
-{
-  if (aDelivery.IsEmpty()) {
-    mData.delivery() = eDeliveryState_Unknown;
-    return NS_OK;
-  }
-
-  if (aDelivery.Equals(DELIVERY_RECEIVED)) {
-    mData.delivery() = eDeliveryState_Received;
-    return NS_OK;
-  }
-
-  if (aDelivery.Equals(DELIVERY_SENT)) {
-    mData.delivery() = eDeliveryState_Sent;
-    return NS_OK;
-  }
-
-  return NS_ERROR_INVALID_ARG;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetRead(JSContext* aCx, JS::MutableHandle<JS::Value> aRead)
-{
-  if (mData.read() == eReadState_Unknown) {
-    aRead.setNull();
-    return NS_OK;
-  }
-
-  aRead.setBoolean(mData.read());
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetRead(JSContext* aCx, JS::Handle<JS::Value> aRead)
-{
-  if (aRead.isNull()) {
-    mData.read() = eReadState_Unknown;
-    return NS_OK;
-  }
-
-  if (!aRead.isBoolean()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  mData.read() = aRead.toBoolean() ? eReadState_Read : eReadState_Unread;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::GetThreadId(JSContext* aCx, JS::MutableHandle<JS::Value> aThreadId)
-{
-  if (!mData.threadId()) {
-    aThreadId.setNull();
-    return NS_OK;
-  }
-
-  aThreadId.setNumber(static_cast<double>(mData.threadId()));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetThreadId(JSContext* aCx, JS::Handle<JS::Value> aThreadId)
-{
-  if (aThreadId.isNull()) {
-    mData.threadId() = 0;
-    return NS_OK;
-  }
-
-  if (!aThreadId.isNumber()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  double number = aThreadId.toNumber();
-  uint64_t integer = static_cast<uint64_t>(number);
-  if (integer == 0 || integer != number) {
-    return NS_ERROR_INVALID_ARG;
-  }
-  mData.threadId() = integer;
-
-  return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/mobilemessage/src/SmsFilter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_mobilemessage_SmsFilter_h
-#define mozilla_dom_mobilemessage_SmsFilter_h
-
-#include "mozilla/dom/mobilemessage/SmsTypes.h"
-#include "nsIDOMSmsFilter.h"
-#include "mozilla/Attributes.h"
-
-namespace mozilla {
-namespace dom {
-
-class SmsFilter MOZ_FINAL : public nsIDOMMozSmsFilter
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIDOMMOZSMSFILTER
-
-  SmsFilter();
-  SmsFilter(const mobilemessage::SmsFilterData& aData);
-
-  const mobilemessage::SmsFilterData& GetData() const;
-
-  static nsresult NewSmsFilter(nsISupports** aSmsFilter);
-
-private:
-  ~SmsFilter() {}
-
-  mobilemessage::SmsFilterData mData;
-};
-
-inline const mobilemessage::SmsFilterData&
-SmsFilter::GetData() const {
-  return mData;
-}
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_mobilemessage_SmsFilter_h
--- a/dom/mobilemessage/src/Types.h
+++ b/dom/mobilemessage/src/Types.h
@@ -43,26 +43,17 @@ enum ReadStatus {
   eReadStatus_NotApplicable = 0,
   eReadStatus_Success,
   eReadStatus_Pending,
   eReadStatus_Error,
   // This state should stay at the end.
   eReadStatus_EndGuard
 };
 
-// For {Mms,Sms}FilterData.read.
-enum ReadState {
-  eReadState_Unknown = -1,
-  eReadState_Unread,
-  eReadState_Read,
-  // This state should stay at the end.
-  eReadState_EndGuard
-};
-
-// For {Mms,Sms}FilterData.messageClass.
+// For {Mms,Sms}MessageData.messageClass.
 enum MessageClass {
   eMessageClass_Normal = 0,
   eMessageClass_Class0,
   eMessageClass_Class1,
   eMessageClass_Class2,
   eMessageClass_Class3,
   // This state should stay at the end.
   eMessageClass_EndGuard
@@ -111,27 +102,16 @@ template <>
 struct ParamTraits<mozilla::dom::mobilemessage::ReadStatus>
   : public ContiguousEnumSerializer<
              mozilla::dom::mobilemessage::ReadStatus,
              mozilla::dom::mobilemessage::eReadStatus_NotApplicable,
              mozilla::dom::mobilemessage::eReadStatus_EndGuard>
 {};
 
 /**
- * Read state serializer.
- */
-template <>
-struct ParamTraits<mozilla::dom::mobilemessage::ReadState>
-  : public ContiguousEnumSerializer<
-             mozilla::dom::mobilemessage::ReadState,
-             mozilla::dom::mobilemessage::eReadState_Unknown,
-             mozilla::dom::mobilemessage::eReadState_EndGuard>
-{};
-
-/**
  * Message class serializer.
  */
 template <>
 struct ParamTraits<mozilla::dom::mobilemessage::MessageClass>
   : public ContiguousEnumSerializer<
              mozilla::dom::mobilemessage::MessageClass,
              mozilla::dom::mobilemessage::eMessageClass_Normal,
              mozilla::dom::mobilemessage::eMessageClass_EndGuard>
--- a/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
+++ b/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
@@ -1,14 +1,13 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "SmsFilter.h"
 #include "MobileMessageDatabaseService.h"
 #include "AndroidBridge.h"
 
 namespace mozilla {
 namespace dom {
 namespace mobilemessage {
 
 NS_IMPL_ISUPPORTS(MobileMessageDatabaseService, nsIMobileMessageDatabaseService)
@@ -42,17 +41,26 @@ MobileMessageDatabaseService::DeleteMess
     return NS_ERROR_FAILURE;
   }
 
   AndroidBridge::Bridge()->DeleteMessage(aMessageIds[0], aRequest);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MobileMessageDatabaseService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
+MobileMessageDatabaseService::CreateMessageCursor(bool aHasStartDate,
+                                                  uint64_t aStartDate,
+                                                  bool aHasEndDate,
+                                                  uint64_t aEndDate,
+                                                  const char16_t** aNumbers,
+                                                  uint32_t aNumbersCount,
+                                                  const nsAString& aDelivery,
+                                                  bool aHasRead,
+                                                  bool aRead,
+                                                  uint64_t aThreadId,
                                                   bool aReverse,
                                                   nsIMobileMessageCursorCallback* aCallback,
                                                   nsICursorContinueCallback** aResult)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -3100,35 +3100,57 @@ MobileMessageDB.prototype = {
               updateThreadInfo();
             }
           }
         }.bind(null, i);
       }
     }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]);
   },
 
-  createMessageCursor: function(filter, reverse, callback) {
+  createMessageCursor: function(aHasStartDate, aStartDate, aHasEndDate,
+                                aEndDate, aNumbers, aNumbersCount, aDelivery,
+                                aHasRead, aRead, aThreadId, aReverse, aCallback) {
     if (DEBUG) {
       debug("Creating a message cursor. Filters:" +
-            " startDate: " + filter.startDate +
-            " endDate: " + filter.endDate +
-            " delivery: " + filter.delivery +
-            " numbers: " + filter.numbers +
-            " read: " + filter.read +
-            " threadId: " + filter.threadId +
-            " reverse: " + reverse);
+            " startDate: " + (aHasStartDate ? aStartDate : "(null)") +
+            " endDate: " + (aHasEndDate ? aEndDate : "(null)") +
+            " delivery: " + aDelivery +
+            " numbers: " + (aNumbersCount ? aNumbers : "(null)") +
+            " read: " + (aHasRead ? aRead : "(null)") +
+            " threadId: " + aThreadId +
+            " reverse: " + aReverse);
+    }
+
+    let filter = {};
+    if (aHasStartDate) {
+      filter.startDate = aStartDate;
     }
-
-    let cursor = new GetMessagesCursor(this, callback);
+    if (aHasEndDate) {
+      filter.endDate = aEndDate;
+    }
+    if (aNumbersCount) {
+      filter.numbers = aNumbers.slice();
+    }
+    if (aDelivery !== null) {
+      filter.delivery = aDelivery;
+    }
+    if (aHasRead) {
+      filter.read = aRead;
+    }
+    if (aThreadId) {
+      filter.threadId = aThreadId;
+    }
+
+    let cursor = new GetMessagesCursor(this, aCallback);
 
     let self = this;
     self.newTxn(READ_ONLY, function(error, txn, stores) {
       let collector = cursor.collector;
       let collect = collector.collect.bind(collector);
-      FilterSearcherHelper.transact(self, txn, error, filter, reverse, collect);
+      FilterSearcherHelper.transact(self, txn, error, filter, aReverse, collect);
     }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]);
 
     return cursor;
   },
 
   markMessageRead: function(messageId, value, aSendReadReport, aRequest) {
     if (DEBUG) debug("Setting message " + messageId + " read to " + value);
     let self = this;
@@ -3306,36 +3328,36 @@ let FilterSearcherHelper = {
    *        Ongoing IDBTransaction context object.
    * @param collect
    *        Result colletor function. It takes three parameters -- txn, message
    *        id, and message timestamp.
    */
   filterTimestamp: function(startDate, endDate, direction, txn, collect) {
     let range = null;
     if (startDate != null && endDate != null) {
-      range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime());
+      range = IDBKeyRange.bound(startDate, endDate);
     } else if (startDate != null) {
-      range = IDBKeyRange.lowerBound(startDate.getTime());
+      range = IDBKeyRange.lowerBound(startDate);
     } else if (endDate != null) {
-      range = IDBKeyRange.upperBound(endDate.getTime());
+      range = IDBKeyRange.upperBound(endDate);
     }
     this.filterIndex("timestamp", range, direction, txn, collect);
   },
 
   /**
    * Instanciate a filtering transaction.
    *
    * @param mmdb
    *        A MobileMessageDB.
    * @param txn
    *        Ongoing IDBTransaction context object.
    * @param error
    *        Previous error while creating the transaction.
    * @param filter
-   *        A SmsFilter object.
+   *        A MobileMessageFilter dictionary.
    * @param reverse
    *        A boolean value indicating whether we should filter message in
    *        reversed order.
    * @param collect
    *        Result colletor function. It takes three parameters -- txn, message
    *        id, and message timestamp.
    */
   transact: function(mmdb, txn, error, filter, reverse, collect) {
@@ -3363,20 +3385,20 @@ let FilterSearcherHelper = {
                            collect);
       return;
     }
 
     // Numeric 0 is smaller than any time stamp, and empty string is larger
     // than all numeric values.
     let startDate = 0, endDate = "";
     if (filter.startDate != null) {
-      startDate = filter.startDate.getTime();
+      startDate = filter.startDate;
     }
     if (filter.endDate != null) {
-      endDate = filter.endDate.getTime();
+      endDate = filter.endDate;
     }
 
     let single, intersectionCollector;
     {
       let num = 0;
       if (filter.delivery) num++;
       if (filter.numbers) num++;
       if (filter.read != undefined) num++;
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -103,18 +103,23 @@ MobileMessageDatabaseService.prototype =
   getMessage: function(aMessageId, aRequest) {
     this.mmdb.getMessage(aMessageId, aRequest);
   },
 
   deleteMessage: function(aMessageIds, aLength, aRequest) {
     this.mmdb.deleteMessage(aMessageIds, aLength, aRequest);
   },
 
-  createMessageCursor: function(aFilter, aReverse, aCallback) {
-    return this.mmdb.createMessageCursor(aFilter, aReverse, aCallback);
+  createMessageCursor: function(aHasStartDate, aStartDate, aHasEndDate,
+                                aEndDate, aNumbers, aNumbersCount, aDelivery,
+                                aHasRead, aRead, aThreadId, aReverse, aCallback) {
+    return this.mmdb.createMessageCursor(aHasStartDate, aStartDate, aHasEndDate,
+                                         aEndDate, aNumbers, aNumbersCount,
+                                         aDelivery, aHasRead, aRead, aThreadId,
+                                         aReverse, aCallback);
   },
 
   markMessageRead: function(aMessageId, aValue, aSendReadReport, aRequest) {
     this.mmdb.markMessageRead(aMessageId, aValue, aSendReadReport, aRequest);
   },
 
   createThreadCursor: function(aCallback) {
     return this.mmdb.createThreadCursor(aCallback);
--- a/dom/mobilemessage/src/ipc/SmsIPCService.cpp
+++ b/dom/mobilemessage/src/ipc/SmsIPCService.cpp
@@ -3,17 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ContentChild.h"
 #include "SmsIPCService.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/dom/mobilemessage/SmsChild.h"
 #include "SmsMessage.h"
-#include "SmsFilter.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/MozMobileMessageManagerBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsString.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::dom::mobilemessage;
@@ -215,23 +214,50 @@ SmsIPCService::DeleteMessage(int32_t *aM
                              nsIMobileMessageCallback* aRequest)
 {
   DeleteMessageRequest data;
   data.messageIds().AppendElements(aMessageIds, aSize);
   return SendRequest(data, aRequest);
 }
 
 NS_IMETHODIMP
-SmsIPCService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
+SmsIPCService::CreateMessageCursor(bool aHasStartDate,
+                                   uint64_t aStartDate,
+                                   bool aHasEndDate,
+                                   uint64_t aEndDate,
+                                   const char16_t** aNumbers,
+                                   uint32_t aNumbersCount,
+                                   const nsAString& aDelivery,
+                                   bool aHasRead,
+                                   bool aRead,
+                                   uint64_t aThreadId,
                                    bool aReverse,
                                    nsIMobileMessageCursorCallback* aCursorCallback,
                                    nsICursorContinueCallback** aResult)
 {
-  const SmsFilterData& data =
-    SmsFilterData(static_cast<SmsFilter*>(aFilter)->GetData());
+  SmsFilterData data;
+
+  data.hasStartDate() = aHasStartDate;
+  data.startDate() = aStartDate;
+  data.hasEndDate() = aHasEndDate;
+  data.startDate() = aEndDate;
+
+  if (aNumbersCount && aNumbers) {
+    nsTArray<nsString>& numbers = data.numbers();
+    uint32_t index;
+
+    for (index = 0; index < aNumbersCount; index++) {
+      numbers.AppendElement(aNumbers[index]);
+    }
+  }
+
+  data.delivery() = aDelivery;
+  data.hasRead() = aHasRead;
+  data.read() = aRead;
+  data.threadId() = aThreadId;
 
   return SendCursorRequest(CreateMessageCursorRequest(data, aReverse),
                            aCursorCallback, aResult);
 }
 
 NS_IMETHODIMP
 SmsIPCService::MarkMessageRead(int32_t aMessageId,
                                bool aValue,
--- a/dom/mobilemessage/src/ipc/SmsParent.cpp
+++ b/dom/mobilemessage/src/ipc/SmsParent.cpp
@@ -9,17 +9,16 @@
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsIDOMMozSmsMessage.h"
 #include "nsIDOMMozMmsMessage.h"
 #include "mozilla/unused.h"
 #include "SmsMessage.h"
 #include "MmsMessage.h"
 #include "nsIMobileMessageDatabaseService.h"
-#include "SmsFilter.h"
 #include "MobileMessageThread.h"
 #include "nsIDOMFile.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
 #include "nsContentUtils.h"
 #include "nsTArrayHelpers.h"
 #include "xpcpublic.h"
@@ -769,20 +768,41 @@ MobileMessageCursorParent::RecvContinue(
 bool
 MobileMessageCursorParent::DoRequest(const CreateMessageCursorRequest& aRequest)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
     do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
   if (dbService) {
-    nsCOMPtr<nsIDOMMozSmsFilter> filter = new SmsFilter(aRequest.filter());
-    bool reverse = aRequest.reverse();
+    const SmsFilterData& filter = aRequest.filter();
+
+    const nsTArray<nsString>& numbers = filter.numbers();
+    nsAutoArrayPtr<const char16_t*> ptrNumbers;
+    uint32_t numbersCount = numbers.Length();
+    if (numbersCount) {
+      uint32_t index;
 
-    rv = dbService->CreateMessageCursor(filter, reverse, this,
+      ptrNumbers = new const char16_t* [numbersCount];
+      for (index = 0; index < numbersCount; index++) {
+        ptrNumbers[index] = numbers[index].get();
+      }
+    }
+
+    rv = dbService->CreateMessageCursor(filter.hasStartDate(),
+                                        filter.startDate(),
+                                        filter.hasEndDate(),
+                                        filter.endDate(),
+                                        ptrNumbers, numbersCount,
+                                        filter.delivery(),
+                                        filter.hasRead(),
+                                        filter.read(),
+                                        filter.threadId(),
+                                        aRequest.reverse(),
+                                        this,
                                         getter_AddRefs(mContinueCallback));
   }
 
   if (NS_FAILED(rv)) {
     return NS_SUCCEEDED(NotifyCursorError(nsIMobileMessageCallback::INTERNAL_ERROR));
   }
 
   return true;
--- a/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
+++ b/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
@@ -72,21 +72,24 @@ struct MmsMessageData
 union MobileMessageData
 {
   MmsMessageData;
   SmsMessageData;
 };
 
 struct SmsFilterData
 {
+  bool          hasStartDate;
   uint64_t      startDate;
+  bool          hasEndDate;
   uint64_t      endDate;
   nsString[]    numbers;
-  DeliveryState delivery;
-  ReadState     read;
+  nsString      delivery;
+  bool          hasRead;
+  bool          read;
   uint64_t      threadId;
 };
 
 struct ThreadData
 {
   uint64_t    id;
   nsString[]  participants;
   uint64_t    timestamp;
--- a/dom/mobilemessage/src/moz.build
+++ b/dom/mobilemessage/src/moz.build
@@ -35,34 +35,32 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'go
     SOURCES += [
         'gonk/SmsService.cpp',
     ]
 
 EXPORTS.mozilla.dom += [
     'DOMMobileMessageError.h',
     'MmsMessage.h',
     'MobileMessageManager.h',
-    'SmsFilter.h',
     'SmsMessage.h',
 ]
 
 UNIFIED_SOURCES += [
     'Constants.cpp',
     'DeletedMessageInfo.cpp',
     'DOMMobileMessageError.cpp',
     'ipc/SmsChild.cpp',
     'ipc/SmsIPCService.cpp',
     'ipc/SmsParent.cpp',
     'MmsMessage.cpp',
     'MobileMessageCallback.cpp',
     'MobileMessageCursorCallback.cpp',
     'MobileMessageManager.cpp',
     'MobileMessageService.cpp',
     'MobileMessageThread.cpp',
-    'SmsFilter.cpp',
     'SmsMessage.cpp',
     'SmsServicesFactory.cpp',
 ]
 
 IPDL_SOURCES += [
     'ipc/PMobileMessageCursor.ipdl',
     'ipc/PSms.ipdl',
     'ipc/PSmsRequest.ipdl',
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -238,28 +238,27 @@ function getMessage(aId) {
  * Retrieve messages from database.
  *
  * Fulfill params:
  *   messages -- an array of {Sms,Mms}Message instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
- * @param aFilter an optional MozSmsFilter instance.
- * @param aReverse a boolean value indicating whether the order of the messages
- *                 should be reversed.
+ * @param aFilter [optional]
+ *        A MobileMessageFilter object.
+ * @param aReverse [optional]
+ *        A boolean value indicating whether the order of the message should be
+ *        reversed. Default: false.
  *
  * @return A deferred promise.
  */
 function getMessages(aFilter, aReverse) {
   let deferred = Promise.defer();
 
-  if (!aFilter) {
-    aFilter = new MozSmsFilter;
-  }
   let messages = [];
   let cursor = manager.getMessages(aFilter, aReverse || false);
   cursor.onsuccess = function(aEvent) {
     if (cursor.result) {
       messages.push(cursor.result);
       cursor.continue();
       return;
     }
--- a/dom/mobilemessage/tests/marionette/mmdb_head.js
+++ b/dom/mobilemessage/tests/marionette/mmdb_head.js
@@ -323,18 +323,31 @@ function createMmdbCursor(aMmdb, aMethod
 /**
  * A convenient function for calling |mmdb.createMessageCursor(...)|.
  *
  * Fulfill params: [<Ci.nsIMobileMessageCallback.FOO>, [<DOM message>]].
  * Reject params: same as fulfill params.
  *
  * @return A deferred promise.
  */
-function createMessageCursor(aMmdb, aFilter, aReverse) {
-  return createMmdbCursor(aMmdb, "createMessageCursor", aFilter, aReverse);
+function createMessageCursor(aMmdb, aStartDate = null, aEndDate = null,
+                             aNumbers = null, aDelivery = null, aRead = null,
+                             aThreadId = null, aReverse = false) {
+  return createMmdbCursor(aMmdb, "createMessageCursor",
+                          aStartDate !== null,
+                          aStartDate || 0,
+                          aEndDate !== null,
+                          aEndDate || 0,
+                          aNumbers || null,
+                          aNumbers && aNumbers.length || 0,
+                          aDelivery || null,
+                          aRead !== null,
+                          aRead || false,
+                          aThreadId || 0,
+                          aReverse || false);
 }
 
 /**
  * A convenient function for calling |mmdb.createThreadCursor(...)|.
  *
  * Fulfill params: [<Ci.nsIMobileMessageCallback.FOO>, [<DOM thread>]].
  * Reject params: same as fulfill params.
  *
--- a/dom/mobilemessage/tests/marionette/test_filter_date.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_date.js
@@ -19,21 +19,21 @@ function simulateIncomingSms() {
         return messages;
       });
   }
 
   return promise;
 }
 
 function test(aStartDate, aEndDate, aExpectedMessages) {
-  let filter = new MozSmsFilter();
-  if (aStartDate) {
+  let filter = {};
+  if (aStartDate !== null) {
     filter.startDate = aStartDate;
   }
-  if (aEndDate) {
+  if (aEndDate !== null) {
     filter.endDate = aEndDate;
   }
 
   return getMessages(filter, false)
     .then(function(aFoundMessages) {
       log("  Found " + aFoundMessages.length + " messages, expected " +
           aExpectedMessages.length);
       is(aFoundMessages.length, aExpectedMessages.length, "aFoundMessages.length");
@@ -59,61 +59,61 @@ startTestCommon(function testCaseMain() 
       startTime = aMessages[0].timestamp;
       endTime = aMessages[NUMBER_OF_MESSAGES - 1].timestamp;
       log("startTime: " + startTime + ", endTime: " + endTime);
     })
 
     // Should return all messages.
     //
     .then(() => log("Testing [startTime, )"))
-    .then(() => test(new Date(startTime), null, allMessages))
+    .then(() => test(startTime, null, allMessages))
     .then(() => log("Testing (, endTime]"))
-    .then(() => test(null, new Date(endTime), allMessages))
+    .then(() => test(null, endTime, allMessages))
     .then(() => log("Testing [startTime, endTime]"))
-    .then(() => test(new Date(startTime), new Date(endTime), allMessages))
+    .then(() => test(startTime, endTime, allMessages))
 
     // Should return only messages with timestamp <= startTime.
     //
     .then(() => log("Testing [, startTime)"))
-    .then(() => test(null, new Date(startTime),
+    .then(() => test(null, startTime,
                      reduceMessages(allMessages,
                                     (function(a, b) {
                                       return b <= a;
                                     }).bind(null, startTime))))
 
     // Should return only messages with timestamp <= startTime + 1.
     //
     .then(() => log("Testing [, startTime + 1)"))
-    .then(() => test(null, new Date(startTime + 1),
+    .then(() => test(null, startTime + 1,
                      reduceMessages(allMessages,
                                     (function(a, b) {
                                       return b <= a;
                                     }).bind(null, startTime + 1))))
 
     // Should return only messages with timestamp >= endTime.
     //
     .then(() => log("Testing [endTime, )"))
-    .then(() => test(new Date(endTime), null,
+    .then(() => test(endTime, null,
                      reduceMessages(allMessages,
                                     (function(a, b) {
                                       return b >= a;
                                     }).bind(null, endTime))))
 
     // Should return only messages with timestamp >= endTime - 1.
     //
     .then(() => log("Testing [endTime - 1, )"))
-    .then(() => test(new Date(endTime - 1), null,
+    .then(() => test(endTime - 1, null,
                      reduceMessages(allMessages,
                                     (function(a, b) {
                                       return b >= a;
                                     }).bind(null, endTime - 1))))
 
     // Should return none.
     //
     .then(() => log("Testing [endTime + 1, )"))
-    .then(() => test(new Date(endTime + 1), null, []))
+    .then(() => test(endTime + 1, null, []))
     .then(() => log("Testing [endTime + 1, endTime + 86400000]"))
-    .then(() => test(new Date(endTime + 1), new Date(endTime + 86400000), []))
+    .then(() => test(endTime + 1, endTime + 86400000, []))
     .then(() => log("Testing (, startTime - 1]"))
-    .then(() => test(null, new Date(startTime - 1), []))
+    .then(() => test(null, startTime - 1, []))
     .then(() => log("Testing [startTime - 86400000, startTime - 1]"))
-    .then(() => test(new Date(startTime - 86400000), new Date(startTime - 1), []));
+    .then(() => test(startTime - 86400000, startTime - 1, []));
 });
--- a/dom/mobilemessage/tests/marionette/test_filter_mixed.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_mixed.js
@@ -54,19 +54,16 @@ let tasks = {
   },
 
   run: function() {
     this.next();
   }
 };
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let request = manager.getMessages(filter, reverse || false);
   request.onsuccess = function(event) {
     if (request.result) {
       messages.push(request.result);
       request.continue();
       return;
     }
@@ -177,19 +174,20 @@ let INVALID_THREAD_ID;
 tasks.push(function assignInvalidThreadID() {
   INVALID_THREAD_ID = threadIds[threadIds.length - 1] + 1;
   log("Set INVALID_THREAD_ID to be " + INVALID_THREAD_ID);
   tasks.next();
 });
 
 tasks.push(function testDeliveryAndNumber() {
   log("Checking delivery == sent && number == 5555315550");
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.numbers = ["5555315550"];
+  let filter = {
+    delivery: "sent",
+    numbers: ["5555315550"],
+  };
   getAllMessages(function(messages) {
     // Only { delivery: "sent", receiver: "+15555315550", read: true }
     is(messages.length, 1, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.delivery, filter.delivery, "message delivery");
       if (!checkSenderOrReceiver(message, filter.numbers[0])) {
         ok(false, "message sender or receiver number");
@@ -204,31 +202,33 @@ tasks.push(function testDeliveryAndNumbe
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testDeliveryAndNumberNotFound() {
   log("Checking delivery == sent && number == INVALID_NUMBER");
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.numbers = [INVALID_NUMBER];
+  let filter = {
+    delivery: "sent",
+    numbers: [INVALID_NUMBER],
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testDeliveryAndRead() {
   log("Checking delivery == received && read == true");
-  let filter = new MozSmsFilter();
-  filter.delivery = "received";
-  filter.read = true;
+  let filter = {
+    delivery: "received",
+    read: true,
+  }
   getAllMessages(function(messages) {
     // { delivery: "received", sender: "5555315550", read: true },
     // { delivery: "received", sender: "5555315552", read: true },
     // { delivery: "received", sender: "5555315554", read: true },
     // { delivery: "received", sender: "5555315556", read: true },
     // { delivery: "received", sender: "5555315558", read: true },
     is(messages.length, NUM_THREADS / 2, "message count");
     for (let i = 0; i < messages.length; i++) {
@@ -245,31 +245,33 @@ tasks.push(function testDeliveryAndRead(
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testDeliveryAndReadNotFound() {
   log("Checking delivery == sent && read == false");
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.read = false;
+  let filter = {
+    delivery: "sent",
+    read: false,
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testDeliveryAndThreadId() {
   log("Checking delivery == received && threadId == " + threadIds[0]);
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.threadId = threadIds[0];
+  let filter = {
+    delivery: "sent",
+    threadId: threadIds[0],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent", receiver: "+15555315550", threadId: threadIds[0]}
     is(messages.length, 1, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.delivery, filter.delivery, "message delivery");
       is(message.threadId, filter.threadId, "message threadId");
     }
@@ -282,31 +284,33 @@ tasks.push(function testDeliveryAndThrea
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testDeliveryAndThreadIdNotFound() {
   log("Checking delivery == sent && threadId == INVALID_THREAD_ID");
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.threadId = INVALID_THREAD_ID;
+  let filter = {
+    delivery: "sent",
+    threadId: INVALID_THREAD_ID,
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testNumberAndRead() {
   log("Checking number == 5555315550 && read == true");
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550"];
-  filter.read = true;
+  let filter = {
+    numbers: ["5555315550"],
+    read: true,
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       if (!checkSenderOrReceiver(message, filter.numbers[0])) {
         ok(false, "message sender or receiver number");
@@ -322,31 +326,33 @@ tasks.push(function testNumberAndRead() 
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testNumberAndReadNotFound() {
   log("Checking number == INVALID_NUMBER && read == true");
-  let filter = new MozSmsFilter();
-  filter.numbers = [INVALID_NUMBER];
-  filter.read = true;
+  let filter = {
+    numbers: [INVALID_NUMBER],
+    read: true,
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testNumberAndThreadId() {
   log("Checking number == 5555315550 && threadId == " + threadIds[0]);
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550"];
-  filter.threadId = threadIds[0];
+  let filter = {
+    numbers: ["5555315550"],
+    threadId: threadIds[0],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       if (!checkSenderOrReceiver(message, filter.numbers[0])) {
         ok(false, "message sender or receiver number");
@@ -362,30 +368,32 @@ tasks.push(function testNumberAndThreadI
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testNumberAndThreadIdNotFound() {
   log("Checking number == INVALID_NUMBER && threadId == INVALID_THREAD_ID");
-  let filter = new MozSmsFilter();
-  filter.numbers = [INVALID_NUMBER];
-  filter.threadId = INVALID_THREAD_ID;
+  let filter = {
+    numbers: [INVALID_NUMBER],
+    threadId: INVALID_THREAD_ID,
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testMultipleNumbers() {
   log("Checking number == 5555315550 || number == 5555315551");
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550", "5555315551"];
+  let filter = {
+    numbers: ["5555315550", "5555315551"],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     // { delivery: "sent",     receiver: "+15555315551", read: true }
     // { delivery: "received", sender: "5555315551",     read: false }
     is(messages.length, 4, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
@@ -396,30 +404,32 @@ tasks.push(function testMultipleNumbers(
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testMultipleNumbersNotFound() {
   log("Checking number == INVALID_NUMBER || number == INVALID_NUMBER2");
-  let filter = new MozSmsFilter();
-  filter.numbers = [INVALID_NUMBER, INVALID_NUMBER2];
+  let filter = {
+    numbers: [INVALID_NUMBER, INVALID_NUMBER2],
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testDeliveryAndMultipleNumbers() {
   log("Checking delivery == sent && (number == 5555315550 || number == 5555315551)");
-  let filter = new MozSmsFilter();
-  filter.delivery = "sent";
-  filter.numbers = ["5555315550", "5555315551"];
+  let filter = {
+    delivery: "sent",
+    numbers: ["5555315550", "5555315551"],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent", receiver: "+15555315550", read: true }
     // { delivery: "sent", receiver: "+15555315551", read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.delivery, filter.delivery, "message delivery");
       if (!(checkSenderOrReceiver(message, filter.numbers[0]) ||
@@ -429,19 +439,20 @@ tasks.push(function testDeliveryAndMulti
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testMultipleNumbersAndRead() {
   log("Checking (number == 5555315550 || number == 5555315551) && read == true");
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550", "5555315551"];
-  filter.read = true;
+  let filter = {
+    numbers: ["5555315550", "5555315551"],
+    read: true,
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     // { delivery: "sent",     receiver: "+15555315551", read: true }
     is(messages.length, 3, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.read, filter.read, "message read");
@@ -452,19 +463,20 @@ tasks.push(function testMultipleNumbersA
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testMultipleNumbersAndThreadId() {
   log("Checking (number == 5555315550 || number == 5555315551) && threadId == " + threadIds[0]);
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550", "5555315551"];
-  filter.threadId = threadIds[0];
+  let filter = {
+    numbers: ["5555315550", "5555315551"],
+    threadId: threadIds[0],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.threadId, filter.threadId, "message threadId");
       if (!(checkSenderOrReceiver(message, filter.numbers[0]) ||
@@ -474,18 +486,19 @@ tasks.push(function testMultipleNumbersA
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testNationalNumber() {
   log("Checking number = 5555315550");
-  let filter = new MozSmsFilter();
-  filter.numbers = ["5555315550"];
+  let filter = {
+    numbers: ["5555315550"],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       if (!checkSenderOrReceiver(message, filter.numbers[0])) {
         ok(false, "message sender or receiver number");
@@ -493,18 +506,19 @@ tasks.push(function testNationalNumber()
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testInternationalNumber() {
   log("Checking number = +15555315550");
-  let filter = new MozSmsFilter();
-  filter.numbers = ["+15555315550"];
+  let filter = {
+    numbers: ["+15555315550"],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       if (!checkSenderOrReceiver(message, "5555315550")) {
         ok(false, "message sender or receiver number");
@@ -512,19 +526,20 @@ tasks.push(function testInternationalNum
     }
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(function testReadAndThreadId() {
   log("Checking read == true && threadId == " + threadIds[0]);
-  let filter = new MozSmsFilter();
-  filter.read = true;
-  filter.threadId = threadIds[0];
+  let filter = {
+    read: true,
+    threadId: threadIds[0],
+  };
   getAllMessages(function(messages) {
     // { delivery: "sent",     receiver: "+15555315550", read: true }
     // { delivery: "received", sender: "5555315550",     read: true }
     is(messages.length, 2, "message count");
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       is(message.read, filter.read, "message read");
       is(message.threadId, filter.threadId, "message threadId");
@@ -538,19 +553,20 @@ tasks.push(function testReadAndThreadId(
 
       tasks.next();
     }, filter, true);
   }, filter);
 });
 
 tasks.push(function testReadAndThreadIdNotFound() {
   log("Checking read == true && threadId == INVALID_THREAD_ID");
-  let filter = new MozSmsFilter();
-  filter.read = true;
-  filter.threadId = INVALID_THREAD_ID;
+  let filter = {
+    read: true,
+    threadId: INVALID_THREAD_ID,
+  };
   getAllMessages(function(messages) {
     is(messages.length, 0, "message count");
 
     tasks.next();
   }, filter);
 });
 
 tasks.push(deleteAllMessages);
--- a/dom/mobilemessage/tests/marionette/test_filter_number.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_number.js
@@ -23,18 +23,17 @@ function genFailingMms(aReceivers) {
     subject: genMmsSubject(' '),
     attachments: [],
   };
 }
 
 function checkMessage(aNeedle, aValidNumbers) {
   log("  Verifying " + aNeedle);
 
-  let filter = new MozSmsFilter();
-  filter.numbers = [aNeedle];
+  let filter = { numbers: [aNeedle] };
   return getMessages(filter)
     .then(function(messages) {
       // Check the messages are sent to/received from aValidNumbers.
       is(messages.length, aValidNumbers.length, "messages.length");
 
       for (let message of messages) {
         let number;
         if (message.type == "sms") {
--- a/dom/mobilemessage/tests/marionette/test_filter_read.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_read.js
@@ -15,19 +15,18 @@ function verifyInitialState() {
   ok(manager instanceof MozMobileMessageManager,
      "manager is instance of " + manager.constructor);
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(simulateIncomingSms);
 }
 
 function deleteAllMsgs(nextFunction) {
   let msgList = new Array();
-  let filter = new MozSmsFilter;
 
-  let cursor = manager.getMessages(filter, false);
+  let cursor = manager.getMessages();
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     // Check if message was found
     if (cursor.result) {
       msgList.push(cursor.result.id);
       // Now get next message in the list
@@ -157,21 +156,20 @@ function markMsgRead(smsMsgs) {
     ok(event.target.error, "domerror obj");
     ok(false, "manager.markMessageRead request returned unexpected error: "
         + event.target.error.name );
     cleanUp();
   };
 }
 
 function getMsgs() {
-  var filter = new MozSmsFilter();
   let foundSmsList = new Array();
 
   // Set filter for read messages
-  filter.read = true;
+  let filter = { read: true };
 
   log("Getting the read SMS messages.");
   let cursor = manager.getMessages(filter, false);
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     log("Received 'onsuccess' event.");
--- a/dom/mobilemessage/tests/marionette/test_filter_received.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_received.js
@@ -15,19 +15,18 @@ function verifyInitialState() {
   ok(manager instanceof MozMobileMessageManager,
      "manager is instance of " + manager.constructor);
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(simulateIncomingSms);
 }
 
 function deleteAllMsgs(nextFunction) {
   let msgList = new Array();
-  let filter = new MozSmsFilter;
 
-  let cursor = manager.getMessages(filter, false);
+  let cursor = manager.getMessages();
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     // Check if message was found
     if (cursor.result) {
       msgList.push(cursor.result.id);
       // Now get next message in the list
@@ -169,21 +168,20 @@ function sendSms() {
     ok(event.target.error, "domerror obj");
     ok(false, "manager.send request returned unexpected error: "
         + event.target.error.name );
     cleanUp();
   };
 }
 
 function getMsgs() {
-  var filter = new MozSmsFilter();
   let foundSmsList = new Array();
 
   // Set filter for received messages
-  filter.delivery = "received";
+  let filter = { delivery: "received" };
 
   log("Getting the received SMS messages.");
   let cursor = manager.getMessages(filter, false);
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     log("Received 'onsuccess' event.");
--- a/dom/mobilemessage/tests/marionette/test_filter_sent.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_sent.js
@@ -15,19 +15,18 @@ function verifyInitialState() {
   ok(manager instanceof MozMobileMessageManager,
      "manager is instance of " + manager.constructor);
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(sendSms);
 }
 
 function deleteAllMsgs(nextFunction) {
   let msgList = new Array();
-  let filter = new MozSmsFilter;
 
-  let cursor = manager.getMessages(filter, false);
+  let cursor = manager.getMessages();
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     // Check if message was found
     if (cursor.result) {
       msgList.push(cursor.result.id);
       // Now get next message in the list
@@ -166,21 +165,20 @@ manager.onreceived = function onreceived
 
   // Wait for emulator to catch up before continuing
   waitFor(getMsgs,function() {
     return(rcvdEmulatorCallback);
   });
 };
 
 function getMsgs() {
-  var filter = new MozSmsFilter();
   let foundSmsList = new Array();
 
   // Set filter for sent messages
-  filter.delivery = "sent";
+  let filter = { delivery: "sent" };
 
   log("Getting the sent SMS messages.");
   let cursor = manager.getMessages(filter, false);
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     log("Received 'onsuccess' event.");
--- a/dom/mobilemessage/tests/marionette/test_filter_unread.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_unread.js
@@ -15,19 +15,18 @@ function verifyInitialState() {
   ok(manager instanceof MozMobileMessageManager,
      "manager is instance of " + manager.constructor);
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(simulateIncomingSms);
 }
 
 function deleteAllMsgs(nextFunction) {
   let msgList = new Array();
-  let filter = new MozSmsFilter;
 
-  let cursor = manager.getMessages(filter, false);
+  let cursor = manager.getMessages();
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     // Check if message was found
     if (cursor.result) {
       msgList.push(cursor.result.id);
       // Now get next message in the list
@@ -151,21 +150,20 @@ function markMsgRead() {
     ok(event.target.error, "domerror obj");
     ok(false, "manager.markMessageRead request returned unexpected error: "
         + event.target.error.name );
     cleanUp();
   };
 }
 
 function getMsgs() {
-  var filter = new MozSmsFilter();
   let foundSmsList = new Array();
 
   // Set filter for read messages
-  filter.read = false;
+  let filter = { read: false };
 
   log("Getting the unread SMS messages.");
   let cursor = manager.getMessages(filter, false);
   ok(cursor instanceof DOMCursor,
       "cursor is instanceof " + cursor.constructor);
 
   cursor.onsuccess = function(event) {
     log("Received 'onsuccess' event.");
--- a/dom/mobilemessage/tests/marionette/test_getthreads.js
+++ b/dom/mobilemessage/tests/marionette/test_getthreads.js
@@ -51,19 +51,16 @@ let tasks = {
   },
 
   run: function() {
     this.next();
   }
 };
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let request = manager.getMessages(filter, reverse || false);
   request.onsuccess = function(event) {
     if (!request.done) {
       messages.push(request.result);
       request.continue();
       return;
     }
@@ -149,18 +146,17 @@ function checkThread(bodies, lastBody, u
   is(thread.participants.length, participants.length,
      "thread.participants.length");
   for (let i = 0; i < participants.length; i++) {
     is(thread.participants[i], participants[i],
        "thread.participants[" + i + "]");
   }
 
   // Check whether the thread does contain all the messages it supposed to have.
-  let filter = new MozSmsFilter;
-  filter.threadId = thread.id;
+  let filter = { threadId: thread.id };
   getAllMessages(function(messages) {
     is(messages.length, bodies.length, "messages.length and bodies.length");
 
     for (let message of messages) {
       let index = bodies.indexOf(message.body);
       ok(index >= 0, "message.body '" + message.body +
          "' should be found in bodies array.");
       bodies.splice(index, 1);
--- a/dom/mobilemessage/tests/marionette/test_invalid_address.js
+++ b/dom/mobilemessage/tests/marionette/test_invalid_address.js
@@ -39,19 +39,16 @@ let tasks = {
   run: function() {
     this.next();
   }
 };
 
 let manager;
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let request = manager.getMessages(filter, reverse || false);
   request.onsuccess = function(event) {
     if (request.result) {
       messages.push(request.result);
       request.continue();
       return;
     }
--- a/dom/mobilemessage/tests/marionette/test_message_classes.js
+++ b/dom/mobilemessage/tests/marionette/test_message_classes.js
@@ -71,17 +71,17 @@ function test_message_class_0() {
       ok(event.message.timestamp >= timeBeforeSend,
          "Message's timestamp should be greater then the timetamp of sending");
       ok(event.message.timestamp <= Date.now(),
          "Message's timestamp should be lesser than the timestamp of now");
       is(event.message.sentTimestamp, SENT_TIMESTAMP,
          "Message's sentTimestamp should be equal to SENT_TIMESTAMP");
 
       // Make sure the message is not stored.
-      let cursor = manager.getMessages(null, false);
+      let cursor = manager.getMessages();
       cursor.onsuccess = function onsuccess() {
         if (cursor.result) {
           // Here we check whether there is any message of the same sender.
           isnot(cursor.result.sender, message.sender, "cursor.result.sender");
 
           cursor.continue();
           return;
         }
--- a/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
@@ -160,17 +160,17 @@ function testSaveSmsSegment(aMmdb) {
     .then(isFileNoDeviceSpaceError)
     .then(() => setStorageFull(false));
 }
 
 function testCreateMessageCursor(aMmdb) {
   log("testCreateMessageCursor()");
 
   setStorageFull(true);
-  return createMessageCursor(aMmdb, {}, false)
+  return createMessageCursor(aMmdb)
     .then(() => setStorageFull(false));
 }
 
 function testCreateThreadCursor(aMmdb) {
   log("testCreateThreadCursor()");
 
   setStorageFull(true);
   return createThreadCursor(aMmdb)
--- a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
@@ -625,17 +625,17 @@ function doVerifyDatabase(aMmdb, aExpect
 
         aExpected.splice(index, 1);
       }
 
       // 4) make sure no thread is missing by checking |aExpected.length == 0|.
       is(aExpected.length, 0, "remaining unmatched threads");
 
       // 5) retrieve all messages.
-      return createMessageCursor(aMmdb, {})
+      return createMessageCursor(aMmdb)
         .then(function(aValues) {
           let [errorCode, domMessages] = aValues;
           is(errorCode, 0, "errorCode");
           // 6) check total number of messages.
           is(domMessages.length, totalMessages, "domMessages.length");
 
           for (let i = 0; i < domMessages.length; i++) {
             let domMessage = domMessages[i];
--- a/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
+++ b/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
@@ -39,19 +39,16 @@ let tasks = {
   run: function() {
     this.next();
   }
 };
 
 let manager;
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let request = manager.getMessages(filter, reverse || false);
   request.onsuccess = function(event) {
     if (request.result) {
       messages.push(request.result);
       request.continue();
       return;
     }
--- a/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
+++ b/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
@@ -47,19 +47,16 @@ let tasks = {
   },
 
   run: function() {
     this.next();
   }
 };
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let request = manager.getMessages(filter, reverse || false);
   request.onsuccess = function(event) {
     if (request.result) {
       messages.push(request.result);
       request.continue();
       return;
     }
--- a/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
+++ b/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
@@ -55,19 +55,16 @@ let tasks = {
   },
 
   run: function() {
     this.next();
   }
 };
 
 function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
   let messages = [];
   let cursor = manager.getMessages(filter, reverse || false);
   cursor.onsuccess = function(event) {
     if (!this.done) {
       messages.push(this.result);
       this.continue();
       return;
     }
--- a/dom/mobilemessage/tests/mochitest/mochitest.ini
+++ b/dom/mobilemessage/tests/mochitest/mochitest.ini
@@ -1,6 +1,5 @@
 [DEFAULT]
 skip-if = e10s
 
 [test_sms_basics.html]
 skip-if = toolkit == 'android' #Bug 909036
-[test_smsfilter.html]
--- a/dom/mobilemessage/tests/mochitest/test_sms_basics.html
+++ b/dom/mobilemessage/tests/mochitest/test_sms_basics.html
@@ -45,17 +45,16 @@ function checkSmsDisabledOrEnabled() {
 function checkInterface(aInterface) {
   ok(!(aInterface in window), aInterface + " should be prefixed");
   ok(("Moz" + aInterface) in window, aInterface + " should be prefixed");
 }
 
 function test() {
   checkInterface("SmsMessage");
   checkInterface("SmsEvent");
-  checkInterface("SmsFilter");
 
   // If sms is disabled and permission is removed, sms is disabled.
   SpecialPowers.pushPrefEnv({"set": [["dom.sms.enabled", false]]}, function() {
     SpecialPowers.pushPermissions([{'type': 'sms', 'remove': true, 'context': document}], test2);
   });
 }
 
 function test2() {
deleted file mode 100644
--- a/dom/mobilemessage/tests/mochitest/test_smsfilter.html
+++ /dev/null
@@ -1,90 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test SmsFilter</title>
-  <script type="application/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">
-<iframe></iframe>
-</div>
-<pre id="test">
-<script type="application/javascript">
-
-/** Test SmsFilter **/
-
-function throwingCheck(aFilter, aProperty, disallowedValue)
-{
-  disallowedValue.forEach(function(v) {
-    var exception = false;
-    try {
-      aFilter[aProperty] = v;
-    } catch (e) {
-      exception = true;
-    }
-    ok(exception, "An exception should have been thrown");
-  });
-}
-
-var filter = new MozSmsFilter();
-
-isnot(filter, null, "filter should be set");
-
-is(filter.startDate, null, "Parameters shouldn't be initialized");
-is(filter.endDate, null, "Parameters shouldn't be initialized");
-is(filter.numbers, null, "Parameters shouldn't be initialized");
-is(filter.delivery, null, "Parameters shouldn't be initialized");
-is(filter.read, null, "Parameters shouldn't be initialized");
-is(filter.threadId, null, "Parameters shouldn't be initialized");
-
-var date = new Date();
-filter.startDate = date;
-is(filter.startDate, "" + date, "Setters and getters should work!");
-filter.startDate = null;
-is(filter.startDate, null, "null should revert the value to default");
-throwingCheck(filter, 'startDate', [ "foo", 42, [1, 2], function () {}, undefined ]);
-
-filter.endDate = date;
-is(filter.endDate, "" + date, "Setters and getters should work!");
-filter.endDate = null;
-is(filter.endDate, null, "null should revert the value to default");
-throwingCheck(filter, 'endDate', [ "foo", 42, [1, 2], function () {}, undefined ]);
-
-var numbers = [ "foo", "bar" ];
-filter.numbers = numbers;
-is(filter.numbers, "" + numbers, "Setters and getters should work!");
-filter.numbers = null;
-is(filter.numbers, null, "null should revert the value to default");
-throwingCheck(filter, 'numbers', [ "foo", 42, function () {}, new Date(), undefined ]);
-
-filter.delivery = "sent";
-is(filter.delivery, "sent", "Setters and getters should work!");
-filter.delivery = "received";
-is(filter.delivery, "received", "Setters and getters should work!");
-filter.delivery = "";
-is(filter.delivery, null, "The empty string should revert the value to default.");
-filter.delivery = null;
-is(filter.delivery, null, "'null' should revert the value to default.");
-throwingCheck(filter, 'delivery', [ "foo", 42, [1, 2], function () {}, new Date(), undefined ]);
-
-filter.read = true;
-is(filter.read, true, "Setters and getters should work!");
-filter.read = false;
-is(filter.read, false, "Setters and getters should work!");
-filter.read = null;
-is(filter.read, null, "'null' should revert the value to default");
-throwingCheck(filter, 'read', [ "foo", 0, [1, 2], function () {}, new Date(), undefined ]);
-
-filter.threadId = 1;
-is(filter.threadId, 1, "Setters and getters should work!");
-filter.threadId = null;
-is(filter.threadId, null, "'null' should revert the value to default");
-throwingCheck(filter, 'threadId', [ "foo", 0, 1.5, [1, 2], function () {}, new Date(), undefined ]);
-
-</script>
-</pre>
-</body>
-</html>
-
--- a/dom/plugins/ipc/hangui/moz.build
+++ b/dom/plugins/ipc/hangui/moz.build
@@ -13,16 +13,17 @@ UNIFIED_SOURCES += [
     'PluginHangUIChild.cpp',
 ]
 include('/ipc/chromium/chromium-config.mozbuild')
 
 DEFINES['NS_NO_XPCOM'] = True
 DEFINES['_HAS_EXCEPTIONS'] = 0
 
 DISABLE_STL_WRAPPING = True
+USE_STATIC_LIBS = True
 
 if CONFIG['GNU_CC']:
     WIN32_EXE_LDFLAGS += ['-municode']
 
 RCINCLUDE = 'HangUIDlg.rc'
 
 OS_LIBS += [
     'comctl32',
--- a/dom/plugins/test/mochitest/dialog_watcher.js
+++ b/dom/plugins/test/mochitest/dialog_watcher.js
@@ -92,22 +92,32 @@ DialogWatcher.prototype.init = function(
   if (!this.getWindowTextW) {
     this.getWindowTextW = user32.declare("GetWindowTextW",
                                          ctypes.winapi_abi,
                                          ctypes.int,
                                          ctypes.uintptr_t,
                                          ctypes.jschar.ptr,
                                          ctypes.int);
   }
+  if (!this.messageBox) {
+    // Handy for debugging this code
+    this.messageBox = user32.declare("MessageBoxW",
+                                     ctypes.winapi_abi,
+                                     ctypes.int,
+                                     ctypes.uintptr_t,
+                                     ctypes.jschar.ptr,
+                                     ctypes.jschar.ptr,
+                                     ctypes.uint32_t);
+  }
 };
 
 DialogWatcher.prototype.getWindowText = function(hwnd) {
   var bufType = ctypes.ArrayType(ctypes.jschar);
   var buffer = new bufType(256);
-  
+
   if (this.getWindowTextW(hwnd, buffer, buffer.length)) {
     return buffer.readString();
   }
 };
 
 DialogWatcher.prototype.processWindowEvents = function(timeout) {
   var onWinEvent = function(self, hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
     var nhwnd = Number(hwnd)
@@ -149,23 +159,25 @@ DialogWatcher.prototype.processWindowEve
   }
 
   if (!timeout) {
     timeout = INFINITE;
   }
 
   var waitStatus = WAIT_OBJECT_0;
   var expectingStart = this.onDialogStart && this.hwnd === undefined;
+  var startWaitTime = Date.now();
   while (this.hwnd === undefined || this.onDialogEnd && this.hwnd) {
     waitStatus = this.msgWaitForMultipleObjects(0, null, 0, expectingStart ?
                                                             INFINITE : timeout, 0);
     if (waitStatus == WAIT_OBJECT_0) {
       var msg = new this.msgType;
       this.peekMessage(msg.address(), 0, 0, 0, PM_NOREMOVE);
-    } else if (waitStatus == WAIT_TIMEOUT) {
+    }
+    if (waitStatus == WAIT_TIMEOUT || (Date.now() - startWaitTime) >= timeout) {
       break;
     }
   }
 
   this.unhookWinEvent(hook);
   // Returns true if the hook was successful, something was found, and we never timed out
   return this.hwnd !== undefined && waitStatus == WAIT_OBJECT_0;
 };
--- a/dom/plugins/test/mochitest/test_hangui.xul
+++ b/dom/plugins/test/mochitest/test_hangui.xul
@@ -4,26 +4,29 @@
                  type="text/css"?>
 <window title="Basic Plugin Tests"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <title>Plugin Hang UI Test</title>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
+          src="utils.js" />
+  <script type="application/javascript"
           src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" />
   <script type="application/javascript"
           src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hangui_common.js" />
 
 <body xmlns="http://www.w3.org/1999/xhtml">
   <iframe id="iframe1" src="hangui_subpage.html" width="400" height="400"></iframe>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const hangUITimeoutPref = "dom.ipc.plugins.hangUITimeoutSecs";
 const hangUIMinDisplayPref = "dom.ipc.plugins.hangUIMinDisplaySecs";
 const timeoutPref = "dom.ipc.plugins.timeoutSecs";
 
 var worker = new ChromeWorker("hangui_iface.js");
@@ -95,17 +98,18 @@ function finishTest() {
 function runTests() {
   if (!SimpleTest.testPluginIsOOP()) {
     ok(true, "Skipping this test when test plugin is not OOP.");
     SimpleTest.finish();
   }
 
   resetVars();
 
-  hanguiExpect("Prime ChromeWorker", false, false, "test1");
+  hanguiOperation("Prime ChromeWorker", 0, false, false, HANGUIOP_NOTHING, 0,
+                  false, "test1");
 }
 
 window.frameLoaded = runTests;
 
 var obsCount = 0;
 
 function onPluginCrashedHangUI(aEvent) {
   ok(true, "Plugin crashed notification received");
@@ -239,17 +243,17 @@ function test4() {
 }
 
 function test3() {
   hanguiContinue("test3: Continue button works", false, "test4");
   p.stall(STALL_DURATION);
 }
 
 function test2() {
-  // This test is identical to test1 because there were some bugs where the 
+  // This test is identical to test1 because there were some bugs where the
   // Hang UI would show on the first hang but not on subsequent hangs
   hanguiExpect("test2: Plugin Hang UI is showing", true, true, "test3");
   p.stall(STALL_DURATION);
 }
 
 function test1() {
   SpecialPowers.setIntPref(hangUITimeoutPref, 1);
   SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -491,16 +491,27 @@ nsGeolocationRequest::StopTimeoutTimer()
 void
 nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
 {
   if (mShutdown) {
     // Ignore SendLocationEvents issued before we were cleared.
     return;
   }
 
+  if (mOptions && mOptions->mMaximumAge > 0) {
+    DOMTimeStamp positionTime_ms;
+    aPosition->GetTimestamp(&positionTime_ms);
+    const uint32_t maximumAge_ms = mOptions->mMaximumAge;
+    const bool isTooOld =
+        DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) > positionTime_ms;
+    if (isTooOld) {
+      return;
+    }
+  }
+
   nsRefPtr<Position> wrapped;
 
   if (aPosition) {
     nsCOMPtr<nsIDOMGeoPositionCoords> coords;
     aPosition->GetCoords(getter_AddRefs(coords));
     if (coords) {
       wrapped = new Position(ToSupports(mLocator), aPosition);
     }
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
 #include "GonkGPSGeolocationProvider.h"
 
 #include <pthread.h>
 #include <hardware/gps.h>
 
+#include "mozilla/Constants.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsContentUtils.h"
 #include "nsGeoPosition.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsINetworkManager.h"
 #include "nsIObserverService.h"
 #include "nsJSUtils.h"
@@ -76,18 +77,18 @@ GonkGPSGeolocationProvider::LocationCall
   class UpdateLocationEvent : public nsRunnable {
   public:
     UpdateLocationEvent(nsGeoPosition* aPosition)
       : mPosition(aPosition)
     {}
     NS_IMETHOD Run() {
       nsRefPtr<GonkGPSGeolocationProvider> provider =
         GonkGPSGeolocationProvider::GetSingleton();
-      provider->mLastGPSDerivedLocationTime = PR_Now();
       nsCOMPtr<nsIGeolocationUpdate> callback = provider->mLocationCallback;
+      provider->mLastGPSPosition = mPosition;
       if (callback) {
         callback->Update(mPosition);
       }
       return NS_OK;
     }
   private:
     nsRefPtr<nsGeoPosition> mPosition;
   };
@@ -96,17 +97,24 @@ GonkGPSGeolocationProvider::LocationCall
 
   nsRefPtr<nsGeoPosition> somewhere = new nsGeoPosition(location->latitude,
                                                         location->longitude,
                                                         location->altitude,
                                                         location->accuracy,
                                                         location->accuracy,
                                                         location->bearing,
                                                         location->speed,
-                                                        location->timestamp);
+                                                        PR_Now() / PR_USEC_PER_MSEC);
+  // Note above: Can't use location->timestamp as the time from the satellite is a
+  // minimum of 16 secs old (see http://leapsecond.com/java/gpsclock.htm).
+  // All code from this point on expects the gps location to be timestamped with the
+  // current time, most notably: the geolocation service which respects maximumAge
+  // set in the DOM JS.
+
+
   NS_DispatchToMainThread(new UpdateLocationEvent(somewhere));
 }
 
 void
 GonkGPSGeolocationProvider::StatusCallback(GpsStatus* status)
 {
 }
 
@@ -694,51 +702,82 @@ GonkGPSGeolocationProvider::NetworkLocat
     return NS_ERROR_FAILURE;
   }
 
   double lat, lon, acc;
   coords->GetLatitude(&lat);
   coords->GetLongitude(&lon);
   coords->GetAccuracy(&acc);
 
-  double delta = MAXFLOAT;
+  double delta = -1.0;
 
   static double sLastMLSPosLat = 0;
   static double sLastMLSPosLon = 0;
 
   if (0 != sLastMLSPosLon || 0 != sLastMLSPosLat) {
     // Use spherical law of cosines to calculate difference
     // Not quite as correct as the Haversine but simpler and cheaper
     // Should the following be a utility function? Others might need this calc.
-    const double radsInDeg = 3.14159265 / 180.0;
+    const double radsInDeg = M_PI / 180.0;
     const double rNewLat = lat * radsInDeg;
     const double rNewLon = lon * radsInDeg;
     const double rOldLat = sLastMLSPosLat * radsInDeg;
     const double rOldLon = sLastMLSPosLon * radsInDeg;
     // WGS84 equatorial radius of earth = 6378137m
-    delta = acos( (sin(rNewLat) * sin(rOldLat)) +
-                  (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon)) )
-                  * 6378137;
+    double cosDelta = (sin(rNewLat) * sin(rOldLat)) +
+                      (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon));
+    if (cosDelta > 1.0) {
+      cosDelta = 1.0;
+    } else if (cosDelta < -1.0) {
+      cosDelta = -1.0;
+    }
+    delta = acos(cosDelta) * 6378137;
   }
 
   sLastMLSPosLat = lat;
   sLastMLSPosLon = lon;
 
   // if the MLS coord change is smaller than this arbitrarily small value
   // assume the MLS coord is unchanged, and stick with the GPS location
   const double kMinMLSCoordChangeInMeters = 10;
 
-  // if we haven't seen anything from the GPS device for 10s,
-  // use this network derived location.
-  const int kMaxGPSDelayBeforeConsideringMLS = 10000;
-  int64_t diff = PR_Now() - provider->mLastGPSDerivedLocationTime;
-  if (provider->mLocationCallback && diff > kMaxGPSDelayBeforeConsideringMLS
-      && delta > kMinMLSCoordChangeInMeters)
-  {
-    provider->mLocationCallback->Update(position);
+  DOMTimeStamp time_ms = 0;
+  if (provider->mLastGPSPosition) {
+    provider->mLastGPSPosition->GetTimestamp(&time_ms);
+  }
+  const int64_t diff_ms = (PR_Now() / PR_USEC_PER_MSEC) - time_ms;
+
+  // We want to distinguish between the GPS being inactive completely
+  // and temporarily inactive. In the former case, we would use a low
+  // accuracy network location; in the latter, we only want a network
+  // location that appears to updating with movement.
+
+  const bool isGPSFullyInactive = diff_ms > 1000 * 60 * 2; // two mins
+  const bool isGPSTempInactive = diff_ms > 1000 * 10; // 10 secs
+
+  if (provider->mLocationCallback) {
+    if (isGPSFullyInactive ||
+       (isGPSTempInactive && delta > kMinMLSCoordChangeInMeters))
+    {
+      if (gGPSDebugging) {
+        nsContentUtils::LogMessageToConsole("geo: Using MLS, GPS age:%fs, MLS Delta:%fm\n",
+                                            diff_ms / 1000.0, delta);
+      }
+      provider->mLocationCallback->Update(position);
+    } else if (provider->mLastGPSPosition) {
+      if (gGPSDebugging) {
+        nsContentUtils::LogMessageToConsole("geo: Using old GPS age:%fs\n",
+                                            diff_ms / 1000.0);
+      }
+
+      // This is a fallback case so that the GPS provider responds with its last
+      // location rather than waiting for a more recent GPS or network location.
+      // The service decides if the location is too old, not the provider.
+      provider->mLocationCallback->Update(provider->mLastGPSPosition);
+    }
   }
 
   provider->InjectLocation(lat, lon, acc);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::NetworkLocationUpdate::LocationUpdatePending()
@@ -774,17 +813,16 @@ GonkGPSGeolocationProvider::Startup()
   if (mNetworkLocationProvider) {
     nsresult rv = mNetworkLocationProvider->Startup();
     if (NS_SUCCEEDED(rv)) {
       nsRefPtr<NetworkLocationUpdate> update = new NetworkLocationUpdate();
       mNetworkLocationProvider->Watch(update);
     }
   }
 
-  mLastGPSDerivedLocationTime = 0;
   mStarted = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::Watch(nsIGeolocationUpdate* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/system/gonk/GonkGPSGeolocationProvider.h
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.h
@@ -16,16 +16,17 @@
 
 #ifndef GonkGPSGeolocationProvider_h
 #define GonkGPSGeolocationProvider_h
 
 #include <hardware/gps.h> // for GpsInterface
 #include "nsCOMPtr.h"
 #include "nsIGeolocationProvider.h"
 #include "nsIObserver.h"
+#include "nsIDOMGeoPosition.h"
 #ifdef MOZ_B2G_RIL
 #include "nsIRadioInterfaceLayer.h"
 #endif
 #include "nsISettingsService.h"
 
 class nsIThread;
 
 #define GONK_GPS_GEOLOCATION_PROVIDER_CID \
@@ -106,19 +107,19 @@ private:
 
   const GpsInterface* mGpsInterface;
 #ifdef MOZ_B2G_RIL
   const AGpsInterface* mAGpsInterface;
   const AGpsRilInterface* mAGpsRilInterface;
   nsCOMPtr<nsIRadioInterface> mRadioInterface;
 #endif
   nsCOMPtr<nsIGeolocationUpdate> mLocationCallback;
-  PRTime mLastGPSDerivedLocationTime;
   nsCOMPtr<nsIThread> mInitThread;
   nsCOMPtr<nsIGeolocationProvider> mNetworkLocationProvider;
+  nsCOMPtr<nsIDOMGeoPosition> mLastGPSPosition;
 
   class NetworkLocationUpdate : public nsIGeolocationUpdate
   {
     public:
       NS_DECL_ISUPPORTS
       NS_DECL_NSIGEOLOCATIONUPDATE
 
       NetworkLocationUpdate() {}
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -698,18 +698,16 @@ var interfaceNamesInGlobalScope =
     {name: "mozRTCPeerConnection", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSettingsEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSmsEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "MozSmsFilter",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSmsMessage",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozSpeakerManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozStkCommandEvent", b2g: true, pref: "dom.icc.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozTimeManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/MozMobileMessageManager.webidl
+++ b/dom/webidl/MozMobileMessageManager.webidl
@@ -1,16 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 interface MozMmsMessage;
-interface MozSmsFilter;
 interface MozSmsMessage;
 
 dictionary SmsSegmentInfo {
   /**
    * The number of total segments for the input string. The value is always
    * larger-equal than 1.
    */
   long segments = 0;
@@ -46,16 +45,41 @@ dictionary SmsSendParameters {
                            // specified under the multi-sim scenario.
 };
 
 dictionary MmsSendParameters {
   unsigned long serviceId; // The ID of the RIL service which needs to be
                            // specified under the multi-sim scenario.
 };
 
+enum MobileMessageFilterDelivery { "sent", "received" };
+
+dictionary MobileMessageFilter
+{
+  // Close lower bound range for filtering by the message timestamp.
+  // Time in milliseconds since Epoch.
+  [EnforceRange] DOMTimeStamp? startDate = null;
+
+  // Close upper bound range for filtering by the message timestamp.
+  // Time in milliseconds since Epoch.
+  [EnforceRange] DOMTimeStamp? endDate = null;
+
+  // An array of string message participant addresses that any of which
+  // appears or matches a message's sendor or recipients addresses.
+  sequence<DOMString>? numbers = null;
+
+  MobileMessageFilterDelivery? delivery = null;
+
+  // Filtering by whether a message has been read or not.
+  boolean? read = null;
+
+  // Filtering by a message's threadId attribute.
+  [EnforceRange] unsigned long long? threadId = 0;
+};
+
 [Pref="dom.sms.enabled"]
 interface MozMobileMessageManager : EventTarget
 {
   [Throws]
   DOMRequest getSegmentInfoForText(DOMString text);
 
   /**
    * Send SMS.
@@ -106,17 +130,17 @@ interface MozMobileMessageManager : Even
   DOMRequest delete(MozSmsMessage message);
   [Throws]
   DOMRequest delete(MozMmsMessage message);
   [Throws]
   DOMRequest delete(sequence<(long or MozSmsMessage or MozMmsMessage)> params);
 
   // Iterates through Moz{Mms,Sms}Message.
   [Throws]
-  DOMCursor getMessages(optional MozSmsFilter? filter = null,
+  DOMCursor getMessages(optional MobileMessageFilter filter,
                         optional boolean reverse = false);
 
   [Throws]
   DOMRequest markMessageRead(long id,
                              boolean read,
                              optional boolean sendReadReport = false);
 
   // Iterates through nsIDOMMozMobileMessageThread.
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -22,16 +22,17 @@ enum RTCStatsType {
 dictionary RTCStats {
   DOMHighResTimeStamp timestamp;
   RTCStatsType type;
   DOMString id;
 };
 
 dictionary RTCRTPStreamStats : RTCStats {
   DOMString ssrc;
+  DOMString mediaType;
   DOMString remoteId;
   boolean isRemote = false;
   DOMString mediaTrackId;
   DOMString transportId;
   DOMString codecId;
 
   // Video encoder/decoder measurements (absent for rtcp)
   double bitrateMean;
--- a/gfx/angle/src/libGLESv2/renderer/d3d9/RenderTarget9.cpp
+++ b/gfx/angle/src/libGLESv2/renderer/d3d9/RenderTarget9.cpp
@@ -75,17 +75,19 @@ RenderTarget9::RenderTarget9(Renderer *r
         {
             requiresInitialization = gl_d3d9::RequiresTextureDataInitialization(internalFormat);
 
             result = device->CreateRenderTarget(width, height, renderFormat,
                                                 gl_d3d9::GetMultisampleType(supportedSamples),
                                                 0, FALSE, &mRenderTarget, NULL);
         }
 
-        if (result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY)
+        if (result == D3DERR_OUTOFVIDEOMEMORY ||
+            result == E_INVALIDARG ||
+            result == E_OUTOFMEMORY)
         {
             gl::error(GL_OUT_OF_MEMORY);
 
             return;
         }
 
         ASSERT(SUCCEEDED(result));
 
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -33,29 +33,34 @@ RenderbufferStorageBySamples(GLContext* 
                                              aSize.width, aSize.height);
     } else {
         aGL->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER,
                                   aInternalFormat,
                                   aSize.width, aSize.height);
     }
 }
 
-
 GLuint
 CreateTexture(GLContext* aGL, GLenum aInternalFormat, GLenum aFormat,
               GLenum aType, const gfx::IntSize& aSize, bool linear)
 {
     GLuint tex = 0;
     aGL->fGenTextures(1, &tex);
     ScopedBindTexture autoTex(aGL, tex);
 
-    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, linear ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
-    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, linear ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
-    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D,
+                        LOCAL_GL_TEXTURE_MIN_FILTER, linear ? LOCAL_GL_LINEAR
+                                                            : LOCAL_GL_NEAREST);
+    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D,
+                        LOCAL_GL_TEXTURE_MAG_FILTER, linear ? LOCAL_GL_LINEAR
+                                                            : LOCAL_GL_NEAREST);
+    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+                        LOCAL_GL_CLAMP_TO_EDGE);
+    aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+                        LOCAL_GL_CLAMP_TO_EDGE);
 
     aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D,
                      0,
                      aInternalFormat,
                      aSize.width, aSize.height,
                      0,
                      aFormat,
                      aType,
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -272,17 +272,17 @@ GLContext::GLContext(const SurfaceCaps& 
     mIsOffscreen(isOffscreen),
     mContextLost(false),
     mVersion(0),
     mProfile(ContextProfile::Unknown),
     mVendor(GLVendor::Other),
     mRenderer(GLRenderer::Other),
     mHasRobustness(false),
 #ifdef DEBUG
-    mGLError(LOCAL_GL_NO_ERROR),
+    mIsInLocalErrorCheck(false),
 #endif
     mSharedContext(sharedContext),
     mCaps(caps),
     mScreen(nullptr),
     mLockedSurface(nullptr),
     mMaxTextureSize(0),
     mMaxCubeMapTextureSize(0),
     mMaxTextureImageSize(0),
@@ -1107,33 +1107,33 @@ GLContext::InitWithPrefix(const char *pr
     }
 
     if (mInitialized) {
         raw_fGetIntegerv(LOCAL_GL_VIEWPORT, mViewportRect);
         raw_fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mScissorRect);
         raw_fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
         raw_fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mMaxCubeMapTextureSize);
         raw_fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
+        raw_fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
 
 #ifdef XP_MACOSX
         if (mWorkAroundDriverBugs) {
             if (mVendor == GLVendor::Intel) {
                 // see bug 737182 for 2D textures, bug 684882 for cube map textures.
                 mMaxTextureSize        = std::min(mMaxTextureSize,        4096);
                 mMaxCubeMapTextureSize = std::min(mMaxCubeMapTextureSize, 512);
                 // for good measure, we align renderbuffers on what we do for 2D textures
                 mMaxRenderbufferSize   = std::min(mMaxRenderbufferSize,   4096);
                 mNeedsTextureSizeChecks = true;
             } else if (mVendor == GLVendor::NVIDIA) {
                 if (nsCocoaFeatures::OnMountainLionOrLater()) {
                     // See bug 879656.  8192 fails, 8191 works.
                     mMaxTextureSize = std::min(mMaxTextureSize, 8191);
                     mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 8191);
-                }
-                else {
+                } else {
                     // See bug 877949.
                     mMaxTextureSize = std::min(mMaxTextureSize, 4096);
                     mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 4096);
                 }
 
                 // Part of the bug 879656, but it also doesn't hurt the 877949
                 mNeedsTextureSizeChecks = true;
             }
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -7,16 +7,17 @@
 #ifndef GLCONTEXT_H_
 #define GLCONTEXT_H_
 
 #include <stdio.h>
 #include <stdint.h>
 #include <ctype.h>
 #include <map>
 #include <bitset>
+#include <queue>
 
 #ifdef DEBUG
 #include <string.h>
 #endif
 
 #ifdef WIN32
 #include <windows.h>
 #endif
@@ -500,38 +501,33 @@ private:
     /**
      * Is this feature supported using the core (unsuffixed) symbols?
      */
     bool IsFeatureProvidedByCoreSymbols(GLFeature feature);
 
 // -----------------------------------------------------------------------------
 // Robustness handling
 public:
-
     bool HasRobustness() const {
         return mHasRobustness;
     }
 
     /**
      * The derived class is expected to provide information on whether or not it
      * supports robustness.
      */
     virtual bool SupportsRobustness() const = 0;
 
-
 private:
     bool mHasRobustness;
 
-
 // -----------------------------------------------------------------------------
 // Error handling
 public:
-
-    static const char* GLErrorToString(GLenum aError)
-    {
+    static const char* GLErrorToString(GLenum aError) {
         switch (aError) {
             case LOCAL_GL_INVALID_ENUM:
                 return "GL_INVALID_ENUM";
             case LOCAL_GL_INVALID_VALUE:
                 return "GL_INVALID_VALUE";
             case LOCAL_GL_INVALID_OPERATION:
                 return "GL_INVALID_OPERATION";
             case LOCAL_GL_STACK_OVERFLOW:
@@ -544,57 +540,124 @@ public:
                 return "GL_TABLE_TOO_LARGE";
             case LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION:
                 return "GL_INVALID_FRAMEBUFFER_OPERATION";
             default:
                 return "";
         }
     }
 
-
     /** \returns the first GL error, and guarantees that all GL error flags are cleared,
      * i.e. that a subsequent GetError call will return NO_ERROR
      */
-    GLenum GetAndClearError()
-    {
+    GLenum GetAndClearError() {
         // the first error is what we want to return
         GLenum error = fGetError();
 
         if (error) {
             // clear all pending errors
             while(fGetError()) {}
         }
 
         return error;
     }
 
-
-    /*** In GL debug mode, we completely override glGetError ***/
-
-    GLenum fGetError()
-    {
-#ifdef DEBUG
-        // debug mode ends up eating the error in AFTER_GL_CALL
-        if (DebugMode()) {
-            GLenum err = mGLError;
-            mGLError = LOCAL_GL_NO_ERROR;
+private:
+    GLenum raw_fGetError() {
+        return mSymbols.fGetError();
+    }
+
+    std::queue<GLenum> mGLErrorQueue;
+
+public:
+    GLenum fGetError() {
+        if (!mGLErrorQueue.empty()) {
+            GLenum err = mGLErrorQueue.front();
+            mGLErrorQueue.pop();
             return err;
         }
-#endif // DEBUG
-
-        return mSymbols.fGetError();
+
+        return GetUnpushedError();
+    }
+
+private:
+    GLenum GetUnpushedError() {
+        return raw_fGetError();
+    }
+
+    void ClearUnpushedErrors() {
+        while (GetUnpushedError()) {
+            // Discard errors.
+        }
+    }
+
+    GLenum GetAndClearUnpushedErrors() {
+        GLenum err = GetUnpushedError();
+        if (err) {
+            ClearUnpushedErrors();
+        }
+        return err;
+    }
+
+    void PushError(GLenum err) {
+        mGLErrorQueue.push(err);
+    }
+
+    void GetAndPushAllErrors() {
+        while (true) {
+            GLenum err = GetUnpushedError();
+            if (!err)
+                break;
+
+            PushError(err);
+        }
     }
 
-
-#ifdef DEBUG
+    ////////////////////////////////////
+    // Use this safer option.
 private:
-
-    GLenum mGLError;
-#endif // DEBUG
-
+#ifdef DEBUG
+    bool mIsInLocalErrorCheck;
+#endif
+
+public:
+    class ScopedLocalErrorCheck {
+        GLContext* const mGL;
+        bool mHasBeenChecked;
+
+    public:
+        ScopedLocalErrorCheck(GLContext* gl)
+            : mGL(gl)
+            , mHasBeenChecked(false)
+        {
+#ifdef DEBUG
+            MOZ_ASSERT(!mGL->mIsInLocalErrorCheck);
+            mGL->mIsInLocalErrorCheck = true;
+#endif
+            mGL->GetAndPushAllErrors();
+        }
+
+        GLenum GetLocalError() {
+#ifdef DEBUG
+            MOZ_ASSERT(mGL->mIsInLocalErrorCheck);
+            mGL->mIsInLocalErrorCheck = false;
+#endif
+
+            MOZ_ASSERT(!mHasBeenChecked);
+            mHasBeenChecked = true;
+
+            return mGL->GetAndClearUnpushedErrors();
+        }
+
+        ~ScopedLocalErrorCheck() {
+            MOZ_ASSERT(mHasBeenChecked);
+        }
+    };
+
+private:
     static void GLAPIENTRY StaticDebugCallback(GLenum source,
                                                GLenum type,
                                                GLuint id,
                                                GLenum severity,
                                                GLsizei length,
                                                const GLchar* message,
                                                const GLvoid* userParam);
     void DebugCallback(GLenum source,
@@ -619,18 +682,17 @@ private:
 #  define MOZ_FUNCTION_NAME __PRETTY_FUNCTION__
 # elif defined(_MSC_VER)
 #  define MOZ_FUNCTION_NAME __FUNCTION__
 # else
 #  define MOZ_FUNCTION_NAME __func__  // defined in C99, supported in various C++ compilers. Just raw function name.
 # endif
 #endif
 
-    void BeforeGLCall(const char* glFunction)
-    {
+    void BeforeGLCall(const char* glFunction) {
         MOZ_ASSERT(IsCurrent());
         if (DebugMode()) {
             GLContext *currentGLContext = nullptr;
 
             currentGLContext = (GLContext*)PR_GetThreadPrivate(sCurrentGLContextTLS);
 
             if (DebugMode() & DebugTrace)
                 printf_stderr("[gl:%p] > %s\n", this, glFunction);
@@ -638,31 +700,33 @@ private:
                 printf_stderr("Fatal: %s called on non-current context %p. "
                               "The current context for this thread is %p.\n",
                               glFunction, this, currentGLContext);
                 NS_ABORT();
             }
         }
     }
 
-    void AfterGLCall(const char* glFunction)
-    {
+    void AfterGLCall(const char* glFunction) {
         if (DebugMode()) {
             // calling fFinish() immediately after every GL call makes sure that if this GL command crashes,
             // the stack trace will actually point to it. Otherwise, OpenGL being an asynchronous API, stack traces
             // tend to be meaningless
             mSymbols.fFinish();
-            mGLError = mSymbols.fGetError();
+            GLenum err = GetUnpushedError();
+            PushError(err);
+
             if (DebugMode() & DebugTrace)
-                printf_stderr("[gl:%p] < %s [0x%04x]\n", this, glFunction, mGLError);
-            if (mGLError != LOCAL_GL_NO_ERROR) {
+                printf_stderr("[gl:%p] < %s [0x%04x]\n", this, glFunction, err);
+
+            if (err != LOCAL_GL_NO_ERROR) {
                 printf_stderr("GL ERROR: %s generated GL error %s(0x%04x)\n",
                               glFunction,
-                              GLErrorToString(mGLError),
-                              mGLError);
+                              GLErrorToString(err),
+                              err);
                 if (DebugMode() & DebugAbortOnError)
                     NS_ABORT();
             }
         }
     }
 
     GLContext *TrackingContext()
     {
@@ -2980,16 +3044,17 @@ protected:
 
     GLint mViewportRect[4];
     GLint mScissorRect[4];
 
     GLint mMaxTextureSize;
     GLint mMaxCubeMapTextureSize;
     GLint mMaxTextureImageSize;
     GLint mMaxRenderbufferSize;
+    GLint mMaxViewportDims[2];
     GLsizei mMaxSamples;
     bool mNeedsTextureSizeChecks;
     bool mWorkAroundDriverBugs;
 
     bool IsTextureSizeSafeToPassToDriver(GLenum target, GLsizei width, GLsizei height) const {
         if (mNeedsTextureSizeChecks) {
             // some drivers incorrectly handle some large texture sizes that are below the
             // max texture size that they report. So we check ourselves against our own values
--- a/gfx/gl/GLContextProviderCGL.mm
+++ b/gfx/gl/GLContextProviderCGL.mm
@@ -243,34 +243,42 @@ CreateOffscreenFBOContext(bool aShare = 
 
     SurfaceCaps dummyCaps = SurfaceCaps::Any();
     nsRefPtr<GLContextCGL> glContext = new GLContextCGL(dummyCaps, shareContext, context, true);
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
+GLContextProviderCGL::CreateHeadless()
+{
+    nsRefPtr<GLContextCGL> glContext = CreateOffscreenFBOContext();
+    if (!glContext)
+        return nullptr;
+
+    if (!glContext->Init())
+        return nullptr;
+
+    return glContext.forget();
+}
+
+already_AddRefed<GLContext>
 GLContextProviderCGL::CreateOffscreen(const gfxIntSize& size,
                                       const SurfaceCaps& caps)
 {
-    nsRefPtr<GLContextCGL> glContext = CreateOffscreenFBOContext();
-    if (glContext &&
-        glContext->Init() &&
-        glContext->InitOffscreen(ToIntSize(size), caps))
-    {
-        return glContext.forget();
-    }
+    nsRefPtr<GLContext> glContext = CreateHeadless();
+    if (!glContext->InitOffscreen(ToIntSize(size), caps))
+        return nullptr;
 
-    // everything failed
-    return nullptr;
+    return glContext.forget();
 }
 
 static nsRefPtr<GLContext> gGlobalContext;
 
-GLContext *
+GLContext*
 GLContextProviderCGL::GetGlobalContext()
 {
     if (!sCGLLibrary.EnsureInitialized()) {
         return nullptr;
     }
 
     if (!gGlobalContext) {
         // There are bugs in some older drivers with pbuffers less
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -873,43 +873,52 @@ GLContextEGL::CreateEGLPixmapOffscreenCo
         return nullptr;
     }
 
     glContext->HoldSurface(thebesSurface);
 
     return glContext.forget();
 }
 
+already_AddRefed<GLContext>
+GLContextProviderEGL::CreateHeadless()
+{
+    if (!sEGLLibrary.EnsureInitialized()) {
+        return nullptr;
+    }
+
+    gfxIntSize dummySize = gfxIntSize(16, 16);
+    nsRefPtr<GLContext> glContext;
+    glContext = GLContextEGL::CreateEGLPBufferOffscreenContext(dummySize);
+    if (!glContext)
+        return nullptr;
+
+    return glContext.forget();
+}
+
 // Under EGL, on Android, pbuffers are supported fine, though
 // often without the ability to texture from them directly.
 already_AddRefed<GLContext>
 GLContextProviderEGL::CreateOffscreen(const gfxIntSize& size,
                                       const SurfaceCaps& caps)
 {
-    if (!sEGLLibrary.EnsureInitialized()) {
-        return nullptr;
-    }
-
-    gfxIntSize dummySize = gfxIntSize(16, 16);
-    nsRefPtr<GLContextEGL> glContext;
-    glContext = GLContextEGL::CreateEGLPBufferOffscreenContext(dummySize);
-
+    nsRefPtr<GLContext> glContext = CreateHeadless();
     if (!glContext)
         return nullptr;
 
     if (!glContext->InitOffscreen(ToIntSize(size), caps))
         return nullptr;
 
     return glContext.forget();
 }
 
 // Don't want a global context on Android as 1) share groups across 2 threads fail on many Tegra drivers (bug 759225)
 // and 2) some mobile devices have a very strict limit on global number of GL contexts (bug 754257)
 // and 3) each EGL context eats 750k on B2G (bug 813783)
-GLContext *
+GLContext*
 GLContextProviderEGL::GetGlobalContext()
 {
     return nullptr;
 }
 
 void
 GLContextProviderEGL::Shutdown()
 {
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -1208,23 +1208,31 @@ DONE_CREATING_PIXMAP:
                                                   true,
                                                   xsurface);
     }
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
+GLContextProviderGLX::CreateHeadless()
+{
+    gfxIntSize dummySize = gfxIntSize(16, 16);
+    nsRefPtr<GLContext> glContext = CreateOffscreenPixmapContext(dummySize);
+    if (!glContext)
+        return nullptr;
+
+    return glContext.forget();
+}
+
+already_AddRefed<GLContext>
 GLContextProviderGLX::CreateOffscreen(const gfxIntSize& size,
                                       const SurfaceCaps& caps)
 {
-    gfxIntSize dummySize = gfxIntSize(16, 16);
-    nsRefPtr<GLContextGLX> glContext =
-        CreateOffscreenPixmapContext(dummySize);
-
+    nsRefPtr<GLContext> glContext = CreateHeadless();
     if (!glContext)
         return nullptr;
 
     if (!glContext->InitOffscreen(ToIntSize(size), caps))
         return nullptr;
 
     return glContext.forget();
 }
--- a/gfx/gl/GLContextProviderImpl.h
+++ b/gfx/gl/GLContextProviderImpl.h
@@ -55,16 +55,20 @@ public:
      * @param aFormat The ContextFormat for this offscreen context.
      *
      * @return Context to use for offscreen rendering
      */
     static already_AddRefed<GLContext>
     CreateOffscreen(const gfxIntSize& size,
                     const SurfaceCaps& caps);
 
+    // Just create a context. We'll add offscreen stuff ourselves.
+    static already_AddRefed<GLContext>
+    CreateHeadless();
+
     /**
      * Create wrapping Gecko GLContext for external gl context.
      *
      * @param aContext External context which will be wrapped by Gecko GLContext.
      * @param aSurface External surface which is used for external context.
      *
      * @return Wrapping Context to use for rendering
      */
--- a/gfx/gl/GLContextProviderNull.cpp
+++ b/gfx/gl/GLContextProviderNull.cpp
@@ -17,18 +17,23 @@ GLContextProviderNull::CreateForWindow(n
 already_AddRefed<GLContext>
 GLContextProviderNull::CreateWrappingExisting(void*, void*)
 {
     return nullptr;
 }
 
 already_AddRefed<GLContext>
 GLContextProviderNull::CreateOffscreen(const gfxIntSize&,
-                                       const SurfaceCaps&,
-                                       ContextFlags)
+                                       const SurfaceCaps&)
+{
+    return nullptr;
+}
+
+already_AddRefed<GLContext>
+GLContextProviderNull::CreateHeadless()
 {
     return nullptr;
 }
 
 GLContext*
 GLContextProviderNull::GetGlobalContext(ContextFlags)
 {
     return nullptr;
--- a/gfx/gl/GLContextProviderWGL.cpp
+++ b/gfx/gl/GLContextProviderWGL.cpp
@@ -602,18 +602,17 @@ CreateWindowOffscreenContext()
     nsRefPtr<GLContextWGL> glContext = new GLContextWGL(caps,
                                                         shareContext, true,
                                                         dc, context, win);
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
-GLContextProviderWGL::CreateOffscreen(const gfxIntSize& size,
-                                      const SurfaceCaps& caps)
+GLContextProviderWGL::CreateHeadless()
 {
     if (!sWGLLib.EnsureInitialized()) {
         return nullptr;
     }
 
     nsRefPtr<GLContextWGL> glContext;
 
     // Always try to create a pbuffer context first, because we
@@ -631,16 +630,28 @@ GLContextProviderWGL::CreateOffscreen(co
     }
 
     if (!glContext ||
         !glContext->Init())
     {
         return nullptr;
     }
 
+    nsRefPtr<GLContext> retGL = glContext;
+    return retGL.forget();
+}
+
+already_AddRefed<GLContext>
+GLContextProviderWGL::CreateOffscreen(const gfxIntSize& size,
+                                      const SurfaceCaps& caps)
+{
+    nsRefPtr<GLContext> glContext = CreateHeadless();
+    if (!glContext)
+        return nullptr;
+
     if (!glContext->InitOffscreen(ToIntSize(size), caps))
         return nullptr;
 
     return glContext.forget();
 }
 
 static nsRefPtr<GLContextWGL> gGlobalContext;
 
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -563,27 +563,30 @@ DrawBuffer::Create(GLContext* const gl,
     } else {
         if (!formats.depth)
             pDepthRB = nullptr;
 
         if (!formats.stencil)
             pStencilRB = nullptr;
     }
 
+    GLContext::ScopedLocalErrorCheck localError(gl);
+
     CreateRenderbuffersForOffscreen(gl, formats, size, caps.antialias,
                                     pColorMSRB, pDepthRB, pStencilRB);
 
     GLuint fb = 0;
     gl->fGenFramebuffers(1, &fb);
     gl->AttachBuffersToFB(0, colorMSRB, depthRB, stencilRB, fb);
 
     UniquePtr<DrawBuffer> ret( new DrawBuffer(gl, size, fb, colorMSRB,
                                               depthRB, stencilRB) );
 
-    if (!gl->IsFramebufferComplete(fb))
+    GLenum err = localError.GetLocalError();
+    if (err || !gl->IsFramebufferComplete(fb))
         return false;
 
     *out_buffer = Move(ret);
     return true;
 }
 
 DrawBuffer::~DrawBuffer()
 {
@@ -619,16 +622,18 @@ ReadBuffer::Create(GLContext* gl,
     }
 
     GLuint depthRB = 0;
     GLuint stencilRB = 0;
 
     GLuint* pDepthRB   = caps.depth   ? &depthRB   : nullptr;
     GLuint* pStencilRB = caps.stencil ? &stencilRB : nullptr;
 
+    GLContext::ScopedLocalErrorCheck localError(gl);
+
     CreateRenderbuffersForOffscreen(gl, formats, surf->mSize, caps.antialias,
                                     nullptr, pDepthRB, pStencilRB);
 
     GLuint colorTex = 0;
     GLuint colorRB = 0;
     GLenum target = 0;
 
     switch (surf->mAttachType) {
@@ -646,17 +651,19 @@ ReadBuffer::Create(GLContext* gl,
 
     GLuint fb = 0;
     gl->fGenFramebuffers(1, &fb);
     gl->AttachBuffersToFB(colorTex, colorRB, depthRB, stencilRB, fb, target);
     gl->mFBOMapping[fb] = surf;
 
     UniquePtr<ReadBuffer> ret( new ReadBuffer(gl, fb, depthRB,
                                               stencilRB, surf) );
-    if (!gl->IsFramebufferComplete(fb)) {
+
+    GLenum err = localError.GetLocalError();
+    if (err || !gl->IsFramebufferComplete(fb)) {
         ret = nullptr;
     }
 
     return Move(ret);
 }
 
 ReadBuffer::~ReadBuffer()
 {
--- a/gfx/gl/SharedSurfaceANGLE.cpp
+++ b/gfx/gl/SharedSurfaceANGLE.cpp
@@ -139,30 +139,38 @@ ChooseConfig(GLContext* gl,
 
     if (gl->DebugMode()) {
         egl->DumpEGLConfig(config);
     }
 
     return config;
 }
 
-// Returns EGL_NO_SURFACE on error.
+// Returns `EGL_NO_SURFACE` (`0`) on error.
 static EGLSurface
 CreatePBufferSurface(GLLibraryEGL* egl,
                      EGLDisplay display,
                      EGLConfig config,
                      const gfx::IntSize& size)
 {
+    auto width = size.width;
+    auto height = size.height;
+
     EGLint attribs[] = {
-        LOCAL_EGL_WIDTH, size.width,
-        LOCAL_EGL_HEIGHT, size.height,
+        LOCAL_EGL_WIDTH, width,
+        LOCAL_EGL_HEIGHT, height,
         LOCAL_EGL_NONE
     };
 
+    DebugOnly<EGLint> preCallErr = egl->fGetError();
+    MOZ_ASSERT(preCallErr == LOCAL_EGL_SUCCESS);
     EGLSurface surface = egl->fCreatePbufferSurface(display, config, attribs);
+    EGLint err = egl->fGetError();
+    if (err != LOCAL_EGL_SUCCESS)
+        return 0;
 
     return surface;
 }
 
 /*static*/ UniquePtr<SharedSurface_ANGLEShareHandle>
 SharedSurface_ANGLEShareHandle::Create(GLContext* gl,
                                        EGLContext context, EGLConfig config,
                                        const gfx::IntSize& size, bool hasAlpha)
--- a/gfx/gl/SharedSurfaceGL.cpp
+++ b/gfx/gl/SharedSurfaceGL.cpp
@@ -18,21 +18,29 @@ using gfx::IntSize;
 using gfx::SurfaceFormat;
 
 /*static*/ UniquePtr<SharedSurface_Basic>
 SharedSurface_Basic::Create(GLContext* gl,
                             const GLFormats& formats,
                             const IntSize& size,
                             bool hasAlpha)
 {
+    UniquePtr<SharedSurface_Basic> ret;
     gl->MakeCurrent();
+
+    GLContext::ScopedLocalErrorCheck localError(gl);
     GLuint tex = CreateTexture(gl, formats.color_texInternalFormat,
                                formats.color_texFormat,
                                formats.color_texType,
                                size);
+    GLenum err = localError.GetLocalError();
+    if (err) {
+        gl->fDeleteTextures(1, &tex);
+        return Move(ret);
+    }
 
     SurfaceFormat format = SurfaceFormat::B8G8R8X8;
     switch (formats.color_texInternalFormat) {
     case LOCAL_GL_RGB:
     case LOCAL_GL_RGB8:
         if (formats.color_texType == LOCAL_GL_UNSIGNED_SHORT_5_6_5)
             format = SurfaceFormat::R5G6B5;
         else
@@ -41,18 +49,17 @@ SharedSurface_Basic::Create(GLContext* g
     case LOCAL_GL_RGBA:
     case LOCAL_GL_RGBA8:
         format = SurfaceFormat::B8G8R8A8;
         break;
     default:
         MOZ_CRASH("Unhandled Tex format.");
     }
 
-    typedef SharedSurface_Basic ptrT;
-    UniquePtr<ptrT> ret( new ptrT(gl, size, hasAlpha, format, tex) );
+    ret.reset( new SharedSurface_Basic(gl, size, hasAlpha, format, tex) );
     return Move(ret);
 }
 
 SharedSurface_Basic::SharedSurface_Basic(GLContext* gl,
                                          const IntSize& size,
                                          bool hasAlpha,
                                          SurfaceFormat format,
                                          GLuint tex)
@@ -69,21 +76,18 @@ SharedSurface_Basic::SharedSurface_Basic
 
     ScopedBindFramebuffer autoFB(mGL, mFB);
     mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
                               LOCAL_GL_COLOR_ATTACHMENT0,
                               LOCAL_GL_TEXTURE_2D,
                               mTex,
                               0);
 
-    GLenum status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
-    if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-        mGL->fDeleteFramebuffers(1, &mFB);
-        mFB = 0;
-    }
+    DebugOnly<GLenum> status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+    MOZ_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
 
     int32_t stride = gfx::GetAlignedStride<4>(size.width * BytesPerPixel(format));
     mData = gfx::Factory::CreateDataSourceSurfaceWithStride(size, format, stride);
     // Leave the extra return for clarity, in case we decide more code should
     // be added after this check, that should run even if mData is null.
     if (NS_WARN_IF(!mData)) {
         return;
     }
@@ -128,24 +132,34 @@ SharedSurface_GLTexture::Create(GLContex
     MOZ_ASSERT(!consGL || prodGL->SharesWith(consGL));
 
     prodGL->MakeCurrent();
 
     GLuint tex = texture;
 
     bool ownsTex = false;
 
+    UniquePtr<SharedSurface_GLTexture> ret;
+
     if (!tex) {
+      GLContext::ScopedLocalErrorCheck localError(prodGL);
+
       tex = CreateTextureForOffscreen(prodGL, formats, size);
+
+      GLenum err = localError.GetLocalError();
+      if (err) {
+          prodGL->fDeleteTextures(1, &tex);
+          return Move(ret);
+      }
+
       ownsTex = true;
     }
 
-    typedef SharedSurface_GLTexture ptrT;
-    UniquePtr<ptrT> ret( new ptrT(prodGL, consGL, size, hasAlpha, tex,
-                                  ownsTex) );
+    ret.reset( new SharedSurface_GLTexture(prodGL, consGL, size,
+                                           hasAlpha, tex, ownsTex) );
     return Move(ret);
 }
 
 SharedSurface_GLTexture::~SharedSurface_GLTexture()
 {
     if (!mGL->MakeCurrent())
         return;
 
--- a/gfx/gl/SurfaceTypes.cpp
+++ b/gfx/gl/SurfaceTypes.cpp
@@ -7,52 +7,52 @@
 
 #include "mozilla/layers/ISurfaceAllocator.h"
 
 namespace mozilla {
 namespace gl {
 
 SurfaceCaps::SurfaceCaps()
 {
-  Clear();
+    Clear();
 }
 
 SurfaceCaps::SurfaceCaps(const SurfaceCaps& other)
 {
-  *this = other;
+    *this = other;
 }
 
 SurfaceCaps&
 SurfaceCaps::operator=(const SurfaceCaps& other)
 {
-  any = other.any;
-  color = other.color;
-  alpha = other.alpha;
-  bpp16 = other.bpp16;
-  depth = other.depth;
-  stencil = other.stencil;
-  antialias = other.antialias;
-  preserve = other.preserve;
-  surfaceAllocator = other.surfaceAllocator;
+    any = other.any;
+    color = other.color;
+    alpha = other.alpha;
+    bpp16 = other.bpp16;
+    depth = other.depth;
+    stencil = other.stencil;
+    antialias = other.antialias;
+    preserve = other.preserve;
+    surfaceAllocator = other.surfaceAllocator;
 
-  return *this;
+    return *this;
 }
 
 void
 SurfaceCaps::Clear()
 {
-  any = false;
-  color = false;
-  alpha = false;
-  bpp16 = false;
-  depth = false;
-  stencil = false;
-  antialias = false;
-  preserve = false;
-  surfaceAllocator = nullptr;
+    any = false;
+    color = false;
+    alpha = false;
+    bpp16 = false;
+    depth = false;
+    stencil = false;
+    antialias = false;
+    preserve = false;
+    surfaceAllocator = nullptr;
 }
 
 SurfaceCaps::~SurfaceCaps()
 {
 }
 
 } // namespace gl
 } // namespace mozilla
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -756,16 +756,17 @@ struct ParamTraits<mozilla::layers::Fram
     WriteParam(aMsg, aParam.mScrollOffset);
     WriteParam(aMsg, aParam.mDisplayPort);
     WriteParam(aMsg, aParam.mDisplayPortMargins);
     WriteParam(aMsg, aParam.mUseDisplayPortMargins);
     WriteParam(aMsg, aParam.mCriticalDisplayPort);
     WriteParam(aMsg, aParam.mCompositionBounds);
     WriteParam(aMsg, aParam.mRootCompositionSize);
     WriteParam(aMsg, aParam.mScrollId);
+    WriteParam(aMsg, aParam.mScrollParentId);
     WriteParam(aMsg, aParam.mResolution);
     WriteParam(aMsg, aParam.mCumulativeResolution);
     WriteParam(aMsg, aParam.mZoom);
     WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
     WriteParam(aMsg, aParam.mMayHaveTouchListeners);
     WriteParam(aMsg, aParam.mMayHaveTouchCaret);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mIsRoot);
@@ -782,16 +783,17 @@ struct ParamTraits<mozilla::layers::Fram
             ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
             ReadParam(aMsg, aIter, &aResult->mUseDisplayPortMargins) &&
             ReadParam(aMsg, aIter, &aResult->mCriticalDisplayPort) &&
             ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
             ReadParam(aMsg, aIter, &aResult->mRootCompositionSize) &&
             ReadParam(aMsg, aIter, &aResult->mScrollId) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollParentId) &&
             ReadParam(aMsg, aIter, &aResult->mResolution) &&
             ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) &&
             ReadParam(aMsg, aIter, &aResult->mZoom) &&
             ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
             ReadParam(aMsg, aIter, &aResult->mMayHaveTouchListeners) &&
             ReadParam(aMsg, aIter, &aResult->mMayHaveTouchCaret) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mIsRoot) &&
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -100,20 +100,27 @@ Compositor::DrawDiagnostics(DiagnosticFl
     return;
   }
 
   DrawDiagnosticsInternal(aFlags, aVisibleRect, aClipRect, aTransform,
                           aFlashCounter);
 }
 
 RenderTargetRect
-Compositor::ClipRectInLayersCoordinates(RenderTargetIntRect aClip) const {
+Compositor::ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const {
+  ContainerLayer* parent = aLayer->AsContainerLayer() ? aLayer->AsContainerLayer() : aLayer->GetParent();
+  while (!parent->UseIntermediateSurface() && parent->GetParent()) {
+    parent = parent->GetParent();
+  }
+
+  RenderTargetIntPoint renderTargetOffset = RenderTargetIntRect::FromUntyped(
+    parent->GetEffectiveVisibleRegion().GetBounds()).TopLeft();
+
   RenderTargetRect result;
-  aClip = aClip + RenderTargetIntPoint(GetCurrentRenderTarget()->GetOrigin().x,
-                                       GetCurrentRenderTarget()->GetOrigin().y);
+  aClip = aClip + renderTargetOffset;
   RenderTargetIntSize destSize = RenderTargetIntSize(GetWidgetSize().width,
                                                      GetWidgetSize().height);
 
   switch (mScreenRotation) {
     case ROTATION_0:
       result = RenderTargetRect(aClip.x, aClip.y, aClip.width, aClip.height);
       break;
     case ROTATION_90:
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -493,17 +493,17 @@ public:
 
   // On b2g the clip rect is in the coordinate space of the physical screen
   // independently of its rotation, while the coordinate space of the layers,
   // on the other hand, depends on the screen orientation.
   // This only applies to b2g as with other platforms, orientation is handled
   // at the OS level rather than in Gecko.
   // In addition, the clip rect needs to be offset by the rendering origin.
   // This becomes important if intermediate surfaces are used.
-  RenderTargetRect ClipRectInLayersCoordinates(RenderTargetIntRect aClip) const;
+  RenderTargetRect ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const;
 
 protected:
   void DrawDiagnosticsInternal(DiagnosticFlags aFlags,
                                const gfx::Rect& aVisibleRect,
                                const gfx::Rect& aClipRect,
                                const gfx::Matrix4x4& transform,
                                uint32_t aFlashCounter);
 
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -66,31 +66,33 @@ namespace layers {
 struct FrameMetrics {
   friend struct IPC::ParamTraits<mozilla::layers::FrameMetrics>;
 public:
   // We use IDs to identify frames across processes.
   typedef uint64_t ViewID;
   static const ViewID NULL_SCROLL_ID;   // This container layer does not scroll.
   static const ViewID START_SCROLL_ID = 2;  // This is the ID that scrolling subframes
                                         // will begin at.
+  static const FrameMetrics sNullMetrics;   // We often need an empty metrics
 
   FrameMetrics()
     : mCompositionBounds(0, 0, 0, 0)
     , mDisplayPort(0, 0, 0, 0)
     , mCriticalDisplayPort(0, 0, 0, 0)
     , mScrollableRect(0, 0, 0, 0)
     , mResolution(1)
     , mCumulativeResolution(1)
     , mTransformScale(1)
     , mDevPixelsPerCSSPixel(1)
     , mMayHaveTouchListeners(false)
     , mMayHaveTouchCaret(false)
     , mIsRoot(false)
     , mHasScrollgrab(false)
     , mScrollId(NULL_SCROLL_ID)
+    , mScrollParentId(NULL_SCROLL_ID)
     , mScrollOffset(0, 0)
     , mZoom(1)
     , mUpdateScrollOffset(false)
     , mScrollGeneration(0)
     , mRootCompositionSize(0, 0)
     , mDisplayPortMargins(0, 0, 0, 0)
     , mUseDisplayPortMargins(false)
     , mPresShellId(-1)
@@ -112,16 +114,17 @@ public:
            mResolution == aOther.mResolution &&
            mCumulativeResolution == aOther.mCumulativeResolution &&
            mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
            mMayHaveTouchListeners == aOther.mMayHaveTouchListeners &&
            mMayHaveTouchCaret == aOther.mMayHaveTouchCaret &&
            mPresShellId == aOther.mPresShellId &&
            mIsRoot == aOther.mIsRoot &&
            mScrollId == aOther.mScrollId &&
+           mScrollParentId == aOther.mScrollParentId &&
            mScrollOffset == aOther.mScrollOffset &&
            mHasScrollgrab == aOther.mHasScrollgrab &&
            mUpdateScrollOffset == aOther.mUpdateScrollOffset;
   }
   bool operator!=(const FrameMetrics& aOther) const
   {
     return !operator==(aOther);
   }
@@ -399,16 +402,26 @@ public:
     return mScrollId;
   }
 
   void SetScrollId(ViewID scrollId)
   {
     mScrollId = scrollId;
   }
 
+  ViewID GetScrollParentId() const
+  {
+    return mScrollParentId;
+  }
+
+  void SetScrollParentId(ViewID aParentId)
+  {
+    mScrollParentId = aParentId;
+  }
+
   void SetRootCompositionSize(const CSSSize& aRootCompositionSize)
   {
     mRootCompositionSize = aRootCompositionSize;
   }
 
   const CSSSize& GetRootCompositionSize() const
   {
     return mRootCompositionSize;
@@ -462,16 +475,19 @@ private:
   bool mIsRoot;
 
   // Whether or not this frame is for an element marked 'scrollgrab'.
   bool mHasScrollgrab;
 
   // A unique ID assigned to each scrollable frame.
   ViewID mScrollId;
 
+  // The ViewID of the scrollable frame to which overscroll should be handed off.
+  ViewID mScrollParentId;
+
   // The position of the top-left of the CSS viewport, relative to the document
   // (or the document relative to the viewport, if that helps understand it).
   //
   // Thus it is relative to the document. It is in the same coordinate space as
   // |mScrollableRect|, but a different coordinate space than |mViewport| and
   // |mDisplayPort|.
   //
   // It is required that the rect:
new file mode 100644
--- /dev/null
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_LAYERMETRICSWRAPPER_H
+#define GFX_LAYERMETRICSWRAPPER_H
+
+#include "Layers.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * A wrapper class around a target Layer with that allows user code to
+ * walk through the FrameMetrics objects on the layer the same way it
+ * would walk through a ContainerLayer hierarchy. Consider the following
+ * layer tree:
+ *
+ *                    +---+
+ *                    | A |
+ *                    +---+
+ *                   /  |  \
+ *                  /   |   \
+ *                 /    |    \
+ *            +---+  +-----+  +---+
+ *            | B |  |  C  |  | D |
+ *            +---+  +-----+  +---+
+ *                   | FMn |
+ *                   |  .  |
+ *                   |  .  |
+ *                   |  .  |
+ *                   | FM1 |
+ *                   | FM0 |
+ *                   +-----+
+ *                   /     \
+ *                  /       \
+ *             +---+         +---+
+ *             | E |         | F |
+ *             +---+         +---+
+ *
+ * In this layer tree, there are six layers with A being the root and B,D,E,F
+ * being leaf nodes. Layer C is in the middle and has n+1 FrameMetrics, labelled
+ * FM0...FMn. FM0 is the FrameMetrics you get by calling c->GetFrameMetrics(0)
+ * and FMn is the FrameMetrics you can obtain by calling
+ * c->GetFrameMetrics(c->GetFrameMetricsCount() - 1). This layer tree is
+ * conceptually equivalent to this one below:
+ *
+ *                    +---+
+ *                    | A |
+ *                    +---+
+ *                   /  |  \
+ *                  /   |   \
+ *                 /    |    \
+ *            +---+  +-----+  +---+
+ *            | B |  | Cn  |  | D |
+ *            +---+  +-----+  +---+
+ *                      |
+ *                      .
+ *                      .
+ *                      .
+ *                      |
+ *                   +-----+
+ *                   | C1  |
+ *                   +-----+
+ *                      |
+ *                   +-----+
+ *                   | C0  |
+ *                   +-----+
+ *                   /     \
+ *                  /       \
+ *             +---+         +---+
+ *             | E |         | F |
+ *             +---+         +---+
+ *
+ * In this layer tree, the layer C has been expanded into a stack of container
+ * layers C1...Cn, where C1 has FrameMetrics FM1 and Cn has FrameMetrics Fn.
+ * Although in this example C (in the first layer tree) and C0 (in the second
+ * layer tree) are both ContainerLayers (because they have children), they
+ * do not have to be. They may just be ThebesLayers or ColorLayers, for example,
+ * which do not have any children. However, the type of C will always be the
+ * same as the type of C0.
+ *
+ * The LayerMetricsWrapper class allows client code to treat the first layer
+ * tree as though it were the second. That is, instead of client code having
+ * to iterate through the FrameMetrics objects directly, it can use a
+ * LayerMetricsWrapper to encapsulate that aspect of the layer tree and just
+ * walk the tree as if it were a stack of ContainerLayers.
+ *
+ * The functions on this class do different things depending on which
+ * simulated ContainerLayer is being wrapped. For example, if the
+ * LayerMetricsWrapper is pretending to be C0, the GetNextSibling() function
+ * will return null even though the underlying layer C does actually have
+ * a next sibling. The LayerMetricsWrapper pretending to be Cn will return
+ * D as the next sibling.
+ *
+ * Implementation notes:
+ *
+ * The AtTopLayer() and AtBottomLayer() functions in this class refer to
+ * Cn and C0 in the second layer tree above; that is, they are predicates
+ * to test if the LayerMetricsWrapper is simulating the topmost or bottommost
+ * layer, as those will have special behaviour.
+ *
+ * It is possible to wrap a nullptr in a LayerMetricsWrapper, in which case
+ * the IsValid() function will return false. This is required to allow
+ * LayerMetricsWrapper to be a MOZ_STACK_CLASS (desirable because it is used
+ * in loops and recursion).
+ *
+ * This class purposely does not expose the wrapped layer directly to avoid
+ * user code from accidentally calling functions directly on it. Instead
+ * any necessary functions should be wrapped in this class. It does expose
+ * the wrapped layer as a void* for printf purposes.
+ *
+ * The implementation may look like it special-cases mIndex == 0 and/or
+ * GetFrameMetricsCount() == 0. This is an artifact of the fact that both
+ * mIndex and GetFrameMetricsCount() are uint32_t and GetFrameMetricsCount()
+ * can return 0 but mIndex cannot store -1. This seems better than the
+ * alternative of making mIndex a int32_t that can store -1, but then having
+ * to cast to uint32_t all over the place.
+ */
+class MOZ_STACK_CLASS LayerMetricsWrapper {
+public:
+  enum StartAt {
+    TOP,
+    BOTTOM,
+  };
+
+  LayerMetricsWrapper()
+    : mLayer(nullptr)
+    , mIndex(0)
+  {
+  }
+
+  explicit LayerMetricsWrapper(Layer* aRoot, StartAt aStart = StartAt::TOP)
+    : mLayer(aRoot)
+    , mIndex(0)
+  {
+    if (!mLayer) {
+      return;
+    }
+
+    switch (aStart) {
+      case StartAt::TOP:
+        mIndex = mLayer->GetFrameMetricsCount();
+        if (mIndex > 0) {
+          mIndex--;
+        }
+        break;
+      case StartAt::BOTTOM:
+        mIndex = 0;
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unknown startAt value");
+        break;
+    }
+  }
+
+  explicit LayerMetricsWrapper(Layer* aLayer, uint32_t aMetricsIndex)
+    : mLayer(aLayer)
+    , mIndex(aMetricsIndex)
+  {
+    MOZ_ASSERT(mLayer);
+    MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetFrameMetricsCount());
+  }
+
+  bool IsValid() const
+  {
+    return mLayer != nullptr;
+  }
+
+  MOZ_EXPLICIT_CONVERSION operator bool() const
+  {
+    return IsValid();
+  }
+
+  bool IsScrollInfoLayer() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    // If we are not at the bottommost layer then it's
+    // a stack of container layers all the way down to
+    // mLayer, which we can ignore. We only care about
+    // non-container descendants.
+    return Metrics().IsScrollable()
+        && mLayer->AsContainerLayer()
+        && !mLayer->GetFirstChild();
+  }
+
+  LayerMetricsWrapper GetParent() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (!AtTopLayer()) {
+      return LayerMetricsWrapper(mLayer, mIndex + 1);
+    }
+    if (mLayer->GetParent()) {
+      return LayerMetricsWrapper(mLayer->GetParent(), StartAt::BOTTOM);
+    }
+    return LayerMetricsWrapper(nullptr);
+  }
+
+  LayerMetricsWrapper GetFirstChild() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (!AtBottomLayer()) {
+      return LayerMetricsWrapper(mLayer, mIndex - 1);
+    }
+    return LayerMetricsWrapper(mLayer->GetFirstChild());
+  }
+
+  LayerMetricsWrapper GetLastChild() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (!AtBottomLayer()) {
+      return LayerMetricsWrapper(mLayer, mIndex - 1);
+    }
+    return LayerMetricsWrapper(mLayer->GetLastChild());
+  }
+
+  LayerMetricsWrapper GetPrevSibling() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtTopLayer()) {
+      return LayerMetricsWrapper(mLayer->GetPrevSibling());
+    }
+    return LayerMetricsWrapper(nullptr);
+  }
+
+  LayerMetricsWrapper GetNextSibling() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtTopLayer()) {
+      return LayerMetricsWrapper(mLayer->GetNextSibling());
+    }
+    return LayerMetricsWrapper(nullptr);
+  }
+
+  const FrameMetrics& Metrics() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (mIndex >= mLayer->GetFrameMetricsCount()) {
+      return FrameMetrics::sNullMetrics;
+    }
+    return mLayer->GetFrameMetrics(mIndex);
+  }
+
+  AsyncPanZoomController* GetApzc() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (mIndex >= mLayer->GetFrameMetricsCount()) {
+      return nullptr;
+    }
+    return mLayer->GetAsyncPanZoomController(mIndex);
+  }
+
+  void SetApzc(AsyncPanZoomController* aApzc) const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (mLayer->GetFrameMetricsCount() == 0) {
+      MOZ_ASSERT(mIndex == 0);
+      MOZ_ASSERT(aApzc == nullptr);
+      return;
+    }
+    MOZ_ASSERT(mIndex < mLayer->GetFrameMetricsCount());
+    mLayer->SetAsyncPanZoomController(mIndex, aApzc);
+  }
+
+  const char* Name() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtBottomLayer()) {
+      return mLayer->Name();
+    }
+    return "DummyContainerLayer";
+  }
+
+  LayerManager* Manager() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    return mLayer->Manager();
+  }
+
+  gfx::Matrix4x4 GetTransform() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtBottomLayer()) {
+      return mLayer->GetTransform();
+    }
+    return gfx::Matrix4x4();
+  }
+
+  RefLayer* AsRefLayer() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtBottomLayer()) {
+      return mLayer->AsRefLayer();
+    }
+    return nullptr;
+  }
+
+  nsIntRegion GetVisibleRegion() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtBottomLayer()) {
+      return mLayer->GetVisibleRegion();
+    }
+    nsIntRegion region = mLayer->GetVisibleRegion();
+    region.Transform(gfx::To3DMatrix(mLayer->GetTransform()));
+    return region;
+  }
+
+  const nsIntRect* GetClipRect() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    return mLayer->GetClipRect();
+  }
+
+  const std::string& GetContentDescription() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    return mLayer->GetContentDescription();
+  }
+
+  // Expose an opaque pointer to the layer. Mostly used for printf
+  // purposes. This is not intended to be a general-purpose accessor
+  // for the underlying layer.
+  const void* GetLayer() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    return (void*)mLayer;
+  }
+
+  bool operator==(const LayerMetricsWrapper& aOther) const
+  {
+    return mLayer == aOther.mLayer
+        && mIndex == aOther.mIndex;
+  }
+
+  bool operator!=(const LayerMetricsWrapper& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+  static const FrameMetrics& TopmostScrollableMetrics(Layer* aLayer)
+  {
+    for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) {
+      if (aLayer->GetFrameMetrics(i - 1).IsScrollable()) {
+        return aLayer->GetFrameMetrics(i - 1);
+      }
+    }
+    return FrameMetrics::sNullMetrics;
+  }
+
+  static const FrameMetrics& BottommostScrollableMetrics(Layer* aLayer)
+  {
+    for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+      if (aLayer->GetFrameMetrics(i).IsScrollable()) {
+        return aLayer->GetFrameMetrics(i);
+      }
+    }
+    return FrameMetrics::sNullMetrics;
+  }
+
+  static const FrameMetrics& BottommostMetrics(Layer* aLayer)
+  {
+    if (aLayer->GetFrameMetricsCount() > 0) {
+      return aLayer->GetFrameMetrics(0);
+    }
+    return FrameMetrics::sNullMetrics;
+  }
+
+private:
+  bool AtBottomLayer() const
+  {
+    return mIndex == 0;
+  }
+
+  bool AtTopLayer() const
+  {
+    return mLayer->GetFrameMetricsCount() == 0 || mIndex == mLayer->GetFrameMetricsCount() - 1;
+  }
+
+private:
+  Layer* mLayer;
+  uint32_t mIndex;
+};
+
+}
+}
+
+#endif /* GFX_LAYERMETRICSWRAPPER_H */
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -22,16 +22,17 @@
 #include "mozilla/dom/AnimationPlayer.h" // for ComputedTimingFunction
 #include "mozilla/gfx/2D.h"             // for DrawTarget
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerComposite
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/LayersMessages.h"  // for TransformFunction, etc
 #include "nsAString.h"
 #include "nsCSSValue.h"                 // for nsCSSValue::Array, etc
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsStyleStruct.h"              // for nsTimingFunction, etc
 #include "protobuf/LayerScopePacket.pb.h"
 
 uint8_t gLayerManagerLayerBuilder;
@@ -42,76 +43,100 @@ namespace layers {
 FILE*
 FILEOrDefault(FILE* aFile)
 {
   return aFile ? aFile : stderr;
 }
 
 typedef FrameMetrics::ViewID ViewID;
 const ViewID FrameMetrics::NULL_SCROLL_ID = 0;
+const FrameMetrics FrameMetrics::sNullMetrics;
 
 using namespace mozilla::gfx;
 
 //--------------------------------------------------
 // LayerManager
-Layer*
-LayerManager::GetPrimaryScrollableLayer()
+FrameMetrics::ViewID
+LayerManager::GetRootScrollableLayerId()
 {
   if (!mRoot) {
-    return nullptr;
+    return FrameMetrics::NULL_SCROLL_ID;
+  }
+
+  nsTArray<LayerMetricsWrapper> queue;
+  queue.AppendElement(LayerMetricsWrapper(mRoot));
+  while (queue.Length()) {
+    LayerMetricsWrapper layer = queue[0];
+    queue.RemoveElementAt(0);
+
+    const FrameMetrics& frameMetrics = layer.Metrics();
+    if (frameMetrics.IsScrollable()) {
+      return frameMetrics.GetScrollId();
+    }
+
+    LayerMetricsWrapper child = layer.GetFirstChild();
+    while (child) {
+      queue.AppendElement(child);
+      child = child.GetNextSibling();
+    }
+  }
+
+  return FrameMetrics::NULL_SCROLL_ID;
+}
+
+void
+LayerManager::GetRootScrollableLayers(nsTArray<Layer*>& aArray)
+{
+  if (!mRoot) {
+    return;
+  }
+
+  FrameMetrics::ViewID rootScrollableId = GetRootScrollableLayerId();
+  if (rootScrollableId == FrameMetrics::NULL_SCROLL_ID) {
+    aArray.AppendElement(mRoot);
+    return;
   }
 
   nsTArray<Layer*> queue;
   queue.AppendElement(mRoot);
   while (queue.Length()) {
     Layer* layer = queue[0];
     queue.RemoveElementAt(0);
 
-    const FrameMetrics& frameMetrics = layer->GetFrameMetrics();
-    if (frameMetrics.IsScrollable()) {
-      return layer;
+    if (LayerMetricsWrapper::TopmostScrollableMetrics(layer).GetScrollId() == rootScrollableId) {
+      aArray.AppendElement(layer);
+      continue;
     }
 
-    if (ContainerLayer* containerLayer = layer->AsContainerLayer()) {
-      Layer* child = containerLayer->GetFirstChild();
-      while (child) {
-        queue.AppendElement(child);
-        child = child->GetNextSibling();
-      }
+    for (Layer* child = layer->GetFirstChild(); child; child = child->GetNextSibling()) {
+      queue.AppendElement(child);
     }
   }
-
-  return mRoot;
 }
 
 void
 LayerManager::GetScrollableLayers(nsTArray<Layer*>& aArray)
 {
   if (!mRoot) {
     return;
   }
 
   nsTArray<Layer*> queue;
   queue.AppendElement(mRoot);
   while (!queue.IsEmpty()) {
     Layer* layer = queue.LastElement();
     queue.RemoveElementAt(queue.Length() - 1);
 
-    const FrameMetrics& frameMetrics = layer->GetFrameMetrics();
-    if (frameMetrics.IsScrollable()) {
+    if (layer->HasScrollableFrameMetrics()) {
       aArray.AppendElement(layer);
       continue;
     }
 
-    if (ContainerLayer* containerLayer = layer->AsContainerLayer()) {
-      Layer* child = containerLayer->GetFirstChild();
-      while (child) {
-        queue.AppendElement(child);
-        child = child->GetNextSibling();
-      }
+    for (Layer* child = layer->GetFirstChild(); child; child = child->GetNextSibling()) {
+      queue.AppendElement(child);
     }
   }
 }
 
 TemporaryRef<DrawTarget>
 LayerManager::CreateOptimalDrawTarget(const gfx::IntSize &aSize,
                                       SurfaceFormat aFormat)
 {
@@ -165,17 +190,16 @@ LayerManager::AreComponentAlphaLayersEna
 
 Layer::Layer(LayerManager* aManager, void* aImplData) :
   mManager(aManager),
   mParent(nullptr),
   mNextSibling(nullptr),
   mPrevSibling(nullptr),
   mImplData(aImplData),
   mMaskLayer(nullptr),
-  mScrollHandoffParentId(FrameMetrics::NULL_SCROLL_ID),
   mPostXScale(1.0f),
   mPostYScale(1.0f),
   mOpacity(1.0),
   mMixBlendMode(CompositionOp::OP_OVER),
   mForceIsolatedGroup(false),
   mContentFlags(0),
   mUseClipRect(false),
   mUseTileSourceRect(false),
@@ -444,30 +468,38 @@ Layer::SetAnimations(const AnimationArra
       }
     }
   }
 
   Mutated();
 }
 
 void
-Layer::SetAsyncPanZoomController(AsyncPanZoomController *controller)
+Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller)
 {
-  mAPZC = controller;
+  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
+  mApzcs[aIndex] = controller;
 }
 
 AsyncPanZoomController*
-Layer::GetAsyncPanZoomController() const
+Layer::GetAsyncPanZoomController(uint32_t aIndex) const
 {
+  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
 #ifdef DEBUG
-  if (mAPZC) {
-    MOZ_ASSERT(GetFrameMetrics().IsScrollable());
+  if (mApzcs[aIndex]) {
+    MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable());
   }
 #endif
-  return mAPZC;
+  return mApzcs[aIndex];
+}
+
+void
+Layer::FrameMetricsChanged()
+{
+  mApzcs.SetLength(GetFrameMetricsCount());
 }
 
 void
 Layer::ApplyPendingUpdatesToSubtree()
 {
   ApplyPendingUpdatesForThisTransaction();
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
     child->ApplyPendingUpdatesToSubtree();
@@ -658,16 +690,43 @@ Layer::CalculateScissorRect(const Render
     if (!gfxUtils::GfxRectToIntRect(ThebesRect(trScissor), &tmp)) {
       return RenderTargetIntRect(currentClip.TopLeft(), RenderTargetIntSize(0, 0));
     }
     scissor = RenderTargetPixel::FromUntyped(tmp);
   }
   return currentClip.Intersect(scissor);
 }
 
+const FrameMetrics&
+Layer::GetFrameMetrics(uint32_t aIndex) const
+{
+  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
+  return mFrameMetrics[aIndex];
+}
+
+bool
+Layer::HasScrollableFrameMetrics() const
+{
+  for (uint32_t i = 0; i < GetFrameMetricsCount(); i++) {
+    if (GetFrameMetrics(i).IsScrollable()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+Layer::IsScrollInfoLayer() const
+{
+  // A scrollable container layer with no children
+  return AsContainerLayer()
+      && HasScrollableFrameMetrics()
+      && !GetFirstChild();
+}
+
 const Matrix4x4
 Layer::GetTransform() const
 {
   Matrix4x4 transform = mTransform;
   if (const ContainerLayer* c = AsContainerLayer()) {
     transform.Scale(c->GetPreXScale(), c->GetPreYScale(), 1.0f);
   }
   transform = transform * Matrix4x4().Scale(mPostXScale, mPostYScale, 1.0f);
@@ -1456,21 +1515,21 @@ Layer::PrintInfo(std::stringstream& aStr
                      mStickyPositionData->mOuter.x, mStickyPositionData->mOuter.y,
                      mStickyPositionData->mOuter.width, mStickyPositionData->mOuter.height,
                      mStickyPositionData->mInner.x, mStickyPositionData->mInner.y,
                      mStickyPositionData->mInner.width, mStickyPositionData->mInner.height).get();
   }
   if (mMaskLayer) {
     aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get();
   }
-  if (!mFrameMetrics.IsDefault()) {
-    AppendToString(aStream, mFrameMetrics, " [metrics=", "]");
-  }
-  if (mScrollHandoffParentId != FrameMetrics::NULL_SCROLL_ID) {
-    aStream << nsPrintfCString(" [scrollParent=%llu]", mScrollHandoffParentId).get();
+  for (uint32_t i = 0; i < mFrameMetrics.Length(); i++) {
+    if (!mFrameMetrics[i].IsDefault()) {
+      aStream << nsPrintfCString(" [metrics%d=", i).get();
+      AppendToString(aStream, mFrameMetrics[i], "", "]");
+    }
   }
 }
 
 // The static helper function sets the transform matrix into the packet
 static void
 DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, const Matrix4x4& aMatrix)
 {
   aLayerMatrix->set_is2d(aMatrix.Is2D());
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -73,16 +73,17 @@ class OverfillCallback;
 namespace layers {
 
 class Animation;
 class AnimationData;
 class AsyncPanZoomController;
 class ClientLayerManager;
 class CommonLayerAttributes;
 class Layer;
+class LayerMetricsWrapper;
 class ThebesLayer;
 class ContainerLayer;
 class ImageLayer;
 class ColorLayer;
 class ImageContainer;
 class CanvasLayer;
 class ReadbackLayer;
 class ReadbackProcessor;
@@ -331,20 +332,30 @@ public:
   virtual void SetRoot(Layer* aLayer) = 0;
   /**
    * Can be called anytime
    */
   Layer* GetRoot() { return mRoot; }
 
   /**
    * Does a breadth-first search from the root layer to find the first
-   * scrollable layer.
+   * scrollable layer, and returns its ViewID. Note that there may be
+   * other layers in the tree which share the same ViewID.
    * Can be called any time.
    */
-  Layer* GetPrimaryScrollableLayer();
+  FrameMetrics::ViewID GetRootScrollableLayerId();
+
+  /**
+   * Does a breadth-first search from the root layer to find the first
+   * scrollable layer, and returns all the layers that have that ViewID
+   * as the first scrollable metrics in their ancestor chain. If no
+   * scrollable layers are found it just returns the root of the tree if
+   * there is one.
+   */
+  void GetRootScrollableLayers(nsTArray<Layer*>& aArray);
 
   /**
    * Returns a list of all descendant layers for which
    * GetFrameMetrics().IsScrollable() is true and that
    * do not already have an ancestor in the return list.
    */
   void GetScrollableLayers(nsTArray<Layer*>& aArray);
 
@@ -813,39 +824,54 @@ public:
       mVisibleRegion = aRegion;
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
    * Set the (sub)document metrics used to render the Layer subtree
-   * rooted at this.
+   * rooted at this. Note that a layer may have multiple FrameMetrics
+   * objects; calling this function will remove all of them and replace
+   * them with the provided FrameMetrics. See the documentation for
+   * SetFrameMetrics(const nsTArray<FrameMetrics>&) for more details.
    */
   void SetFrameMetrics(const FrameMetrics& aFrameMetrics)
   {
-    if (mFrameMetrics != aFrameMetrics) {
+    if (mFrameMetrics.Length() != 1 || mFrameMetrics[0] != aFrameMetrics) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
-      mFrameMetrics = aFrameMetrics;
+      mFrameMetrics.ReplaceElementsAt(0, mFrameMetrics.Length(), aFrameMetrics);
+      FrameMetricsChanged();
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
-   * Set the ViewID of the ContainerLayer to which overscroll should be handed
-   * off. A value of NULL_SCROLL_ID means that the default handoff-parent-finding
-   * behaviour should be used (i.e. walk up the layer tree to find the next
-   * scrollable ancestor layer).
+   * Set the (sub)document metrics used to render the Layer subtree
+   * rooted at this. There might be multiple metrics on this layer
+   * because the layer may, for example, be contained inside multiple
+   * nested scrolling subdocuments. In general a Layer having multiple
+   * FrameMetrics objects is conceptually equivalent to having a stack
+   * of ContainerLayers that have been flattened into this Layer.
+   * See the documentation in LayerMetricsWrapper.h for a more detailed
+   * explanation of this conceptual equivalence.
+   *
+   * Note also that there is actually a many-to-many relationship between
+   * Layers and FrameMetrics, because multiple Layers may have identical
+   * FrameMetrics objects. This happens when those layers belong to the
+   * same scrolling subdocument and therefore end up with the same async
+   * transform when they are scrolled by the APZ code.
    */
-  void SetScrollHandoffParentId(FrameMetrics::ViewID aScrollParentId)
+  void SetFrameMetrics(const nsTArray<FrameMetrics>& aMetricsArray)
   {
-    if (mScrollHandoffParentId != aScrollParentId) {
-      MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ScrollHandoffParentId", this));
-      mScrollHandoffParentId = aScrollParentId;
+    if (mFrameMetrics != aMetricsArray) {
+      MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
+      mFrameMetrics = aMetricsArray;
+      FrameMetricsChanged();
       Mutated();
     }
   }
 
   /*
    * Compositor event handling
    * =========================
    * When a touch-start event (or similar) is sent to the AsyncPanZoomController,
@@ -1168,18 +1194,21 @@ public:
   }
 
   // These getters can be used anytime.
   float GetOpacity() { return mOpacity; }
   gfx::CompositionOp GetMixBlendMode() const { return mMixBlendMode; }
   const nsIntRect* GetClipRect() { return mUseClipRect ? &mClipRect : nullptr; }
   uint32_t GetContentFlags() { return mContentFlags; }
   const nsIntRegion& GetVisibleRegion() const { return mVisibleRegion; }
-  const FrameMetrics& GetFrameMetrics() const { return mFrameMetrics; }
-  FrameMetrics::ViewID GetScrollHandoffParentId() const { return mScrollHandoffParentId; }
+  const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const;
+  uint32_t GetFrameMetricsCount() const { return mFrameMetrics.Length(); }
+  const nsTArray<FrameMetrics>& GetAllFrameMetrics() { return mFrameMetrics; }
+  bool HasScrollableFrameMetrics() const;
+  bool IsScrollInfoLayer() const;
   const EventRegions& GetEventRegions() const { return mEventRegions; }
   ContainerLayer* GetParent() { return mParent; }
   Layer* GetNextSibling() { return mNextSibling; }
   const Layer* GetNextSibling() const { return mNextSibling; }
   Layer* GetPrevSibling() { return mPrevSibling; }
   const Layer* GetPrevSibling() const { return mPrevSibling; }
   virtual Layer* GetFirstChild() const { return nullptr; }
   virtual Layer* GetLastChild() const { return nullptr; }
@@ -1469,19 +1498,26 @@ public:
   /**
    * Clear the invalid rect, marking the layer as being identical to what is currently
    * composited.
    */
   void ClearInvalidRect() { mInvalidRegion.SetEmpty(); }
 
   // These functions allow attaching an AsyncPanZoomController to this layer,
   // and can be used anytime.
-  // A layer has an APZC only-if GetFrameMetrics().IsScrollable()
-  void SetAsyncPanZoomController(AsyncPanZoomController *controller);
-  AsyncPanZoomController* GetAsyncPanZoomController() const;
+  // A layer has an APZC at index aIndex only-if GetFrameMetrics(aIndex).IsScrollable();
+  // attempting to get an APZC for a non-scrollable metrics will return null.
+  // The aIndex for these functions must be less than GetFrameMetricsCount().
+  void SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller);
+  AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const;
+  // The FrameMetricsChanged function is used internally to ensure the APZC array length
+  // matches the frame metrics array length.
+private:
+  void FrameMetricsChanged();
+public:
 
   void ApplyPendingUpdatesForThisTransaction();
 
 #ifdef DEBUG
   void SetDebugColorIndex(uint32_t aIndex) { mDebugColorIndex = aIndex; }
   uint32_t GetDebugColorIndex() { return mDebugColorIndex; }
 #endif
 
@@ -1575,18 +1611,17 @@ protected:
   LayerManager* mManager;
   ContainerLayer* mParent;
   Layer* mNextSibling;
   Layer* mPrevSibling;
   void* mImplData;
   nsRefPtr<Layer> mMaskLayer;
   gfx::UserData mUserData;
   nsIntRegion mVisibleRegion;
-  FrameMetrics mFrameMetrics;
-  FrameMetrics::ViewID mScrollHandoffParentId;
+  nsTArray<FrameMetrics> mFrameMetrics;
   EventRegions mEventRegions;
   gfx::Matrix4x4 mTransform;
   // A mutation of |mTransform| that we've queued to be applied at the
   // end of the next transaction (if nothing else overrides it in the
   // meantime).
   nsAutoPtr<gfx::Matrix4x4> mPendingTransform;
   float mPostXScale;
   float mPostYScale;
@@ -1596,17 +1631,17 @@ protected:
   nsAutoPtr<AnimationArray> mPendingAnimations;
   InfallibleTArray<AnimData> mAnimationData;
   float mOpacity;
   gfx::CompositionOp mMixBlendMode;
   bool mForceIsolatedGroup;
   nsIntRect mClipRect;
   nsIntRect mTileSourceRect;
   nsIntRegion mInvalidRegion;
-  nsRefPtr<AsyncPanZoomController> mAPZC;
+  nsTArray<nsRefPtr<AsyncPanZoomController> > mApzcs;
   uint32_t mContentFlags;
   bool mUseClipRect;
   bool mUseTileSourceRect;
   bool mIsFixedPosition;
   LayerPoint mAnchor;
   LayerMargin mMargins;
   struct StickyPositionData {
     FrameMetrics::ViewID mScrollId;
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -120,28 +120,32 @@ AppendToString(std::stringstream& aStrea
   aStream << pfx;
   AppendToString(aStream, m.mCompositionBounds, "{ cb=");
   AppendToString(aStream, m.mScrollableRect, " sr=");
   AppendToString(aStream, m.GetScrollOffset(), " s=");
   AppendToString(aStream, m.mDisplayPort, " dp=");
   AppendToString(aStream, m.mCriticalDisplayPort, " cdp=");
   if (!detailed) {
     AppendToString(aStream, m.GetScrollId(), " scrollId=");
+    if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
+      AppendToString(aStream, m.GetScrollParentId(), " scrollParent=");
+    }
     aStream << nsPrintfCString(" z=%.3f }", m.GetZoom().scale).get();
   } else {
     AppendToString(aStream, m.GetDisplayPortMargins(), " dpm=");
     aStream << nsPrintfCString(" um=%d", m.GetUseDisplayPortMargins()).get();
     AppendToString(aStream, m.GetRootCompositionSize(), " rcs=");
     AppendToString(aStream, m.GetViewport(), " v=");
     aStream << nsPrintfCString(" z=(ld=%.3f r=%.3f cr=%.3f z=%.3f ts=%.3f)",
             m.mDevPixelsPerCSSPixel.scale, m.mResolution.scale,
             m.mCumulativeResolution.scale, m.GetZoom().scale,
             m.mTransformScale.scale).get();
     aStream << nsPrintfCString(" u=(%d %lu)",
             m.GetScrollOffsetUpdated(), m.GetScrollGeneration()).get();
+    AppendToString(aStream, m.GetScrollParentId(), " p=");
     aStream << nsPrintfCString(" i=(%ld %lld) }",
             m.GetPresShellId(), m.GetScrollId()).get();
   }
   aStream << sfx;
 }
 
 void
 AppendToString(std::stringstream& aStream, const Matrix4x4& m,
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -7,16 +7,17 @@
 #include "Compositor.h"                 // for Compositor
 #include "CompositorParent.h"           // for CompositorParent, etc
 #include "InputData.h"                  // for InputData, etc
 #include "Layers.h"                     // for Layer, etc
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/Point.h"          // for Point
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncPanZoomController.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
@@ -155,17 +156,18 @@ APZCTreeManager::UpdatePanZoomController
   // we are sure that the layer was removed and not just transplanted elsewhere. Doing that
   // as part of a recursive tree walk is hard and so maintaining a list and removing
   // APZCs that are still alive is much simpler.
   Collect(mRootApzc, &state.mApzcsToDestroy);
   mRootApzc = nullptr;
 
   if (aRoot) {
     mApzcTreeLog << "[start]\n";
-    UpdatePanZoomControllerTree(state, aRoot,
+    LayerMetricsWrapper root(aRoot);
+    UpdatePanZoomControllerTree(state, root,
                                 // aCompositor is null in gtest scenarios
                                 aCompositor ? aCompositor->RootLayerTreeId() : 0,
                                 Matrix4x4(), nullptr, nullptr, nsIntRegion());
     mApzcTreeLog << "[end]\n";
   }
 
   for (size_t i = 0; i < state.mApzcsToDestroy.Length(); i++) {
     APZCTM_LOG("Destroying APZC at %p\n", state.mApzcsToDestroy[i].get());
@@ -200,23 +202,23 @@ ComputeTouchSensitiveRegion(GeckoContent
   nsIntRegion unobscured;
   unobscured.Sub(nsIntRect(roundedVisible.x, roundedVisible.y,
                            roundedVisible.width, roundedVisible.height),
                  aObscured);
   return unobscured;
 }
 
 AsyncPanZoomController*
-APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
+APZCTreeManager::PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
                                      const nsIntRegion& aObscured,
                                      AsyncPanZoomController*& aOutParent,
-                                     AsyncPanZoomController*& aOutNextSibling,
+                                     AsyncPanZoomController* aNextSibling,
                                      TreeBuildingState& aState)
 {
   if (!aMetrics.IsScrollable()) {
     return nullptr;
   }
 
   const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
   if (!(state && state->mController.get())) {
@@ -232,23 +234,23 @@ APZCTreeManager::PrepareAPZCForLayer(con
   // with the same FrameMetrics data. This is needed because in some cases content
   // that is supposed to scroll together is split into multiple layers because of
   // e.g. non-scrolling content interleaved in z-index order.
   ScrollableLayerGuid guid(aLayersId, aMetrics);
   auto insertResult = aState.mApzcMap.insert(std::make_pair(guid, static_cast<AsyncPanZoomController*>(nullptr)));
   if (!insertResult.second) {
     apzc = insertResult.first->second;
   }
-  APZCTM_LOG("Found APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, guid.mLayersId, guid.mScrollId);
+  APZCTM_LOG("Found APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), guid.mLayersId, guid.mScrollId);
 
   // If we haven't encountered a layer already with the same metrics, then we need to
   // do the full reuse-or-make-an-APZC algorithm, which is contained inside the block
   // below.
   if (apzc == nullptr) {
-    apzc = aLayer->GetAsyncPanZoomController();
+    apzc = aLayer.GetApzc();
 
     // If the content represented by the scrollable layer has changed (which may
     // be possible because of DLBI heuristics) then we don't want to keep using
     // the same old APZC for the new content. Null it out so we run through the
     // code to find another one or create one.
     if (apzc && !apzc->Matches(guid)) {
       apzc = nullptr;
     }
@@ -285,37 +287,36 @@ APZCTreeManager::PrepareAPZCForLayer(con
       // so that it doesn't continue pointing to APZCs that should no longer
       // be in the tree. These pointers will get reset properly as we continue
       // building the tree. Also remove it from the set of APZCs that are going
       // to be destroyed, because it's going to remain active.
       aState.mApzcsToDestroy.RemoveElement(apzc);
       apzc->SetPrevSibling(nullptr);
       apzc->SetLastChild(nullptr);
     }
-    APZCTM_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, aMetrics.GetScrollId());
+    APZCTM_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
 
     apzc->NotifyLayersUpdated(aMetrics,
         aState.mIsFirstPaint && (aLayersId == aState.mOriginatingLayersId));
-    apzc->SetScrollHandoffParentId(aLayer->GetScrollHandoffParentId());
 
     nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
     apzc->SetLayerHitTestData(unobscured, aAncestorTransform);
     APZCTM_LOG("Setting region %s as visible region for APZC %p\n",
         Stringify(unobscured).c_str(), apzc);
 
     mApzcTreeLog << "APZC " << guid
                  << "\tcb=" << aMetrics.mCompositionBounds
                  << "\tsr=" << aMetrics.mScrollableRect
-                 << (aLayer->GetVisibleRegion().IsEmpty() ? "\tscrollinfo" : "")
+                 << (aLayer.GetVisibleRegion().IsEmpty() ? "\tscrollinfo" : "")
                  << (apzc->HasScrollgrab() ? "\tscrollgrab" : "")
-                 << "\t" << aLayer->GetContentDescription();
+                 << "\t" << aLayer.GetContentDescription();
 
     // Bind the APZC instance into the tree of APZCs
-    if (aOutNextSibling) {
-      aOutNextSibling->SetPrevSibling(apzc);
+    if (aNextSibling) {
+      aNextSibling->SetPrevSibling(apzc);
     } else if (aOutParent) {
       aOutParent->SetLastChild(apzc);
     } else {
       mRootApzc = apzc;
       apzc->MakeRoot();
     }
 
     // For testing, log the parent scroll id of every APZC that has a
@@ -372,47 +373,48 @@ APZCTreeManager::PrepareAPZCForLayer(con
     APZCTM_LOG("Adding region %s to visible region of APZC %p\n", Stringify(unobscured).c_str(), apzc);
   }
 
   return apzc;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
-                                             Layer* aLayer, uint64_t aLayersId,
+                                             const LayerMetricsWrapper& aLayer,
+                                             uint64_t aLayersId,
                                              const gfx::Matrix4x4& aAncestorTransform,
                                              AsyncPanZoomController* aParent,
                                              AsyncPanZoomController* aNextSibling,
                                              const nsIntRegion& aObscured)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  mApzcTreeLog << aLayer->Name() << '\t';
+  mApzcTreeLog << aLayer.Name() << '\t';
 
   AsyncPanZoomController* apzc = PrepareAPZCForLayer(aLayer,
-        aLayer->GetFrameMetrics(), aLayersId, aAncestorTransform,
+        aLayer.Metrics(), aLayersId, aAncestorTransform,
         aObscured, aParent, aNextSibling, aState);
-  aLayer->SetAsyncPanZoomController(apzc);
+  aLayer.SetApzc(apzc);
 
   mApzcTreeLog << '\n';
 
   // Accumulate the CSS transform between layers that have an APZC.
   // In the terminology of the big comment above APZCTreeManager::GetInputTransforms, if
   // we are at layer M, then aAncestorTransform is NC * OC * PC, and we left-multiply MC and
   // compute ancestorTransform to be MC * NC * OC * PC. This gets passed down as the ancestor
   // transform to layer L when we recurse into the children below. If we are at a layer
   // with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
   // the new accumulation as we go down.
-  Matrix4x4 transform = aLayer->GetTransform();
+  Matrix4x4 transform = aLayer.GetTransform();
   Matrix4x4 ancestorTransform = transform;
   if (!apzc) {
     ancestorTransform = ancestorTransform * aAncestorTransform;
   }
 
-  uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId);
+  uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
 
   nsIntRegion obscured;
   if (aLayersId == childLayersId) {
     // If the child layer is in the same process, transform
     // aObscured from aLayer's ParentLayerPixels to aLayer's LayerPixels,
     // which are the children layers' ParentLayerPixels.
     // If we cross a process boundary, we assume that we can start with
     // an empty obscured region because nothing in the parent process will
@@ -424,28 +426,28 @@ APZCTreeManager::UpdatePanZoomController
     // that case.
     obscured = aObscured;
     obscured.Transform(To3DMatrix(transform).Inverse());
   }
 
   // If there's no APZC at this level, any APZCs for our child layers will
   // have our siblings as siblings.
   AsyncPanZoomController* next = apzc ? nullptr : aNextSibling;
-  for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
+  for (LayerMetricsWrapper child = aLayer.GetLastChild(); child; child = child.GetPrevSibling()) {
     gfx::TreeAutoIndent indent(mApzcTreeLog);
     next = UpdatePanZoomControllerTree(aState, child, childLayersId,
                                        ancestorTransform, aParent, next,
                                        obscured);
 
     // Each layer obscures its previous siblings, so we augment the obscured
     // region as we loop backwards through the children.
-    nsIntRegion childRegion = child->GetVisibleRegion();
-    childRegion.Transform(gfx::To3DMatrix(child->GetTransform()));
-    if (child->GetClipRect()) {
-      childRegion.AndWith(*child->GetClipRect());
+    nsIntRegion childRegion = child.GetVisibleRegion();
+    childRegion.Transform(gfx::To3DMatrix(child.GetTransform()));
+    if (child.GetClipRect()) {
+      childRegion.AndWith(*child.GetClipRect());
     }
 
     obscured.OrWith(childRegion);
   }
 
   // Return the APZC that should be the sibling of other APZCs as we continue
   // moving towards the first child at this depth in the layer tree.
   // If this layer doesn't have an APZC, we promote any APZCs in the subtree
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -39,16 +39,17 @@ enum AllowedTouchBehavior {
   UNKNOWN =            1 << 4
 };
 
 class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 class APZPaintLogHelper;
 class OverscrollHandoffChain;
+class LayerMetricsWrapper;
 
 /**
  * ****************** NOTE ON LOCK ORDERING IN APZ **************************
  *
  * There are two kinds of locks used by APZ: APZCTreeManager::mTreeLock
  * ("the tree lock") and AsyncPanZoomController::mMonitor ("APZC locks").
  *
  * To avoid deadlock, we impose a lock ordering between these locks, which is:
@@ -373,36 +374,37 @@ private:
                                                                   bool* aOutInOverscrolledApzc);
   nsEventStatus ProcessTouchInput(MultiTouchInput& aInput,
                                   ScrollableLayerGuid* aOutTargetGuid);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
                              ScrollableLayerGuid* aOutTargetGuid);
   void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
                                         const ZoomConstraints& aConstraints);
 
-  AsyncPanZoomController* PrepareAPZCForLayer(const Layer* aLayer,
+  AsyncPanZoomController* PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
                                               const FrameMetrics& aMetrics,
                                               uint64_t aLayersId,
                                               const gfx::Matrix4x4& aAncestorTransform,
                                               const nsIntRegion& aObscured,
                                               AsyncPanZoomController*& aOutParent,
-                                              AsyncPanZoomController*& aOutNextSibling,
+                                              AsyncPanZoomController* aNextSibling,
                                               TreeBuildingState& aState);
 
   /**
    * Recursive helper function to build the APZC tree. The tree of APZC instances has
    * the same shape as the layer tree, but excludes all the layers that are not scrollable.
    * Note that this means APZCs corresponding to layers at different depths in the tree
    * may end up becoming siblings. It also means that the "root" APZC may have siblings.
    * This function walks the layer tree backwards through siblings and constructs the APZC
    * tree also as a last-child-prev-sibling tree because that simplifies the hit detection
    * code.
    */
   AsyncPanZoomController* UpdatePanZoomControllerTree(TreeBuildingState& aState,
-                                                      Layer* aLayer, uint64_t aLayersId,
+                                                      const LayerMetricsWrapper& aLayer,
+                                                      uint64_t aLayersId,
                                                       const gfx::Matrix4x4& aAncestorTransform,
                                                       AsyncPanZoomController* aParent,
                                                       AsyncPanZoomController* aNextSibling,
                                                       const nsIntRegion& aObscured);
 
 private:
   /* Whenever walking or mutating the tree rooted at mRootApzc, mTreeLock must be held.
    * This lock does not need to be held while manipulating a single APZC instance in
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -734,17 +734,16 @@ AsyncPanZoomController::AsyncPanZoomCont
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mLastAsyncScrollTime(GetFrameTime()),
      mLastAsyncScrollOffset(0, 0),
      mCurrentAsyncScrollOffset(0, 0),
      mAsyncScrollTimeoutTask(nullptr),
      mTouchBlockBalance(0),
      mTreeManager(aTreeManager),
-     mScrollParentId(FrameMetrics::NULL_SCROLL_ID),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr)
 {
   MOZ_COUNT_CTOR(AsyncPanZoomController);
 
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
@@ -2409,16 +2408,17 @@ void AsyncPanZoomController::NotifyLayer
   ReentrantMonitorAutoEnter lock(mMonitor);
   bool isDefault = mFrameMetrics.IsDefault();
 
   mLastContentPaintMetrics = aLayerMetrics;
   UpdateTransformScale();
 
   mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
   mFrameMetrics.mMayHaveTouchCaret = aLayerMetrics.mMayHaveTouchCaret;
+  mFrameMetrics.SetScrollParentId(aLayerMetrics.GetScrollParentId());
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
 
   LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.mScrollableRect);
   LogRendertraceRect(GetGuid(), "painted displayport", "lightgreen",
     aLayerMetrics.mDisplayPort + aLayerMetrics.GetScrollOffset());
   if (!aLayerMetrics.mCriticalDisplayPort.IsEmpty()) {
     LogRendertraceRect(GetGuid(), "painted critical displayport", "darkgreen",
       aLayerMetrics.mCriticalDisplayPort + aLayerMetrics.GetScrollOffset());
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -891,22 +891,18 @@ private:
   nsRefPtr<AsyncPanZoomController> mParent;
 
 
   /* ===================================================================
    * The functions and members in this section are used for scrolling,
    * including handing off scroll to another APZC, and overscrolling.
    */
 public:
-  void SetScrollHandoffParentId(FrameMetrics::ViewID aScrollParentId) {
-    mScrollParentId = aScrollParentId;
-  }
-
   FrameMetrics::ViewID GetScrollHandoffParentId() const {
-    return mScrollParentId;
+    return mFrameMetrics.GetScrollParentId();
   }
 
   /**
    * Attempt to scroll in response to a touch-move from |aStartPoint| to
    * |aEndPoint|, which are in our (transformed) screen coordinates.
    * Due to overscroll handling, there may not actually have been a touch-move
    * at these points, but this function will scroll as if there had been.
    * If this attempt causes overscroll (i.e. the layer cannot be scrolled
@@ -930,18 +926,16 @@ public:
 
   /**
    * If overscrolled, start a snap-back animation and return true.
    * Otherwise return false.
    */
   bool SnapBackIfOverscrolled();
 
 private:
-  FrameMetrics::ViewID mScrollParentId;
-
   /**
    * A helper function for calling APZCTreeManager::DispatchScroll().
    * Guards against the case where the APZC is being concurrently destroyed
    * (and thus mTreeManager is being nulled out).
    */
   bool CallDispatchScroll(const ScreenPoint& aStartPoint,
                           const ScreenPoint& aEndPoint,
                           const OverscrollHandoffChain& aOverscrollHandoffChain,
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -27,16 +27,17 @@
 #include "nsIWidgetListener.h"
 #include "nsTArray.h"                   // for AutoInfallibleTArray
 #include "nsXULAppAPI.h"                // for XRE_GetProcessType, etc
 #include "TiledLayerBuffer.h"
 #include "mozilla/dom/WindowBinding.h"  // for Overfill Callback
 #include "gfxPrefs.h"
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
+#include "LayerMetricsWrapper.h"
 #endif
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 ClientLayerManager::ClientLayerManager(nsIWidget* aWidget)
@@ -636,40 +637,36 @@ ClientLayerManager::GetBackendName(nsASt
 }
 
 bool
 ClientLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
                                               FrameMetrics& aMetrics,
                                               bool aDrawingCritical)
 {
 #ifdef MOZ_WIDGET_ANDROID
-  Layer* primaryScrollable = GetPrimaryScrollableLayer();
-  if (primaryScrollable) {
-    const FrameMetrics& metrics = primaryScrollable->GetFrameMetrics();
-
-    // This is derived from the code in
-    // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
-    CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel();
-    const CSSRect& metricsDisplayPort =
-      (aDrawingCritical && !metrics.mCriticalDisplayPort.IsEmpty()) ?
-        metrics.mCriticalDisplayPort : metrics.mDisplayPort;
-    LayerRect displayPort = (metricsDisplayPort + metrics.GetScrollOffset()) * paintScale;
+  MOZ_ASSERT(aMetrics.IsScrollable());
+  // This is derived from the code in
+  // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
+  CSSToLayerScale paintScale = aMetrics.LayersPixelsPerCSSPixel();
+  const CSSRect& metricsDisplayPort =
+    (aDrawingCritical && !aMetrics.mCriticalDisplayPort.IsEmpty()) ?
+      aMetrics.mCriticalDisplayPort : aMetrics.mDisplayPort;
+  LayerRect displayPort = (metricsDisplayPort + aMetrics.GetScrollOffset()) * paintScale;
 
-    ScreenPoint scrollOffset;
-    CSSToScreenScale zoom;
-    bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
-      aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
-      scrollOffset, zoom);
-    aMetrics.SetScrollOffset(scrollOffset / zoom);
-    aMetrics.SetZoom(zoom);
-    return ret;
-  }
+  ScreenPoint scrollOffset;
+  CSSToScreenScale zoom;
+  bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
+    aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
+    scrollOffset, zoom);
+  aMetrics.SetScrollOffset(scrollOffset / zoom);
+  aMetrics.SetZoom(zoom);
+  return ret;
+#else
+  return false;
 #endif
-
-  return false;
 }
 
 ClientLayer::~ClientLayer()
 {
   if (HasShadow()) {
     PLayerChild::Send__delete__(GetShadow());
   }
   MOZ_COUNT_DTOR(ClientLayer);
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -8,16 +8,17 @@
 #include "UnitTransforms.h"             // for TransformTo
 #include "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxRect.h"                    // for gfxRect
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect, RectTyped
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/LayersMessages.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRect.h"                     // for nsIntRect
 #include "LayersLogging.h"
 
 namespace mozilla {
 namespace layers {
@@ -58,38 +59,40 @@ ClientTiledThebesLayer::FillSpecificAttr
 
 static LayerRect
 ApplyParentLayerToLayerTransform(const gfx::Matrix4x4& aTransform, const ParentLayerRect& aParentLayerRect)
 {
   return TransformTo<LayerPixel>(aTransform, aParentLayerRect);
 }
 
 static gfx::Matrix4x4
-GetTransformToAncestorsParentLayer(Layer* aStart, Layer* aAncestor)
+GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAncestor)
 {
   gfx::Matrix4x4 transform;
-  Layer* ancestorParent = aAncestor->GetParent();
-  for (Layer* iter = aStart; iter != ancestorParent; iter = iter->GetParent()) {
-    transform = transform * iter->GetTransform();
+  const LayerMetricsWrapper& ancestorParent = aAncestor.GetParent();
+  for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM);
+       ancestorParent ? iter != ancestorParent : iter.IsValid();
+       iter = iter.GetParent()) {
+    transform = transform * iter.GetTransform();
     // If the layer has a non-transient async transform then we need to apply it here
     // because it will get applied by the APZ in the compositor as well
-    const FrameMetrics& metrics = iter->GetFrameMetrics();
+    const FrameMetrics& metrics = iter.Metrics();
     transform = transform * gfx::Matrix4x4().Scale(metrics.mResolution.scale, metrics.mResolution.scale, 1.f);
   }
   return transform;
 }
 
 void
-ClientTiledThebesLayer::GetAncestorLayers(Layer** aOutScrollAncestor,
-                                          Layer** aOutDisplayPortAncestor)
+ClientTiledThebesLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
+                                          LayerMetricsWrapper* aOutDisplayPortAncestor)
 {
-  Layer* scrollAncestor = nullptr;
-  Layer* displayPortAncestor = nullptr;
-  for (Layer* ancestor = this; ancestor; ancestor = ancestor->GetParent()) {
-    const FrameMetrics& metrics = ancestor->GetFrameMetrics();
+  LayerMetricsWrapper scrollAncestor;
+  LayerMetricsWrapper displayPortAncestor;
+  for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) {
+    const FrameMetrics& metrics = ancestor.Metrics();
     if (!scrollAncestor && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
       scrollAncestor = ancestor;
     }
     if (!metrics.mDisplayPort.IsEmpty()) {
       displayPortAncestor = ancestor;
       // Any layer that has a displayport must be scrollable, so we can break
       // here.
       break;
@@ -115,35 +118,35 @@ ClientTiledThebesLayer::BeginPaint()
     // Give up if there is a complex CSS transform on the layer. We might
     // eventually support these but for now it's too complicated to handle
     // given that it's a pretty rare scenario.
     return;
   }
 
   // Get the metrics of the nearest scrollable layer and the nearest layer
   // with a displayport.
-  Layer* scrollAncestor = nullptr;
-  Layer* displayPortAncestor = nullptr;
+  LayerMetricsWrapper scrollAncestor;
+  LayerMetricsWrapper displayPortAncestor;
   GetAncestorLayers(&scrollAncestor, &displayPortAncestor);
 
   if (!displayPortAncestor || !scrollAncestor) {
     // No displayport or scroll ancestor, so we can't do progressive rendering.
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_B2G)
     // Both Android and b2g are guaranteed to have a displayport set, so this
     // should never happen.
     NS_WARNING("Tiled Thebes layer with no scrollable container ancestor");
 #endif
     return;
   }
 
   TILING_LOG("TILING %p: Found scrollAncestor %p and displayPortAncestor %p\n", this,
-    scrollAncestor, displayPortAncestor);
+    scrollAncestor.GetLayer(), displayPortAncestor.GetLayer());
 
-  const FrameMetrics& scrollMetrics = scrollAncestor->GetFrameMetrics();
-  const FrameMetrics& displayportMetrics = displayPortAncestor->GetFrameMetrics();
+  const FrameMetrics& scrollMetrics = scrollAncestor.Metrics();
+  const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics();
 
   // Calculate the transform required to convert ParentLayer space of our
   // display port ancestor to the Layer space of this layer.
   gfx::Matrix4x4 transformDisplayPortToLayer =
     GetTransformToAncestorsParentLayer(this, displayPortAncestor);
   transformDisplayPortToLayer.Invert();
 
   // Note that below we use GetZoomToParent() in a number of places. Because this
@@ -178,17 +181,23 @@ ClientTiledThebesLayer::BeginPaint()
   // Calculate the scroll offset since the last transaction
   mPaintData.mScrollOffset = displayportMetrics.GetScrollOffset() * displayportMetrics.GetZoomToParent();
   TILING_LOG("TILING %p: Scroll offset %s\n", this, Stringify(mPaintData.mScrollOffset).c_str());
 }
 
 bool
 ClientTiledThebesLayer::UseFastPath()
 {
-  const FrameMetrics& parentMetrics = GetParent()->GetFrameMetrics();
+  LayerMetricsWrapper scrollAncestor;
+  GetAncestorLayers(&scrollAncestor, nullptr);
+  if (!scrollAncestor) {
+    return true;
+  }
+  const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
+
   bool multipleTransactionsNeeded = gfxPrefs::UseProgressiveTilePainting()
                                  || gfxPrefs::UseLowPrecisionBuffer()
                                  || !parentMetrics.mCriticalDisplayPort.IsEmpty();
   bool isFixed = GetIsFixedPosition() || GetParent()->GetIsFixedPosition();
   return !multipleTransactionsNeeded || isFixed || parentMetrics.mDisplayPort.IsEmpty();
 }
 
 bool
@@ -331,29 +340,32 @@ ClientTiledThebesLayer::RenderLayer()
     mValidRegion = nsIntRegion();
   }
 
   TILING_LOG("TILING %p: Initial visible region %s\n", this, Stringify(mVisibleRegion).c_str());
   TILING_LOG("TILING %p: Initial valid region %s\n", this, Stringify(mValidRegion).c_str());
   TILING_LOG("TILING %p: Initial low-precision valid region %s\n", this, Stringify(mLowPrecisionValidRegion).c_str());
 
   nsIntRegion neededRegion = mVisibleRegion;
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+  // This is handled by PadDrawTargetOutFromRegion in TiledContentClient for mobile
   if (MayResample()) {
     // If we're resampling then bilinear filtering can read up to 1 pixel
     // outside of our texture coords. Make the visible region a single rect,
     // and pad it out by 1 pixel (restricted to tile boundaries) so that
     // we always have valid content or transparent pixels to sample from.
     nsIntRect bounds = neededRegion.GetBounds();
     nsIntRect wholeTiles = bounds;
     wholeTiles.Inflate(nsIntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()));
     nsIntRect padded = bounds;
     padded.Inflate(1);
     padded.IntersectRect(padded, wholeTiles);
     neededRegion = padded;
   }
+#endif
 
   nsIntRegion invalidRegion;
   invalidRegion.Sub(neededRegion, mValidRegion);
   if (invalidRegion.IsEmpty()) {
     EndPaint();
     return;
   }
 
--- a/gfx/layers/client/ClientTiledThebesLayer.h
+++ b/gfx/layers/client/ClientTiledThebesLayer.h
@@ -72,18 +72,18 @@ public:
 
   virtual void ClearCachedResources() MOZ_OVERRIDE;
 
   /**
    * Helper method to find the nearest ancestor layers which
    * scroll and have a displayport. The parameters are out-params
    * which hold the return values; the values passed in may be null.
    */
-  void GetAncestorLayers(Layer** aOutScrollAncestor,
-                         Layer** aOutDisplayPortAncestor);
+  void GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
+                         LayerMetricsWrapper* aOutDisplayPortAncestor);
 
 private:
   ClientLayerManager* ClientManager()
   {
     return static_cast<ClientLayerManager*>(mManager);
   }
 
   /**
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -14,16 +14,17 @@
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxRect.h"                    // for gfxRect
 #include "mozilla/MathAlgorithms.h"     // for Abs
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Tools.h"          // for BytesPerPixel
 #include "mozilla/layers/CompositableForwarder.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder
 #include "TextureClientPool.h"
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for gfxContext::AddRef, etc
 #include "nsSize.h"                     // for nsIntSize
 #include "gfxReusableSharedImageSurfaceWrapper.h"
 #include "nsExpirationTracker.h"        // for nsExpirationTracker
 #include "nsMathUtils.h"               // for NS_roundf
@@ -154,34 +155,34 @@ ComputeViewTransform(const FrameMetrics&
                      / aCompositorMetrics.GetParentResolution();
   ScreenPoint translation = (aCompositorMetrics.GetScrollOffset() - aContentMetrics.GetScrollOffset())
                          * aCompositorMetrics.GetZoom();
   return ViewTransform(scale, -translation);
 }
 
 bool
 SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics(
-    Layer* aLayer,
+    const LayerMetricsWrapper& aLayer,
     bool aHasPendingNewThebesContent,
     bool aLowPrecision,
     ViewTransform& aViewTransform)
 {
   MOZ_ASSERT(aLayer);
 
   CompositorChild* compositor = nullptr;
-  if (aLayer->Manager() &&
-      aLayer->Manager()->AsClientLayerManager()) {
-    compositor = aLayer->Manager()->AsClientLayerManager()->GetCompositorChild();
+  if (aLayer.Manager() &&
+      aLayer.Manager()->AsClientLayerManager()) {
+    compositor = aLayer.Manager()->AsClientLayerManager()->GetCompositorChild();
   }
 
   if (!compositor) {
     return false;
   }
 
-  const FrameMetrics& contentMetrics = aLayer->GetFrameMetrics();
+  const FrameMetrics& contentMetrics = aLayer.Metrics();
   FrameMetrics compositorMetrics;
 
   if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(),
                                                 compositorMetrics)) {
     return false;
   }
 
   aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
@@ -314,31 +315,32 @@ ClientTiledLayerBuffer::GetContentType(S
   gfxContentType content =
     mThebesLayer->CanUseOpaqueSurface() ? gfxContentType::COLOR :
                                           gfxContentType::COLOR_ALPHA;
   SurfaceMode mode = mThebesLayer->GetSurfaceMode();
 
   if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
 #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
      mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
+  }
 #else
       if (!mThebesLayer->GetParent() ||
           !mThebesLayer->GetParent()->SupportsComponentAlphaChildren() ||
           !gfxPrefs::TiledDrawTargetEnabled()) {
         mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
       } else {
         content = gfxContentType::COLOR;
       }
-#endif
   } else if (mode == SurfaceMode::SURFACE_OPAQUE) {
     if (mThebesLayer->MayResample()) {
       mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
       content = gfxContentType::COLOR_ALPHA;
     }
   }
+#endif
 
   if (aMode) {
     *aMode = mode;
   }
   return content;
 }
 
 gfxMemorySharedReadLock::gfxMemorySharedReadLock()
@@ -1148,22 +1150,16 @@ ClientTiledLayerBuffer::ValidateTile(Til
       if (backBufferOnWhite && !mCompositableClient->AddTextureClient(backBufferOnWhite)) {
         NS_WARNING("Failed to add tile TextureClient.");
         aTile.DiscardFrontBuffer();
         aTile.DiscardBackBuffer();
         return aTile;
       }
     }
 
-    if (backBuffer->HasInternalBuffer()) {
-      // If our new buffer has an internal buffer, we don't want to keep another
-      // TextureClient around unnecessarily, so discard the back-buffer.
-      aTile.DiscardBackBuffer();
-    }
-
     // prepare an array of Moz2D tiles that will be painted into in PostValidate
     gfx::Tile moz2DTile;
     RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget();
     RefPtr<DrawTarget> dtOnWhite;
     if (backBufferOnWhite) {
       dtOnWhite = backBufferOnWhite->BorrowDrawTarget();
       moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite);
     } else {
@@ -1185,18 +1181,18 @@ ClientTiledLayerBuffer::ValidateTile(Til
                          dirtyRect->height);
       drawRect.Scale(mResolution);
 
       gfx::IntRect copyRect(NS_roundf((dirtyRect->x - mSinglePaintBufferOffset.x) * mResolution),
                             NS_roundf((dirtyRect->y - mSinglePaintBufferOffset.y) * mResolution),
                             drawRect.width,
                             drawRect.height);
       gfx::IntPoint copyTarget(NS_roundf(drawRect.x), NS_roundf(drawRect.y));
-      // Mark the newly updated area as invalid in the front buffer
-      aTile.mInvalidFront.Or(aTile.mInvalidFront, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
+      // Mark the newly updated area as invalid in the back buffer
+      aTile.mInvalidBack.Or(aTile.mInvalidBack, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
 
       if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
         dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
         dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
       } else if (content == gfxContentType::COLOR_ALPHA) {
         dt->ClearRect(drawRect);
       }
     }
@@ -1361,37 +1357,37 @@ ClientTiledLayerBuffer::ValidateTile(Til
  * it reflects what the compositor is actually rendering. This operation
  * basically replaces the nontransient async transform that was injected
  * in GetTransformToAncestorsParentLayer with the complete async transform.
  * This function then returns the scroll ancestor's composition bounds,
  * transformed into the thebes layer's LayerPixel coordinates, accounting
  * for the compositor state.
  */
 static LayerRect
-GetCompositorSideCompositionBounds(Layer* aScrollAncestor,
+GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor,
                                    const Matrix4x4& aTransformToCompBounds,
                                    const ViewTransform& aAPZTransform)
 {
   Matrix4x4 nonTransientAPZUntransform = Matrix4x4().Scale(
-    aScrollAncestor->GetFrameMetrics().mResolution.scale,
-    aScrollAncestor->GetFrameMetrics().mResolution.scale,
+    aScrollAncestor.Metrics().mResolution.scale,
+    aScrollAncestor.Metrics().mResolution.scale,
     1.f);
   nonTransientAPZUntransform.Invert();
 
   // Take off the last "term" of aTransformToCompBounds, which
   // is the APZ's nontransient async transform. Replace it with
   // the APZ's async transform (this includes the nontransient
   // component as well).
   Matrix4x4 transform = aTransformToCompBounds
                       * nonTransientAPZUntransform
                       * Matrix4x4(aAPZTransform);
   transform.Invert();
 
   return TransformTo<LayerPixel>(transform,
-            aScrollAncestor->GetFrameMetrics().mCompositionBounds);
+            aScrollAncestor.Metrics().mCompositionBounds);
 }
 
 bool
 ClientTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
                                                        const nsIntRegion& aOldValidRegion,
                                                        nsIntRegion& aRegionToPaint,
                                                        BasicTiledLayerPaintData* aPaintData,
                                                        bool aIsRepeated)
@@ -1412,34 +1408,36 @@ ClientTiledLayerBuffer::ComputeProgressi
   bool drawingLowPrecision = IsLowPrecision();
 
   // Find out if we have any non-stale content to update.
   nsIntRegion staleRegion;
   staleRegion.And(aInvalidRegion, aOldValidRegion);
 
   TILING_LOG("TILING %p: Progressive update stale region %s\n", mThebesLayer, Stringify(staleRegion).c_str());
 
-  Layer* scrollAncestor = nullptr;
+  LayerMetricsWrapper scrollAncestor;
   mThebesLayer->GetAncestorLayers(&scrollAncestor, nullptr);
 
   // Find out the current view transform to determine which tiles to draw
   // first, and see if we should just abort this paint. Aborting is usually
   // caused by there being an incoming, more relevant paint.
   ViewTransform viewTransform;
 #if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_ANDROID_APZ)
-  FrameMetrics compositorMetrics = scrollAncestor->GetFrameMetrics();
+  FrameMetrics contentMetrics = scrollAncestor.Metrics();
   bool abortPaint = false;
   // On Android, only the primary scrollable layer is async-scrolled, and the only one
   // that the Java-side code can provide details about. If we're tiling some other layer
   // then we already have all the information we need about it.
-  if (scrollAncestor == mManager->GetPrimaryScrollableLayer()) {
+  if (contentMetrics.GetScrollId() == mManager->GetRootScrollableLayerId()) {
+    FrameMetrics compositorMetrics = contentMetrics;
+    // The ProgressiveUpdateCallback updates the compositorMetrics
     abortPaint = mManager->ProgressiveUpdateCallback(!staleRegion.Contains(aInvalidRegion),
                                                      compositorMetrics,
                                                      !drawingLowPrecision);
-    viewTransform = ComputeViewTransform(scrollAncestor->GetFrameMetrics(), compositorMetrics);
+    viewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
   }
 #else
   MOZ_ASSERT(mSharedFrameMetricsHelper);
 
   bool abortPaint =
     mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics(
       scrollAncestor,
       !staleRegion.Contains(aInvalidRegion),
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -349,17 +349,17 @@ public:
 
   /**
    * This is called by the BasicTileLayer to determine if it is still interested
    * in the update of this display-port to continue. We can return true here
    * to abort the current update and continue with any subsequent ones. This
    * is useful for slow-to-render pages when the display-port starts lagging
    * behind enough that continuing to draw it is wasted effort.
    */
-  bool UpdateFromCompositorFrameMetrics(Layer* aLayer,
+  bool UpdateFromCompositorFrameMetrics(const LayerMetricsWrapper& aLayer,
                                         bool aHasPendingNewThebesContent,
                                         bool aLowPrecision,
                                         ViewTransform& aViewTransform);
 
   /**
    * Determines if the compositor's upcoming composition bounds has fallen
    * outside of the contents display port. If it has then the compositor
    * will start to checker board. Checker boarding is when the compositor
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/WidgetUtils.h"        // for ComputeTransformForRotation
 #include "mozilla/dom/AnimationPlayer.h" // for AnimationPlayer
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for RoundedToInt, PointTyped
 #include "mozilla/gfx/Rect.h"           // for RoundedToInt, RectTyped
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "nsCSSPropList.h"
 #include "nsCoord.h"                    // for NSAppUnitsToFloatPixels, etc
 #include "nsDebug.h"                    // for NS_ASSERTION, etc
 #include "nsDeviceContext.h"            // for nsDeviceContext
 #include "nsDisplayList.h"              // for nsDisplayTransform, etc
 #include "nsMathUtils.h"                // for NS_round
 #include "nsPoint.h"                    // for nsPoint
 #include "nsRect.h"                     // for nsIntRect
@@ -237,127 +238,125 @@ IntervalOverlap(gfxFloat aTranslation, g
   } else {
     return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0));
   }
 }
 
 void
 AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer,
                                                    Layer* aTransformedSubtreeRoot,
+                                                   FrameMetrics::ViewID aTransformScrollId,
                                                    const Matrix4x4& aPreviousTransformForRoot,
                                                    const Matrix4x4& aCurrentTransformForRoot,
                                                    const LayerMargin& aFixedLayerMargins)
 {
   bool isRootFixed = aLayer->GetIsFixedPosition() &&
     !aLayer->GetParent()->GetIsFixedPosition();
   bool isStickyForSubtree = aLayer->GetIsStickyPosition() &&
-    aLayer->GetStickyScrollContainerId() ==
-      aTransformedSubtreeRoot->GetFrameMetrics().GetScrollId();
-  if (aLayer != aTransformedSubtreeRoot && (isRootFixed || isStickyForSubtree)) {
-    // Insert a translation so that the position of the anchor point is the same
-    // before and after the change to the transform of aTransformedSubtreeRoot.
-    // This currently only works for fixed layers with 2D transforms.
-
-    // Accumulate the transforms between this layer and the subtree root layer.
-    Matrix ancestorTransform;
-    if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
-                                     ancestorTransform)) {
-      return;
-    }
-
-    Matrix oldRootTransform;
-    Matrix newRootTransform;
-    if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) ||
-        !aCurrentTransformForRoot.Is2D(&newRootTransform)) {
-      return;
-    }
-
-    // Calculate the cumulative transforms between the subtree root with the
-    // old transform and the current transform.
-    Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform;
-    Matrix newCumulativeTransform = ancestorTransform * newRootTransform;
-    if (newCumulativeTransform.IsSingular()) {
-      return;
-    }
-    Matrix newCumulativeTransformInverse = newCumulativeTransform;
-    newCumulativeTransformInverse.Invert();
-
-    // Now work out the translation necessary to make sure the layer doesn't
-    // move given the new sub-tree root transform.
-    Matrix layerTransform;
-    if (!GetBaseTransform2D(aLayer, &layerTransform)) {
-      return;
-    }
-
-    // Calculate any offset necessary, in previous transform sub-tree root
-    // space. This is used to make sure fixed position content respects
-    // content document fixed position margins.
-    LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins);
+    aLayer->GetStickyScrollContainerId() == aTransformScrollId;
+  bool isFixedOrSticky = (isRootFixed || isStickyForSubtree);
 
-    // Add the above offset to the anchor point so we can offset the layer by
-    // and amount that's specified in old subtree layer space.
-    const LayerPoint& anchorInOldSubtreeLayerSpace = aLayer->GetFixedPositionAnchor();
-    LayerPoint offsetAnchorInOldSubtreeLayerSpace = anchorInOldSubtreeLayerSpace + offsetInOldSubtreeLayerSpace;
-
-    // Add the local layer transform to the two points to make the equation
-    // below this section more convenient.
-    Point anchor(anchorInOldSubtreeLayerSpace.x, anchorInOldSubtreeLayerSpace.y);
-    Point offsetAnchor(offsetAnchorInOldSubtreeLayerSpace.x, offsetAnchorInOldSubtreeLayerSpace.y);
-    Point locallyTransformedAnchor = layerTransform * anchor;
-    Point locallyTransformedOffsetAnchor = layerTransform * offsetAnchor;
+  // We want to process all the fixed and sticky children of
+  // aTransformedSubtreeRoot. Also, once we do encounter such a child, we don't
+  // need to recurse any deeper because the fixed layers are relative to their
+  // nearest scrollable layer.
+  if (aLayer == aTransformedSubtreeRoot || !isFixedOrSticky) {
+    // ApplyAsyncContentTransformToTree will call this function again for
+    // nested scrollable layers, so we don't need to recurse if the layer is
+    // scrollable.
+    if (aLayer == aTransformedSubtreeRoot || !aLayer->HasScrollableFrameMetrics()) {
+      for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) {
+        AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, aTransformScrollId,
+                                  aPreviousTransformForRoot,
+                                  aCurrentTransformForRoot, aFixedLayerMargins);
+      }
+    }
+    return;
+  }
 
-    // Transforming the locallyTransformedAnchor by oldCumulativeTransform
-    // returns the layer's anchor point relative to the parent of
-    // aTransformedSubtreeRoot, before the new transform was applied.
-    // Then, applying newCumulativeTransformInverse maps that point relative
-    // to the layer's parent, which is the same coordinate space as
-    // locallyTransformedAnchor again, allowing us to subtract them and find
-    // out the offset necessary to make sure the layer stays stationary.
-    Point oldAnchorPositionInNewSpace =
-      newCumulativeTransformInverse * (oldCumulativeTransform * locallyTransformedOffsetAnchor);
-    Point translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor;
+  // Insert a translation so that the position of the anchor point is the same
+  // before and after the change to the transform of aTransformedSubtreeRoot.
+  // This currently only works for fixed layers with 2D transforms.
 
-    if (aLayer->GetIsStickyPosition()) {
-      // For sticky positioned layers, the difference between the two rectangles
-      // defines a pair of translation intervals in each dimension through which
-      // the layer should not move relative to the scroll container. To
-      // accomplish this, we limit each dimension of the |translation| to that
-      // part of it which overlaps those intervals.
-      const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter();
-      const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
+  // Accumulate the transforms between this layer and the subtree root layer.
+  Matrix ancestorTransform;
+  if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
+                                   ancestorTransform)) {
+    return;
+  }
 
-      translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
-                      IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
-      translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
-                      IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
-    }
-
-    // Finally, apply the 2D translation to the layer transform.
-    TranslateShadowLayer2D(aLayer, ThebesPoint(translation));
-
-    // The transform has now been applied, so there's no need to iterate over
-    // child layers.
+  Matrix oldRootTransform;
+  Matrix newRootTransform;
+  if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) ||
+      !aCurrentTransformForRoot.Is2D(&newRootTransform)) {
     return;
   }
 
-  // Fixed layers are relative to their nearest scrollable layer, so when we
-  // encounter a scrollable layer, bail. ApplyAsyncContentTransformToTree will
-  // have already recursed on this layer and called AlignFixedAndStickyLayers
-  // on it with its own transforms.
-  if (aLayer->GetFrameMetrics().IsScrollable() &&
-      aLayer != aTransformedSubtreeRoot) {
+  // Calculate the cumulative transforms between the subtree root with the
+  // old transform and the current transform.
+  Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform;
+  Matrix newCumulativeTransform = ancestorTransform * newRootTransform;
+  if (newCumulativeTransform.IsSingular()) {
+    return;
+  }
+  Matrix newCumulativeTransformInverse = newCumulativeTransform;
+  newCumulativeTransformInverse.Invert();
+
+  // Now work out the translation necessary to make sure the layer doesn't
+  // move given the new sub-tree root transform.
+  Matrix layerTransform;
+  if (!GetBaseTransform2D(aLayer, &layerTransform)) {
     return;
   }
 
-  for (Layer* child = aLayer->GetFirstChild();
-       child; child = child->GetNextSibling()) {
-    AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot,
-                              aPreviousTransformForRoot,
-                              aCurrentTransformForRoot, aFixedLayerMargins);
+  // Calculate any offset necessary, in previous transform sub-tree root
+  // space. This is used to make sure fixed position content respects
+  // content document fixed position margins.
+  LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins);
+
+  // Add the above offset to the anchor point so we can offset the layer by
+  // and amount that's specified in old subtree layer space.
+  const LayerPoint& anchorInOldSubtreeLayerSpace = aLayer->GetFixedPositionAnchor();
+  LayerPoint offsetAnchorInOldSubtreeLayerSpace = anchorInOldSubtreeLayerSpace + offsetInOldSubtreeLayerSpace;
+
+  // Add the local layer transform to the two points to make the equation
+  // below this section more convenient.
+  Point anchor(anchorInOldSubtreeLayerSpace.x, anchorInOldSubtreeLayerSpace.y);
+  Point offsetAnchor(offsetAnchorInOldSubtreeLayerSpace.x, offsetAnchorInOldSubtreeLayerSpace.y);
+  Point locallyTransformedAnchor = layerTransform * anchor;
+  Point locallyTransformedOffsetAnchor = layerTransform * offsetAnchor;
+
+  // Transforming the locallyTransformedAnchor by oldCumulativeTransform
+  // returns the layer's anchor point relative to the parent of
+  // aTransformedSubtreeRoot, before the new transform was applied.
+  // Then, applying newCumulativeTransformInverse maps that point relative
+  // to the layer's parent, which is the same coordinate space as
+  // locallyTransformedAnchor again, allowing us to subtract them and find
+  // out the offset necessary to make sure the layer stays stationary.
+  Point oldAnchorPositionInNewSpace =
+    newCumulativeTransformInverse * (oldCumulativeTransform * locallyTransformedOffsetAnchor);
+  Point translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor;
+
+  if (aLayer->GetIsStickyPosition()) {
+    // For sticky positioned layers, the difference between the two rectangles
+    // defines a pair of translation intervals in each dimension through which
+    // the layer should not move relative to the scroll container. To
+    // accomplish this, we limit each dimension of the |translation| to that
+    // part of it which overlaps those intervals.
+    const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter();
+    const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
+
+    translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
+                    IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
+    translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
+                    IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
   }
+
+  // Finally, apply the 2D translation to the layer transform.
+  TranslateShadowLayer2D(aLayer, ThebesPoint(translation));
 }
 
 static void
 SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
             StyleAnimationValue& aEnd, Animatable* aValue)
 {
   StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
@@ -501,25 +500,25 @@ SampleAnimations(Layer* aLayer, TimeStam
        child = child->GetNextSibling()) {
     activeAnimations |= SampleAnimations(child, aPoint);
   }
 
   return activeAnimations;
 }
 
 static bool
-SampleAPZAnimations(Layer* aLayer, TimeStamp aPoint)
+SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aPoint)
 {
   bool activeAnimations = false;
-  for (Layer* child = aLayer->GetFirstChild(); child;
-        child = child->GetNextSibling()) {
+  for (LayerMetricsWrapper child = aLayer.GetFirstChild(); child;
+        child = child.GetNextSibling()) {
     activeAnimations |= SampleAPZAnimations(child, aPoint);
   }
 
-  if (AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController()) {
+  if (AsyncPanZoomController* apzc = aLayer.GetApzc()) {
     activeAnimations |= apzc->AdvanceAnimations(aPoint);
   }
 
   return activeAnimations;
 }
 
 Matrix4x4
 AdjustAndCombineWithCSSTransform(const Matrix4x4& asyncTransform, Layer* aLayer)
@@ -548,127 +547,137 @@ AsyncCompositionManager::ApplyAsyncConte
 {
   bool appliedTransform = false;
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
     appliedTransform |=
       ApplyAsyncContentTransformToTree(child);
   }
 
-  if (AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController()) {
-    LayerComposite* layerComposite = aLayer->AsLayerComposite();
-    Matrix4x4 oldTransform = aLayer->GetTransform();
+  LayerComposite* layerComposite = aLayer->AsLayerComposite();
+  Matrix4x4 oldTransform = aLayer->GetTransform();
+
+  Matrix4x4 combinedAsyncTransformWithoutOverscroll;
+  Matrix4x4 combinedAsyncTransform;
+  bool hasAsyncTransform = false;
+  LayerMargin fixedLayerMargins(0, 0, 0, 0);
+
+  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+    AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(i);
+    if (!controller) {
+      continue;
+    }
+
+    hasAsyncTransform = true;
 
     ViewTransform asyncTransformWithoutOverscroll, overscrollTransform;
     ScreenPoint scrollOffset;
     controller->SampleContentTransformForFrame(&asyncTransformWithoutOverscroll,
                                                scrollOffset,
                                                &overscrollTransform);
 
-    const FrameMetrics& metrics = aLayer->GetFrameMetrics();
+    const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
     CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel();
     CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ?
                         metrics.mDisplayPort : metrics.mCriticalDisplayPort);
-    LayerMargin fixedLayerMargins(0, 0, 0, 0);
     ScreenPoint offset(0, 0);
+    // XXX this call to SyncFrameMetrics is not currently being used. It will be cleaned
+    // up as part of bug 776030 or one of its dependencies.
     SyncFrameMetrics(scrollOffset, asyncTransformWithoutOverscroll.mScale.scale,
                      metrics.mScrollableRect, mLayersUpdated, displayPort,
                      paintScale, mIsFirstPaint, fixedLayerMargins, offset);
 
     mIsFirstPaint = false;
     mLayersUpdated = false;
 
     // Apply the render offset
     mLayerManager->GetCompositor()->SetScreenRenderOffset(offset);
 
-    Matrix4x4 transform = AdjustAndCombineWithCSSTransform(
-        asyncTransformWithoutOverscroll * overscrollTransform, aLayer);
+    combinedAsyncTransformWithoutOverscroll *= asyncTransformWithoutOverscroll;
+    combinedAsyncTransform *= (asyncTransformWithoutOverscroll * overscrollTransform);
+  }
+
+  if (hasAsyncTransform) {
+    Matrix4x4 transform = AdjustAndCombineWithCSSTransform(combinedAsyncTransform, aLayer);
 
     // GetTransform already takes the pre- and post-scale into account.  Since we
     // will apply the pre- and post-scale again when computing the effective
     // transform, we must apply the inverses here.
     if (ContainerLayer* container = aLayer->AsContainerLayer()) {
       transform.Scale(1.0f/container->GetPreXScale(),
                       1.0f/container->GetPreYScale(),
                       1);
     }
     transform = transform * Matrix4x4().Scale(1.0f/aLayer->GetPostXScale(),
                                               1.0f/aLayer->GetPostYScale(),
                                               1);
     layerComposite->SetShadowTransform(transform);
     NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(),
                  "overwriting animated transform!");
 
+    const FrameMetrics& bottom = LayerMetricsWrapper::BottommostScrollableMetrics(aLayer);
+    MOZ_ASSERT(bottom.IsScrollable());  // must be true because hasAsyncTransform is true
+
     // Apply resolution scaling to the old transform - the layer tree as it is
-    // doesn't have the necessary transform to display correctly.
-    LayoutDeviceToLayerScale resolution = metrics.mCumulativeResolution;
+    // doesn't have the necessary transform to display correctly. We use the
+    // bottom-most scrollable metrics because that should have the most accurate
+    // cumulative resolution for aLayer.
+    LayoutDeviceToLayerScale resolution = bottom.mCumulativeResolution;
     oldTransform.Scale(resolution.scale, resolution.scale, 1);
 
     // For the purpose of aligning fixed and sticky layers, we disregard
     // the overscroll transform when computing the 'aCurrentTransformForRoot'
     // parameter. This ensures that the overscroll transform is not unapplied,
     // and therefore that the visual effect applies to fixed and sticky layers.
     Matrix4x4 transformWithoutOverscroll = AdjustAndCombineWithCSSTransform(
-        asyncTransformWithoutOverscroll, aLayer);
-    AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform,
+        combinedAsyncTransformWithoutOverscroll, aLayer);
+    // Since fixed/sticky layers are relative to their nearest scrolling ancestor,
+    // we use the ViewID from the bottommost scrollable metrics here.
+    AlignFixedAndStickyLayers(aLayer, aLayer, bottom.GetScrollId(), oldTransform,
                               transformWithoutOverscroll, fixedLayerMargins);
 
     appliedTransform = true;
   }
 
-  if (aLayer->AsContainerLayer() && aLayer->GetScrollbarDirection() != Layer::NONE) {
-    ApplyAsyncTransformToScrollbar(aLayer->AsContainerLayer());
+  if (aLayer->GetScrollbarDirection() != Layer::NONE) {
+    ApplyAsyncTransformToScrollbar(aLayer);
   }
   return appliedTransform;
 }
 
 static bool
-LayerHasNonContainerDescendants(ContainerLayer* aContainer)
+LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar)
 {
-  for (Layer* child = aContainer->GetFirstChild();
-       child; child = child->GetNextSibling()) {
-    ContainerLayer* container = child->AsContainerLayer();
-    if (!container || LayerHasNonContainerDescendants(container)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-static bool
-LayerIsScrollbarTarget(Layer* aTarget, ContainerLayer* aScrollbar)
-{
-  AsyncPanZoomController* apzc = aTarget->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzc = aTarget.GetApzc();
   if (!apzc) {
     return false;
   }
-  const FrameMetrics& metrics = aTarget->GetFrameMetrics();
+  const FrameMetrics& metrics = aTarget.Metrics();
   if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) {
     return false;
   }
   return true;
 }
 
 static void
-ApplyAsyncTransformToScrollbarForContent(ContainerLayer* aScrollbar,
-                                         Layer* aContent, bool aScrollbarIsChild)
+ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
+                                         const LayerMetricsWrapper& aContent,
+                                         bool aScrollbarIsDescendant)
 {
   // We only apply the transform if the scroll-target layer has non-container
   // children (i.e. when it has some possibly-visible content). This is to
   // avoid moving scroll-bars in the situation that only a scroll information
   // layer has been built for a scroll frame, as this would result in a
   // disparity between scrollbars and visible content.
-  if (aContent->AsContainerLayer() &&
-      !LayerHasNonContainerDescendants(aContent->AsContainerLayer())) {
+  if (aContent.IsScrollInfoLayer()) {
     return;
   }
 
-  const FrameMetrics& metrics = aContent->GetFrameMetrics();
-  AsyncPanZoomController* apzc = aContent->GetAsyncPanZoomController();
+  const FrameMetrics& metrics = aContent.Metrics();
+  AsyncPanZoomController* apzc = aContent.GetApzc();
 
   Matrix4x4 asyncTransform = apzc->GetCurrentAsyncTransform();
   Matrix4x4 nontransientTransform = apzc->GetNontransientAsyncTransform();
   Matrix4x4 nontransientUntransform = nontransientTransform;
   nontransientUntransform.Invert();
   Matrix4x4 transientTransform = asyncTransform * nontransientUntransform;
 
   // |transientTransform| represents the amount by which we have scrolled and
@@ -693,91 +702,102 @@ ApplyAsyncTransformToScrollbarForContent
   if (aScrollbar->GetScrollbarDirection() == Layer::HORIZONTAL) {
     float scale = metrics.CalculateCompositedSizeInCssPixels().width / metrics.mScrollableRect.width;
     scrollbarTransform = scrollbarTransform * Matrix4x4().Scale(1.f / transientTransform._11, 1.f, 1.f);
     scrollbarTransform = scrollbarTransform * Matrix4x4().Translate(-transientTransform._41 * scale, 0, 0);
   }
 
   Matrix4x4 transform = scrollbarTransform * aScrollbar->GetTransform();
 
-  if (aScrollbarIsChild) {
+  if (aScrollbarIsDescendant) {
     // If the scrollbar layer is a child of the content it is a scrollbar for, then we
     // need to do an extra untransform to cancel out the transient async transform on
     // the content. This is needed because otherwise that transient async transform is
     // part of the effective transform of this scrollbar, and the scrollbar will jitter
     // as the content scrolls.
     transientTransform.Invert();
     transform = transform * transientTransform;
   }
 
   // GetTransform already takes the pre- and post-scale into account.  Since we
   // will apply the pre- and post-scale again when computing the effective
   // transform, we must apply the inverses here.
-  transform.Scale(1.0f/aScrollbar->GetPreXScale(),
-                  1.0f/aScrollbar->GetPreYScale(),
-                  1);
+  if (ContainerLayer* container = aScrollbar->AsContainerLayer()) {
+    transform.Scale(1.0f/container->GetPreXScale(),
+                    1.0f/container->GetPreYScale(),
+                    1);
+  }
   transform = transform * Matrix4x4().Scale(1.0f/aScrollbar->GetPostXScale(),
                                             1.0f/aScrollbar->GetPostYScale(),
                                             1);
   aScrollbar->AsLayerComposite()->SetShadowTransform(transform);
 }
 
-static Layer*
-FindScrolledLayerForScrollbar(ContainerLayer* aLayer, bool* aOutIsAncestor)
+static LayerMetricsWrapper
+FindScrolledLayerForScrollbar(Layer* aScrollbar, bool* aOutIsAncestor)
 {
   // XXX: once bug 967844 is implemented there might be multiple scrolled layers
   // that correspond to the scrollbar's scrollId. Verify that we deal with those
   // cases correctly.
 
-  // Search all siblings of aLayer and of its ancestors.
-  for (Layer* ancestor = aLayer; ancestor; ancestor = ancestor->GetParent()) {
-    for (Layer* scrollTarget = ancestor;
+  // Search all siblings of aScrollbar and of its ancestors.
+  LayerMetricsWrapper scrollbar(aScrollbar, LayerMetricsWrapper::StartAt::BOTTOM);
+  for (LayerMetricsWrapper ancestor = scrollbar; ancestor; ancestor = ancestor.GetParent()) {
+    for (LayerMetricsWrapper scrollTarget = ancestor;
          scrollTarget;
-         scrollTarget = scrollTarget->GetPrevSibling()) {
-      if (scrollTarget != aLayer &&
-          LayerIsScrollbarTarget(scrollTarget, aLayer)) {
+         scrollTarget = scrollTarget.GetPrevSibling()) {
+      if (scrollTarget != scrollbar &&
+          LayerIsScrollbarTarget(scrollTarget, aScrollbar)) {
         *aOutIsAncestor = (scrollTarget == ancestor);
         return scrollTarget;
       }
     }
-    for (Layer* scrollTarget = ancestor->GetNextSibling();
+    for (LayerMetricsWrapper scrollTarget = ancestor.GetNextSibling();
          scrollTarget;
-         scrollTarget = scrollTarget->GetNextSibling()) {
-      if (LayerIsScrollbarTarget(scrollTarget, aLayer)) {
+         scrollTarget = scrollTarget.GetNextSibling()) {
+      if (LayerIsScrollbarTarget(scrollTarget, aScrollbar)) {
         *aOutIsAncestor = false;
         return scrollTarget;
       }
     }
   }
-  return nullptr;
+  return LayerMetricsWrapper();
 }
 
 void
-AsyncCompositionManager::ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer)
+AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer)
 {
   // If this layer corresponds to a scrollbar, then there should be a layer that
   // is a previous sibling or a parent that has a matching ViewID on its FrameMetrics.
   // That is the content that this scrollbar is for. We pick up the transient
   // async transform from that layer and use it to update the scrollbar position.
   // Note that it is possible that the content layer is no longer there; in
   // this case we don't need to do anything because there can't be an async
   // transform on the content.
   bool isAncestor = false;
-  Layer* scrollTarget = FindScrolledLayerForScrollbar(aLayer, &isAncestor);
+  const LayerMetricsWrapper& scrollTarget = FindScrolledLayerForScrollbar(aLayer, &isAncestor);
   if (scrollTarget) {
     ApplyAsyncTransformToScrollbarForContent(aLayer, scrollTarget, isAncestor);
   }
 }
 
 void
 AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer)
 {
   LayerComposite* layerComposite = aLayer->AsLayerComposite();
 
-  const FrameMetrics& metrics = aLayer->GetFrameMetrics();
+  FrameMetrics metrics = LayerMetricsWrapper::TopmostScrollableMetrics(aLayer);
+  if (!metrics.IsScrollable()) {
+    // On Fennec it's possible that the there is no scrollable layer in the
+    // tree, and this function just gets called with the root layer. In that
+    // case TopmostScrollableMetrics will return an empty FrameMetrics but we
+    // still want to use the actual non-scrollable metrics from the layer.
+    metrics = LayerMetricsWrapper::BottommostMetrics(aLayer);
+  }
+
   // We must apply the resolution scale before a pan/zoom transform, so we call
   // GetTransform here.
   Matrix4x4 oldTransform = aLayer->GetTransform();
 
   CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel();
 
   LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom);
 
@@ -887,17 +907,17 @@ AsyncCompositionManager::TransformScroll
   if (mContentRect.height * userZoom.scale < metrics.mCompositionBounds.height) {
     underZoomScale.height = (mContentRect.height * userZoom.scale) /
       metrics.mCompositionBounds.height;
   }
   oldTransform.Scale(underZoomScale.width, underZoomScale.height, 1);
 
   // Make sure fixed position layers don't move away from their anchor points
   // when we're asynchronously panning or zooming
-  AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform,
+  AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                             aLayer->GetLocalTransform(), fixedLayerMargins);
 }
 
 void
 ClearAsyncTransforms(Layer* aLayer)
 {
   if (!aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation()) {
     aLayer->AsLayerComposite()->SetShadowTransform(aLayer->GetBaseTransform());
@@ -935,21 +955,21 @@ AsyncCompositionManager::TransformShadow
   // Attempt to apply an async content transform to any layer that has
   // an async pan zoom controller (which means that it is rendered
   // async using Gecko). If this fails, fall back to transforming the
   // primary scrollable layer.  "Failing" here means that we don't
   // find a frame that is async scrollable.  Note that the fallback
   // code also includes Fennec which is rendered async.  Fennec uses
   // its own platform-specific async rendering that is done partially
   // in Gecko and partially in Java.
-  wantNextFrame |= SampleAPZAnimations(root, aCurrentFrame);
+  wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), aCurrentFrame);
   if (!ApplyAsyncContentTransformToTree(root)) {
     nsAutoTArray<Layer*,1> scrollableLayers;
 #ifdef MOZ_WIDGET_ANDROID
-    scrollableLayers.AppendElement(mLayerManager->GetPrimaryScrollableLayer());
+    mLayerManager->GetRootScrollableLayers(scrollableLayers);
 #else
     mLayerManager->GetScrollableLayers(scrollableLayers);
 #endif
 
     for (uint32_t i = 0; i < scrollableLayers.Length(); i++) {
       if (scrollableLayers[i]) {
         TransformScrollableLayer(scrollableLayers[i]);
       }
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -125,17 +125,17 @@ private:
   void TransformScrollableLayer(Layer* aLayer);
   // Return true if an AsyncPanZoomController content transform was
   // applied for |aLayer|.
   bool ApplyAsyncContentTransformToTree(Layer* aLayer);
   /**
    * Update the shadow transform for aLayer assuming that is a scrollbar,
    * so that it stays in sync with the content that is being scrolled by APZ.
    */
-  void ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer);
+  void ApplyAsyncTransformToScrollbar(Layer* aLayer);
 
   void SetFirstPaintViewport(const LayerIntPoint& aOffset,
                              const CSSToLayerScale& aZoom,
                              const CSSRect& aCssPageRect);
   void SetPageRect(const CSSRect& aCssPageRect);
   void SyncViewportInfo(const LayerIntRect& aDisplayPort,
                         const CSSToLayerScale& aDisplayResolution,
                         bool aLayersUpdated,
@@ -164,16 +164,17 @@ private:
    * overscroll-related transform, which we don't want to adjust for.
    * For sticky position layers, the translation is further intersected with
    * the layer's sticky scroll ranges.
    * This function will also adjust layers so that the given content document
    * fixed position margins will be respected during asynchronous panning and
    * zooming.
    */
   void AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot,
+                                 FrameMetrics::ViewID aTransformScrollId,
                                  const gfx::Matrix4x4& aPreviousTransformForRoot,
                                  const gfx::Matrix4x4& aCurrentTransformForRoot,
                                  const LayerMargin& aFixedLayerMargins);
 
   /**
    * DRAWING PHASE ONLY
    *
    * For reach RefLayer in our layer tree, look up its referent and connect it
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/gfx/Point.h"          // for Point, IntPoint
 #include "mozilla/gfx/Rect.h"           // for IntRect, Rect
 #include "mozilla/layers/Compositor.h"  // for Compositor, etc
 #include "mozilla/layers/CompositorTypes.h"  // for DiagnosticFlags::CONTAINER
 #include "mozilla/layers/Effects.h"     // for Effect, EffectChain, etc
 #include "mozilla/layers/TextureHost.h"  // for CompositingRenderTarget
 #include "mozilla/layers/AsyncPanZoomController.h"  // for AsyncPanZoomController
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsISupportsUtils.h"           // for NS_ADDREF, NS_RELEASE
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for nsIntRect
 #include "nsRegion.h"                   // for nsIntRegion
@@ -115,31 +116,23 @@ static void DrawLayerInfo(const RenderTa
 static void PrintUniformityInfo(Layer* aLayer)
 {
   // Don't want to print a log for smaller layers
   if (aLayer->GetEffectiveVisibleRegion().GetBounds().width < 300 ||
       aLayer->GetEffectiveVisibleRegion().GetBounds().height < 300) {
     return;
   }
 
-  FrameMetrics frameMetrics = aLayer->GetFrameMetrics();
-  if (!frameMetrics.IsScrollable()) {
+  Matrix4x4 transform = aLayer->AsLayerComposite()->GetShadowTransform();
+  if (!transform.Is2D()) {
     return;
   }
-
-  AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController();
-  if (apzc) {
-    ViewTransform asyncTransform, overscrollTransform;
-    ScreenPoint scrollOffset;
-    apzc->SampleContentTransformForFrame(&asyncTransform,
-                                         scrollOffset,
-                                         &overscrollTransform);
-    printf_stderr("UniformityInfo Layer_Move %llu %p %f, %f\n",
-          TimeStamp::Now(), aLayer, scrollOffset.x.value, scrollOffset.y.value);
-  }
+  Point translation = transform.As2D().GetTranslation();
+  printf_stderr("UniformityInfo Layer_Move %llu %p %f, %f\n",
+            TimeStamp::Now(), aLayer, translation.x.value, translation.y.value);
 }
 
 /* all of the per-layer prepared data we need to maintain */
 struct PreparedLayer
 {
   PreparedLayer(LayerComposite *aLayer, RenderTargetIntRect aClipRect, bool aRestoreVisibleRegion, nsIntRegion &aVisibleRegion) :
     mLayer(aLayer), mClipRect(aClipRect), mRestoreVisibleRegion(aRestoreVisibleRegion), mSavedVisibleRegion(aVisibleRegion) {}
   LayerComposite* mLayer;
@@ -178,16 +171,26 @@ ContainerPrepare(ContainerT* aContainer,
     }
 
     RenderTargetIntRect clipRect = layerToRender->GetLayer()->
         CalculateScissorRect(aClipRect, &aManager->GetWorldTransform());
     if (clipRect.IsEmpty()) {
       continue;
     }
 
+    RenderTargetRect quad = layerToRender->GetLayer()->
+      TransformRectToRenderTarget(LayerPixel::FromUntyped(
+        layerToRender->GetLayer()->GetEffectiveVisibleRegion().GetBounds()));
+
+    Compositor* compositor = aManager->GetCompositor();
+    if (!layerToRender->GetLayer()->AsContainerLayer() &&
+        !quad.Intersects(compositor->ClipRectInLayersCoordinates(layerToRender->GetLayer(), clipRect))) {
+      continue;
+    }
+
     CULLING_LOG("Preparing sublayer %p\n", layerToRender->GetLayer());
 
     nsIntRegion savedVisibleRegion;
     bool restoreVisibleRegion = false;
     gfx::Matrix matrix;
     bool is2D = layerToRender->GetLayer()->GetBaseTransform().Is2D(&matrix);
     if (i + 1 < children.Length() &&
         is2D && !matrix.HasNonIntegerTranslation()) {
@@ -249,31 +252,39 @@ RenderLayers(ContainerT* aContainer,
 
   float opacity = aContainer->GetEffectiveOpacity();
 
   // If this is a scrollable container layer, and it's overscrolled, the layer's
   // contents are transformed in a way that would leave blank regions in the
   // composited area. If the layer has a background color, fill these areas
   // with the background color by drawing a rectangle of the background color
   // over the entire composited area before drawing the container contents.
-  if (AsyncPanZoomController* apzc = aContainer->GetAsyncPanZoomController()) {
-    // Make sure not to do this on a "scrollinfo" layer (one with an empty visible
-    // region) because it's just a placeholder for APZ purposes.
-    if (apzc->IsOverscrolled() && !aContainer->GetVisibleRegion().IsEmpty()) {
+  // Make sure not to do this on a "scrollinfo" layer because it's just a
+  // placeholder for APZ purposes.
+  if (aContainer->HasScrollableFrameMetrics() && !aContainer->IsScrollInfoLayer()) {
+    bool overscrolled = false;
+    for (uint32_t i = 0; i < aContainer->GetFrameMetricsCount(); i++) {
+      AsyncPanZoomController* apzc = aContainer->GetAsyncPanZoomController(i);
+      if (apzc && apzc->IsOverscrolled()) {
+        overscrolled = true;
+        break;
+      }
+    }
+    if (overscrolled) {
       gfxRGBA color = aContainer->GetBackgroundColor();
       // If the background is completely transparent, there's no point in
       // drawing anything for it. Hopefully the layers behind, if any, will
       // provide suitable content for the overscroll effect.
       if (color.a != 0.0) {
         EffectChain effectChain(aContainer);
         effectChain.mPrimaryEffect = new EffectSolidColor(ToColor(color));
         gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
         compositor->DrawQuad(
           RenderTargetPixel::ToUnknown(
-            compositor->ClipRectInLayersCoordinates(aClipRect)),
+            compositor->ClipRectInLayersCoordinates(aContainer, aClipRect)),
           clipRect, effectChain, opacity, Matrix4x4());
       }
     }
   }
 
   for (size_t i = 0u; i < aContainer->mPrepared->mLayers.Length(); i++) {
     PreparedLayer& preparedData = aContainer->mPrepared->mLayers[i];
     LayerComposite* layerToRender = preparedData.mLayer;
@@ -357,17 +368,16 @@ RenderIntermediate(ContainerT* aContaine
                    RefPtr<CompositingRenderTarget> surface)
 {
   Compositor* compositor = aManager->GetCompositor();
   RefPtr<CompositingRenderTarget> previousTarget = compositor->GetCurrentRenderTarget();
 
   if (!surface) {
     return;
   }
-
   compositor->SetRenderTarget(surface);
   // pre-render all of the layers into our temporary
   RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect));
   // Unbind the current surface and rebind the previous one.
   compositor->SetRenderTarget(previousTarget);
 }
 
 template<class ContainerT> void
@@ -416,18 +426,21 @@ ContainerRender(ContainerT* aContainer,
     gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
     aManager->GetCompositor()->DrawQuad(rect, clipRect, effectChain, opacity,
                                         aContainer->GetEffectiveTransform());
   } else {
     RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect));
   }
   aContainer->mPrepared = nullptr;
 
-  if (aContainer->GetFrameMetrics().IsScrollable()) {
-    const FrameMetrics& frame = aContainer->GetFrameMetrics();
+  for (uint32_t i = 0; i < aContainer->GetFrameMetricsCount(); i++) {
+    if (!aContainer->GetFrameMetrics(i).IsScrollable()) {
+      continue;
+    }
+    const FrameMetrics& frame = aContainer->GetFrameMetrics(i);
     LayerRect layerBounds = frame.mCompositionBounds * ParentLayerToLayerScale(1.0);
     gfx::Rect rect(layerBounds.x, layerBounds.y, layerBounds.width, layerBounds.height);
     gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
     aManager->GetCompositor()->DrawDiagnostics(DiagnosticFlags::CONTAINER,
                                                rect, clipRect,
                                                aContainer->GetEffectiveTransform());
   }
 }
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/gfx/2D.h"             // for DrawTarget
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/gfx/Point.h"          // for IntSize, Point
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"          // for Color, SurfaceFormat
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/Effects.h"     // for Effect, EffectChain, etc
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/LayersTypes.h"  // for etc
 #include "ipc/CompositorBench.h"        // for CompositorBench
 #include "ipc/ShadowLayerUtils.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_WARNING, NS_RUNTIMEABORT, etc
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
@@ -791,35 +792,43 @@ float
 LayerManagerComposite::ComputeRenderIntegrity()
 {
   // We only ever have incomplete rendering when progressive tiles are enabled.
   Layer* root = GetRoot();
   if (!gfxPrefs::UseProgressiveTilePainting() || !root) {
     return 1.f;
   }
 
-  const FrameMetrics& rootMetrics = root->GetFrameMetrics();
+  FrameMetrics rootMetrics = LayerMetricsWrapper::TopmostScrollableMetrics(root);
+  if (!rootMetrics.IsScrollable()) {
+    // The root may not have any scrollable metrics, in which case rootMetrics
+    // will just be an empty FrameMetrics. Instead use the actual metrics from
+    // the root layer.
+    rootMetrics = LayerMetricsWrapper(root).Metrics();
+  }
   ParentLayerIntRect bounds = RoundedToInt(rootMetrics.mCompositionBounds);
   nsIntRect screenRect(bounds.x,
                        bounds.y,
                        bounds.width,
                        bounds.height);
 
   float lowPrecisionMultiplier = 1.0f;
   float highPrecisionMultiplier = 1.0f;
 
 #ifdef MOZ_WIDGET_ANDROID
   // Use the transform on the primary scrollable layer and its FrameMetrics
   // to find out how much of the viewport the current displayport covers
-  Layer* primaryScrollable = GetPrimaryScrollableLayer();
-  if (primaryScrollable) {
+  nsTArray<Layer*> rootScrollableLayers;
+  GetRootScrollableLayers(rootScrollableLayers);
+  if (rootScrollableLayers.Length() > 0) {
     // This is derived from the code in
     // AsyncCompositionManager::TransformScrollableLayer
-    const FrameMetrics& metrics = primaryScrollable->GetFrameMetrics();
-    Matrix4x4 transform = primaryScrollable->GetEffectiveTransform();
+    Layer* rootScrollable = rootScrollableLayers[0];
+    const FrameMetrics& metrics = LayerMetricsWrapper::TopmostScrollableMetrics(rootScrollable);
+    Matrix4x4 transform = rootScrollable->GetEffectiveTransform();
     transform.ScalePost(metrics.mResolution.scale, metrics.mResolution.scale, 1);
 
     // Clip the screen rect to the document bounds
     Rect documentBounds =
       transform.TransformBounds(Rect(metrics.mScrollableRect.x - metrics.GetScrollOffset().x,
                                      metrics.mScrollableRect.y - metrics.GetScrollOffset().y,
                                      metrics.mScrollableRect.width,
                                      metrics.mScrollableRect.height));
--- a/gfx/layers/composite/ThebesLayerComposite.cpp
+++ b/gfx/layers/composite/ThebesLayerComposite.cpp
@@ -167,29 +167,16 @@ ThebesLayerComposite::CleanupResources()
 
 void
 ThebesLayerComposite::GenEffectChain(EffectChain& aEffect)
 {
   aEffect.mLayerRef = this;
   aEffect.mPrimaryEffect = mBuffer->GenEffect(GetEffectFilter());
 }
 
-CSSToScreenScale
-ThebesLayerComposite::GetEffectiveResolution()
-{
-  for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
-    const FrameMetrics& metrics = parent->GetFrameMetrics();
-    if (metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
-      return metrics.GetZoom();
-    }
-  }
-
-  return CSSToScreenScale(1.0);
-}
-
 void
 ThebesLayerComposite::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   ThebesLayer::PrintInfo(aStream, aPrefix);
   if (mBuffer && mBuffer->IsAttached()) {
     aStream << "\n";
     nsAutoCString pfx(aPrefix);
     pfx += "  ";
--- a/gfx/layers/composite/ThebesLayerComposite.h
+++ b/gfx/layers/composite/ThebesLayerComposite.h
@@ -81,17 +81,15 @@ public:
 
 protected:
 
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) MOZ_OVERRIDE;
 
 private:
   gfx::Filter GetEffectFilter() { return gfx::Filter::LINEAR; }
 
-  CSSToScreenScale GetEffectiveResolution();
-
 private:
   RefPtr<ContentHost> mBuffer;
 };
 
 } /* layers */
 } /* mozilla */
 #endif /* GFX_ThebesLayerComposite_H */
--- a/gfx/layers/composite/TiledContentHost.cpp
+++ b/gfx/layers/composite/TiledContentHost.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TiledContentHost.h"
 #include "ThebesLayerComposite.h"       // for ThebesLayerComposite
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsRect.h"                     // for nsIntRect
 #include "nsSize.h"                     // for nsIntSize
 #include "mozilla/layers/TiledContentClient.h"
@@ -362,37 +363,37 @@ TiledContentHost::Composite(EffectChain&
   // already has some opacity, we want to skip this behaviour. Otherwise
   // we end up changing the expected overall transparency of the content,
   // and it just looks wrong.
   gfxRGBA backgroundColor(0);
   if (aOpacity == 1.0f && gfxPrefs::LowPrecisionOpacity() < 1.0f) {
     // Background colors are only stored on scrollable layers. Grab
     // the one from the nearest scrollable ancestor layer.
     for (Layer* ancestor = GetLayer(); ancestor; ancestor = ancestor->GetParent()) {
-      if (ancestor->GetFrameMetrics().IsScrollable()) {
+      if (ancestor->HasScrollableFrameMetrics()) {
         backgroundColor = ancestor->GetBackgroundColor();
         break;
       }
     }
   }
   float lowPrecisionOpacityReduction =
         (aOpacity == 1.0f && backgroundColor.a == 1.0f)
         ? gfxPrefs::LowPrecisionOpacity() : 1.0f;
 
   nsIntRegion tmpRegion;
-  const nsIntRegion* renderRegion;
+  const nsIntRegion* renderRegion = aVisibleRegion;
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
   if (PaintWillResample()) {
     // If we're resampling, then the texture image will contain exactly the
     // entire visible region's bounds, and we should draw it all in one quad
     // to avoid unexpected aliasing.
     tmpRegion = aVisibleRegion->GetBounds();
     renderRegion = &tmpRegion;
-  } else {
-    renderRegion = aVisibleRegion;
   }
+#endif
 
   // Render the low and high precision buffers.
   RenderLayerBuffer(mLowPrecisionTiledBuffer,
                     lowPrecisionOpacityReduction < 1.0f ? &backgroundColor : nullptr,
                     aEffectChain, lowPrecisionOpacityReduction * aOpacity,
                     aFilter, aClipRect, *renderRegion, aTransform);
   RenderLayerBuffer(mTiledBuffer, nullptr, aEffectChain, aOpacity, aFilter,
                     aClipRect, *renderRegion, aTransform);
@@ -429,18 +430,18 @@ TiledContentHost::RenderTile(const TileH
     // to warn, the texture update would have already caught this.
     return;
   }
 
   nsIntRect screenBounds = aScreenRegion.GetBounds();
   Rect layerQuad(screenBounds.x, screenBounds.y, screenBounds.width, screenBounds.height);
   RenderTargetRect quad = RenderTargetRect::FromUnknown(aTransform.TransformBounds(layerQuad));
 
-  if (!quad.Intersects(mCompositor->ClipRectInLayersCoordinates(
-        RenderTargetIntRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height)))) {
+  if (!quad.Intersects(mCompositor->ClipRectInLayersCoordinates(mLayer,
+      RenderTargetIntRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height)))) {
     return;
   }
 
   if (aBackgroundColor) {
     aEffectChain.mPrimaryEffect = new EffectSolidColor(ToColor(*aBackgroundColor));
     nsIntRegionRectIterator it(aScreenRegion);
     for (const nsIntRect* rect = it.Next(); rect != nullptr; rect = it.Next()) {
       Rect graphicsRect(rect->x, rect->y, rect->width, rect->height);
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -314,17 +314,16 @@ LayerTransactionParent::RecvUpdate(const
       if (PLayerParent* maskLayer = common.maskLayerParent()) {
         layer->SetMaskLayer(cast(maskLayer)->AsLayer());
       } else {
         layer->SetMaskLayer(nullptr);
       }
       layer->SetAnimations(common.animations());
       layer->SetInvalidRegion(common.invalidRegion());
       layer->SetFrameMetrics(common.metrics());
-      layer->SetScrollHandoffParentId(common.scrollParentId());
       layer->SetBackgroundColor(common.backgroundColor().value());
       layer->SetContentDescription(common.contentDescription());
 
       typedef SpecificLayerAttributes Specific;
       const SpecificLayerAttributes& specific = attrs.specific();
       switch (specific.type()) {
       case Specific::Tnull_t:
         break;
@@ -675,27 +674,34 @@ LayerTransactionParent::RecvGetAnimation
   transform._43 *= devPerCss;
 
   *aTransform = transform;
   return true;
 }
 
 bool
 LayerTransactionParent::RecvSetAsyncScrollOffset(PLayerParent* aLayer,
+                                                 const FrameMetrics::ViewID& aId,
                                                  const int32_t& aX, const int32_t& aY)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return false;
   }
 
   Layer* layer = cast(aLayer)->AsLayer();
   if (!layer) {
     return false;
   }
-  AsyncPanZoomController* controller = layer->GetAsyncPanZoomController();
+  AsyncPanZoomController* controller = nullptr;
+  for (uint32_t i = 0; i < layer->GetFrameMetricsCount(); i++) {
+    if (layer->GetFrameMetrics(i).GetScrollId() == aId) {
+      controller = layer->GetAsyncPanZoomController(i);
+      break;
+    }
+  }
   if (!controller) {
     return false;
   }
   controller->SetTestAsyncScrollOffset(CSSPoint(aX, aY));
   return true;
 }
 
 bool
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -119,17 +119,17 @@ protected:
   virtual bool RecvForceComposite() MOZ_OVERRIDE;
   virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) MOZ_OVERRIDE;
   virtual bool RecvLeaveTestMode() MOZ_OVERRIDE;
   virtual bool RecvGetOpacity(PLayerParent* aParent,
                               float* aOpacity) MOZ_OVERRIDE;
   virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
                                          MaybeTransform* aTransform)
                                          MOZ_OVERRIDE;
-  virtual bool RecvSetAsyncScrollOffset(PLayerParent* aLayer,
+  virtual bool RecvSetAsyncScrollOffset(PLayerParent* aLayer, const FrameMetrics::ViewID& aId,
                                         const int32_t& aX, const int32_t& aY) MOZ_OVERRIDE;
   virtual bool RecvGetAPZTestData(APZTestData* aOutData);
 
   virtual PLayerParent* AllocPLayerParent() MOZ_OVERRIDE;
   virtual bool DeallocPLayerParent(PLayerParent* actor) MOZ_OVERRIDE;
 
   virtual PCompositableParent* AllocPCompositableParent(const TextureInfo& aInfo) MOZ_OVERRIDE;
   virtual bool DeallocPCompositableParent(PCompositableParent* actor) MOZ_OVERRIDE;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -210,18 +210,17 @@ struct CommonLayerAttributes {
   uint64_t scrollbarTargetContainerId;
   uint32_t scrollbarDirection;
   int8_t mixBlendMode;
   bool forceIsolatedGroup;
   nullable PLayer maskLayer;
   // Animated colors will only honored for ColorLayers.
   Animation[] animations;
   nsIntRegion invalidRegion;
-  FrameMetrics metrics;
-  ViewID scrollParentId;
+  FrameMetrics[] metrics;
   LayerColor backgroundColor;
   string contentDescription;
 };
 
 struct ThebesLayerAttributes {
   nsIntRegion validRegion;
 };
 struct ContainerLayerAttributes {
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -14,16 +14,17 @@ include protocol PRenderFrame;
 include protocol PTexture;
 
 include "mozilla/GfxMessageUtils.h";
 
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h";
 using class mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h";
+using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 
 /**
  * The layers protocol is spoken between thread contexts that manage
  * layer (sub)trees.  The protocol comprises atomically publishing
  * layer subtrees to a "shadow" thread context (which grafts the
  * subtree into its own tree), and atomically updating a published
  * subtree.  ("Atomic" in this sense is wrt painting.)
  */
@@ -78,17 +79,17 @@ parent:
   // of the corresponding frame and transform origin and after converting to CSS
   // pixels. If the layer is not transformed by animation, the return value will
   // be void_t.
   sync GetAnimationTransform(PLayer layer) returns (MaybeTransform transform);
 
   // The next time this layer is composited, add this async scroll offset in
   // CSS pixels.
   // Useful for testing rendering of async scrolling.
-  async SetAsyncScrollOffset(PLayer layer, int32_t x, int32_t y);
+  async SetAsyncScrollOffset(PLayer layer, ViewID id, int32_t x, int32_t y);
 
   // Drop any front buffers that might be retained on the compositor
   // side.
   async ClearCachedResources();
 
   // Schedule a composite if one isn't already scheduled.
   async ForceComposite();
 
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -602,18 +602,17 @@ ShadowLayerForwarder::EndTransaction(Inf
     if (Layer* maskLayer = mutant->GetMaskLayer()) {
       common.maskLayerChild() = Shadow(maskLayer->AsShadowableLayer());
     } else {
       common.maskLayerChild() = nullptr;
     }
     common.maskLayerParent() = nullptr;
     common.animations() = mutant->GetAnimations();
     common.invalidRegion() = mutant->GetInvalidRegion();
-    common.metrics() = mutant->GetFrameMetrics();
-    common.scrollParentId() = mutant->GetScrollHandoffParentId();
+    common.metrics() = mutant->GetAllFrameMetrics();
     common.backgroundColor() = mutant->GetBackgroundColor();
     common.contentDescription() = mutant->GetContentDescription();
     attrs.specific() = null_t();
     mutant->FillSpecificAttributes(attrs.specific());
 
     MOZ_LAYERS_LOG(("[LayersForwarder] OpSetLayerAttributes(%p)\n", mutant));
 
     mTxn->AddEdit(OpSetLayerAttributes(nullptr, Shadow(shadow), attrs));
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -158,16 +158,17 @@ EXPORTS.mozilla.layers += [
     'ipc/LayerTransactionChild.h',
     'ipc/LayerTransactionParent.h',
     'ipc/ShadowLayers.h',
     'ipc/ShadowLayersManager.h',
     'ipc/SharedBufferManagerChild.h',
     'ipc/SharedBufferManagerParent.h',
     'ipc/SharedPlanarYCbCrImage.h',
     'ipc/SharedRGBImage.h',
+    'LayerMetricsWrapper.h',
     'LayersTypes.h',
     'opengl/CompositingRenderTargetOGL.h',
     'opengl/CompositorOGL.h',
     'opengl/GrallocTextureClient.h',
     'opengl/GrallocTextureHost.h',
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -1108,33 +1108,35 @@ nsRect nsRegion::GetLargestRectangle (co
                     yaxis.StopAt(bestRectIndices.top));
     bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.x,
                     yaxis.StopAt(bestRectIndices.bottom) - bestRect.y);
   }
 
   return bestRect;
 }
 
+std::ostream& operator<<(std::ostream& stream, const nsRegion& m) {
+  stream << "[";
+
+  int n;
+  pixman_box32_t *boxes = pixman_region32_rectangles(const_cast<pixman_region32_t*>(&m.mImpl), &n);
+  for (int i=0; i<n; i++) {
+    if (i != 0) {
+      stream << "; ";
+    }
+    stream << boxes[i].x1 << "," << boxes[i].y1 << "," << boxes[i].x2 << "," << boxes[i].y2;
+  }
+
+  stream << "]";
+  return stream;
+}
+
 nsCString
 nsRegion::ToString() const {
-    nsCString result;
-    result.Append('[');
-
-    int n;
-    pixman_box32_t *boxes = pixman_region32_rectangles(const_cast<pixman_region32_t*>(&mImpl), &n);
-    for (int i=0; i<n; i++) {
-        if (i != 0) {
-            result.AppendLiteral("; ");
-        }
-        result.Append(nsPrintfCString("%d,%d,%d,%d", boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2));
-
-    }
-    result.Append(']');
-
-    return result;
+  return nsCString(mozilla::ToString(this).c_str());
 }
 
 
 nsRegion nsIntRegion::ToAppUnits (nscoord aAppUnitsPerPixel) const
 {
   nsRegion result;
   nsIntRegionRectIterator rgnIter(*this);
   const nsIntRect* currentRect;
--- a/gfx/src/nsRegion.h
+++ b/gfx/src/nsRegion.h
@@ -5,16 +5,17 @@
 
 #ifndef nsRegion_h__
 #define nsRegion_h__
 
 #include <stddef.h>                     // for size_t
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include <sys/types.h>                  // for int32_t
 #include "gfxCore.h"                    // for NS_GFX
+#include "mozilla/ToString.h"           // for mozilla::ToString
 #include "nsCoord.h"                    // for nscoord
 #include "nsError.h"                    // for nsresult
 #include "nsPoint.h"                    // for nsIntPoint, nsPoint
 #include "nsRect.h"                     // for nsIntRect, nsRect
 #include "nsMargin.h"                   // for nsIntMargin
 #include "nsStringGlue.h"               // for nsCString
 #include "xpcom-config.h"               // for CPP_THROW_NEW
 #include "mozilla/TypedEnum.h"          // for the VisitEdges typed enum
@@ -63,16 +64,18 @@ public:
  ~nsRegion () { pixman_region32_fini(&mImpl); }
   nsRegion& operator = (const nsRect& aRect) { Copy (aRect); return *this; }
   nsRegion& operator = (const nsRegion& aRegion) { Copy (aRegion); return *this; }
   bool operator==(const nsRegion& aRgn) const
   {
     return IsEqual(aRgn);
   }
 
+  friend std::ostream& operator<<(std::ostream& stream, const nsRegion& m);
+
   void Swap(nsRegion* aOther)
   {
     pixman_region32_t tmp = mImpl;
     mImpl = aOther->mImpl;
     aOther->mImpl = tmp;
   }
 
   static
@@ -457,16 +460,20 @@ public:
   nsIntRegion& operator = (const nsIntRect& aRect) { mImpl = ToRect (aRect); return *this; }
   nsIntRegion& operator = (const nsIntRegion& aRegion) { mImpl = aRegion.mImpl; return *this; }
 
   bool operator==(const nsIntRegion& aRgn) const
   {
     return IsEqual(aRgn);
   }
 
+  friend std::ostream& operator<<(std::ostream& stream, const nsIntRegion& m) {
+    return stream << m.mImpl;
+  }
+
   void Swap(nsIntRegion* aOther)
   {
     mImpl.Swap(&aOther->mImpl);
   }
 
   void AndWith(const nsIntRegion& aOther)
   {
     And(*this, aOther);
--- a/gfx/src/nsThemeConstants.h
+++ b/gfx/src/nsThemeConstants.h
@@ -277,8 +277,10 @@
 #define NS_THEME_WINDOW_BUTTON_MAXIMIZE                    238
 #define NS_THEME_WINDOW_BUTTON_RESTORE                     239
 #define NS_THEME_WINDOW_BUTTON_BOX                         240
 #define NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED               241
 
 // moz-apperance style used in setting proper glass margins
 #define NS_THEME_WIN_EXCLUDE_GLASS                         242
 
+#define NS_THEME_MAC_VIBRANCY_LIGHT                        243
+#define NS_THEME_MAC_VIBRANCY_DARK                         244
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -7,16 +7,17 @@
 #include "gmock/gmock.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/UniquePtr.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
@@ -1490,48 +1491,48 @@ TEST_F(APZHitTestingTester, HitTesting1)
   EXPECT_EQ(Matrix4x4(), transformToGecko);
 
   uint32_t paintSequenceNumber = 0;
 
   // Now we have a root APZC that will match the page
   SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Now we have a sub APZC with a better fit
   SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
-  EXPECT_NE(root->GetAsyncPanZoomController(), layers[3]->GetAsyncPanZoomController());
+  EXPECT_NE(root->GetAsyncPanZoomController(0), layers[3]->GetAsyncPanZoomController(0));
   hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(layers[3]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[3]->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(25, 25)
   EXPECT_EQ(Point(25, 25), transformToApzc * Point(25, 25));
   EXPECT_EQ(Point(25, 25), transformToGecko * Point(25, 25));
 
   // At this point, layers[4] obscures layers[3] at the point (15, 15) so
   // hitting there should hit the root APZC
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
 
   // Now test hit testing when we have two scrollable layers
   SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Hit test ouside the reach of layer[3,4] but inside root
   hit = GetTargetAPZC(ScreenPoint(90, 90));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(90, 90)
   EXPECT_EQ(Point(90, 90), transformToApzc * Point(90, 90));
   EXPECT_EQ(Point(90, 90), transformToGecko * Point(90, 90));
 
   // Hit test ouside the reach of any layer
   hit = GetTargetAPZC(ScreenPoint(1000, 10));
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(Matrix4x4(), transformToApzc);
@@ -1550,19 +1551,19 @@ TEST_F(APZHitTestingTester, HitTesting2)
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   // At this point, the following holds (all coordinates in screen pixels):
   // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
   // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
   // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
   // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
 
-  AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController();
-  AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController();
-  AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController(0);
+  AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController(0);
+  AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController(0);
 
   // Hit an area that's clearly on the root layer but not any of the child layers.
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
   EXPECT_EQ(apzcroot, hit.get());
   EXPECT_EQ(Point(75, 25), transformToApzc * Point(75, 25));
   EXPECT_EQ(Point(75, 25), transformToGecko * Point(75, 25));
 
   // Hit an area on the root that would be on layers[3] if layers[2]
@@ -1665,90 +1666,92 @@ TEST_F(APZCTreeManagerTester, Scrollable
 
   // both layers have the same scrollId
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   AsyncPanZoomController* nullAPZC = nullptr;
   // so they should have the same APZC
-  EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 
   // Change the scrollId of layers[1], and verify the APZC changes
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 
   // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
   // APZC for both again
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   AsyncPanZoomController* nullAPZC = nullptr;
   // Ensure all the scrollable layers have an APZC
-  EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(nullAPZC, layers[3]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController());
-  EXPECT_EQ(nullAPZC, layers[5]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController());
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController(0));
+  EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController(0));
   // Ensure those that scroll together have the same APZCs
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), layers[6]->GetAsyncPanZoomController());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), layers[6]->GetAsyncPanZoomController(0));
   // Ensure those that don't scroll together have different APZCs
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[4]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[7]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[4]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[7]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
 
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), hit.get());
   hit = GetTargetAPZC(ScreenPoint(275, 375));
-  EXPECT_EQ(layers[8]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[8]->GetAsyncPanZoomController(0), hit.get());
   hit = GetTargetAPZC(ScreenPoint(250, 100));
-  EXPECT_EQ(layers[7]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[7]->GetAsyncPanZoomController(0), hit.get());
 }
 
 class APZOverscrollHandoffTester : public APZCTreeManagerTester {
 protected:
   UniquePtr<ScopedLayerTreeRegistration> registration;
   TestAsyncPanZoomController* rootApzc;
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
-    aChild->SetScrollHandoffParentId(aParent->GetFrameMetrics().GetScrollId());
+    FrameMetrics metrics = aChild->GetFrameMetrics(0);
+    metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
+    aChild->SetFrameMetrics(metrics);
   }
 
   void CreateOverscrollHandoffLayerTree1() {
     const char* layerTreeSyntax = "c(c)";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 50, 100, 50))
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
   }
 
   void CreateOverscrollHandoffLayerTree2() {
     const char* layerTreeSyntax = "c(c(c))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 50, 100, 50))
@@ -1758,17 +1761,17 @@ protected:
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     SetScrollHandoff(layers[2], layers[1]);
     // No ScopedLayerTreeRegistration as that just needs to be done once per test
     // and this is the second layer tree for a particular test.
     MOZ_ASSERT(registration);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
   }
 
   void CreateOverscrollHandoffLayerTree3() {
     const char* layerTreeSyntax = "c(c(c)c(c))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),  // root
       nsIntRegion(nsIntRect(0, 0, 100, 50)),   // scrolling parent 1
       nsIntRegion(nsIntRect(0, 0, 100, 50)),   // scrolling child 1
@@ -1793,29 +1796,29 @@ protected:
       nsIntRegion(nsIntRect(0, 20, 100, 80))   // child
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 120));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
     rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
   }
 };
 
 // Here we test that if the processing of a touch block is deferred while we
 // wait for content to send a prevent-default message, overscroll is still
 // handed off correctly when the block is processed.
 TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
   // Set up the APZC tree.
   CreateOverscrollHandoffLayerTree1();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
 
   // Queue input events for a pan.
   int time = 0;
   ApzcPanNoFling(childApzc, time, 90, 30);
@@ -1832,32 +1835,32 @@ TEST_F(APZOverscrollHandoffTester, Defer
 // blocks being queued, and the first block is only processed after the second
 // one has been queued, overscroll handoff for the first block follows
 // the original layer structure while overscroll handoff for the second block
 // follows the new layer structure.
 TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
   // Set up an initial APZC tree.
   CreateOverscrollHandoffLayerTree1();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
 
   // Queue input events for a pan.
   int time = 0;
   ApzcPanNoFling(childApzc, time, 90, 30);
 
   // Modify t