Merge b2g-inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 27 Feb 2015 13:23:30 -0500
changeset 261487 b94bcbc389e828344479f700d3970537c4abfff2
parent 261455 ff17afa674114c7ed0370e6da03e48a4e0ceecb6 (current diff)
parent 261486 ae4495c44a76a90b4dcc85f5b288a2bfe4c93893 (diff)
child 261488 b49a6d6505585cfb0fd091c07e4a197ae0d0d74f
child 261491 5def1d193a0c6f6a8b0ae175ef2be25b46ee248a
child 261621 d97e4aee9a3a4d429c634cd2df5bf8585cc199b1
child 261661 ee5d1f234412b31e665ed64079d5c883fa84c5d5
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
b94bcbc389e8 / 39.0a1 / 20150228030231 / files
nightly linux64
b94bcbc389e8 / 39.0a1 / 20150228030231 / files
nightly mac
b94bcbc389e8 / 39.0a1 / 20150228030231 / files
nightly win32
b94bcbc389e8 / 39.0a1 / 20150228030231 / files
nightly win64
b94bcbc389e8 / 39.0a1 / 20150228030231 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c. a=merge CLOSED TREE
b2g/installer/package-manifest.in
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,19 +10,19 @@
   <!--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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
@@ -129,16 +129,16 @@
   <project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
   <!-- dolphin specific things -->
   <project name="device/sprd" path="device/sprd" revision="8609ba49ab518822691528312b9c87b656164c58"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="4e58336019b5cbcfd134caf55b142236cf986618"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="4387fe988e5a1001f29ce05fcfda03ed2d32137b"/>
   <project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
   <project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
-  <project name="kernel/common" path="kernel" revision="0b4249f88cb8faadfbf80be207af14198dec08e3"/>
+  <project name="kernel/common" path="kernel" revision="1ef12a094a59d966a2576d0991a3618b591a7da9"/>
   <project name="platform/system/core" path="system/core" revision="53d584d4a4b4316e4de9ee5f210d662f89b44e7e"/>
-  <project name="u-boot" path="u-boot" revision="6980cf8f8cf9c1d43ff92b7af13425a5ed531d12"/>
+  <project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
   <project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="6974f8e771d4d8e910357a6739ab124768891e8f"/>
   <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="c985699a5140b6dd5c7a43295e4d4397ce9b1267"/>
   <project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
   <project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
 </manifest>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,18 +14,18 @@
   <!--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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="7512026a377271a0cade12d70846557f0bc7781c"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,18 +12,18 @@
   <!--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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,19 +10,19 @@
   <!--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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,19 +10,19 @@
   <!--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="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,18 +14,18 @@
   <!--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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="7512026a377271a0cade12d70846557f0bc7781c"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,19 +10,19 @@
   <!--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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,18 +12,18 @@
   <!--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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "7512026a377271a0cade12d70846557f0bc7781c", 
+        "git_revision": "f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "53713e514b92cccfd55404cebd2b627cb9804bc2", 
+    "revision": "c00390d70e15726ba3e4c50bd2353fbf991f25c9", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,18 +12,18 @@
   <!--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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,19 +10,19 @@
   <!--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="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/>
@@ -136,17 +136,17 @@
   <project name="platform/system/vold" path="system/vold" revision="eb59d2afd5f6e1cbab2ef985a8dd1c7105b499e8"/>
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="ea531874885eed7f68802048218ed86dde927f58"/>
   <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="df7e0cfbbc7e954ed26c73ac17832a5ff035f046"/>
   <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="73f7e7f12c8c5459f7a39e2fa343f083c942864d"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="4df51d9abf6cc9a6ec49b965e621699e0e6dc4fb"/>
   <default remote="caf" revision="refs/tags/android-5.0.0_r6" sync-j="4"/>
   <!-- Nexus 5 specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="ba62cc8b78c30d36181b8060a2016cc8da166236"/>
-  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="dc525eeb67301a44f514a7ac40a59ec592b34e31"/>
+  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="a6f00b0d7519168329ca4a3f66d3be30d66e0c9e"/>
   <project name="device/qcom/common" path="device/qcom/common" remote="caf" revision="3697e5acf25629b82658334e3f8ee3b6df5becab"/>
   <project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="1268f640184df5ef759ada669f101a613451673a"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="0cb8574d338bf9f15b45ace7c08ad6deae9673ee"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="16abda2258c9aa1ed78f00bb0a9b2b43b4cb919e"/>
   <project name="platform/hardware/broadcom/libbt" path="hardware/broadcom/libbt" revision="3e856528121ae0af0ca26c97cb563160c7e16d85"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="d9e716e14e685f4fc124ae591a1cd0899bc4d7b5"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="505aa92674337b76ce7d038800f2a6d7dc340ac9"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="0a21f566d8c9b01b9754d639e13358bdcb6c7650"/>
--- a/dom/downloads/DownloadsAPI.js
+++ b/dom/downloads/DownloadsAPI.js
@@ -16,16 +16,22 @@ Cu.import("resource://gre/modules/Downlo
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
                                    "@mozilla.org/telephony/volume-service;1",
                                     "nsIVolumeService");
 
+/**
+  * The content process implementations of navigator.mozDownloadManager and its
+  * DOMDownload download objects.  Uses DownloadsIPC.jsm to communicate with
+  * DownloadsAPI.jsm in the parent process.
+  */
+
 function debug(aStr) {
 #ifdef MOZ_DEBUG
   dump("-*- DownloadsAPI.js : " + aStr + "\n");
 #endif
 }
 
 function DOMDownloadManagerImpl() {
   debug("DOMDownloadManagerImpl constructor");
@@ -35,16 +41,25 @@ DOMDownloadManagerImpl.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   // nsIDOMGlobalPropertyInitializer implementation
   init: function(aWindow) {
     debug("DownloadsManager init");
     this.initDOMRequestHelper(aWindow,
                               ["Downloads:Added",
                                "Downloads:Removed"]);
+
+    // Get the manifest URL if this is an installed app
+    let appsService = Cc["@mozilla.org/AppsService;1"]
+                        .getService(Ci.nsIAppsService);
+    let principal = aWindow.document.nodePrincipal;
+    // This returns the empty string if we're not an installed app.  Coerce to
+    // null.
+    this._manifestURL = appsService.getManifestURLByLocalId(principal.appId) ||
+                          null;
   },
 
   uninit: function() {
     debug("uninit");
     downloadsCache.evict(this._window);
   },
 
   set ondownloadstart(aHandler) {
@@ -74,33 +89,18 @@ DOMDownloadManagerImpl.prototype = {
           aReject("GetDownloadsError");
         }
       );
     }.bind(this));
   },
 
   clearAllDone: function() {
     debug("clearAllDone()");
-    return this.createPromise(function (aResolve, aReject) {
-      DownloadsIPC.clearAllDone().then(
-        function(aDownloads) {
-          // Turn the list of download objects into DOM objects and
-          // send them.
-          let array = new this._window.Array();
-          for (let id in aDownloads) {
-            let dom = createDOMDownloadObject(this._window, aDownloads[id]);
-            array.push(this._prepareForContent(dom));
-          }
-          aResolve(array);
-        }.bind(this),
-        function() {
-          aReject("ClearAllDoneError");
-        }
-      );
-    }.bind(this));
+    // This is a void function; we just kick it off.  No promises, etc.
+    DownloadsIPC.clearAllDone();
   },
 
   remove: function(aDownload) {
     debug("remove " + aDownload.url + " " + aDownload.id);
     return this.createPromise(function (aResolve, aReject) {
       if (!downloadsCache.has(this._window, aDownload.id)) {
         debug("no download " + aDownload.id);
         aReject("InvalidDownload");
@@ -116,16 +116,77 @@ DOMDownloadManagerImpl.prototype = {
         }.bind(this),
         function() {
           aReject("RemoveError");
         }
       );
     }.bind(this));
   },
 
+  adoptDownload: function(aAdoptDownloadDict) {
+    // Our AdoptDownloadDict only includes simple types, which WebIDL enforces.
+    // We have no object/any types so we do not need to worry about invoking
+    // JSON.stringify (and it inheriting our security privileges).
+    debug("adoptDownload");
+    return this.createPromise(function (aResolve, aReject) {
+      if (!aAdoptDownloadDict) {
+        debug("Download dictionary is required!");
+        aReject("InvalidDownload");
+        return;
+      }
+      if (!aAdoptDownloadDict.storageName || !aAdoptDownloadDict.storagePath ||
+          !aAdoptDownloadDict.contentType) {
+        debug("Missing one of: storageName, storagePath, contentType");
+        aReject("InvalidDownload");
+        return;
+      }
+
+      // Convert storageName/storagePath to a local filesystem path.
+      let volume;
+      // getVolumeByName throws if you give it something it doesn't like
+      // because XPConnect converts the NS_ERROR_NOT_AVAILABLE to an
+      // exception.  So catch it.
+      try {
+        volume = volumeService.getVolumeByName(aAdoptDownloadDict.storageName);
+      } catch (ex) {}
+      if (!volume) {
+        debug("Invalid storage name: " + aAdoptDownloadDict.storageName);
+        aReject("InvalidDownload");
+        return;
+      }
+      let computedPath = volume.mountPoint + '/' +
+                           aAdoptDownloadDict.storagePath;
+      // We validate that there is actually a file at the given path in the
+      // parent process in DownloadsAPI.js because that's where the file
+      // access would actually occur either way.
+
+      // Create a DownloadsAPI.jsm 'jsonDownload' style representation.
+      let jsonDownload = {
+        url: aAdoptDownloadDict.url,
+        path: computedPath,
+        contentType: aAdoptDownloadDict.contentType,
+        startTime: aAdoptDownloadDict.startTime.valueOf() || Date.now(),
+        sourceAppManifestURL: this._manifestURL
+      };
+
+      DownloadsIPC.adoptDownload(jsonDownload).then(
+        function(aResult) {
+          let domDownload = createDOMDownloadObject(this._window, aResult);
+          aResolve(this._prepareForContent(domDownload));
+        }.bind(this),
+        function(aResult) {
+          // This will be one of: AdoptError (generic catch-all),
+          // AdoptNoSuchFile, AdoptFileIsDirectory
+          aReject(aResult.error);
+        }
+      );
+    }.bind(this));
+  },
+
+
   /**
     * Turns a chrome download object into a content accessible one.
     * When we have __DOM_IMPL__ available we just use that, otherwise
     * we run _create() with the wrapped js object.
     */
   _prepareForContent: function(aChromeObject) {
     if (aChromeObject.__DOM_IMPL__) {
       return aChromeObject.__DOM_IMPL__;
@@ -290,16 +351,21 @@ DOMDownloadImpl.prototype = {
     if (["downloading",
          "stopped",
          "succeeded",
          "finalized"].indexOf(aState) != -1) {
       this._state = aState;
     }
   },
 
+  /**
+    * Initialize a DOMDownload instance for the given window using the
+    * 'jsonDownload' serialized format of the download encoded by
+    * DownloadsAPI.jsm.
+    */
   _init: function(aWindow, aDownload) {
     this._window = aWindow;
     this.id = aDownload.id;
     this._update(aDownload);
     Services.obs.addObserver(this, "downloads-state-change-" + this.id,
                              /* ownsWeak */ true);
     debug("observer set for " + this.id);
   },
@@ -309,22 +375,23 @@ DOMDownloadImpl.prototype = {
     */
   _update: function(aDownload) {
     debug("update " + uneval(aDownload));
     if (this.id != aDownload.id) {
       return;
     }
 
     let props = ["totalBytes", "currentBytes", "url", "path", "storageName",
-                 "storagePath", "state", "contentType", "startTime"];
+                 "storagePath", "state", "contentType", "startTime",
+                 "sourceAppManifestURL"];
     let changed = false;
     let changedProps = {};
 
     props.forEach((prop) => {
-      if (aDownload[prop] && (aDownload[prop] != this[prop])) {
+      if (prop in aDownload && (aDownload[prop] != this[prop])) {
         this[prop] = aDownload[prop];
         changedProps[prop] = changed = true;
       }
     });
 
     // When the path changes, we should update the storage name and
     // storage path used for our downloaded file in case our download
     // was re-targetted to a different storage and/or filename.
--- a/dom/downloads/DownloadsAPI.jsm
+++ b/dom/downloads/DownloadsAPI.jsm
@@ -8,21 +8,29 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 this.EXPORTED_SYMBOLS = [];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Downloads.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
+/**
+  * Parent process logic that services download API requests from the
+  * DownloadAPI.js instances in content processeses.  The actual work of managing
+  * downloads is done by Toolkit's Downloads.jsm.  This module is loaded by B2G's
+  * shell.js
+  */
+
 function debug(aStr) {
 #ifdef MOZ_DEBUG
   dump("-*- DownloadsAPI.jsm : " + aStr + "\n");
 #endif
 }
 
 function sendPromiseMessage(aMm, aMessageName, aData, aError) {
   debug("sendPromiseMessage " + aMessageName);
@@ -44,17 +52,18 @@ let DownloadsAPI = {
 
     this._ids = new WeakMap(); // Maps toolkit download objects to ids.
     this._index = {};          // Maps ids to downloads.
 
     ["Downloads:GetList",
      "Downloads:ClearAllDone",
      "Downloads:Remove",
      "Downloads:Pause",
-     "Downloads:Resume"].forEach((msgName) => {
+     "Downloads:Resume",
+     "Downloads:Adopt"].forEach((msgName) => {
       ppmm.addMessageListener(msgName, this);
     });
 
     let self = this;
     Task.spawn(function () {
       let list = yield Downloads.getList(Downloads.ALL);
       yield list.addView(self);
 
@@ -87,17 +96,19 @@ let DownloadsAPI = {
     */
   jsonDownload: function(aDownload) {
     let res = {
       totalBytes: aDownload.totalBytes,
       currentBytes: aDownload.currentBytes,
       url: aDownload.source.url,
       path: aDownload.target.path,
       contentType: aDownload.contentType,
-      startTime: aDownload.startTime.getTime()
+      startTime: aDownload.startTime.getTime(),
+      sourceAppManifestURL: aDownload._unknownProperties &&
+                              aDownload._unknownProperties.sourceAppManifestURL
     };
 
     if (aDownload.error) {
       res.error = aDownload.error;
     }
 
     res.id = this.downloadId(aDownload);
 
@@ -160,16 +171,19 @@ let DownloadsAPI = {
       this.remove(aMessage.data, aMessage.target);
       break;
     case "Downloads:Pause":
       this.pause(aMessage.data, aMessage.target);
       break;
     case "Downloads:Resume":
       this.resume(aMessage.data, aMessage.target);
       break;
+    case "Downloads:Adopt":
+      this.adoptDownload(aMessage.data, aMessage.target);
+      break;
     default:
       debug("Invalid message: " + aMessage.name);
     }
   },
 
   getList: function(aData, aMm) {
     debug("getList called!");
     let self = this;
@@ -181,27 +195,19 @@ let DownloadsAPI = {
         res.push(self.jsonDownload(aDownload));
       });
       aMm.sendAsyncMessage("Downloads:GetList:Return", res);
     }).then(null, Components.utils.reportError);
   },
 
   clearAllDone: function(aData, aMm) {
     debug("clearAllDone called!");
-    let self = this;
     Task.spawn(function () {
       let list = yield Downloads.getList(Downloads.ALL);
-      yield list.removeFinished();
-      list = yield Downloads.getList(Downloads.ALL);
-      let downloads = yield list.getAll();
-      let res = [];
-      downloads.forEach((aDownload) => {
-        res.push(self.jsonDownload(aDownload));
-      });
-      aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res);
+      list.removeFinished();
     }).then(null, Components.utils.reportError);
   },
 
   remove: function(aData, aMm) {
     debug("remove id " + aData.id);
     let download = this.getDownloadById(aData.id);
     if (!download) {
       sendPromiseMessage(aMm, "Downloads:Remove:Return",
@@ -257,12 +263,103 @@ let DownloadsAPI = {
       function() {
         sendPromiseMessage(aMm, "Downloads:Resume:Return", aData);
       },
       function() {
         sendPromiseMessage(aMm, "Downloads:Resume:Return",
                            aData, "ResumeError");
       }
     );
+  },
+
+  /**
+    * Receive a download to adopt in the same representation we produce from
+    * our "jsonDownload" normalizer and add it to the list of downloads.
+    */
+  adoptDownload: function(aData, aMm) {
+    let adoptJsonRep = aData.jsonDownload;
+    debug("adoptDownload " + uneval(adoptJsonRep));
+
+    Task.spawn(function* () {
+      // Verify that the file exists on disk.  This will result in a rejection
+      // if the file does not exist.  We will also use this information for the
+      // file size to avoid weird inconsistencies.  We ignore the filesystem
+      // timestamp in favor of whatever the caller is telling us.
+      let fileInfo = yield OS.File.stat(adoptJsonRep.path);
+
+      // We also require that the file is not a directory.
+      if (fileInfo.isDir) {
+        throw new Error("AdoptFileIsDirectory");
+      }
+
+      // We need to create a Download instance to add to the list.  Create a
+      // serialized representation and then from there the instance.
+      let serializedRep = {
+        // explicit initializations in toSerializable
+        source: {
+          url: adoptJsonRep.url
+          // This is where isPrivate would go if adoption supported private
+          // browsing.
+        },
+        target: {
+          path: adoptJsonRep.path,
+        },
+        startTime: adoptJsonRep.startTime,
+        // kPlainSerializableDownloadProperties propagations
+        succeeded: true, // (all adopted downloads are required to be completed)
+        totalBytes: fileInfo.size,
+        contentType: adoptJsonRep.contentType,
+        // unknown properties added/used by the DownloadsAPI
+        currentBytes: fileInfo.size,
+        sourceAppManifestURL: adoptJsonRep.sourceAppManifestURL
+      };
+
+      let download = yield Downloads.createDownload(serializedRep);
+
+      // The ALL list is a DownloadCombinedList instance that combines the
+      // PUBLIC (persisted to disk) and PRIVATE (ephemeral) download lists..
+      // When we call add on it, it dispatches to the appropriate list based on
+      // the 'isPrivate' field of the source.  (Which we don't initialize and
+      // defaults to false.)
+      let allDownloadList = yield Downloads.getList(Downloads.ALL);
+
+      // This add will automatically notify all views of the added download,
+      // including DownloadsAPI instances and the DownloadAutoSaveView that's
+      // subscribed to the PUBLIC list and will save the download.
+      yield allDownloadList.add(download);
+
+      debug("download adopted");
+      // The notification above occurred synchronously, and so we will have
+      // already dispatched an added notification for our download to the child
+      // process in question.  As such, we only need to relay the download id
+      // since the download will already have been cached.
+      return download;
+    }.bind(this)).then(
+      (download) => {
+        sendPromiseMessage(aMm, "Downloads:Adopt:Return",
+                           {
+                             id: this.downloadId(download),
+                             promiseId: aData.promiseId
+                           });
+      },
+      (ex) => {
+        let reportAs = "AdoptError";
+        // Provide better error codes for expected errors.
+        if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
+          reportAs = "AdoptNoSuchFile";
+        } else if (ex.message === "AdoptFileIsDirectory") {
+          reportAs = ex.message;
+        } else {
+          // Anything else is unexpected and should be reported to help track
+          // down what's going wrong.
+          debug("unexpected download error: " + ex);
+          Cu.reportError(ex);
+        }
+        sendPromiseMessage(aMm, "Downloads:Adopt:Return",
+                           {
+                             promiseId: aData.promiseId
+                           },
+                           reportAs);
+    });
   }
 };
 
 DownloadsAPI.init();
--- a/dom/downloads/DownloadsIPC.jsm
+++ b/dom/downloads/DownloadsIPC.jsm
@@ -31,35 +31,34 @@ function debug(aStr) {
   dump("-*- DownloadsIPC.jsm : " + aStr + "\n");
 #endif
 }
 
 const ipcMessages = ["Downloads:Added",
                      "Downloads:Removed",
                      "Downloads:Changed",
                      "Downloads:GetList:Return",
-                     "Downloads:ClearAllDone:Return",
                      "Downloads:Remove:Return",
                      "Downloads:Pause:Return",
-                     "Downloads:Resume:Return"];
+                     "Downloads:Resume:Return",
+                     "Downloads:Adopt:Return"];
 
 this.DownloadsIPC = {
   downloads: {},
 
   init: function() {
     debug("init");
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     ipcMessages.forEach((aMessage) => {
       cpmm.addMessageListener(aMessage, this);
     });
 
     // We need to get the list of current downloads.
     this.ready = false;
     this.getListPromises = [];
-    this.clearAllPromises = [];
     this.downloadPromises = {};
     cpmm.sendAsyncMessage("Downloads:GetList", {});
     this._promiseId = 0;
   },
 
   notifyChanges: function(aId) {
     // TODO: use the subject instead of stringifying.
     if (this.downloads[aId]) {
@@ -88,22 +87,16 @@ this.DownloadsIPC = {
 
         if (!this.ready) {
           this.getListPromises.forEach(aPromise =>
                                        aPromise.resolve(this.downloads));
           this.getListPromises.length = 0;
         }
         this.ready = true;
         break;
-      case "Downloads:ClearAllDone:Return":
-        this._updateDownloadsArray(download);
-        this.clearAllPromises.forEach(aPromise =>
-                                      aPromise.resolve(this.downloads));
-        this.clearAllPromises.length = 0;
-        break;
       case "Downloads:Added":
         this.downloads[download.id] = download;
         this.notifyChanges(download.id);
         break;
       case "Downloads:Removed":
         if (this.downloads[download.id]) {
           this.downloads[download.id] = download;
           this.notifyChanges(download.id);
@@ -134,16 +127,17 @@ this.DownloadsIPC = {
 
         if (changed) {
           this.notifyChanges(download.id);
         }
         break;
       case "Downloads:Remove:Return":
       case "Downloads:Pause:Return":
       case "Downloads:Resume:Return":
+      case "Downloads:Adopt:Return":
         if (this.downloadPromises[download.promiseId]) {
           if (!download.error) {
             this.downloadPromises[download.promiseId].resolve(download);
           } else {
             this.downloadPromises[download.promiseId].reject(download);
           }
           delete this.downloadPromises[download.promiseId];
         }
@@ -162,24 +156,21 @@ this.DownloadsIPC = {
       deferred.resolve(this.downloads);
     } else {
       this.getListPromises.push(deferred);
     }
     return deferred.promise;
   },
 
   /**
-    * Returns a promise that is resolved with the list of current downloads.
-    */
+   * Void function to trigger removal of completed downloads.
+   */
   clearAllDone: function() {
     debug("clearAllDone");
-    let deferred = Promise.defer();
-    this.clearAllPromises.push(deferred);
     cpmm.sendAsyncMessage("Downloads:ClearAllDone", {});
-    return deferred.promise;
   },
 
   promiseId: function() {
     return this._promiseId++;
   },
 
   remove: function(aId) {
     debug("remove " + aId);
@@ -206,16 +197,26 @@ this.DownloadsIPC = {
     let deferred = Promise.defer();
     let pId = this.promiseId();
     this.downloadPromises[pId] = deferred;
     cpmm.sendAsyncMessage("Downloads:Resume",
                           { id: aId, promiseId: pId });
     return deferred.promise;
   },
 
+  adoptDownload: function(aJsonDownload) {
+    debug("adoptDownload");
+    let deferred = Promise.defer();
+    let pId = this.promiseId();
+    this.downloadPromises[pId] = deferred;
+    cpmm.sendAsyncMessage("Downloads:Adopt",
+                          { jsonDownload: aJsonDownload, promiseId: pId });
+    return deferred.promise;
+  },
+
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "xpcom-shutdown") {
       ipcMessages.forEach((aMessage) => {
         cpmm.removeMessageListener(aMessage, this);
       });
     }
   }
 };
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/clear_all_done_helper.js
@@ -0,0 +1,67 @@
+/**
+ * A helper to clear out the existing downloads known to the mozDownloadManager
+ * / downloads.js.
+ *
+ * It exists because previously mozDownloadManager.clearAllDone() thought that
+ * when it returned that all the completed downloads would be cleared out.  It
+ * was wrong and this led to various intermittent test failurse.  In discussion
+ * on https://bugzil.la/979446#c13 and onwards, it was decided that
+ * clearAllDone() was in the wrong and that the jsdownloads API it depends on
+ * was not going to change to make it be in the right.
+ *
+ * The existing uses of clearAllDone() in tests seemed to be about:
+ * - Exploding if there was somehow still a download in progress
+ * - Clearing out the download list at the start of a test so that calls to
+ *   getDownloads() wouldn't have to worry about existing downloads, etc.
+ *
+ * From discussion, the right way to handle clearing is to wait for the expected
+ * removal events to occur for the existing downloads.  So that's what we do.
+ * We still generate a test failure if there are any in-progress downloads.
+ *
+ * @param {Boolean} [getDownloads=false]
+ *   If true, invoke getDownloads after clearing the download list and return
+ *   its value.
+ */
+function clearAllDoneHelper(getDownloads) {
+  var clearedPromise = new Promise(function(resolve, reject) {
+    function gotDownloads(downloads) {
+      // If there are no downloads, we're already done.
+      if (downloads.length === 0) {
+        resolve();
+        return;
+      }
+
+      // Track the set of expected downloads that will be finalized.
+      var expectedIds = new Set();
+      function changeHandler(evt) {
+        var download = evt.download;
+        if (download.state === "finalized") {
+          expectedIds.delete(download.id);
+          if (expectedIds.size === 0) {
+            resolve();
+          }
+        }
+      }
+      downloads.forEach(function(download) {
+        if (download.state === "downloading") {
+          ok(false, "A download is still active: " + download.path);
+          reject("Active download");
+        }
+        download.onstatechange = changeHandler;
+        expectedIds.add(download.id);
+      });
+      navigator.mozDownloadManager.clearAllDone();
+    }
+    function gotBadNews(err) {
+      ok(false, "Problem clearing all downloads: " + err);
+      reject(err);
+    }
+    navigator.mozDownloadManager.getDownloads().then(gotDownloads, gotBadNews);
+ });
+ if (!getDownloads) {
+   return clearedPromise;
+ }
+ return clearedPromise.then(function() {
+   return navigator.mozDownloadManager.getDownloads();
+ });
+}
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/common_app.js
@@ -0,0 +1,19 @@
+function is(a, b, msg) {
+  alert((a === b ? 'OK' : 'KO') + ' ' + a + ' should equal ' + b + ': ' + msg);
+}
+
+function ok(a, msg) {
+  alert((a ? 'OK' : 'KO')+ ' ' + msg);
+}
+
+function info(msg) {
+  alert('INFO ' + msg);
+}
+
+function cbError() {
+  alert('KO error');
+}
+
+function finish() {
+  alert('DONE');
+}
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/file_app.sjs
@@ -0,0 +1,55 @@
+var gBasePath = "tests/dom/downloads/tests/";
+var gTemplate = "file_app.template.webapp";
+
+function handleRequest(request, response) {
+  var query = getQuery(request);
+
+  var testToken = query.testToken || '';
+  var appType = query.appType || 'web';
+
+  var template = gBasePath + gTemplate;
+  response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
+  var body = readTemplate(template)
+               .replace(/TESTTOKEN/g, testToken)
+               .replace(/APPTYPE/g, appType);
+  response.write();
+}
+
+// Copy-pasted incantations. There ought to be a better way to synchronously read
+// a file into a string, but I guess we're trying to discourage that.
+function readTemplate(path) {
+  var file = Components.classes["@mozilla.org/file/directory_service;1"].
+                        getService(Components.interfaces.nsIProperties).
+                        get("CurWorkD", Components.interfaces.nsILocalFile);
+  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
+                        createInstance(Components.interfaces.nsIFileInputStream);
+  var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
+                       createInstance(Components.interfaces.nsIConverterInputStream);
+  var split = path.split("/");
+  for(var i = 0; i < split.length; ++i) {
+    file.append(split[i]);
+  }
+  fis.init(file, -1, -1, false);
+  cis.init(fis, "UTF-8", 0, 0);
+
+  var data = "";
+  let (str = {}) {
+    let read = 0;
+    do {
+      read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
+      data += str.value;
+    } while (read != 0);
+  }
+  cis.close();
+  return data;
+}
+
+function getQuery(request) {
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+  return query;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/file_app.template.webapp
@@ -0,0 +1,7 @@
+{
+  "name": "Really Rapid Release (hosted)",
+  "description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
+  "type": "APPTYPE",
+  "launch_path": "/tests/dom/downloads/tests/TESTTOKEN",
+  "icons": { "128": "default_icon" }
+}
--- a/dom/downloads/tests/mochitest.ini
+++ b/dom/downloads/tests/mochitest.ini
@@ -1,12 +1,24 @@
 [DEFAULT]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' # bug 979446, frequent failures
+# The actual requirement for mozDownloadManager is MOZ_GONK because of
+# the nsIVolumeService dependency.  Until https://bugzil.la/1130264 is
+# addressed, there is no way for mulet to run these tests.
+run-if = toolkit == 'gonk'
 support-files =
   serve_file.sjs
+  clear_all_done_helper.js
+  file_app.template.webapp
+  file_app.sjs
+  common_app.js
+  shim_app_as_test.js
+  shim_app_as_test_chrome.js
+  testapp_downloads_adopt_download.html
+  testapp_downloads_adopt_download.js
+  testapp_downloads_adopt_download.manifest
 
 [test_downloads_navigator_object.html]
 [test_downloads_basic.html]
 [test_downloads_large.html]
+[test_downloads_adopt_download.html]
 [test_downloads_bad_file.html]
 [test_downloads_pause_remove.html]
 [test_downloads_pause_resume.html]
-skip-if = toolkit=='gonk' # b2g(bug 947167) b2g-debug(bug 947167)
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/shim_app_as_test.js
@@ -0,0 +1,210 @@
+/**
+ * Support logic to run a test file as an installed app.  This file is derived
+ * from dom/requestsync/tests/test_basic_app.html but uses
+ * DOMApplicationRegistry in a chrome script (shim_app_as_test_chrome.js) to
+ * directly install the apps instead of mozApps.install because mozApps.install
+ * can't install privileged/certified apps.  (This is the same mechanism used by
+ * the Firefox OS Gaia email app's backend test runner.)
+ *
+ * You really only want to do this if your test cares about the app's origin
+ * or you REALLY want to double-check AvailableIn and other WebIDL-provided
+ * security mechanisms.
+ *
+ * If you trust WebIDL, your life may be made significantly easier by just
+ * setting the pref "dom.ignore_webidl_scope_checks" to true, which makes
+ * BindingUtils.cpp's IsInPrivilegedApp and IsInCertifiedApp return true no
+ * matter what *on the main thread*.  You are potentially out of luck on
+ * workers since at the time of writing this since the values stored on
+ * WorkerPrivateParent are based on the app status and ignore the pref.
+ *
+ * TO USE THIS:
+ *
+ * Make sure you have the usual header boilerplate:
+ *   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ *   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ *
+ * You also want to add this file!
+ *   <script type="application/javascript" src="shim_app_as_test.js"></script>
+ *
+ * In your script body, issue a call like so:
+ * runAppTest({
+ *   appFile: 'testapp_downloads_adopt_download.html',
+ *   appManifest: 'testapp_downloads_adopt_download.manifest',
+ *   appType: 'certified',
+ *   extraPrefs: {
+ *     set: [["dom.mozDownloads.enabled", true]]
+ *   }
+ * });
+ *
+ * You shouldn't be adding other stuff to that file.  Instead, you want
+ * everything in your testapp_*.html file.  And you probably just want to copy
+ * and paste from an existing one of those...
+ */
+
+  var gManifestURL;
+  var gApp;
+  var gOptions;
+
+  // Load the chrome script.
+  var gChromeHelper = SpecialPowers.loadChromeScript(
+                        SimpleTest.getTestFileURL('shim_app_as_test_chrome.js'));
+
+  function installApp() {
+    info("installing app");
+    var useOrigin = document.location.origin;
+    gChromeHelper.sendAsyncMessage(
+      'install',
+      {
+        origin: useOrigin,
+        manifestURL: SimpleTest.getTestFileURL(gOptions.appManifest),
+      });
+  }
+
+  function installedApp(appInfo) {
+    gApp = appInfo;
+    ok(!!appInfo, 'installed app');
+    runTests();
+  }
+  gChromeHelper.addMessageListener('installed', installedApp);
+
+  function uninstallApp() {
+    info('uninstalling app');
+    gChromeHelper.sendAsyncMessage('uninstall', gApp);
+  }
+
+  function uninstalledApp(success) {
+    ok(success, 'uninstalled app');
+    runTests();
+  }
+  gChromeHelper.addMessageListener('uninstalled', uninstalledApp);
+
+  function testApp() {
+    var cleanupFrame;
+    var handleTestMessage = function(message) {
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/^KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/^INFO/.exec(message)) {
+        info("Message from app: " + message.substring(5));
+      } else if (/^DONE$/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        cleanupFrame();
+        runTests();
+      }
+    };
+
+    // Bug 1097479 means that embed-webapps does not work if you are already
+    // OOP, as we are for b2g.  So we need to have the chrome script run our
+    // app in a sibling iframe to the one we're living in.  When that bug is
+    // fixed or we are run in a non-b2g context, we can set this value to false
+    // or otherwise conditionalize based on behaviour.
+    var needSiblingIframeHack = true;
+
+    if (needSiblingIframeHack) {
+      gChromeHelper.sendAsyncMessage('run', gApp);
+
+      gChromeHelper.addMessageListener('appMessage', handleTestMessage);
+      gChromeHelper.addMessageListener('appError', function(data) {
+        ok(false, "Error in app frame: " + data.message);
+      });
+
+      cleanupFrame = function() {
+        gChromeHelper.sendAsyncMessage('close', {});
+      };
+    } else {
+      var ifr = document.createElement('iframe');
+      ifr.setAttribute('mozbrowser', 'true');
+      ifr.setAttribute('mozapp', gApp.manifestURL);
+
+      cleanupFrame = function() {
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
+      };
+
+      // Set us up to listen for messages from the app.
+      var listener = function(e) {
+        var message = e.detail.message; // e.detail.message;
+        handleTestMessage(message);
+      };
+
+      // This event is triggered when the app calls "alert".
+      ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+      ifr.addEventListener('mozbrowsererror', function(evt) {
+        ok(false, "Error in app frame: " + evt.detail);
+      });
+
+      ifr.setAttribute('src', gApp.manifest.launch_path);
+      var domParent = document.getElementById('content');
+      if (!domParent) {
+        document.createElement('div');
+        document.body.insertBefore(domParent, document.body.firstChild);
+      }
+      domParent.appendChild(ifr);
+    }
+  }
+
+  var tests = [
+    // Permissions
+    function() {
+      info("pushing permissions");
+      SpecialPowers.pushPermissions(
+        [{ "type": "browser", "allow": 1, "context": document },
+         { "type": "embed-apps", "allow": 1, "context": document },
+         { "type": "webapps-manage", "allow": 1, "context": document }
+        ],
+        runTests);
+    },
+
+    // Preferences
+    function() {
+      info("pushing preferences: " + gOptions.extraPrefs.set);
+      SpecialPowers.pushPrefEnv({
+        "set": gOptions.extraPrefs.set
+      }, runTests);
+    },
+
+    function() {
+      info("enabling use of mozbrowser");
+      //SpecialPowers.setAllAppsLaunchable(true);
+      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+      runTests();
+    },
+
+    // No confirmation needed when an app is installed
+    function() {
+      SpecialPowers.autoConfirmAppInstall(function() {
+        SpecialPowers.autoConfirmAppUninstall(runTests);
+      });
+    },
+
+    // Installing the app
+    installApp,
+
+    // Run tests in app
+    testApp,
+
+    // Uninstall the app
+    uninstallApp,
+  ];
+
+  function runTests() {
+    if (!tests.length) {
+      ok(true, 'DONE!');
+      SimpleTest.finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  function runAppTest(options) {
+    gOptions = options;
+    var href = document.location.href;
+    gManifestURL = href.substring(0, href.lastIndexOf('/') + 1) +
+      options.appManifest;
+    runTests();
+  }
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/shim_app_as_test_chrome.js
@@ -0,0 +1,178 @@
+/**
+ * This is the chrome helper for shim_app_as_test.js.  Its load is triggered by
+ * shim_app_as_test.js by a call to SpecialPowers.loadChromeScript and runs
+ * in the parent process in a sandbox created with the system principal.  (Which
+ * seems like it can never get collected because it's reachable via the
+ * apparently singleton SpecialPowersObserverAPI instance and there's no logic
+ * to support reaping.  Wuh-oh.)
+ *
+ * It exists to help install fake privileged/certified applications.  It needs
+ * to exist because:
+ * - We need to poke at DOMApplicationRegistry directly.
+ * - By using SpecialPowers.loadChromeScript we are able to ensure this file
+ *   is run in the parent process.  This is important because
+ *   DOMApplicationRegistry only lives in the parent process!
+ * - By running entirely in a chrome privileged compartment, we avoid crazy
+ *   wrapper problems that we would otherwise face with our shenanigans of
+ *   directly meddling with DOMApplicationRegistry.  (And hopefully save
+ *   anyone changing DOMApplicationRegistry from frustration/hating us if
+ *   things were just barely working.)
+ * - Bug 1097479 means that embed-webapps doesn't work when the content process
+ *   that is telling us to do things is itself OOP.  So it falls upon us to
+ *   handle the running of the app by creating a sibling mozbrowser/mozapp
+ *   iframe to the one running the mochitests.
+ *
+ * Note that in this file we try to do *only* those things that can't otherwise
+ * be cleanly done using SpecialPowers.
+ *
+ * Want to better understand our execution context?  Check out
+ * SpecialPowersObserverAPI.js and search on SPLoadChromeScript.
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+Cu.import('resource://gre/modules/Webapps.jsm'); // for DOMApplicationRegistry
+Cu.import('resource://gre/modules/AppsUtils.jsm'); // for AppUtils
+Cu.import('resource://gre/modules/Services.jsm'); // for AppUtils
+
+// Yes, you would think there was something like this already exposed easily
+// in a JSM somewhere.  No.
+function fetchManifest(manifestURL) {
+  return new Promise(function(resolve, reject) {
+    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                .createInstance(Ci.nsIXMLHttpRequest);
+    xhr.open("GET", manifestURL, true);
+    xhr.responseType = "json";
+
+    xhr.addEventListener("load", function() {
+      if (xhr.status == 200) {
+        resolve(xhr.response);
+      } else {
+        reject();
+      }
+    });
+
+    xhr.addEventListener("error", function() {
+      reject();
+    });
+
+    xhr.send(null);
+  });
+}
+
+/**
+ * Install an app using confirmInstall using pre-chewed data.  This avoids the
+ * check in the normal installApp flow that gets all judgemental about the
+ * installation of privileged and certified apps.
+ */
+function installApp(req) {
+  fetchManifest(req.manifestURL).then(function(manifestObj) {
+    var data = {
+      // cloneAppObj normalizes the representation for us
+      app: AppsUtils.cloneAppObject({
+        installOrigin: req.origin,
+        origin: req.origin,
+        manifestURL: req.manifestURL,
+        appStatus: AppsUtils.getAppManifestStatus(manifestObj),
+        receipts: [],
+        categories: []
+      }),
+
+      from: req.origin, // unused?
+      oid: 0, // unused?
+      requestID: 0, // unused-ish
+      appId: 0, // unused
+      isBrowser: false,
+      isPackage: false, // used
+      // magic to auto-ack... don't think we care about this...
+      forceSuccessAck: false
+      // stuff that probably doesn't matter: 'mm', 'apkInstall',
+    };
+    // cloneAppObject does not propagate the manifest
+    data.app.manifest = manifestObj;
+
+    return DOMApplicationRegistry.confirmInstall(data).then(
+      function() {
+        var appId =
+          DOMApplicationRegistry.getAppLocalIdByManifestURL(req.manifestURL);
+        // act like this is a privileged app having all of its permissions
+        // authorized at first run.
+        DOMApplicationRegistry.updatePermissionsForApp(
+          appId,
+          /* preinstalled */ true,
+          /* system update? */ true);
+
+        sendAsyncMessage(
+          'installed',
+          {
+            appId: appId,
+            manifestURL: req.manifestURL,
+            manifest: manifestObj
+          });
+      },
+      function(err) {
+        sendAsyncMessage('installed', false);
+      });
+  });
+}
+
+function uninstallApp(appInfo) {
+  DOMApplicationRegistry.uninstall(appInfo.manifestURL).then(
+    function() {
+      sendAsyncMessage('uninstalled', true);
+    },
+    function() {
+      sendAsyncMessage('uninstalled', false);
+    });
+}
+
+var activeIframe = null;
+
+/**
+ * Run our app in a sibling mozbrowser/mozapp iframe to the mochitest iframe.
+ * This is needed because we can't nest mozbrowser/mozapp iframes inside our
+ * already-OOP iframe until bug 1097479 is resolved.
+ */
+function runApp(appInfo) {
+  let shellDomWindow = Services.wm.getMostRecentWindow('navigator:browser');
+  let sysAppFrame = shellDomWindow.document.body.querySelector('#systemapp');
+  let sysAppDoc = sysAppFrame.contentDocument;
+
+  let siblingFrame = sysAppDoc.body.querySelector('#test-container');
+
+  let ifr = activeIframe = sysAppDoc.createElement('iframe');
+  ifr.setAttribute('mozbrowser', 'true');
+  ifr.setAttribute('remote', 'true');
+  ifr.setAttribute('mozapp', appInfo.manifestURL);
+
+  ifr.addEventListener('mozbrowsershowmodalprompt', function(evt) {
+    var message = evt.detail.message;
+    // only send the message as long as we haven't been told to clean up.
+    if (activeIframe) {
+      sendAsyncMessage('appMessage', message);
+    }
+  }, false);
+  ifr.addEventListener('mozbrowsererror', function(evt) {
+    if (activeIframe) {
+      sendAsyncMessage('appError', { message: '' + evt.detail });
+    }
+  });
+
+  ifr.setAttribute('src', appInfo.manifest.launch_path);
+  siblingFrame.parentElement.appendChild(ifr);
+}
+
+function closeApp() {
+  if (activeIframe) {
+    activeIframe.parentElement.removeChild(activeIframe);
+    activeIframe = null;
+  }
+}
+
+addMessageListener('install', installApp);
+addMessageListener('uninstall', uninstallApp);
+addMessageListener('run', runApp);
+addMessageListener('close', closeApp);
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/test_downloads_adopt_download.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=825318
+-->
+<head>
+  <title>Test for Bug 825318 mozDownloadManager.adoptDownload</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="shim_app_as_test.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=825318">Mozilla Bug 825318</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+runAppTest({
+  appFile: 'testapp_downloads_adopt_download.html',
+  appManifest: 'testapp_downloads_adopt_download.manifest',
+  appType: 'certified',
+  extraPrefs: {
+    set: [["dom.mozDownloads.enabled", true]]
+  }
+});
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/downloads/tests/test_downloads_basic.html
+++ b/dom/downloads/tests/test_downloads_basic.html
@@ -67,29 +67,34 @@ function downloadChange(evt) {
   var download = evt.download;
   checkConsistentDownloadAttributes(download);
   is(download.totalBytes, 1024, "Download total size is 1024 bytes");
 
   if (download.state === "succeeded") {
     is(download.currentBytes, 1024, "Download current size is 1024 bytes");
     SimpleTest.finish();
   } else if (download.state === "downloading") {
+    // Note that this case may or may not trigger, depending on whether the
+    // download is initially reported with 0 bytes (we should happen) or with
+    // 1024 bytes (we should not happen).  If we do happen, an additional 8
+    // TEST-PASS events should be logged.
     ok(download.currentBytes > lastKnownCurrentBytes,
        "Download current size is larger than last download change event");
     lastKnownCurrentBytes = download.currentBytes;
   } else {
     ok(false, "Unexpected download state = " + download.state);
   }
 }
 
 function downloadStart(evt) {
   var download = evt.download;
   checkConsistentDownloadAttributes(download);
 
-  is(download.currentBytes, 0, "Download current size is zero");
+  // We used to check that the currentBytes was 0.  This was incorrect.  It
+  // is very common to first hear about the download already at 1024 bytes.
   is(download.state, "downloading", "Download state is downloading");
 
   download.onstatechange = downloadChange;
 }
 
 var steps = [
   // Start by setting the pref to true.
   function() {
--- a/dom/downloads/tests/test_downloads_large.html
+++ b/dom/downloads/tests/test_downloads_large.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=938023
 -->
 <head>
   <title>Test for Bug 938023 Downloads API</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="clear_all_done_helper.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
@@ -41,17 +42,17 @@ function next(args) {
 // Catch all error function.
 function error() {
   ok(false, "API failure");
   SimpleTest.finish();
 }
 
 function getDownloads(downloads) {
   ok(downloads.length == 1, "One downloads after getDownloads");
-  navigator.mozDownloadManager.clearAllDone().then(clearAllDone, error);
+  clearAllDoneHelper(true).then(clearAllDone, error);
 }
 
 function clearAllDone(downloads) {
   ok(downloads.length == 0, "No downloads after clearAllDone");
   SimpleTest.finish();
 }
 
 function downloadChange(evt) {
@@ -71,17 +72,17 @@ var steps = [
     }, next);
   },
 
   // Setup permission and clear current list.
   function() {
     SpecialPowers.pushPermissions([
       {type: "downloads", allow: true, context: document}
     ], function() {
-      navigator.mozDownloadManager.clearAllDone().then(next, error);
+      clearAllDoneHelper(true).then(next, error);
     });
   },
 
   function(downloads) {
     ok(downloads.length == 0, "Start with an empty download list.");
     next();
   },
 
--- a/dom/downloads/tests/test_downloads_pause_remove.html
+++ b/dom/downloads/tests/test_downloads_pause_remove.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=938023
 -->
 <head>
   <title>Test for Bug 938023 Downloads API</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="clear_all_done_helper.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
@@ -78,17 +79,17 @@ var steps = [
     }, next);
   },
 
   // Setup permission and clear current list.
   function() {
     SpecialPowers.pushPermissions([
       {type: "downloads", allow: true, context: document}
     ], function() {
-      navigator.mozDownloadManager.clearAllDone().then(next, error);
+      clearAllDoneHelper(true).then(next, error);
     });
   },
 
   function(downloads) {
     ok(downloads.length == 0, "Start with an empty download list.");
     next();
   },
 
--- a/dom/downloads/tests/test_downloads_pause_resume.html
+++ b/dom/downloads/tests/test_downloads_pause_resume.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=938023
 -->
 <head>
   <title>Test for Bug 938023 Downloads API</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="clear_all_done_helper.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
@@ -49,18 +50,17 @@ function error() {
 
 function checkDownloadList(downloads) {
   ok(downloads.length == 0, "No downloads left");
   SimpleTest.finish();
 }
 
 function checkResumeSucceeded(download) {
   ok(download.state == "succeeded", "Download resumed successfully.");
-  navigator.mozDownloadManager.clearAllDone()
-             .then(checkDownloadList, error);
+  clearAllDoneHelper(true).then(checkDownloadList, error);
 }
 
 function downloadChange(evt) {
   var download = evt.download;
 
   if (download.state == "downloading" && !pausing) {
     pausing = true;
     download.pause();
@@ -80,17 +80,17 @@ var steps = [
     }, next);
   },
 
   // Setup permission and clear current list.
   function() {
     SpecialPowers.pushPermissions([
       {type: "downloads", allow: true, context: document}
     ], function() {
-      navigator.mozDownloadManager.clearAllDone().then(next, error);
+      clearAllDoneHelper(true).then(next, error);
     });
   },
 
   function(downloads) {
     ok(downloads.length == 0, "Start with an empty download list.");
     next();
   },
 
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/testapp_downloads_adopt_download.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="application/javascript" src="common_app.js"></script>
+  <meta charset="utf-8">
+</head>
+<body>
+<div id="blah">initial text</div>
+<pre id="test">
+<!-- because of certified CSP, this code must NOT be inline -->
+<script class="testbody" type="text/javascript;version=1.7" src="testapp_downloads_adopt_download.js"></script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/testapp_downloads_adopt_download.js
@@ -0,0 +1,218 @@
+/**
+ * Test the adoptDownload API.  Specifically, we expect that when we call
+ * adoptDownload with a valid payload that:
+ * - The method will be resolved with a valid, fully populated DOMDownload
+ *   instance, including an id.
+ * - An ondownloadstart notification will be generated and the DOMDownload
+ *   instance it receives will be logically equivalent.
+ *
+ * We also explicitly verify that invalid adoptDownload payloads result in a
+ * rejection and that no download is added.
+ *
+ * This test explicitly does not test that the download is correctly persisted
+ * to the database.  This is done because Downloads.jsm does not provide a means
+ * of safely restarting itself, so Firefox would need to be restarted.  Because
+ * the adoptDownload code is using the Downloads API in a straightforward
+ * manner, it's not considered likely this would regress, and certainly not
+ * considered worth the automated testing overhead of a restart.
+ */
+
+function checkInvalidResult(dict, expectedErr, explanation) {
+  navigator.mozDownloadManager.ondownloadstart = function() {
+    ok(false, "No download should have been added!");
+  };
+  navigator.mozDownloadManager.adoptDownload(dict).then(
+    function() {
+      ok(false, "Invalid adoptDownload did not reject!");
+      runTests();
+    },
+    function(rejectedWith) {
+      is(rejectedWith, expectedErr, explanation + " rejection value");
+      runTests();
+    });
+}
+
+// Pick a date that Date.now() could not possibly return by picking a date in
+// the past.  (We want to make sure the date we provide works.)
+var arbitraryDate = new Date(Date.now() - 60000);
+
+var blobContents = new Uint8Array(256);
+var memBlob = new Blob([blobContents], { type: 'application/octet-stream' });
+var blobStorageName;
+var blobStoragePath = 'blobby.blob';
+
+function checkAdoptedDownload(download, validPayload) {
+  is(download.totalBytes, memBlob.size, 'size');
+  is(download.url, validPayload.url, 'url');
+  // The filesystem path is not practical to check since we can't hard-code it
+  // and the only way to check is to effectively duplicate the logic in
+  // DownloadsAPI.js.  The good news, however, is that the value is
+  // round-tripped from storageName/storagePath to path and back again, and we
+  // also verify the file exists on disk, so we can be reasonably confident this
+  // is correct.  We output it to aid in debugging if things should break,
+  // of course.
+  info('path (not checked): ' + download.path);
+  is(download.storageName, validPayload.storageName, 'storageName');
+  is(download.storagePath, validPayload.storagePath, 'storagePath');
+  is(download.state, 'succeeded', 'state');
+  is(download.contentType, validPayload.contentType, 'contentType');
+  is(download.startTime.valueOf(), arbitraryDate.valueOf(), 'startTime');
+  is(download.sourceAppManifestURL,
+     'http://mochi.test:8888/' +
+       'tests/dom/downloads/tests/testapp_downloads_adopt_download.manifest',
+    'app manifest');
+};
+
+var tests = [
+  function saveBlobToDeviceStorage() {
+    // Only sdcard can handle arbitrary MIME types and is guaranteed to be a
+    // thing.
+    var storage = navigator.getDeviceStorage('sdcard');
+    // We used the non-array helper, so the name we get may be different than
+    // what we asked for.
+    blobStorageName = storage.storageName;
+    ok(!!storage, 'have storage');
+    var req = storage.addNamed(memBlob, blobStoragePath);
+    req.onerror = function() {
+      ok(false, 'problem saving blob to storage: ' + req.error.name);
+    };
+    req.onsuccess = function(evt) {
+      ok(true, 'saved blob: ' + evt.target.result);
+      runTests();
+    };
+  },
+  function addValid() {
+      var validPayload = {
+        // All currently expected consumers are unable to provide a valid URL, and
+        // as a result need to provide an empty string.
+        url: "",
+        storageName: blobStorageName,
+        storagePath: blobStoragePath,
+        contentType: memBlob.type,
+        startTime: arbitraryDate
+      };
+    // Wrap the notification in a check so we can force our logic to be
+    // consistently ordered in the test even if it's not in reality.
+    var notifiedPromise = new Promise(function(resolve, reject) {
+      navigator.mozDownloadManager.ondownloadstart = function(evt) {
+        resolve(evt.download);
+      };
+    });
+
+    // Start the download
+    navigator.mozDownloadManager.adoptDownload(validPayload).then(
+      function(apiDownload) {
+        checkAdoptedDownload(apiDownload, validPayload);
+        ok(!!apiDownload.id, "Need a download id!");
+        notifiedPromise.then(function(notifiedDownload) {
+          checkAdoptedDownload(notifiedDownload, validPayload);
+          is(apiDownload.id, notifiedDownload.id,
+             "Notification should be for the download we adopted");
+          runTests();
+        });
+      },
+      function() {
+        ok(false, "adoptDownload should not have rejected");
+        runTests();
+      });
+  },
+
+  function dictionaryNotProvided() {
+    checkInvalidResult(undefined, "InvalidDownload");
+  },
+  // Missing fields immediately result in rejection with InvalidDownload
+  function missingStorageName() {
+    checkInvalidResult({
+      url: "",
+      // no storageName
+      storagePath: "relpath/filename.txt",
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "InvalidDownload", "missing storage name");
+  },
+  function nullStorageName() {
+    checkInvalidResult({
+      url: "",
+      storageName: null,
+      storagePath: "relpath/filename.txt",
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "InvalidDownload", "null storage name");
+  },
+  function missingStoragePath() {
+    checkInvalidResult({
+      url: "",
+      storageName: blobStorageName,
+      // no storagePath
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "InvalidDownload", "missing storage path");
+  },
+  function nullStoragePath() {
+    checkInvalidResult({
+      url: "",
+      storageName: blobStorageName,
+      storagePath: null,
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "InvalidDownload", "null storage path");
+  },
+  function missingContentType() {
+    checkInvalidResult({
+      url: "",
+      storageName: "sdcard",
+      storagePath: "relpath/filename.txt",
+      // no contentType
+      startTime: arbitraryDate
+    }, "InvalidDownload", "missing content type");
+  },
+  function nullContentType() {
+    checkInvalidResult({
+      url: "",
+      storageName: "sdcard",
+      storagePath: "relpath/filename.txt",
+      contentType: null,
+      startTime: arbitraryDate
+    }, "InvalidDownload", "null content type");
+  },
+  // Incorrect storage names are likewise immediately invalidated
+  function invalidStorageName() {
+    checkInvalidResult({
+      url: "",
+      storageName: "ALMOST CERTAINLY DOES NOT EXIST",
+      storagePath: "relpath/filename.txt",
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "InvalidDownload", "invalid storage name");
+  },
+  // The existence of the file is validated in the parent process
+  function legitStorageInvalidPath() {
+    checkInvalidResult({
+      url: "",
+      storageName: blobStorageName,
+      storagePath: "ALMOST CERTAINLY DOES NOT EXIST",
+      contentType: "text/plain",
+      startTime: arbitraryDate
+    }, "AdoptNoSuchFile", "invalid path");
+  },
+  function allDone() {
+    // Just in case, make sure no other mochitest could mess with us after we've
+    // finished.
+    navigator.mozDownloadManager.ondownloadstart = null;
+    runTests();
+  }
+];
+
+function runTests() {
+  if (!tests.length) {
+    finish();
+    return;
+  }
+
+  var test = tests.shift();
+  if (test.name) {
+    info('starting test: ' + test.name);
+  }
+  test();
+}
+runTests();
new file mode 100644
--- /dev/null
+++ b/dom/downloads/tests/testapp_downloads_adopt_download.manifest
@@ -0,0 +1,10 @@
+{
+  "name": "Downloads certified test fake app",
+  "description": "Test",
+  "launch_path": "http://mochi.test:8888/tests/dom/downloads/tests/testapp_downloads_adopt_download.html",
+  "type": "certified",
+  "permissions": {
+    "device-storage:sdcard":{ "access": "readcreate" },
+    "downloads": {}
+  }
+}
--- a/dom/webidl/Downloads.webidl
+++ b/dom/webidl/Downloads.webidl
@@ -31,20 +31,42 @@ interface DOMDownloadManager : EventTarg
   // download objects.
   Promise<sequence<DOMDownload>> getDownloads();
 
   // Removes one download from the downloads set. Returns a promise resolved
   // with the finalized download.
   [UnsafeInPrerendering]
   Promise<DOMDownload> remove(DOMDownload download);
 
-  // Removes all the completed downloads from the set.  Returns an
-  // array of the completed downloads that were removed.
+  // Removes all completed downloads.  This kicks off an asynchronous process
+  // that will eventually complete, but will not have completed by the time this
+  // method returns.  If you care about the side-effects of this method, know
+  // that each existing download will have its onstatechange method invoked and
+  // will have a new state of "finalized".  (After the download is finalized, no
+  // further events will be generated on it.)
   [UnsafeInPrerendering]
-  Promise<sequence<DOMDownload>> clearAllDone();
+  void clearAllDone();
+
+  // Add completed downloads from applications that must perform the download
+  // process themselves. For example, email.  The method is resolved with a
+  // fully populated DOMDownload instance on success, or rejected in the
+  // event all required options were not provided.
+  //
+  // The adopted download will also be reported via the ondownloadstart event
+  // handler.
+  //
+  // Applications must currently be certified to use this, but it could be
+  // widened at a later time.
+  //
+  // Note that "download" is not actually optional, but WebIDL requires that it
+  // be marked as such because it is not followed by a required argument.  The
+  // promise will be rejected if the dictionary is omitted or the specified
+  // file does not exist on disk.
+  [AvailableIn=CertifiedApps]
+  Promise<DOMDownload> adoptDownload(optional AdoptDownloadDict download);
 
   // Fires when a new download starts.
   attribute EventHandler ondownloadstart;
 };
 
 [JSImplementation="@mozilla.org/downloads/download;1",
  Pref="dom.mozDownloads.enabled",
  CheckPermissions="downloads"]
@@ -54,39 +76,48 @@ interface DOMDownload : EventTarget {
 
   // The number of bytes that we have currently downloaded.
   readonly attribute long long currentBytes;
 
   // The url of the resource.
   readonly attribute DOMString url;
 
   // The full path in local storage where the file will end up once the download
-  // is complete.
+  // is complete. This is equivalent to the concatenation of the 'storagePath'
+  // to the 'mountPoint' of the nsIVolume associated with the 'storageName'
+  // (with delimiter).
   readonly attribute DOMString path;
 
   // The DeviceStorage volume name on which the file is being downloaded.
   readonly attribute DOMString storageName;
 
   // The DeviceStorage path on the volume with 'storageName' of the file being
   // downloaded.
   readonly attribute DOMString storagePath;
 
-  // The state of the download.
+  // The state of the download.  One of: downloading, stopped, succeeded, or
+  // finalized.  A finalized download is a download that has been removed /
+  // cleared and is no longer tracked by the download manager and will not
+  // receive any further onstatechange updates.
   readonly attribute DownloadState state;
 
   // The mime type for this resource.
   readonly attribute DOMString contentType;
 
   // The timestamp this download started.
   readonly attribute Date startTime;
 
   // An opaque identifier for this download. All instances of the same
   // download (eg. in different windows) will have the same id.
   readonly attribute DOMString id;
 
+  // The manifestURL of the application that added this download. Only used for
+  // downloads added via the adoptDownload API call.
+  readonly attribute DOMString? sourceAppManifestURL;
+
   // A DOM error object, that will be not null when a download is stopped
   // because something failed.
   readonly attribute DOMError? error;
 
   // Pauses the download.
   [UnsafeInPrerendering]
   Promise<DOMDownload> pause();
 
@@ -95,8 +126,46 @@ interface DOMDownload : EventTarget {
   [UnsafeInPrerendering]
   Promise<DOMDownload> resume();
 
   // This event is triggered anytime a property of the object changes:
   // - when the transfer progresses, updating currentBytes.
   // - when the state and/or error attributes change.
   attribute EventHandler onstatechange;
 };
+
+// Used to initialize the DOMDownload object for adopted downloads.
+// fields directly maps to the DOMDownload fields.
+dictionary AdoptDownloadDict {
+  // The URL of this resource if there is one available. An empty string if
+  // the download is not accessible via URL. An empty string is chosen over
+  // null so that existinc code does not need to null-check but the value is
+  // still falsey.  (Note: If you do have a usable URL, you should probably not
+  // be using the adoptDownload API and instead be initiating downloads the
+  // normal way.)
+  DOMString url;
+
+  // The storageName of the DeviceStorage instance the file was saved to.
+  // Required but marked as optional so the bindings don't auto-coerce the value
+  // null to "null".
+  DOMString? storageName;
+  // The path of the file within the DeviceStorage instance named by
+  // 'storageName'.  This is used to automatically compute the 'path' of the
+  // download.  Note that when DeviceStorage gives you a path to a file, the
+  // first path segment is the name of the specific device storage and you do
+  // *not* want to include this.  For example, if DeviceStorage tells you the
+  // file has a path of '/sdcard1/actual/path/file.ext', then the storageName
+  // should be 'sdcard1' and the storagePath should be 'actual/path/file.ext'.
+  //
+  // The existence of the file will be validated will be validated with stat()
+  // and the size the file-system tells us will be what we use.
+  //
+  // Required but marked as optional so the bindings don't auto-coerce the value
+  // null to "null".
+  DOMString? storagePath;
+
+  // The mime type for this resource.  Required, but marked as optional because
+  // WebIDL otherwise auto-coerces the value null to "null".
+  DOMString? contentType;
+
+  // The time the download was started. If omitted, the current time is used.
+  Date? startTime;
+};
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -304,16 +304,26 @@ public:
     MOZ_ASSERT(IsValid());
 
     if (AtBottomLayer()) {
       return mLayer->GetEventRegions();
     }
     return EventRegions();
   }
 
+  bool HasTransformAnimation() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    if (AtBottomLayer()) {
+      return mLayer->HasTransformAnimation();
+    }
+    return false;
+  }
+
   RefLayer* AsRefLayer() const
   {
     MOZ_ASSERT(IsValid());
 
     if (AtBottomLayer()) {
       return mLayer->AsRefLayer();
     }
     return nullptr;
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -757,16 +757,27 @@ Layer::GetLocalTransform()
   transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f);
   if (ContainerLayer* c = AsContainerLayer()) {
     transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f);
   }
 
   return transform;
 }
 
+bool
+Layer::HasTransformAnimation() const
+{
+  for (uint32_t i = 0; i < mAnimations.Length(); i++) {
+    if (mAnimations[i].property() == eCSSProperty_transform) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void
 Layer::ApplyPendingUpdatesForThisTransaction()
 {
   if (mPendingTransform && *mPendingTransform != mTransform) {
     MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this));
     mTransform = *mPendingTransform;
     Mutated();
   }
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1279,16 +1279,18 @@ public:
   // Note that all lengths in animation data are either in CSS pixels or app
   // units and must be converted to device pixels by the compositor.
   AnimationArray& GetAnimations() { return mAnimations; }
   InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
 
   uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
   void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
 
+  bool HasTransformAnimation() const;
+
   /**
    * Returns the local transform for this layer: either mTransform or,
    * for shadow layers, GetShadowTransform()
    */
   const gfx::Matrix4x4 GetLocalTransform();
 
   /**
    * Returns the local opacity for this layer: either mOpacity or,
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -86,21 +86,24 @@ GetTransformToAncestorsParentLayer(Layer
       transform.PostScale(metrics.GetPresShellResolution(), metrics.GetPresShellResolution(), 1.f);
     }
   }
   return transform;
 }
 
 void
 ClientTiledPaintedLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
-                                          LayerMetricsWrapper* aOutDisplayPortAncestor)
+                                           LayerMetricsWrapper* aOutDisplayPortAncestor,
+                                           bool* aOutHasTransformAnimation)
 {
   LayerMetricsWrapper scrollAncestor;
   LayerMetricsWrapper displayPortAncestor;
+  bool hasTransformAnimation = false;
   for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) {
+    hasTransformAnimation |= ancestor.HasTransformAnimation();
     const FrameMetrics& metrics = ancestor.Metrics();
     if (!scrollAncestor && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
       scrollAncestor = ancestor;
     }
     if (!metrics.GetDisplayPort().IsEmpty()) {
       displayPortAncestor = ancestor;
       // Any layer that has a displayport must be scrollable, so we can break
       // here.
@@ -108,16 +111,19 @@ ClientTiledPaintedLayer::GetAncestorLaye
     }
   }
   if (aOutScrollAncestor) {
     *aOutScrollAncestor = scrollAncestor;
   }
   if (aOutDisplayPortAncestor) {
     *aOutDisplayPortAncestor = displayPortAncestor;
   }
+  if (aOutHasTransformAnimation) {
+    *aOutHasTransformAnimation = hasTransformAnimation;
+  }
 }
 
 void
 ClientTiledPaintedLayer::BeginPaint()
 {
   mPaintData.mLowPrecisionPaintCount = 0;
   mPaintData.mPaintFinished = false;
   mPaintData.mCompositionBounds.SetEmpty();
@@ -129,47 +135,53 @@ ClientTiledPaintedLayer::BeginPaint()
     // given that it's a pretty rare scenario.
     return;
   }
 
   // Get the metrics of the nearest scrollable layer and the nearest layer
   // with a displayport.
   LayerMetricsWrapper scrollAncestor;
   LayerMetricsWrapper displayPortAncestor;
-  GetAncestorLayers(&scrollAncestor, &displayPortAncestor);
+  bool hasTransformAnimation;
+  GetAncestorLayers(&scrollAncestor, &displayPortAncestor, &hasTransformAnimation);
 
   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 PaintedLayer with no scrollable container ancestor");
 #endif
     return;
   }
 
-  TILING_LOG("TILING %p: Found scrollAncestor %p and displayPortAncestor %p\n", this,
-    scrollAncestor.GetLayer(), displayPortAncestor.GetLayer());
+  TILING_LOG("TILING %p: Found scrollAncestor %p, displayPortAncestor %p, transform %d\n", this,
+    scrollAncestor.GetLayer(), displayPortAncestor.GetLayer(), hasTransformAnimation);
 
   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();
 
   // Compute the critical display port that applies to this layer in the
-  // LayoutDevice space of this layer.
-  ParentLayerRect criticalDisplayPort =
-    (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
-    + displayportMetrics.mCompositionBounds.TopLeft();
-  mPaintData.mCriticalDisplayPort = RoundedOut(
-    ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
+  // LayoutDevice space of this layer, but only if there is no OMT animation
+  // on this layer. If there is an OMT animation then we need to draw the whole
+  // visible region of this layer as determined by layout, because we don't know
+  // what parts of it might move into view in the compositor.
+  if (!hasTransformAnimation) {
+    ParentLayerRect criticalDisplayPort =
+      (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
+      + displayportMetrics.mCompositionBounds.TopLeft();
+    mPaintData.mCriticalDisplayPort = RoundedOut(
+      ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
+  }
   TILING_LOG("TILING %p: Critical displayport %s\n", this, Stringify(mPaintData.mCriticalDisplayPort).c_str());
 
   // Store the resolution from the displayport ancestor layer. Because this is Gecko-side,
   // before any async transforms have occurred, we can use the zoom for this.
   mPaintData.mResolution = displayportMetrics.GetZoom();
   TILING_LOG("TILING %p: Resolution %f\n", this, mPaintData.mResolution.scale);
 
   // Store the applicable composition bounds in this layer's Layer units.
@@ -212,61 +224,58 @@ ClientTiledPaintedLayer::IsScrollingOnCo
                               aParentMetrics.GetScrollOffset().x,
                               COORDINATE_EPSILON) ||
          !FuzzyEqualsAdditive(compositorMetrics.GetScrollOffset().y,
                               aParentMetrics.GetScrollOffset().y,
                               COORDINATE_EPSILON);
 }
 
 bool
-ClientTiledPaintedLayer::UseFastPath()
-{
-  // The fast path doesn't allow rendering at low resolution. It will draw the low-res
-  // area at full resolution and cause OOM.
-  if (gfxPrefs::UseLowPrecisionBuffer()) {
+ClientTiledPaintedLayer::UseProgressiveDraw() {
+  if (!gfxPlatform::GetPlatform()->UseProgressivePaint()) {
+    // pref is disabled, so never do progressive
+    return false;
+  }
+
+  if (ClientManager()->HasShadowTarget()) {
+    // This condition is true when we are in a reftest scenario. We don't want
+    // to draw progressively here because it can cause intermittent reftest
+    // failures because the harness won't wait for all the tiles to be drawn.
     return false;
   }
 
-  LayerMetricsWrapper scrollAncestor;
-  GetAncestorLayers(&scrollAncestor, nullptr);
-  if (!scrollAncestor) {
-    return true;
+  if (mPaintData.mCriticalDisplayPort.IsEmpty()) {
+    // This catches three scenarios:
+    // 1) This layer doesn't have a scrolling ancestor
+    // 2) This layer is subject to OMTA transforms
+    // 3) Low-precision painting is disabled
+    // In all of these cases, we don't want to draw this layer progressively.
+    return false;
   }
-  const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
 
-  bool multipleTransactionsNeeded = gfxPlatform::GetPlatform()->UseProgressivePaint()
-                                 || !parentMetrics.GetCriticalDisplayPort().IsEmpty();
-  bool isFixed = GetIsFixedPosition() || GetParent()->GetIsFixedPosition();
-  bool isScrollable = parentMetrics.IsScrollable();
-
-  return !multipleTransactionsNeeded || isFixed || !isScrollable;
-}
-
-bool
-ClientTiledPaintedLayer::UseProgressiveDraw() {
-  // Don't draw progressively in a reftest scenario (that's what the HasShadowTarget() check is for).
-  if (!gfxPlatform::GetPlatform()->UseProgressivePaint() || ClientManager()->HasShadowTarget()) {
+  if (GetIsFixedPosition() || GetParent()->GetIsFixedPosition()) {
+    // This layer is fixed-position and so even if it does have a scrolling
+    // ancestor it will likely be entirely on-screen all the time, so we
+    // should draw it all at once
     return false;
   }
 
   // XXX We probably want to disable progressive drawing for non active APZ layers in the future
   //     but we should wait for a proper test case before making this change.
-
 #if 0 //!defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ANDROID_APZ)
   LayerMetricsWrapper scrollAncestor;
-  GetAncestorLayers(&scrollAncestor, nullptr);
-  if (!scrollAncestor) {
-    return true;
-  }
+  GetAncestorLayers(&scrollAncestor, nullptr, nullptr);
+  MOZ_ASSERT(scrollAncestor); // because mPaintData.mCriticalDisplayPort is non-empty
   const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
+  if (!IsScrollingOnCompositor(parentMetrics)) {
+    return false;
+  }
+#endif
 
-  return !IsScrollingOnCompositor(parentMetrics);
-#else
   return true;
-#endif
 }
 
 bool
 ClientTiledPaintedLayer::RenderHighPrecision(nsIntRegion& aInvalidRegion,
                                             const nsIntRegion& aVisibleRegion,
                                             LayerManager::DrawPaintedLayerCallback aCallback,
                                             void* aCallbackData)
 {
@@ -436,26 +445,16 @@ ClientTiledPaintedLayer::RenderLayer()
   }
 
   if (!ClientManager()->IsRepeatTransaction()) {
     // Only paint the mask layer on the first transaction.
     if (GetMaskLayer()) {
       ToClientLayer(GetMaskLayer())->RenderLayer();
     }
 
-    // In some cases we can take a fast path and just be done with it.
-    if (UseFastPath()) {
-      TILING_LOG("TILING %p: Taking fast-path\n", this);
-      mValidRegion = neededRegion;
-      mContentClient->mTiledBuffer.PaintThebes(mValidRegion, invalidRegion, callback, data);
-      ClientManager()->Hold(this);
-      mContentClient->UseTiledLayerBuffer(TiledContentClient::TILED_BUFFER);
-      return;
-    }
-
     // For more complex cases we need to calculate a bunch of metrics before we
     // can do the paint.
     BeginPaint();
     if (mPaintData.mPaintFinished) {
       return;
     }
 
     // Make sure that tiles that fall outside of the visible region or outside of the
--- a/gfx/layers/client/ClientTiledPaintedLayer.h
+++ b/gfx/layers/client/ClientTiledPaintedLayer.h
@@ -76,37 +76,32 @@ 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(LayerMetricsWrapper* aOutScrollAncestor,
-                         LayerMetricsWrapper* aOutDisplayPortAncestor);
+                         LayerMetricsWrapper* aOutDisplayPortAncestor,
+                         bool* aOutHasTransformAnimation);
 
 private:
   ClientLayerManager* ClientManager()
   {
     return static_cast<ClientLayerManager*>(mManager);
   }
 
   /**
    * For the initial PaintThebes of a transaction, calculates all the data
    * needed for that paint and any repeated transactions.
    */
   void BeginPaint();
 
   /**
-   * Determine if we can use a fast path to just do a single high-precision,
-   * non-progressive paint.
-   */
-  bool UseFastPath();
-
-  /**
    * Check if the layer is being scrolled by APZ on the compositor.
    */
   bool IsScrollingOnCompositor(const FrameMetrics& aParentMetrics);
 
   /**
    * Check if we should use progressive draw on this layer. We will
    * disable progressive draw based on a preference or if the layer
    * is not being scrolled.
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -1355,17 +1355,17 @@ ClientTiledLayerBuffer::ComputeProgressi
 
   // 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", mPaintedLayer, Stringify(staleRegion).c_str());
 
   LayerMetricsWrapper scrollAncestor;
-  mPaintedLayer->GetAncestorLayers(&scrollAncestor, nullptr);
+  mPaintedLayer->GetAncestorLayers(&scrollAncestor, nullptr, 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 contentMetrics = scrollAncestor.Metrics();
   bool abortPaint = false;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3234,17 +3234,17 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
     gPaintCount++;
 #endif
   }
 
 #ifdef MOZ_DUMP_PAINTING
   if (gfxPrefs::DumpClientLayers()) {
     std::stringstream ss;
     FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, false);
-    printf_stderr("%s", ss.str().c_str());
+    print_stderr(ss);
   }
 #endif
 
   // Update the widget's opaque region information. This sets
   // glass boundaries on Windows. Also set up the window dragging region
   // and plugin clip regions and bounds.
   if ((aFlags & PAINT_WIDGET_LAYERS) &&
       !willFlushRetainedLayers &&