merge b2g-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 28 Aug 2014 13:01:53 +0200
changeset 202066 70c1c94cfaecca52a63cca04a07f96f62b3128da
parent 202018 3be45b58fc4787d8267e1b5879a3b25d9e391ac4 (current diff)
parent 202065 a2d24399428a5200e8c977ffe2355604f7ac4cfd (diff)
child 202122 2a15dc07ddaa262b994e9bbd6623773714226b30
push id27389
push usercbook@mozilla.com
push dateThu, 28 Aug 2014 11:02:32 +0000
treeherdermozilla-central@70c1c94cfaec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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 b2g-inbound to mozilla-central a=merge
dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl
dom/mobilemessage/src/SmsFilter.cpp
dom/mobilemessage/src/SmsFilter.h
dom/mobilemessage/tests/mochitest/test_smsfilter.html
--- 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/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/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/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/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/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
--- 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);
@@ -1361,37 +1362,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 +1413,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;
@@ -249,20 +242,28 @@ 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);
@@ -416,18 +417,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,17 +363,17 @@ 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;
--- 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/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -7,16 +7,17 @@
 #include "gmock/gmock.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/UniquePtr.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
@@ -1490,48 +1491,48 @@ TEST_F(APZHitTestingTester, HitTesting1)
   EXPECT_EQ(Matrix4x4(), transformToGecko);
 
   uint32_t paintSequenceNumber = 0;
 
   // Now we have a root APZC that will match the page
   SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Now we have a sub APZC with a better fit
   SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
-  EXPECT_NE(root->GetAsyncPanZoomController(), layers[3]->GetAsyncPanZoomController());
+  EXPECT_NE(root->GetAsyncPanZoomController(0), layers[3]->GetAsyncPanZoomController(0));
   hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(layers[3]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[3]->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(25, 25)
   EXPECT_EQ(Point(25, 25), transformToApzc * Point(25, 25));
   EXPECT_EQ(Point(25, 25), transformToGecko * Point(25, 25));
 
   // At this point, layers[4] obscures layers[3] at the point (15, 15) so
   // hitting there should hit the root APZC
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
 
   // Now test hit testing when we have two scrollable layers
   SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Hit test ouside the reach of layer[3,4] but inside root
   hit = GetTargetAPZC(ScreenPoint(90, 90));
-  EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
   // expect hit point at LayerIntPoint(90, 90)
   EXPECT_EQ(Point(90, 90), transformToApzc * Point(90, 90));
   EXPECT_EQ(Point(90, 90), transformToGecko * Point(90, 90));
 
   // Hit test ouside the reach of any layer
   hit = GetTargetAPZC(ScreenPoint(1000, 10));
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(Matrix4x4(), transformToApzc);
@@ -1550,19 +1551,19 @@ TEST_F(APZHitTestingTester, HitTesting2)
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   // At this point, the following holds (all coordinates in screen pixels):
   // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
   // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
   // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
   // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
 
-  AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController();
-  AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController();
-  AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController(0);
+  AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController(0);
+  AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController(0);
 
   // Hit an area that's clearly on the root layer but not any of the child layers.
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
   EXPECT_EQ(apzcroot, hit.get());
   EXPECT_EQ(Point(75, 25), transformToApzc * Point(75, 25));
   EXPECT_EQ(Point(75, 25), transformToGecko * Point(75, 25));
 
   // Hit an area on the root that would be on layers[3] if layers[2]
@@ -1665,90 +1666,92 @@ TEST_F(APZCTreeManagerTester, Scrollable
 
   // both layers have the same scrollId
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   AsyncPanZoomController* nullAPZC = nullptr;
   // so they should have the same APZC
-  EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 
   // Change the scrollId of layers[1], and verify the APZC changes
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 
   // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
   // APZC for both again
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   AsyncPanZoomController* nullAPZC = nullptr;
   // Ensure all the scrollable layers have an APZC
-  EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(nullAPZC, layers[3]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController());
-  EXPECT_EQ(nullAPZC, layers[5]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController());
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController(0));
+  EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController(0));
   // Ensure those that scroll together have the same APZCs
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
-  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), layers[6]->GetAsyncPanZoomController());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
+  EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), layers[6]->GetAsyncPanZoomController(0));
   // Ensure those that don't scroll together have different APZCs
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[4]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
-  EXPECT_NE(layers[7]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[4]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+  EXPECT_NE(layers[7]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
 
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), hit.get());
   hit = GetTargetAPZC(ScreenPoint(275, 375));
-  EXPECT_EQ(layers[8]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[8]->GetAsyncPanZoomController(0), hit.get());
   hit = GetTargetAPZC(ScreenPoint(250, 100));
-  EXPECT_EQ(layers[7]->GetAsyncPanZoomController(), hit.get());
+  EXPECT_EQ(layers[7]->GetAsyncPanZoomController(0), hit.get());
 }
 
 class APZOverscrollHandoffTester : public APZCTreeManagerTester {
 protected:
   UniquePtr<ScopedLayerTreeRegistration> registration;
   TestAsyncPanZoomController* rootApzc;
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
-    aChild->SetScrollHandoffParentId(aParent->GetFrameMetrics().GetScrollId());
+    FrameMetrics metrics = aChild->GetFrameMetrics(0);
+    metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
+    aChild->SetFrameMetrics(metrics);
   }
 
   void CreateOverscrollHandoffLayerTree1() {
     const char* layerTreeSyntax = "c(c)";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 50, 100, 50))
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
   }
 
   void CreateOverscrollHandoffLayerTree2() {
     const char* layerTreeSyntax = "c(c(c))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 50, 100, 50))
@@ -1758,17 +1761,17 @@ protected:
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     SetScrollHandoff(layers[2], layers[1]);
     // No ScopedLayerTreeRegistration as that just needs to be done once per test
     // and this is the second layer tree for a particular test.
     MOZ_ASSERT(registration);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
   }
 
   void CreateOverscrollHandoffLayerTree3() {
     const char* layerTreeSyntax = "c(c(c)c(c))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),  // root
       nsIntRegion(nsIntRect(0, 0, 100, 50)),   // scrolling parent 1
       nsIntRegion(nsIntRect(0, 0, 100, 50)),   // scrolling child 1
@@ -1793,29 +1796,29 @@ protected:
       nsIntRegion(nsIntRect(0, 20, 100, 80))   // child
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 120));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+    rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
     rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
   }
 };
 
 // Here we test that if the processing of a touch block is deferred while we
 // wait for content to send a prevent-default message, overscroll is still
 // handed off correctly when the block is processed.
 TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
   // Set up the APZC tree.
   CreateOverscrollHandoffLayerTree1();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
 
   // Queue input events for a pan.
   int time = 0;
   ApzcPanNoFling(childApzc, time, 90, 30);
@@ -1832,32 +1835,32 @@ TEST_F(APZOverscrollHandoffTester, Defer
 // blocks being queued, and the first block is only processed after the second
 // one has been queued, overscroll handoff for the first block follows
 // the original layer structure while overscroll handoff for the second block
 // follows the new layer structure.
 TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
   // Set up an initial APZC tree.
   CreateOverscrollHandoffLayerTree1();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
 
   // Queue input events for a pan.
   int time = 0;
   ApzcPanNoFling(childApzc, time, 90, 30);
 
   // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
   // between the child and the root.
   CreateOverscrollHandoffLayerTree2();
   nsRefPtr<Layer> middle = layers[1];
   childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
-  TestAsyncPanZoomController* middleApzc = (TestAsyncPanZoomController*)middle->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* middleApzc = (TestAsyncPanZoomController*)middle->GetAsyncPanZoomController(0);
 
   // Queue input events for another pan.
   ApzcPanNoFling(childApzc, time, 30, 90);
 
   // Allow the first pan to be processed.
   childApzc->ContentReceivedTouch(false);
 
   // Make sure things have scrolled according to the handoff chain in
@@ -1877,20 +1880,20 @@ TEST_F(APZOverscrollHandoffTester, Layer
 }
 
 // Here we test that if two flings are happening simultaneously, overscroll
 // is handed off correctly for each.
 TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) {
   // Set up an initial APZC tree.
   CreateOverscrollHandoffLayerTree3();
 
-  TestAsyncPanZoomController* parent1 = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
-  TestAsyncPanZoomController* child1 = (TestAsyncPanZoomController*)layers[2]->GetAsyncPanZoomController();
-  TestAsyncPanZoomController* parent2 = (TestAsyncPanZoomController*)layers[3]->GetAsyncPanZoomController();
-  TestAsyncPanZoomController* child2 = (TestAsyncPanZoomController*)layers[4]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* parent1 = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
+  TestAsyncPanZoomController* child1 = (TestAsyncPanZoomController*)layers[2]->GetAsyncPanZoomController(0);
+  TestAsyncPanZoomController* parent2 = (TestAsyncPanZoomController*)layers[3]->GetAsyncPanZoomController(0);
+  TestAsyncPanZoomController* child2 = (TestAsyncPanZoomController*)layers[4]->GetAsyncPanZoomController(0);
 
   // Pan on the lower child.
   int time = 0;
   ApzcPan(child2, time, 45, 5);
 
   // Pan on the upper child.
   ApzcPan(child1, time, 95, 55);
 
@@ -1909,33 +1912,33 @@ TEST_F(APZOverscrollHandoffTester, Simul
   child2->AssertStateIsReset();
   parent2->AssertStateIsFling();
 }
 
 TEST_F(APZOverscrollHandoffTester, Scrollgrab) {
   // Set up the layer tree
   CreateScrollgrabLayerTree();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
   // and leave some more (another 15 px) for the child.
   int time = 0;
   ApzcPan(childApzc, time, 80, 45);
 
   // Check that the parent and child have scrolled as much as we expect.
   EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y);
 }
 
 TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
   // Set up the layer tree
   CreateScrollgrabLayerTree();
 
-  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+  TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
 
   // Pan on the child, not enough to fully scroll the scrollgrab parent.
   int time = 0;
   ApzcPan(childApzc, time, 80, 70);
 
   // Check that it is the scrollgrab parent that's in a fling, not the child.
   rootApzc->AssertStateIsFling();
   childApzc->AssertStateIsReset();
--- a/gfx/tests/gtest/TestLayers.cpp
+++ b/gfx/tests/gtest/TestLayers.cpp
@@ -1,16 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 #include "TestLayers.h"
 #include "gtest/gtest.h"
 #include "gmock/gmock.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 class TestLayerManager: public LayerManager {
 public:
   TestLayerManager()
@@ -317,8 +318,135 @@ TEST(Layers, RepositionChild) {
   ValidateTreePointers(layers);
 
   //   0
   // 3 2 1
   ASSERT_EQ(layers[2], layers[3]->GetNextSibling());
   ASSERT_EQ(layers[1], layers[2]->GetNextSibling());
   ASSERT_EQ(nullptr, layers[1]->GetNextSibling());
 }
+
+TEST(LayerMetricsWrapper, SimpleTree) {
+  nsTArray<nsRefPtr<Layer> > layers;
+  nsRefPtr<LayerManager> lm;
+  nsRefPtr<Layer> root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
+  LayerMetricsWrapper wrapper(root);
+
+  ASSERT_EQ(root.get(), wrapper.GetLayer());
+  wrapper = wrapper.GetFirstChild();
+  ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
+  ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+  wrapper = wrapper.GetFirstChild();
+  ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+  wrapper = wrapper.GetFirstChild();
+  ASSERT_EQ(layers[3].get(), wrapper.GetLayer());
+  ASSERT_FALSE(wrapper.GetFirstChild().IsValid());
+  wrapper = wrapper.GetNextSibling();
+  ASSERT_EQ(layers[4].get(), wrapper.GetLayer());
+  ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+  wrapper = wrapper.GetParent();
+  ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+  wrapper = wrapper.GetNextSibling();
+  ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+  ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+  wrapper = wrapper.GetLastChild();
+  ASSERT_EQ(layers[6].get(), wrapper.GetLayer());
+  wrapper = wrapper.GetParent();
+  ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+  LayerMetricsWrapper layer5 = wrapper;
+  wrapper = wrapper.GetPrevSibling();
+  ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+  wrapper = wrapper.GetParent();
+  ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
+  ASSERT_TRUE(layer5 == wrapper.GetLastChild());
+  LayerMetricsWrapper rootWrapper(root);
+  ASSERT_TRUE(rootWrapper == wrapper.GetParent());
+}
+
+static FrameMetrics
+MakeMetrics(FrameMetrics::ViewID aId) {
+  FrameMetrics metrics;
+  metrics.SetScrollId(aId);
+  return metrics;
+}
+
+TEST(LayerMetricsWrapper, MultiFramemetricsTree) {
+  nsTArray<nsRefPtr<Layer> > layers;
+  nsRefPtr<LayerManager> lm;
+  nsRefPtr<Layer> root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
+
+  nsTArray<FrameMetrics> metrics;
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 1));
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 2));
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));      // bottom of root layer
+  root->SetFrameMetrics(metrics);
+
+  metrics.Clear();
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 3));
+  layers[1]->SetFrameMetrics(metrics);
+
+  metrics.Clear();
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 4));
+  layers[2]->SetFrameMetrics(metrics);
+
+  metrics.Clear();
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 5));
+  layers[4]->SetFrameMetrics(metrics);
+
+  metrics.Clear();
+  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 6));
+  layers[5]->SetFrameMetrics(metrics);
+
+  LayerMetricsWrapper wrapper(root, LayerMetricsWrapper::StartAt::TOP);
+  nsTArray<Layer*> expectedLayers;
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[0].get());
+  expectedLayers.AppendElement(layers[1].get());
+  expectedLayers.AppendElement(layers[2].get());
+  expectedLayers.AppendElement(layers[2].get());
+  expectedLayers.AppendElement(layers[3].get());
+  nsTArray<FrameMetrics::ViewID> expectedIds;
+  expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 0);
+  expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+  expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 1);
+  expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 2);
+  expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+  expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+  expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 3);
+  expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+  expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 4);
+  expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+  for (int i = 0; i < 10; i++) {
+    ASSERT_EQ(expectedLayers[i], wrapper.GetLayer());
+    ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId());
+    wrapper = wrapper.GetFirstChild();
+  }
+  ASSERT_FALSE(wrapper.IsValid());
+
+  wrapper = LayerMetricsWrapper(root, LayerMetricsWrapper::StartAt::BOTTOM);
+  for (int i = 5; i < 10; i++) {
+    ASSERT_EQ(expectedLayers[i], wrapper.GetLayer());
+    ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId());
+    wrapper = wrapper.GetFirstChild();
+  }
+  ASSERT_FALSE(wrapper.IsValid());
+
+  wrapper = LayerMetricsWrapper(layers[4], LayerMetricsWrapper::StartAt::BOTTOM);
+  ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 5, wrapper.Metrics().GetScrollId());
+  wrapper = wrapper.GetParent();
+  ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 4, wrapper.Metrics().GetScrollId());
+  ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+  ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+  wrapper = wrapper.GetParent();
+  ASSERT_EQ(FrameMetrics::NULL_SCROLL_ID, wrapper.Metrics().GetScrollId());
+  ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+  wrapper = wrapper.GetNextSibling();
+  ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 6, wrapper.Metrics().GetScrollId());
+  ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+}
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -645,16 +645,17 @@ static void UnmarkFrameForDisplay(nsIFra
     f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
   }
 }
 
 static void RecordFrameMetrics(nsIFrame* aForFrame,
                                nsIFrame* aScrollFrame,
                                const nsIFrame* aReferenceFrame,
                                ContainerLayer* aRoot,
+                               ViewID aScrollParentId,
                                const nsRect& aViewport,
                                bool aForceNullScrollId,
                                bool aIsRoot,
                                const ContainerLayerParameters& aContainerParameters) {
   nsPresContext* presContext = aForFrame->PresContext();
   int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
   LayoutDeviceToLayerScale resolution(aContainerParameters.mXScale, aContainerParameters.mYScale);
 
@@ -701,16 +702,17 @@ static void RecordFrameMetrics(nsIFrame*
     nsIAtom* originOfLastScroll = scrollableFrame->OriginOfLastScroll();
     if (originOfLastScroll && originOfLastScroll != nsGkAtoms::apz) {
       metrics.SetScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration());
     }
   }
 
   metrics.SetScrollId(scrollId);
   metrics.SetIsRoot(aIsRoot);
+  metrics.SetScrollParentId(aScrollParentId);
 
   // Only the root scrollable frame for a given presShell should pick up
   // the presShell's resolution. All the other frames are 1.0.
   if (aScrollFrame == presShell->GetRootScrollFrame()) {
     metrics.mResolution = ParentLayerToLayerScale(presShell->GetXResolution(),
                                                   presShell->GetYResolution());
   } else {
     metrics.mResolution = ParentLayerToLayerScale(1.0f);
@@ -1286,17 +1288,17 @@ void nsDisplayList::PaintForFrame(nsDisp
   bool isRoot = presContext->IsRootContentDocument();
 
   nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
 
   nsRect viewport(aBuilder->ToReferenceFrame(aForFrame), aForFrame->GetSize());
 
   RecordFrameMetrics(aForFrame, rootScrollFrame,
                      aBuilder->FindReferenceFrameFor(aForFrame),
-                     root, viewport,
+                     root, FrameMetrics::NULL_SCROLL_ID, viewport,
                      !isRoot, isRoot, containerParameters);
 
   // NS_WARNING is debug-only, so don't even bother checking the conditions in
   // a release build.
 #ifdef DEBUG
   bool usingDisplayport = false;
   if (rootScrollFrame) {
     nsIContent* content = rootScrollFrame->GetContent();
@@ -3674,19 +3676,18 @@ nsDisplaySubDocument::BuildLayer(nsDispl
   if (ContainerLayer* container = layer->AsContainerLayer()) {
     nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
     bool isRootContentDocument = presContext->IsRootContentDocument();
 
     nsRect viewport = mFrame->GetRect() -
                       mFrame->GetPosition() +
                       mFrame->GetOffsetToCrossDoc(ReferenceFrame());
 
-    container->SetScrollHandoffParentId(mScrollParentId);
     RecordFrameMetrics(mFrame, rootScrollFrame, ReferenceFrame(),
-                       container, viewport,
+                       container, mScrollParentId, viewport,
                        false, isRootContentDocument, params);
   }
 
   return layer.forget();
 }
 
 nsRect
 nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
@@ -3985,19 +3986,18 @@ nsDisplayScrollLayer::BuildLayer(nsDispl
   nsRefPtr<ContainerLayer> layer = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            params, nullptr);
 
   nsRect viewport = mScrollFrame->GetRect() -
                     mScrollFrame->GetPosition() +
                     mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
 
-  layer->SetScrollHandoffParentId(mScrollParentId);
   RecordFrameMetrics(mScrolledFrame, mScrollFrame, ReferenceFrame(), layer,
-                     viewport, false, false, params);
+                     mScrollParentId, viewport, false, false, params);
 
   if (mList.IsOpaque()) {
     nsRect displayport;
     bool usingDisplayport =
       nsLayoutUtils::GetDisplayPort(mScrolledFrame->GetContent(), &displayport);
     mDisplayPortContentsOpaque = mList.GetBounds(aBuilder).Contains(
         GetScrolledContentRectToDraw(aBuilder, usingDisplayport ? &displayport : nullptr));
   } else {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2370,22 +2370,22 @@ public class GeckoAppShell
         if (SmsManager.getInstance() == null) {
             return;
         }
 
         SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
     }
 
     @WrapElementForJNI(stubName = "CreateMessageListWrapper")
-    public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+    public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
-        SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId);
+        SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId);
     }
 
     @WrapElementForJNI(stubName = "GetNextMessageInListWrapper")
     public static void getNextMessageInList(int aListId, int aRequestId) {
         if (SmsManager.getInstance() == null) {
             return;
         }
 
--- a/mobile/android/base/GeckoSmsManager.java
+++ b/mobile/android/base/GeckoSmsManager.java
@@ -732,69 +732,69 @@ public class GeckoSmsManager
 
     if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) {
       Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
       notifySmsDeleteFailed(kUnknownError, aRequestId);
     }
   }
 
   @Override
-  public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+  public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
     class CreateMessageListRunnable implements Runnable {
       private long     mStartDate;
       private long     mEndDate;
       private String[] mNumbers;
       private int      mNumbersCount;
-      private int      mDeliveryState;
+      private String   mDelivery;
       private boolean  mReverse;
       private int      mRequestId;
 
-      CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+      CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
         mStartDate = aStartDate;
         mEndDate = aEndDate;
         mNumbers = aNumbers;
         mNumbersCount = aNumbersCount;
-        mDeliveryState = aDeliveryState;
+        mDelivery = aDelivery;
         mReverse = aReverse;
         mRequestId = aRequestId;
       }
 
       @Override
       public void run() {
         Cursor cursor = null;
         boolean closeCursor = true;
 
         try {
           // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
           ArrayList<String> restrictions = new ArrayList<String>();
 
-          if (mStartDate != 0) {
+          if (mStartDate >= 0) {
             restrictions.add("date >= " + mStartDate);
           }
 
-          if (mEndDate != 0) {
+          if (mEndDate >= 0) {
             restrictions.add("date <= " + mEndDate);
           }
 
           if (mNumbersCount > 0) {
             String numberRestriction = "address IN ('" + mNumbers[0] + "'";
 
             for (int i=1; i<mNumbersCount; ++i) {
               numberRestriction += ", '" + mNumbers[i] + "'";
             }
             numberRestriction += ")";
 
             restrictions.add(numberRestriction);
           }
 
-          if (mDeliveryState == kDeliveryStateUnknown) {
+          if (mDelivery == null) {
             restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
-          } else if (mDeliveryState == kDeliveryStateSent) {
+          } else if (mDelivery == "sent") {
             restrictions.add("type = " + kSmsTypeSentbox);
-          } else if (mDeliveryState == kDeliveryStateReceived) {
+          } else if (mDelivery == "received") {
             restrictions.add("type = " + kSmsTypeInbox);
           } else {
             throw new UnexpectedDeliveryStateException();
           }
 
           String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : "";
 
           for (int i=1; i<restrictions.size(); ++i) {
@@ -848,17 +848,17 @@ public class GeckoSmsManager
           // that would be less efficient.
           if (cursor != null && closeCursor) {
             cursor.close();
           }
         }
       }
     }
 
-    if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) {
+    if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId))) {
       Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
       notifyReadingMessageListFailed(kUnknownError, aRequestId);
     }
   }
 
   @Override
   public void getNextMessageInList(int aListId, int aRequestId) {
     class GetNextMessageInListRunnable implements Runnable {
--- a/mobile/android/base/SmsManager.java
+++ b/mobile/android/base/SmsManager.java
@@ -23,12 +23,12 @@ interface ISmsManager
 {
   public void start();
   public void stop();
   public void shutdown();
 
   public void send(String aNumber, String aMessage, int aRequestId);
   public void getMessage(int aMessageId, int aRequestId);
   public void deleteMessage(int aMessageId, int aRequestId);
-  public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId);
+  public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId);
   public void getNextMessageInList(int aListId, int aRequestId);
   public void clearMessageList(int aListId);
 }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1156,19 +1156,24 @@ AndroidBridge::CreateMessageList(const d
                                           NewJavaString(&jniFrame, EmptyString()));
 
     for (uint32_t i = 0; i < aFilter.numbers().Length(); ++i) {
         jstring elem = NewJavaString(&jniFrame, aFilter.numbers()[i]);
         env->SetObjectArrayElement(numbers, i, elem);
         env->DeleteLocalRef(elem);
     }
 
-    mozilla::widget::android::GeckoAppShell::CreateMessageListWrapper(aFilter.startDate(),
-                             aFilter.endDate(), numbers, aFilter.numbers().Length(),
-                             aFilter.delivery(), aReverse, requestId);
+    int64_t startDate = aFilter.hasStartDate() ? aFilter.startDate() : -1;
+    int64_t endDate = aFilter.hasEndDate() ? aFilter.endDate() : -1;
+    GeckoAppShell::CreateMessageListWrapper(startDate, endDate,
+                                            numbers, aFilter.numbers().Length(),
+                                            aFilter.delivery(),
+                                            aFilter.hasRead(), aFilter.read(),
+                                            aFilter.threadId(),
+                                            aReverse, requestId);
 }
 
 void
 AndroidBridge::GetNextMessageInList(int32_t aListId, nsIMobileMessageCallback* aRequest)
 {
     ALOG_BRIDGE("AndroidBridge::GetNextMessageInList");
 
     uint32_t requestId;
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -106,17 +106,17 @@ void GeckoAppShell::InitStubs(JNIEnv *jE
     jAlertsProgressListener_OnProgress = getStaticMethod("alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jCancelVibrate = getStaticMethod("cancelVibrate", "()V");
     jCheckURIVisited = getStaticMethod("checkUriVisited", "(Ljava/lang/String;)V");
     jClearMessageList = getStaticMethod("clearMessageList", "(I)V");
     jCloseCamera = getStaticMethod("closeCamera", "()V");
     jCloseNotification = getStaticMethod("closeNotification", "(Ljava/lang/String;)V");
     jConnectionGetMimeType = getStaticMethod("connectionGetMimeType", "(Ljava/net/URLConnection;)Ljava/lang/String;");
     jCreateInputStream = getStaticMethod("createInputStream", "(Ljava/net/URLConnection;)Ljava/io/InputStream;");
-    jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;IIZI)V");
+    jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;ILjava/lang/String;ZZJZI)V");
     jCreateShortcut = getStaticMethod("createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jDeleteMessageWrapper = getStaticMethod("deleteMessage", "(II)V");
     jDisableBatteryNotifications = getStaticMethod("disableBatteryNotifications", "()V");
     jDisableNetworkNotifications = getStaticMethod("disableNetworkNotifications", "()V");
     jDisableScreenOrientationNotifications = getStaticMethod("disableScreenOrientationNotifications", "()V");
     jDisableSensor = getStaticMethod("disableSensor", "(I)V");
     jEnableBatteryNotifications = getStaticMethod("enableBatteryNotifications", "()V");
     jEnableLocation = getStaticMethod("enableLocation", "(Z)V");
@@ -330,31 +330,34 @@ jobject GeckoAppShell::CreateInputStream
     }
 
     jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jCreateInputStream, a0);
     AndroidBridge::HandleUncaughtException(env);
     jobject ret = static_cast<jobject>(env->PopLocalFrame(temp));
     return ret;
 }
 
-void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6) {
+void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, const nsAString& a4, bool a5, bool a6, int64_t a7, bool a8, int32_t a9) {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (env->PushLocalFrame(1) != 0) {
+    if (env->PushLocalFrame(2) != 0) {
         AndroidBridge::HandleUncaughtException(env);
         MOZ_CRASH("Exception should have caused crash.");
     }
 
-    jvalue args[7];
+    jvalue args[10];
     args[0].j = a0;
     args[1].j = a1;
     args[2].l = a2;
     args[3].i = a3;
-    args[4].i = a4;
+    args[4].l = AndroidBridge::NewJavaString(env, a4);
     args[5].z = a5;
-    args[6].i = a6;
+    args[6].z = a6;
+    args[7].j = a7;
+    args[8].z = a8;
+    args[9].i = a9;
 
     env->CallStaticVoidMethodA(mGeckoAppShellClass, jCreateMessageListWrapper, args);
     AndroidBridge::HandleUncaughtException(env);
     env->PopLocalFrame(nullptr);
 }
 
 void GeckoAppShell::CreateShortcut(const nsAString& a0, const nsAString& a1, const nsAString& a2) {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -24,17 +24,17 @@ public:
     static void AlertsProgressListener_OnProgress(const nsAString& a0, int64_t a1, int64_t a2, const nsAString& a3);
     static void CancelVibrate();
     static void CheckURIVisited(const nsAString& a0);
     static void ClearMessageList(int32_t a0);
     static void CloseCamera();
     static void CloseNotification(const nsAString& a0);
     static jstring ConnectionGetMimeType(jobject a0);
     static jobject CreateInputStream(jobject a0);
-    static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6);
+    static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, const nsAString& a4, bool a5, bool a6, int64_t a7, bool a8, int32_t a9);
     static void CreateShortcut(const nsAString& a0, const nsAString& a1, const nsAString& a2);
     static void DeleteMessageWrapper(int32_t a0, int32_t a1);
     static void DisableBatteryNotifications();
     static void DisableNetworkNotifications();
     static void DisableScreenOrientationNotifications();
     static void DisableSensor(int32_t a0);
     static void EnableBatteryNotifications();
     static void EnableLocation(bool a0);