Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 28 Aug 2014 16:11:04 +0200
changeset 223816 f6beabe7cfb60c8a405199cff263b5e7ecd5cd93
parent 223815 12a5e1b45d1cade707483ac8a7574c26fedd0bdc (current diff)
parent 223753 47c9418fbc28608e6b9b2c0b1f1664b183bd5b30 (diff)
child 223817 8883019fca40023d4f5d6941491d03f3e339dd10
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge 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], Fra