Merge fx-team with local clone
authorVictor Porof <vporof@mozilla.com>
Fri, 19 Jun 2015 19:25:07 -0400
changeset 280599 a598e3c30b1afe3e3c1e8ed3d10a656eaa0873c8
parent 280598 3fff7decfb61c1fcbe9d75e0b15411642fa3ece6 (current diff)
parent 280596 6b80a1818bf1c0a88015471a36d6d0bb27248ebc (diff)
child 280600 aa13a5de46046cf8e58d37b00a01e7d7d8bd5164
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team with local clone
browser/devtools/performance/views/jit-optimizations.js
media/libvpx/apple-clang.patch
media/libvpx/build/make/obj_int_extract.c
media/libvpx/vp8/common/arm/neon/buildintrapredictorsmby_neon.asm
media/libvpx/vp8/common/arm/neon/copymem16x16_neon.asm
media/libvpx/vp8/common/arm/neon/copymem8x4_neon.asm
media/libvpx/vp8/common/arm/neon/copymem8x8_neon.asm
media/libvpx/vp8/common/arm/neon/dc_only_idct_add_neon.asm
media/libvpx/vp8/common/arm/neon/dequant_idct_neon.asm
media/libvpx/vp8/common/arm/neon/dequantizeb_neon.asm
media/libvpx/vp8/common/arm/neon/idct_dequant_0_2x_neon.asm
media/libvpx/vp8/common/arm/neon/idct_dequant_full_2x_neon.asm
media/libvpx/vp8/common/arm/neon/iwalsh_neon.asm
media/libvpx/vp8/common/arm/neon/loopfilter_neon.asm
media/libvpx/vp8/common/arm/neon/loopfiltersimplehorizontaledge_neon.asm
media/libvpx/vp8/common/arm/neon/loopfiltersimpleverticaledge_neon.asm
media/libvpx/vp8/common/arm/neon/mbloopfilter_neon.asm
media/libvpx/vp8/common/arm/neon/sad16_neon.asm
media/libvpx/vp8/common/arm/neon/sad8_neon.asm
media/libvpx/vp8/common/arm/neon/save_reg_neon.asm
media/libvpx/vp8/common/arm/neon/shortidct4x4llm_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict16x16_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict4x4_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict8x4_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict8x8_neon.asm
media/libvpx/vp8/common/arm/neon/variance_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance16x16_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance16x16s_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance8x8_neon.asm
media/libvpx/vp8/common/arm/reconintra_arm.c
media/libvpx/vp8/common/pragmas.h
media/libvpx/vp8/common/x86/loopfilter_block_sse2.asm
media/libvpx/vp8/common/x86/postproc_x86.c
media/libvpx/vp8/encoder/arm/armv5te/boolhuff_armv5te.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_armv5.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_mbrow_armv5.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_partitions_armv5.asm
media/libvpx/vp8/encoder/arm/armv6/vp8_fast_quantize_b_armv6.asm
media/libvpx/vp8/encoder/arm/armv6/vp8_subtract_armv6.asm
media/libvpx/vp8/encoder/arm/boolhuff_arm.c
media/libvpx/vp8/encoder/arm/neon/fastquantizeb_neon.asm
media/libvpx/vp8/encoder/arm/neon/picklpf_arm.c
media/libvpx/vp8/encoder/arm/neon/shortfdct_neon.asm
media/libvpx/vp8/encoder/arm/neon/subtract_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_memcpy_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_mse16x16_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_shortwalsh4x4_neon.asm
media/libvpx/vp8/encoder/arm/quantize_arm.c
media/libvpx/vp8/encoder/psnr.c
media/libvpx/vp8/encoder/psnr.h
media/libvpx/vp8/encoder/vp8_asm_enc_offsets.c
media/libvpx/vp8/encoder/x86/quantize_sse4.asm
media/libvpx/vp8/encoder/x86/quantize_ssse3.asm
media/libvpx/vp9/common/arm/neon/vp9_avg_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_convolve8_avg_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_convolve8_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_copy_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_dc_only_idct_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct16x16_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct16x16_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct32x32_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct32x32_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct4x4_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct4x4_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct8x8_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct8x8_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_iht4x4_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_iht8x8_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_loopfilter_16_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_loopfilter_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_reconintra_neon.asm
media/libvpx/vp9/common/generic/vp9_systemdependent.c
media/libvpx/vp9/common/vp9_onyx.h
media/libvpx/vp9/common/vp9_pragmas.h
media/libvpx/vp9/common/x86/vp9_postproc_mmx.asm
media/libvpx/vp9/common/x86/vp9_postproc_x86.h
media/libvpx/vp9/decoder/vp9_onyxd.h
media/libvpx/vp9/decoder/vp9_onyxd_if.c
media/libvpx/vp9/decoder/vp9_onyxd_int.h
media/libvpx/vp9/decoder/vp9_thread.c
media/libvpx/vp9/decoder/vp9_thread.h
media/libvpx/vp9/encoder/vp9_onyx_if.c
media/libvpx/vp9/encoder/vp9_onyx_int.h
media/libvpx/vp9/encoder/vp9_psnr.c
media/libvpx/vp9/encoder/vp9_psnr.h
media/libvpx/vp9/encoder/vp9_vaq.c
media/libvpx/vp9/encoder/vp9_vaq.h
media/libvpx/vp9/encoder/x86/vp9_mcomp_x86.h
media/libvpx/vp9/encoder/x86/vp9_quantize_ssse3.asm
media/libvpx/vp9/encoder/x86/vp9_sad_mmx.asm
media/libvpx/vp9/encoder/x86/vp9_subpel_variance_impl_sse2.asm
media/libvpx/vp9/encoder/x86/vp9_variance_impl_mmx.asm
media/libvpx/vp9/encoder/x86/vp9_variance_impl_sse2.asm
media/libvpx/vp9/encoder/x86/vp9_variance_mmx.c
media/libvpx/vpx_ports/asm_offsets.h
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copy_y_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copyframe_func_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copysrcframe_func_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_extendframeborders_neon.asm
media/libvpx/vpx_scale/arm/neon/yv12extend_arm.c
media/libvpx/vpx_scale/vpx_scale_asm_offsets.c
xpcom/base/StackWalk.h
xpcom/base/nsStackWalk.cpp
xpcom/base/nsStackWalk.h
xpcom/base/nsStackWalkPrivate.h
xpcom/ds/TimeStamp.cpp
xpcom/ds/TimeStamp.h
xpcom/ds/TimeStamp_darwin.cpp
xpcom/ds/TimeStamp_posix.cpp
xpcom/ds/TimeStamp_windows.cpp
xpcom/ds/TimeStamp_windows.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1167064: Switch to bluetooth APIv2.
+Bug 1151175 - Update libvpx to 1.4.0.
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="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="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="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="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="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="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "55bac3c151bff4f0ce0e8715962c4676fefb0887", 
+        "git_revision": "d8bac9577a537b5e6bafc3f7ed088af5ea60c99e", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "583e49d4740a5640f551d2ff07b271724e3b7e98", 
+    "revision": "b83fdbafb4cb4ed2bd8de1148d64ad2ef46d3d46", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="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="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="55bac3c151bff4f0ce0e8715962c4676fefb0887"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d8bac9577a537b5e6bafc3f7ed088af5ea60c99e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <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="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1283,26 +1283,30 @@ nsContextMenu.prototype = {
     timerCallback.prototype = {
       notify: function sLA_timer_notify(aTimer) {
         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
         return;
       }
     }
 
     // setting up a new channel for 'right click - save link as ...'
-    // which should be treated the same way as a toplevel load, hence
-    // we use TYPE_DOCUMENT, see also bug: 1136055
+    // ideally we should use:
+    // * doc            - as the loadingNode, and/or
+    // * this.principal - as the loadingPrincipal
+    // for now lets use systemPrincipal to bypass mixedContentBlocker
+    // checks after redirects, see bug: 1136055
     var ioService = Cc["@mozilla.org/network/io-service;1"].
                     getService(Ci.nsIIOService);
+    var principal = Services.scriptSecurityManager.getSystemPrincipal();
     var channel = ioService.newChannelFromURI2(makeURI(linkURL),
                                                null, // aLoadingNode
-                                               this.principal, // aLoadingPrincipal
+                                               principal, // aLoadingPrincipal
                                                null, // aTriggeringPrincipal
                                                Ci.nsILoadInfo.SEC_NORMAL,
-                                               Ci.nsIContentPolicy.TYPE_DOCUMENT);
+                                               Ci.nsIContentPolicy.TYPE_OTHER);
     if (linkDownload)
       channel.contentDispositionFilename = linkDownload;
     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
       let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
       channel.setPrivate(docIsPrivate);
     }
     channel.notificationCallbacks = new callbacks();
 
--- a/browser/base/content/test/referrer/browser.ini
+++ b/browser/base/content/test/referrer/browser.ini
@@ -1,11 +1,13 @@
 [DEFAULT]
 support-files =
   file_referrer_policyserver.sjs
   file_referrer_testserver.sjs
   head.js
 
 [browser_referrer_middle_click.js]
 [browser_referrer_open_link_in_private.js]
+skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_open_link_in_tab.js]
 [browser_referrer_open_link_in_window.js]
+skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_simple_click.js]
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -617,16 +617,31 @@ nsBrowserContentHandler.prototype = {
     // flag is also present and the command line is valid.
     var osintFlagIdx = cmdLine.findFlag("osint", false);
     var urlFlagIdx = cmdLine.findFlag("url", false);
     if (urlFlagIdx > -1 && (osintFlagIdx > -1 ||
         cmdLine.state == nsICommandLine.STATE_REMOTE_EXPLICIT)) {
       var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
       if (cmdLine.length != urlFlagIdx + 2 || /firefoxurl:/.test(urlParam))
         throw NS_ERROR_ABORT;
+      var isDefault = false;
+      try {
+        var url = Services.urlFormatter.formatURLPref("app.support.baseURL") +
+                  "win10-default-browser";
+        if (urlParam == url) {
+          var shellSvc = Components.classes["@mozilla.org/browser/shell-service;1"]
+                                   .getService(Components.interfaces.nsIShellService);
+          isDefault = shellSvc.isDefaultBrowser(false, false);
+        }
+      } catch (ex) {}
+      if (isDefault) {
+        // Firefox is already the default HTTP handler.
+        // We don't have to show the instruction page.
+        throw NS_ERROR_ABORT;
+      }
       cmdLine.handleFlag("osint", false)
     }
   },
 };
 var gBrowserContentHandler = new nsBrowserContentHandler();
 
 function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate)
 {
--- a/browser/components/shell/nsWindowsShellService.cpp
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -22,16 +22,17 @@
 #include "nsBrowserCompsCID.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIWindowsRegKey.h"
 #include "nsUnicharUtils.h"
 #include "nsIWinTaskbar.h"
 #include "nsISupportsPrimitives.h"
+#include "nsIURLFormatter.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/WindowsVersion.h"
 
 #include "windows.h"
 #include "shellapi.h"
 
 #ifdef _WIN32_WINNT
@@ -683,16 +684,46 @@ nsWindowsShellService::LaunchModernSetti
            L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
     pActivator->Release();
     return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 nsresult
+nsWindowsShellService::InvokeHTTPOpenAsVerb()
+{
+  nsCOMPtr<nsIURLFormatter> formatter(
+    do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
+  if (!formatter) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsString urlStr;
+  nsresult rv = formatter->FormatURLPref(
+    NS_LITERAL_STRING("app.support.baseURL"), urlStr);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!StringBeginsWith(urlStr, NS_LITERAL_STRING("https://"))) {
+    return NS_ERROR_FAILURE;
+  }
+  urlStr.AppendLiteral("win10-default-browser");
+
+  SHELLEXECUTEINFOW seinfo = { sizeof(SHELLEXECUTEINFOW) };
+  seinfo.lpVerb = L"openas";
+  seinfo.lpFile = urlStr.get();
+  seinfo.nShow = SW_SHOWNORMAL;
+  if (!ShellExecuteExW(&seinfo)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
 nsWindowsShellService::LaunchHTTPHandlerPane()
 {
   OPENASINFO info;
   info.pcszFile = L"http";
   info.pcszClass = nullptr;
   info.oaifInFlags = OAIF_FORCE_REGISTRATION | 
                      OAIF_URL_PROTOCOL |
                      OAIF_REGISTER_EXT;
@@ -710,36 +741,48 @@ nsWindowsShellService::SetDefaultBrowser
     appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
   } else {
     appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
   }
 
   nsresult rv = LaunchHelper(appHelperPath);
   if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
     if (aClaimAllTypes) {
-      rv = LaunchControlPanelDefaultPrograms();
+      if (IsWin10OrLater()) {
+        rv = LaunchModernSettingsDialogDefaultApps();
+      } else {
+        rv = LaunchControlPanelDefaultPrograms();
+      }
       // The above call should never really fail, but just in case
       // fall back to showing the HTTP association screen only.
       if (NS_FAILED(rv)) {
-        rv = LaunchHTTPHandlerPane();
+        if (IsWin10OrLater()) {
+          rv = InvokeHTTPOpenAsVerb();
+        } else {
+          rv = LaunchHTTPHandlerPane();
+        }
       }
     } else {
       // Windows 10 blocks attempts to load the HTTP Handler
       // association dialog, so the modern Settings dialog
       // is opened with the Default Apps view loaded.
       if (IsWin10OrLater()) {
-        rv = LaunchModernSettingsDialogDefaultApps();
+        rv = InvokeHTTPOpenAsVerb();
       } else {
         rv = LaunchHTTPHandlerPane();
       }
 
       // The above call should never really fail, but just in case
       // fall back to showing control panel for all defaults
       if (NS_FAILED(rv)) {
-        rv = LaunchControlPanelDefaultPrograms();
+        if (IsWin10OrLater()) {
+          rv = LaunchModernSettingsDialogDefaultApps();
+        } else {
+          rv = LaunchControlPanelDefaultPrograms();
+        }
       }
     }
   }
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs) {
     (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
   }
--- a/browser/components/shell/nsWindowsShellService.h
+++ b/browser/components/shell/nsWindowsShellService.h
@@ -24,15 +24,16 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHELLSERVICE
   NS_DECL_NSIWINDOWSSHELLSERVICE
 
 protected:
   bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
   nsresult LaunchControlPanelDefaultPrograms();
   nsresult LaunchModernSettingsDialogDefaultApps();
+  nsresult InvokeHTTPOpenAsVerb();
   nsresult LaunchHTTPHandlerPane();
 
 private:
   bool      mCheckedThisSession;
 };
 
 #endif // nswindowsshellservice_h____
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -106,18 +106,20 @@ browser.jar:
     content/browser/devtools/performance/views/toolbar.js              (performance/views/toolbar.js)
     content/browser/devtools/performance/views/details.js              (performance/views/details.js)
     content/browser/devtools/performance/views/details-subview.js      (performance/views/details-abstract-subview.js)
     content/browser/devtools/performance/views/details-waterfall.js    (performance/views/details-waterfall.js)
     content/browser/devtools/performance/views/details-js-call-tree.js      (performance/views/details-js-call-tree.js)
     content/browser/devtools/performance/views/details-js-flamegraph.js     (performance/views/details-js-flamegraph.js)
     content/browser/devtools/performance/views/details-memory-call-tree.js  (performance/views/details-memory-call-tree.js)
     content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
+    content/browser/devtools/performance/views/details-optimizations.js     (performance/views/details-optimizations.js)
+    content/browser/devtools/performance/views/optimizations-list.js        (performance/views/optimizations-list.js)
+    content/browser/devtools/performance/views/frames-list.js               (performance/views/frames-list.js)
     content/browser/devtools/performance/views/recordings.js           (performance/views/recordings.js)
-    content/browser/devtools/performance/views/jit-optimizations.js    (performance/views/jit-optimizations.js)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
 *   content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
     content/browser/devtools/framework/toolbox.xul                     (framework/toolbox.xul)
     content/browser/devtools/framework/options-panel.css               (framework/options-panel.css)
--- a/browser/devtools/performance/modules/logic/recording-model.js
+++ b/browser/devtools/performance/modules/logic/recording-model.js
@@ -180,18 +180,17 @@ RecordingModel.prototype = {
       return Date.now() - this._localStartTime;
     } else {
       return this._duration;
     }
   },
 
   /**
    * Returns configuration object of specifying whether the recording
-   * was started withTicks, withMemory and withAllocations and other
-   * recording options.
+   * was started withTicks, withMemory and withAllocations, and other configurations.
    * @return object
    */
   getConfiguration: function () {
     return this._configuration;
   },
 
   /**
    * Gets the accumulated markers in the current recording.
--- a/browser/devtools/performance/modules/logic/tree-model.js
+++ b/browser/devtools/performance/modules/logic/tree-model.js
@@ -454,27 +454,28 @@ FrameNode.prototype = {
    */
   _computeInfo: function() {
     let categoryData = CATEGORY_MAPPINGS[this.category] || {};
     let parsedData = FrameUtils.parseLocation(this.location, this.line, this.column);
     parsedData.nodeType = "Frame";
     parsedData.categoryData = categoryData;
     parsedData.isContent = this.isContent;
     parsedData.isMetaCategory = this.isMetaCategory;
+    parsedData.hasOptimizations = this.hasOptimizations();
 
     return this._data = parsedData;
   },
 
   /**
    * Returns whether or not the frame node has an JITOptimizations model.
    *
    * @return {Boolean}
    */
   hasOptimizations: function () {
-    return !!this._optimizations;
+    return !this.isMetaCategory && !!this._optimizations;
   },
 
   /**
    * Returns the underlying JITOptimizations model representing
    * the optimization attempts occuring in this frame.
    *
    * @return {JITOptimizations|null}
    */
--- a/browser/devtools/performance/modules/widgets/tree-view.js
+++ b/browser/devtools/performance/modules/widgets/tree-view.js
@@ -11,16 +11,18 @@
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { L10N } = require("devtools/performance/global");
 const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
 
 const MILLISECOND_UNITS = L10N.getStr("table.ms");
 const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
 const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
+const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr("table.view-optimizations.tooltiptext");
+
 const CALL_TREE_INDENTATION = 16; // px
 
 const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
   let dataA = frameA.getDisplayedData();
   let dataB = frameB.getDisplayedData();
   if (this.inverted) {
     // Invert trees, sort by selfPercentage, and then totalPercentage
     if (dataA.selfPercentage === dataB.selfPercentage) {
@@ -80,20 +82,24 @@ const sum = vals => vals.reduce((a, b) =
  * @param number autoExpandDepth [optional]
  *        The depth to which the tree should automatically expand. Defualts to
  *        the caller's `autoExpandDepth` if a caller exists, otherwise defaults
  *        to DEFAULT_AUTO_EXPAND_DEPTH.
  * @param object visibleCells
  *        An object specifying which cells are visible in the tree. Defaults to
  *        the caller's `visibleCells` if a caller exists, otherwise defaults
  *        to DEFAULT_VISIBLE_CELLS.
+ * @param boolean showOptimizationHint [optional]
+ *        Whether or not to show an icon indicating if the frame has optimization
+ *        data.
  */
 function CallView({
   caller, frame, level, hidden, inverted,
-  sortingPredicate, autoExpandDepth, visibleCells
+  sortingPredicate, autoExpandDepth, visibleCells,
+  showOptimizationHint
 }) {
   AbstractTreeItem.call(this, {
     parent: caller,
     level: level|0 - (hidden ? 1 : 0)
   });
 
   this.sortingPredicate = sortingPredicate != null
     ? sortingPredicate
@@ -109,16 +115,17 @@ function CallView({
     ? visibleCells
     : caller ? caller.visibleCells
              : Object.create(DEFAULT_VISIBLE_CELLS);
 
   this.caller = caller;
   this.frame = frame;
   this.hidden = hidden;
   this.inverted = inverted;
+  this.showOptimizationHint = showOptimizationHint;
 
   this._onUrlClick = this._onUrlClick.bind(this);
 };
 
 CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
   /**
    * Creates the view for this tree node.
    * @param nsIDOMNode document
@@ -251,16 +258,26 @@ CallView.prototype = Heritage.extend(Abs
   },
   _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
     let cell = doc.createElement("hbox");
     cell.className = "call-tree-cell";
     cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
     cell.setAttribute("type", "function");
     cell.appendChild(arrowNode);
 
+    // Render optimization link to JIT view if the frame
+    // has optimizations
+    if (this.root.showOptimizationHint && frameInfo.hasOptimizations && !frameInfo.isMetaCategory) {
+      let icon = doc.createElement("description");
+      icon.setAttribute("tooltiptext", VIEW_OPTIMIZATIONS_TOOLTIP);
+      icon.setAttribute("type", "linkable");
+      icon.className = "opt-icon";
+      cell.appendChild(icon);
+    }
+
     // Don't render a name label node if there's no function name. A different
     // location label node will be rendered instead.
     if (frameName) {
       let nameNode = doc.createElement("description");
       nameNode.className = "plain call-tree-name";
       nameNode.setAttribute("flex", "1");
       nameNode.setAttribute("crop", "end");
       nameNode.setAttribute("value", frameName);
@@ -275,16 +292,17 @@ CallView.prototype = Heritage.extend(Abs
     // Don't render an expando-arrow for leaf nodes.
     let hasDescendants = Object.keys(this.frame.calls).length > 0;
     if (!hasDescendants) {
       arrowNode.setAttribute("invisible", "");
     }
 
     return cell;
   },
+
   _appendFunctionDetailsCells: function(doc, cell, frameInfo) {
     if (frameInfo.fileName) {
       let urlNode = doc.createElement("description");
       urlNode.className = "plain call-tree-url";
       urlNode.setAttribute("flex", "1");
       urlNode.setAttribute("crop", "end");
       urlNode.setAttribute("value", frameInfo.fileName);
       urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -20,19 +20,21 @@
   <script type="application/javascript" src="performance/views/overview.js"/>
   <script type="application/javascript" src="performance/views/toolbar.js"/>
   <script type="application/javascript" src="performance/views/details-subview.js"/>
   <script type="application/javascript" src="performance/views/details-waterfall.js"/>
   <script type="application/javascript" src="performance/views/details-js-call-tree.js"/>
   <script type="application/javascript" src="performance/views/details-js-flamegraph.js"/>
   <script type="application/javascript" src="performance/views/details-memory-call-tree.js"/>
   <script type="application/javascript" src="performance/views/details-memory-flamegraph.js"/>
+  <script type="application/javascript" src="performance/views/details-optimizations.js"/>
   <script type="application/javascript" src="performance/views/details.js"/>
   <script type="application/javascript" src="performance/views/recordings.js"/>
-  <script type="application/javascript" src="performance/views/jit-optimizations.js"/>
+  <script type="application/javascript" src="performance/views/optimizations-list.js"/>
+  <script type="application/javascript" src="performance/views/frames-list.js"/>
 
   <popupset id="performance-options-popupset">
     <menupopup id="performance-filter-menupopup"/>
     <menupopup id="performance-options-menupopup">
       <menuitem id="option-show-platform-data"
                 type="checkbox"
                 data-pref="show-platform-data"
                 label="&profilerUI.showPlatformData;"
@@ -135,16 +137,21 @@
                          label="Allocations Tree"
                          hidden="true"
                          data-view="memory-calltree" />
           <toolbarbutton id="select-memory-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="Allocations Chart"
                          hidden="true"
                          data-view="memory-flamegraph" />
+          <toolbarbutton id="select-optimizations-view"
+                         class="devtools-toolbarbutton devtools-button"
+                         label="Optimizations"
+                         hidden="true"
+                         data-view="optimizations" />
         </hbox>
         <spacer flex="1"></spacer>
         <hbox id="performance-toolbar-controls-options"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="performance-options-button"
                          class="devtools-toolbarbutton devtools-option-toolbarbutton"
                          popup="performance-options-menupopup"
                          tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
@@ -288,30 +295,16 @@
                     <label class="plain call-tree-header"
                            type="function"
                            crop="end"
                            value="&profilerUI.table.function;"
                            tooltiptext="&profilerUI.table.function.tooltip;"/>
                   </hbox>
                   <vbox class="call-tree-cells-container" flex="1"/>
                 </vbox>
-
-                <splitter id="js-call-tree-splitter" class="devtools-side-splitter"/>
-
-                <vbox id="jit-optimizations-view" hidden="true">
-                  <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
-                    <hbox id="jit-optimizations-header">
-                      <span class="jit-optimizations-title">&profilerUI.JITOptimizationsTitle;</span>
-                      <span class="header-function-name" />
-                      <span class="header-file opt-url debugger-link" />
-                      <span class="header-line opt-line" />
-                    </hbox>
-                  </toolbar>
-                  <vbox id="jit-optimizations-raw-view"></vbox>
-                </vbox>
               </hbox>
 
               <!-- JS FlameChart -->
               <hbox id="js-flamegraph-view" flex="1">
               </hbox>
 
               <!-- Memory Tree -->
               <vbox id="memory-calltree-view" flex="1">
@@ -333,15 +326,55 @@
                 </hbox>
                 <vbox class="call-tree-cells-container" flex="1"/>
               </vbox>
 
               <!-- Memory FlameChart -->
               <hbox id="memory-flamegraph-view" flex="1">
               </hbox>
 
+              <!-- JIT View -->
+              <hbox id="optimizations-view" flex="1">
+                <hbox id="graph-placeholder" flex="1">
+                </hbox>
+                <splitter id="optimizations-splitter" class="devtools-side-splitter"/>
+                <tabbox id="optimizations-tabs"
+                        class="devtools-sidebar-tabs"
+                        handleCtrlTab="false">
+                  <tabs>
+                    <tab id="optimizations-optimizations-tab"
+                         label="Optimizations" />
+                    <tab id="optimizations-frames-tab"
+                         label="Frames" />
+                  </tabs>
+                  <tabpanels flex="1">
+
+                    <!-- Optimizations Panel -->
+                    <tabpanel id="optimizations-tabpanel"
+                              class="tabpanel-content">
+                      <vbox id="jit-optimizations-view">
+                        <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
+                          <hbox id="jit-optimizations-header">
+                            <span class="jit-optimizations-title">&profilerUI.JITOptimizationsTitle;</span>
+                            <span class="header-function-name" />
+                            <span class="header-file opt-url debugger-link" />
+                            <span class="header-line opt-line" />
+                          </hbox>
+                        </toolbar>
+                        <vbox id="jit-optimizations-raw-view"></vbox>
+                      </vbox>
+                    </tabpanel>
+
+                    <!-- Frames Panel -->
+                    <tabpanel id="frames-tabpanel"
+                              class="tabpanel-content">
+                    </tabpanel>
+                  </tabpanels>
+                </tabbox>
+              </hbox>
+              <!-- /JIT View -->
             </deck>
           </deck>
         </vbox>
       </deck>
     </vbox>
   </hbox>
 </window>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -61,18 +61,18 @@ support-files =
 [browser_perf-front-profiler-02.js]
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
 [browser_perf-front-01.js]
 [browser_perf-front-02.js]
 [browser_perf-highlighted.js]
-[browser_perf-jit-view-01.js]
-[browser_perf-jit-view-02.js]
+#[browser_perf-jit-view-01.js] bug 1176056
+#[browser_perf-jit-view-02.js] bug 1176056
 [browser_perf-loading-01.js]
 [browser_perf-loading-02.js]
 [browser_perf-marker-details-01.js]
 skip-if = os == 'linux' # Bug 1172120
 [browser_perf-options-01.js]
 [browser_perf-options-02.js]
 [browser_perf-options-03.js]
 [browser_perf-options-invert-call-tree-01.js]
@@ -132,15 +132,16 @@ skip-if = os == 'linux' # Bug 1172120
 [browser_profiler_tree-view-03.js]
 [browser_profiler_tree-view-04.js]
 [browser_profiler_tree-view-05.js]
 [browser_profiler_tree-view-06.js]
 [browser_profiler_tree-view-07.js]
 [browser_profiler_tree-view-08.js]
 [browser_profiler_tree-view-09.js]
 [browser_profiler_tree-view-10.js]
+[browser_profiler_tree-view-11.js]
 [browser_timeline-filters-01.js]
 [browser_timeline-filters-02.js]
 [browser_timeline-waterfall-background.js]
 [browser_timeline-waterfall-generic.js]
 [browser_timeline-waterfall-rerender.js]
 [browser_timeline-waterfall-sidebar.js]
 skip-if = os == 'linux' # Bug 1161817
--- a/browser/devtools/performance/test/browser_perf-options-enable-optimizations.js
+++ b/browser/devtools/performance/test/browser_perf-options-enable-optimizations.js
@@ -1,44 +1,45 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that `enable-jit-optimizations` sets the recording to subsequently
- * display optimizations info.
+ * enable the Optimizations View.
  */
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, PerformanceController, $, DetailsView, JsCallTreeView } = panel.panelWin;
+  let { EVENTS, PerformanceController, $, DetailsView, WaterfallView, OptimizationsView } = panel.panelWin;
   Services.prefs.setBoolPref(JIT_PREF, true);
 
 
   yield startRecording(panel);
-  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+  let rendered = once(OptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
   yield stopRecording(panel);
 
-  yield DetailsView.selectView("js-calltree");
-  ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
+  yield DetailsView.selectView("optimizations");
+  ok(DetailsView.isViewSelected(OptimizationsView), "The Optimizations View is now selected.");
   yield rendered;
 
   let recording = PerformanceController.getCurrentRecording();
   is(recording.getConfiguration().withJITOptimizations, true, "recording model has withJITOptimizations as true");
 
   // Set back to false, should not affect display of first recording
   info("Disabling enable-jit-optimizations");
   Services.prefs.setBoolPref(JIT_PREF, false);
-  is($("#jit-optimizations-view").hidden, false, "JIT Optimizations panel is displayed when feature enabled.");
+  is($("#select-optimizations-view").hidden, false,
+    "JIT Optimizations selector still available since the recording has it enabled.");
 
   yield startRecording(panel);
-  rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+  rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   yield stopRecording(panel);
 
-  yield DetailsView.selectView("js-calltree");
-  ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
+  ok(DetailsView.isViewSelected(WaterfallView), "The waterfall view is now selected.");
   yield rendered;
 
   recording = PerformanceController.getCurrentRecording();
   is(recording.getConfiguration().withJITOptimizations, false, "recording model has withJITOptimizations as false");
-  is($("#jit-optimizations-view").hidden, true, "JIT Optimizations panel is hidden when feature disabled");
+  is($("#select-optimizations-view").hidden, true,
+    "JIT Optimizations selector is hidden if recording did not enable optimizations.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-recording-model-02.js
+++ b/browser/devtools/performance/test/browser_perf-recording-model-02.js
@@ -8,19 +8,19 @@
 let BUFFER_SIZE = 20000;
 
 function* spawnTest() {
   let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
   let config = { bufferSize: BUFFER_SIZE };
 
   let model = yield front.startRecording(config);
   let [_, stats] = yield onceSpread(front, "profiler-status");
-  is(stats.totalSize, BUFFER_SIZE, `profiler-status event has correct totalSize: ${stats.totalSize}`);
-  ok(stats.position < BUFFER_SIZE, `profiler-status event has correct position: ${stats.position}`);
-  is(stats.generation, 0, `profiler-status event has correct generation: ${stats.generation}`);
+  is(stats.totalSize, BUFFER_SIZE, `profiler-status event has totalSize: ${stats.totalSize}`);
+  ok(stats.position < BUFFER_SIZE, `profiler-status event has position: ${stats.position}`);
+  ok(stats.generation >= 0, `profiler-status event has generation: ${stats.generation}`);
   ok(stats.isActive, `profiler-status event is isActive`);
   is(typeof stats.currentTime, "number", `profiler-status event has currentTime`);
 
   // Halt once more for a buffer status to ensure we're beyond 0
   yield once(front, "profiler-status");
 
   let lastBufferStatus = 0;
   let checkCount = 0;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-11.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that if a recording set `withJITOptimizations` on, then an
+ * icon is next to the frame with optimizations
+ */
+
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
+const { CATEGORY_MASK } = devtools.require("devtools/performance/global");
+
+function* spawnTest() {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
+  let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
+
+  let profilerData = { threads: [gThread] };
+
+  Services.prefs.setBoolPref(JIT_PREF, true);
+  Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
+  Services.prefs.setBoolPref(INVERT_PREF, false);
+
+  // Make two recordings, so we have one to switch to later, as the
+  // second one will have fake sample data
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  yield DetailsView.selectView("js-calltree");
+
+  yield injectAndRenderProfilerData();
+
+  let rows = $$("#js-calltree-view .call-tree-item");
+  is(rows.length, 4, "4 call tree rows exist");
+  for (let row of rows) {
+    let name = $(".call-tree-name", row).value;
+    switch (name) {
+      case "A":
+        ok($(".opt-icon", row), "found an opt icon on a leaf node with opt data");
+        break;
+      case "C":
+        ok(!$(".opt-icon", row), "frames without opt data do not have an icon");
+        break;
+      case "Gecko":
+        ok(!$(".opt-icon", row), "meta category frames with opt data do not have an icon");
+        break;
+      case "(root)":
+        ok(!$(".opt-icon", row), "root frame certainly does not have opt data");
+        break;
+      default:
+        ok(false, `Unidentified frame: ${name}`);
+        break;
+    }
+  }
+
+  yield teardown(panel);
+  finish();
+
+  function *injectAndRenderProfilerData() {
+    // Get current recording and inject our mock data
+    info("Injecting mock profile data");
+    let recording = PerformanceController.getCurrentRecording();
+    recording._profile = profilerData;
+
+    // Force a rerender
+    let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+    JsCallTreeView.render(OverviewView.getTimeInterval());
+    yield rendered;
+  }
+}
+
+let gUniqueStacks = new RecordingUtils.UniqueStacks();
+
+function uniqStr(s) {
+  return gUniqueStacks.getOrAddStringIndex(s);
+}
+
+// Since deflateThread doesn't handle deflating optimization info, use
+// placeholder names A_O1, B_O2, and B_O3, which will be used to manually
+// splice deduped opts into the profile.
+let gThread = RecordingUtils.deflateThread({
+  samples: [{
+    time: 0,
+    frames: [
+      { location: "(root)" }
+    ]
+  }, {
+    time: 5,
+    frames: [
+      { location: "(root)" },
+      { location: "A (http://foo:1)" },
+    ]
+  }, {
+    time: 5 + 1,
+    frames: [
+      { location: "(root)" },
+      { location: "C (http://foo/bar/baz:56)" }
+    ]
+  }, {
+    time: 5 + 1 + 2,
+    frames: [
+      { location: "(root)" },
+      { category: CATEGORY_MASK("other"),  location: "PlatformCode" }
+    ]
+  }],
+  markers: []
+}, gUniqueStacks);
+
+// 3 RawOptimizationSites
+let gRawSite1 = {
+  _testFrameInfo: { name: "A", line: "12", file: "@baz" },
+  line: 12,
+  column: 2,
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+        keyedBy: uniqStr("constructor"),
+        name: uniqStr("Foo"),
+        location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+        keyedBy: uniqStr("primitive"),
+        location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+gThread.frameTable.data.forEach((frame) => {
+  const LOCATION_SLOT = gThread.frameTable.schema.location;
+  const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
+
+  let l = gThread.stringTable[frame[LOCATION_SLOT]];
+  switch (l) {
+  case "A (http://foo:1)":
+    frame[LOCATION_SLOT] = uniqStr("A (http://foo:1)");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite1;
+    break;
+  case "PlatformCode":
+    frame[LOCATION_SLOT] = uniqStr("PlatformCode");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite1;
+    break;
+  }
+});
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -6,60 +6,57 @@
 /**
  * CallTree view containing profiler call tree, controlled by DetailsView.
  */
 let JsCallTreeView = Heritage.extend(DetailsSubview, {
 
   rerenderPrefs: [
     "invert-call-tree",
     "show-platform-data",
-    "flatten-tree-recursion"
+    "flatten-tree-recursion",
   ],
 
   rangeChangeDebounceTime: 75, // ms
 
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
     DetailsSubview.initialize.call(this);
 
-    this._onPrefChanged = this._onPrefChanged.bind(this);
     this._onLink = this._onLink.bind(this);
 
     this.container = $("#js-calltree-view .call-tree-cells-container");
-    JITOptimizationsView.initialize();
   },
 
   /**
    * Unbinds events.
    */
   destroy: function () {
     this.container = null;
-    JITOptimizationsView.destroy();
     DetailsSubview.destroy.call(this);
   },
 
   /**
    * Method for handling all the set up for rendering a new call tree.
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
    */
   render: function (interval={}) {
+    let recording = PerformanceController.getCurrentRecording();
+    let profile = recording.getProfile();
     let options = {
       contentOnly: !PerformanceController.getOption("show-platform-data"),
       invertTree: PerformanceController.getOption("invert-call-tree"),
-      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion")
+      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
+      showOptimizationHint: recording.getConfiguration().withJITOptimizations,
     };
-    let recording = PerformanceController.getCurrentRecording();
-    let profile = recording.getProfile();
     let threadNode = this._prepareCallTree(profile, interval, options);
     this._populateCallTree(threadNode, options);
-    this._toggleJITOptimizationsView(recording);
     this.emit(EVENTS.JS_CALL_TREE_RENDERED);
   },
 
   /**
    * Fired on the "link" event for the call tree in this container.
    */
   _onLink: function (_, treeItem) {
     let { url, line } = treeItem.frame.getInfo();
@@ -103,49 +100,37 @@ let JsCallTreeView = Heritage.extend(Det
 
     let root = new CallView({
       frame: frameNode,
       inverted: inverted,
       // The synthesized root node is hidden in inverted call trees.
       hidden: inverted,
       // Call trees should only auto-expand when not inverted. Passing undefined
       // will default to the CALL_TREE_AUTO_EXPAND depth.
-      autoExpandDepth: inverted ? 0 : undefined
+      autoExpandDepth: inverted ? 0 : undefined,
+      enableOptimizations: options.enableOptimizations
     });
 
     // Bind events.
     root.on("link", this._onLink);
 
-    // Pipe "focus" events to the view, used by
-    // tests and JITOptimizationsView.
-    root.on("focus", (_, node) => this.emit("focus", node));
+    // Pipe "focus" events to the view, mostly for tests
+    root.on("focus", () => this.emit("focus"));
+    // TODO tests for optimization event and rendering
+    // optimization bubbles in call tree
+    root.on("optimization", (_, node) => this.emit("optimization", node));
 
     // Clear out other call trees.
     this.container.innerHTML = "";
     root.attachTo(this.container);
 
     // When platform data isn't shown, hide the cateogry labels, since they're
     // only available for C++ frames. Pass *false* to make them invisible.
     root.toggleCategories(!options.contentOnly);
 
     // Return the CallView for tests
     return root;
   },
 
-  /**
-   * Displays or hides the optimizations view based on the recordings
-   * optimizations feature.
-   *
-   * @param {RecordingModel} recording
-   */
-  _toggleJITOptimizationsView: function (recording) {
-    if (recording && recording.getConfiguration().withJITOptimizations) {
-      JITOptimizationsView.show();
-      JITOptimizationsView.render();
-    } else {
-      JITOptimizationsView.hide();
-    }
-  },
-
   toString: () => "[object JsCallTreeView]"
 });
 
 EventEmitter.decorate(JsCallTreeView);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/views/details-optimizations.js
@@ -0,0 +1,174 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+let OptimizationsView = Heritage.extend(DetailsSubview, {
+
+  rerenderPrefs: [
+    "show-platform-data",
+    "flatten-tree-recursion",
+  ],
+
+  rangeChangeDebounceTime: 75, // ms
+
+  /**
+   * Sets up the view with event binding.
+   */
+  initialize: function () {
+    DetailsSubview.initialize.call(this);
+    this.reset = this.reset.bind(this);
+    this.tabs = $("#optimizations-tabs");
+    this._onFramesListSelect = this._onFramesListSelect.bind(this);
+
+    OptimizationsListView.initialize();
+    FramesListView.initialize({ container: $("#frames-tabpanel") });
+    FramesListView.on("select", this._onFramesListSelect);
+  },
+
+  /**
+   * Unbinds events.
+   */
+  destroy: function () {
+    DetailsSubview.destroy.call(this);
+    this.tabs = this._threadNode = this._frameNode = null;
+
+    FramesListView.off("select", this._onFramesListSelect);
+    FramesListView.destroy();
+    OptimizationsListView.destroy();
+  },
+
+  /**
+   * Selects a tab by name.
+   *
+   * @param {string} name
+   *                 Can be "frames" or "optimizations"
+   */
+  selectTabByName: function (name="frames") {
+    switch(name) {
+    case "optimizations":
+      this.tabs.selectedIndex = 0;
+      break;
+    case "frames":
+      this.tabs.selectedIndex = 1;
+      break;
+    }
+  },
+
+  /**
+   * Method for handling all the set up for rendering a new call tree.
+   *
+   * @param object interval [optional]
+   *        The { startTime, endTime }, in milliseconds.
+   */
+  render: function (interval={}) {
+    let options = {
+      contentOnly: !PerformanceController.getOption("show-platform-data"),
+      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
+      // Always invert the tree for the optimizations view so we can quickly
+      // get leaves
+      invertTree: true,
+    };
+    let recording = PerformanceController.getCurrentRecording();
+    let profile = recording.getProfile();
+
+    this.reset();
+    // TODO bug 1175662
+    // Share thread nodes between details view
+    this.threadNode = this._prepareThreadNode(profile, interval, options);
+    this.emit(EVENTS.OPTIMIZATIONS_RENDERED);
+  },
+
+  /**
+   * The main thread node used in this recording that contains
+   * all potential frame nodes to select.
+   */
+  set threadNode(threadNode) {
+    if (threadNode === this._threadNode) {
+      return;
+    }
+    this._threadNode = threadNode;
+    // Also clear out the current frame node as its no
+    // longer relevent
+    this.frameNode = null;
+    this._setAndRenderFramesList();
+  },
+  get threadNode() {
+    return this._threadNode;
+  },
+
+  /**
+   * frameNode is the frame node selected currently to inspect
+   * the optimization tiers over time and strategies.
+   */
+  set frameNode(frameNode) {
+    if (frameNode === this._frameNode) {
+      return;
+    }
+    this._frameNode = frameNode;
+
+    // If no frame selected, jump to the frame list view. If just selected
+    // a frame, jump to optimizations view.
+    // TODO test for this bug 1176056
+    this.selectTabByName(frameNode ? "optimizations" : "frames");
+    this._setAndRenderTierGraph();
+    this._setAndRenderOptimizationsList();
+  },
+
+  get frameNode() {
+    return this._frameNode;
+  },
+
+  /**
+   * Clears the frameNode so that tier and opts list
+   * views are cleared.
+   */
+  reset: function () {
+    this.threadNode = this.frameNode = null;
+  },
+
+  /**
+   * Called when the recording is stopped and prepares data to
+   * populate the graph.
+   */
+  _prepareThreadNode: function (profile, { startTime, endTime }, options) {
+    let thread = profile.threads[0];
+    let { contentOnly, invertTree, flattenRecursion } = options;
+    let threadNode = new ThreadNode(thread, { startTime, endTime, contentOnly, invertTree, flattenRecursion });
+    return threadNode;
+  },
+
+  /**
+   * Renders the tier graph.
+   */
+  _setAndRenderTierGraph: function () {
+    // TODO bug 1150299
+  },
+
+  /**
+   * Renders the frames list.
+   */
+  _setAndRenderFramesList: function () {
+    FramesListView.setCurrentThread(this.threadNode);
+    FramesListView.render();
+  },
+
+  /**
+   * Renders the optimizations list.
+   */
+  _setAndRenderOptimizationsList: function () {
+    OptimizationsListView.setCurrentFrame(this.frameNode);
+    OptimizationsListView.render();
+  },
+
+  /**
+   * Called when a frame is selected via the FramesListView
+   */
+  _onFramesListSelect: function (_, frameNode) {
+    this.frameNode = frameNode;
+  },
+
+  toString: () => "[object OptimizationsView]"
+});
+
+EventEmitter.decorate(OptimizationsView);
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -34,16 +34,21 @@ let DetailsView = {
       actors: ["memory"],
       features: ["withAllocations"]
     },
     "memory-flamegraph": {
       id: "memory-flamegraph-view",
       view: MemoryFlameGraphView,
       actors: ["memory", "timeline"],
       features: ["withAllocations"]
+    },
+    "optimizations": {
+      id: "optimizations-view",
+      view: OptimizationsView,
+      features: ["withJITOptimizations"],
     }
   },
 
   /**
    * Sets up the view with event binding, initializes subviews.
    */
   initialize: Task.async(function *() {
     this.el = $("#details-pane");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/views/frames-list.js
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
+
+/**
+ * View for rendering a list of all youngest-frames in a profiler recording.
+ */
+
+let FramesListView = {
+
+  // Current `<li>` element selected.
+  _selectedItem: null,
+
+  /**
+   * Initialization function called when the tool starts up.
+   */
+  initialize: function ({ container }) {
+    this._onFrameListClick = this._onFrameListClick.bind(this);
+
+    this.container = container;
+    this.list = document.createElementNS(HTML_NS, "ul");
+    this.list.setAttribute("class", "frames-list");
+    this.list.addEventListener("click", this._onFrameListClick, false);
+
+    this.container.appendChild(this.list);
+  },
+
+  /**
+   * Destruction function called when the tool cleans up.
+   */
+  destroy: function () {
+    this.list.removeEventListener("click", this._onFrameListClick, false);
+    this.container.innerHTML = "";
+    this.container = this.list = null;
+  },
+
+  /**
+   * Sets the thread node used for subsequent rendering.
+   *
+   * @param {ThreadNode} threadNode
+   */
+  setCurrentThread: function (threadNode) {
+    this.threadNode = threadNode;
+  },
+
+  /**
+   * Renders a list of leaf frames with optimizations in
+   * order of hotness from the current ThreadNode.
+   */
+  render: function () {
+    this.list.innerHTML = "";
+
+    if (!this.threadNode) {
+      return;
+    }
+
+    let totalSamples = this.threadNode.samples;
+    let sortedFrames = this.threadNode.calls.sort((a, b) => a.youngestFrameSamples < b.youngestFrameSamples ? 1 : -1);
+    for (let frame of sortedFrames) {
+      if (!frame.hasOptimizations()) {
+        continue;
+      }
+      let info = frame.getInfo();
+      let el = document.createElementNS(HTML_NS, "li");
+      let percentage = frame.youngestFrameSamples / totalSamples * 100;
+      let percentageText = L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS;
+      let label = `(${percentageText}) ${info.functionName}`;
+      el.textContent = label;
+      el.setAttribute("tooltip", label);
+      el.setAttribute("data-location", frame.location);
+      this.list.appendChild(el);
+    }
+  },
+
+  /**
+   * Fired when a frame in the list is clicked.
+   */
+  _onFrameListClick: function (e) {
+    // If no threadNode (no renders), abort;
+    // also only allow left click to trigger this event
+    if (!this.threadNode || e.button !== 0) {
+      return;
+    }
+
+    let target = e.target;
+    let location = target.getAttribute("data-location");
+    if (!location) {
+      return;
+    }
+
+    for (let frame of this.threadNode.calls) {
+      if (frame.location === location) {
+        // If found, set the selected class on element, remove it
+        // from previous element, and emit event "select"
+        if (this._selectedItem) {
+          this._selectedItem.classList.remove("selected");
+        }
+        this._selectedItem = target;
+        target.classList.add("selected");
+        console.log("Emitting select on", this, frame);
+        this.emit("select", frame);
+        break;
+      }
+    }
+  },
+
+  toString: () => "[object FramesListView]"
+};
+
+EventEmitter.decorate(FramesListView);
rename from browser/devtools/performance/views/jit-optimizations.js
rename to browser/devtools/performance/views/optimizations-list.js
--- a/browser/devtools/performance/views/jit-optimizations.js
+++ b/browser/devtools/performance/views/optimizations-list.js
@@ -5,57 +5,51 @@
 
 const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
 const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
 const JIT_SAMPLES = L10N.getStr("jit.samples2");
 const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
 const PROPNAME_MAX_LENGTH = 4;
 
 /**
- * View for rendering JIT Optimization data. The terminology and types
- * used here can be referenced:
+ * View for rendering a list of all optmizations found in a frame.
+ * The terminology and types used here can be referenced:
  * @see browser/devtools/performance/modules/logic/jit.js
  */
 
-let JITOptimizationsView = {
+let OptimizationsListView = {
 
   _currentFrame: null,
 
   /**
    * Initialization function called when the tool starts up.
    */
   initialize: function () {
     this.reset = this.reset.bind(this);
-    this._onFocusFrame = this._onFocusFrame.bind(this);
 
     this.el = $("#jit-optimizations-view");
     this.$headerName = $("#jit-optimizations-header .header-function-name");
     this.$headerFile = $("#jit-optimizations-header .header-file");
     this.$headerLine = $("#jit-optimizations-header .header-line");
 
     this.tree = new TreeWidget($("#jit-optimizations-raw-view"), {
       sorted: false,
       emptyText: JIT_EMPTY_TEXT
     });
 
     // Start the tree by resetting.
     this.reset();
-
-    PerformanceController.on(EVENTS.RECORDING_SELECTED, this.reset);
-    JsCallTreeView.on("focus", this._onFocusFrame);
   },
 
   /**
    * Destruction function called when the tool cleans up.
    */
   destroy: function () {
     this.tree = null;
     this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
-    PerformanceController.off(EVENTS.RECORDING_SELECTED, this.reset);
-    JsCallTreeView.off("focus", this._onFocusFrame);
   },
 
   /**
    * Takes a FrameNode, with corresponding optimization data to be displayed
    * in the view.
    *
    * @param {FrameNode} frameNode
    */
@@ -88,42 +82,22 @@ let JITOptimizationsView = {
 
   /**
    * Clears out data in the tree.
    */
   clear: function () {
     this.tree.clear();
   },
 
-  show: function () {
-    this.el.hidden = false;
-  },
-
-  hide: function () {
-    this.el.hidden = true;
-  },
-
-  /**
-   * Helper to determine whether or not this view should be enabled.
-   */
-  isEnabled: function () {
-    let recording = PerformanceController.getCurrentRecording();
-    return !!(recording && recording.getConfiguration().withJITOptimizations);
-  },
-
   /**
    * Takes a JITOptimizations object and builds a view containing all attempted
    * optimizations for this frame. This view is very verbose and meant for those
    * who understand JIT compilers.
    */
   render: function () {
-    if (!this.isEnabled()) {
-      return;
-    }
-
     let frameNode = this.getCurrentFrame();
 
     if (!frameNode) {
       this.reset();
       return;
     }
 
     let view = this.tree;
@@ -380,36 +354,13 @@ let JITOptimizationsView = {
 
   _isLinkableURL: function (url) {
     return url && url.indexOf &&
        (url.indexOf("http") === 0 ||
         url.indexOf("resource://") === 0 ||
         url.indexOf("file://") === 0);
   },
 
-  /**
-   * Called when the JSCallTreeView focuses on a frame.
-   */
-
-  _onFocusFrame: function (_, view) {
-    if (!view.frame) {
-      return;
-    }
-
-    // Only attempt to rerender if this is new -- focus is called even
-    // when the window removes focus and comes back, so this prevents
-    // repeating rendering of the same frame
-    let shouldRender = this.getCurrentFrame() !== view.frame;
-
-    // Save the frame even if the view is disabled, so we can
-    // render it if it becomes enabled
-    this.setCurrentFrame(view.frame);
-
-    if (shouldRender) {
-      this.render();
-    }
-  },
-
-  toString: () => "[object JITOptimizationsView]"
+  toString: () => "[object OptimizationsListView]"
 
 };
 
-EventEmitter.decorate(JITOptimizationsView);
+EventEmitter.decorate(OptimizationsListView);
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.properties
@@ -106,16 +106,22 @@ table.idle=(idle)
 # labels which, when clicked, jump to the debugger.
 table.url.tooltiptext=View source in Debugger
 
 # LOCALIZATION NOTE (table.zoom.tooltiptext):
 # This string is displayed in the call tree as the tooltip text for the 'zoom'
 # buttons (small magnifying glass icons) which spawn a new tab.
 table.zoom.tooltiptext=Inspect frame in new tab
 
+# LOCALIZATION NOTE (table.view-optimizations.tooltiptext):
+# This string is displayed in the icon displayed next to frames that
+# have optimization data
+table.view-optimizations.tooltiptext=View optimizations in JIT View
+
+
 # LOCALIZATION NOTE (recordingsList.saveDialogTitle):
 # This string is displayed as a title for saving a recording to disk.
 recordingsList.saveDialogTitle=Save profile…
 
 # LOCALIZATION NOTE (recordingsList.saveDialogJSONFilter):
 # This string is displayed as a filter for saving a recording to disk.
 recordingsList.saveDialogJSONFilter=JSON Files
 
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -64,16 +64,20 @@
   list-style-image: url(performance-icons.svg#details-call-tree);
 }
 
 #select-js-flamegraph-view,
 #select-memory-flamegraph-view {
   list-style-image: url(performance-icons.svg#details-flamegraph);
 }
 
+#select-optimizations-view {
+  list-style-image: url(profiler-stopwatch.svg);
+}
+
 /* Recording buttons */
 
 #main-record-button {
   list-style-image: url(profiler-stopwatch.svg);
 }
 
 #main-record-button[checked] {
   list-style-image: url(profiler-stopwatch-checked.svg);
@@ -632,36 +636,63 @@ menuitem.marker-color-graphs-blue:before
 }
 .opt-url:hover {
   text-decoration: underline;
 }
 .opt-url.debugger-link {
   cursor: pointer;
 }
 
-#jit-optimizations-view .opt-icon::before {
+.opt-icon::before {
   content: "";
   background-image: url(chrome://browser/skin/devtools/webconsole.svg);
   background-repeat: no-repeat;
   background-size: 72px 60px;
+  /* show grey "i" bubble by default */
+  background-position: -36px -36px;
   width: 12px;
   height: 12px;
   display: inline-block;
 
-  margin: 5px 6px 0 0;
   max-height: 12px;
 }
-.theme-light #jit-optimizations-view .opt-icon::before {
+
+#jit-optimizations-view .opt-icon::before {
+  margin: 5px 6px 0 0;
+}
+description.opt-icon {
+  margin: 0px 0px 0px 0px;
+}
+description.opt-icon::before {
+  margin: 1px 4px 0px 0px;
+}
+.theme-light .opt-icon::before {
   background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
 }
-
-#jit-optimizations-view .opt-icon[severity=warning]::before {
+.opt-icon[severity=warning]::before {
   background-position: -24px -24px;
 }
+.opt-icon[type=linkable]::before {
+  cursor: pointer;
+}
 
+ul.frames-list {
+  list-style-type: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+ul.frames-list li {
+  cursor: pointer;
+}
+
+ul.frames-list li.selected {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
 
 /**
  * Configurable Options
  *
  * Elements can be tagged with a class and visibility is controlled via a
  * preference being applied or removed.
  */
 
--- a/build/mobile/robocop/Actions.java
+++ b/build/mobile/robocop/Actions.java
@@ -4,17 +4,27 @@
 
 package org.mozilla.gecko;
 import android.database.Cursor;
 
 public interface Actions {
 
     /** Special keys supported by sendSpecialKey() */
     public enum SpecialKey {
-        DOWN, UP, LEFT, RIGHT, ENTER, MENU, BACK
+        DOWN,
+        UP,
+        LEFT,
+        RIGHT,
+        ENTER,
+        MENU,
+        /**
+         * @deprecated Use Solo.goBack() in Robocop instead.
+         */
+        @Deprecated
+        BACK
     }
 
     public interface EventExpecter {
         /** Blocks until the event has been received. Subsequent calls will return immediately. */
         public void blockForEvent();
         public void blockForEvent(long millis, boolean failOnTimeout);
 
         /** Blocks until the event has been received and returns data associated with the event. */
@@ -35,17 +45,17 @@ public interface Actions {
 
     public interface RepeatedEventExpecter extends EventExpecter {
         /** Blocks until at least one event has been received, and no events have been received in the last <code>millis</code> milliseconds. */
         public void blockUntilClear(long millis);
     }
 
     /**
      * Sends an event to Gecko.
-     * 
+     *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     void sendGeckoEvent(String geckoEvent, String data);
 
     /**
      * Sends a preferences get event to Gecko.
      *
      * @param requestId The id of this request.
@@ -67,32 +77,32 @@ public interface Actions {
      * @param requestId The id of this request.
      */
     void sendPreferencesRemoveObserversEvent(int requestid);
 
     /**
      * Listens for a gecko event to be sent from the Gecko instance.
      * The returned object can be used to test if the event has been
      * received. Note that only one event is listened for.
-     * 
+     *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     RepeatedEventExpecter expectGeckoEvent(String geckoEvent);
 
     /**
      * Listens for a paint event. Note that calling expectPaint() will
      * invalidate the event expecters returned from any previous calls
      * to expectPaint(); calling any methods on those invalidated objects
      * will result in undefined behaviour.
      */
     RepeatedEventExpecter expectPaint();
 
-    /** 
-     * Send a string to the application 
-     * 
+    /**
+     * Send a string to the application
+     *
      * @param keysToSend The string to send
      */
     void sendKeys(String keysToSend);
 
     /**
      * Send a special keycode to the element
      *
      * @param key The special key to send
--- a/configure.in
+++ b/configure.in
@@ -3574,17 +3574,17 @@ dnl = If NSS was not detected in the sys
 dnl = use the one in the source tree (mozilla/security/nss)
 dnl ========================================================
 
 MOZ_ARG_WITH_BOOL(system-nss,
 [  --with-system-nss       Use system installed NSS],
     _USE_SYSTEM_NSS=1 )
 
 if test -n "$_USE_SYSTEM_NSS"; then
-    AM_PATH_NSS(3.19.1, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+    AM_PATH_NSS(3.19.2, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
 fi
 
 if test -n "$MOZ_NATIVE_NSS"; then
    NSS_LIBS="$NSS_LIBS -lcrmf"
 else
    NSS_CFLAGS='-I$(LIBXUL_DIST)/include/nss'
 
    if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT"; then
@@ -8690,16 +8690,18 @@ AC_SUBST(MOZ_CHILD_PROCESS_BUNDLE)
 # "Firefox"), but may vary for full rebrandings (e.g. Iceweasel). Used
 # for application.ini's "Name" field, which controls profile location in
 # the absence of a "Profile" field (see below), and various system
 # integration hooks (Unix remoting, Windows MessageWindow name, etc.)
 # - MOZ_APP_DISPLAYNAME: Used in user-visible fields (DLL properties,
 # Mac Bundle name, Updater, Installer), it is typically used for nightly
 # builds (e.g. Aurora for Firefox).
 # - MOZ_APP_VERSION: Defines the application version number.
+# - MOZ_APP_VERSION_ABOUT: Defines the application version number. Used
+# in the "About" window. If not set, defaults to MOZ_APP_VERSION.
 # - MOZ_APP_NAME: Used for e.g. the binary program file name. If not set,
 # defaults to a lowercase form of MOZ_APP_BASENAME.
 # - MOZ_APP_REMOTINGNAME: Used for the internal program name, which affects
 # profile name and remoting. If not set, defaults to MOZ_APP_NAME.
 # - MOZ_APP_PROFILE: When set, used for application.ini's
 # "Profile" field, which controls profile location.
 # - MOZ_APP_ID: When set, used for application.ini's "ID" field, and
 # crash reporter server url.
@@ -8708,16 +8710,20 @@ AC_SUBST(MOZ_CHILD_PROCESS_BUNDLE)
 if test -z "$MOZ_APP_NAME"; then
    MOZ_APP_NAME=`echo $MOZ_APP_BASENAME | tr A-Z a-z`
 fi
 
 if test -z "$MOZ_APP_REMOTINGNAME"; then
    MOZ_APP_REMOTINGNAME=$MOZ_APP_NAME
 fi
 
+if test -z "$MOZ_APP_VERSION_ABOUT"; then
+   MOZ_APP_VERSION_ABOUT=$MOZ_APP_VERSION
+fi
+
 # For extensions and langpacks, we require a max version that is compatible
 # across security releases. MOZ_APP_MAXVERSION is our method for doing that.
 # 24.0a1 and 24.0a2 aren't affected
 # 24.0 becomes 24.*
 # 24.1.1 becomes 24.*
 IS_ALPHA=`echo $MOZ_APP_VERSION | grep a`
 if test -z "$IS_ALPHA"; then
   changequote(,)
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3143,35 +3143,35 @@ Element::MozRequestFullScreen(JSContext*
       new AsyncEventDispatcher(OwnerDoc(),
                                NS_LITERAL_STRING("mozfullscreenerror"),
                                true,
                                false);
     asyncDispatcher->PostDOMEvent();
     return;
   }
 
-  FullScreenOptions opts;
-  opts.mIsCallerChrome = nsContentUtils::IsCallerChrome();
+  auto request = MakeUnique<FullscreenRequest>(this);
+  request->mIsCallerChrome = nsContentUtils::IsCallerChrome();
 
   RequestFullscreenOptions fsOptions;
   // We need to check if options is convertible to a dict first before
   // trying to init fsOptions; otherwise Init() would throw, and we want to
   // silently ignore non-dictionary values
   if (aCx && IsConvertibleToDictionary(aCx, aOptions)) {
     if (!fsOptions.Init(aCx, aOptions)) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     if (fsOptions.mVrDisplay) {
-      opts.mVRHMDDevice = fsOptions.mVrDisplay->GetHMD();
+      request->mVRHMDDevice = fsOptions.mVrDisplay->GetHMD();
     }
   }
 
-  OwnerDoc()->AsyncRequestFullScreen(this, opts);
+  OwnerDoc()->AsyncRequestFullScreen(Move(request));
 }
 
 void
 Element::MozRequestPointerLock()
 {
   OwnerDoc()->RequestPointerLock(this);
 }
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -19,16 +19,17 @@
 #include "nsFrameManager.h"
 #include "nsRefreshDriver.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/PendingAnimationTracker.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsFrame.h"
 #include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
 #include "ClientLayerManager.h"
 #include "nsQueryObject.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 
 #include "nsIScrollableFrame.h"
 
@@ -2488,16 +2489,44 @@ nsDOMWindowUtils::SetAsyncZoom(nsIDOMNod
   if (!forwarder || !forwarder->HasShadowManager()) {
     return NS_ERROR_UNEXPECTED;
   }
   forwarder->GetShadowManager()->SendSetAsyncZoom(viewId, aValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::FlushApzRepaints(bool* aOutResult)
+{
+  nsIWidget* widget = GetWidget();
+  if (!widget) {
+    *aOutResult = false;
+    return NS_OK;
+  }
+  // If APZ is not enabled, this function is a no-op.
+  if (!widget->AsyncPanZoomEnabled()) {
+    *aOutResult = false;
+    return NS_OK;
+  }
+  LayerManager* manager = widget->GetLayerManager();
+  if (!manager) {
+    *aOutResult = false;
+    return NS_OK;
+  }
+  ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
+  if (!forwarder || !forwarder->HasShadowManager()) {
+    *aOutResult = false;
+    return NS_OK;
+  }
+  forwarder->GetShadowManager()->SendFlushApzRepaints();
+  *aOutResult = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
                                            const nsAString& aProperty,
                                            const nsAString& aValue1,
                                            const nsAString& aValue2,
                                            double* aResult)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -11,18 +11,16 @@
 #include "nsDocument.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Likely.h"
-#include "mozilla/LinkedList.h"
-#include "mozilla/UniquePtr.h"
 #include <algorithm>
 
 #include "mozilla/Logging.h"
 #include "plstr.h"
 #include "prprf.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsIInterfaceRequestor.h"
@@ -11279,61 +11277,42 @@ nsDocument::RestorePreviousFullScreenSta
 }
 
 bool
 nsDocument::IsFullScreenDoc()
 {
   return GetFullScreenElement() != nullptr;
 }
 
-FullScreenOptions::FullScreenOptions()
-{
-}
-
 class nsCallRequestFullScreen : public nsRunnable
 {
 public:
-  explicit nsCallRequestFullScreen(Element* aElement,
-                                   const FullScreenOptions& aOptions)
-    : mElement(aElement),
-      mDoc(aElement->OwnerDoc()),
-      mOptions(aOptions),
-      mHadRequestPending(static_cast<nsDocument*>(mDoc.get())->
-                         mAsyncFullscreenPending)
-  {
-    static_cast<nsDocument*>(mDoc.get())->
-      mAsyncFullscreenPending = true;
-  }
+  explicit nsCallRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+    : mRequest(Move(aRequest)) { }
 
   NS_IMETHOD Run()
   {
-    static_cast<nsDocument*>(mDoc.get())->
-      mAsyncFullscreenPending = mHadRequestPending;
-    nsDocument* doc = static_cast<nsDocument*>(mDoc.get());
-    doc->RequestFullScreen(mElement, mOptions);
+    mRequest->GetDocument()->RequestFullScreen(Move(mRequest));
     return NS_OK;
   }
 
-  nsRefPtr<Element> mElement;
-  nsCOMPtr<nsIDocument> mDoc;
-  FullScreenOptions mOptions;
-  bool mHadRequestPending;
+  UniquePtr<FullscreenRequest> mRequest;
 };
 
 void
-nsDocument::AsyncRequestFullScreen(Element* aElement,
-                                   FullScreenOptions& aOptions)
-{
-  NS_ASSERTION(aElement,
-    "Must pass non-null element to nsDocument::AsyncRequestFullScreen");
-  if (!aElement) {
-    return;
-  }
+nsDocument::AsyncRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+{
+  if (!aRequest->GetElement()) {
+    MOZ_ASSERT_UNREACHABLE(
+      "Must pass non-null element to nsDocument::AsyncRequestFullScreen");
+    return;
+  }
+
   // Request full-screen asynchronously.
-  nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(aElement, aOptions));
+  nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(Move(aRequest)));
   NS_DispatchToCurrentThread(event);
 }
 
 static void
 LogFullScreenDenied(bool aLogFailure, const char* aMessage, nsIDocument* aDoc)
 {
   if (!aLogFailure) {
     return;
@@ -11521,20 +11500,20 @@ IsInActiveTab(nsIDocument* aDoc)
 }
 
 nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
 {
   // Ensure the frame element is the fullscreen element in this document.
   // If the frame element is already the fullscreen element in this document,
   // this has no effect.
   nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
-  FullScreenOptions opts;
-  opts.mIsCallerChrome = false;
-  opts.mShouldNotifyNewOrigin = false;
-  RequestFullScreen(content->AsElement(), opts);
+  auto request = MakeUnique<FullscreenRequest>(content->AsElement());
+  request->mIsCallerChrome = false;
+  request->mShouldNotifyNewOrigin = false;
+  RequestFullScreen(Move(request));
 
   return NS_OK;
 }
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
@@ -11597,26 +11576,43 @@ nsDocument::FullscreenElementReadyCheck(
     if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(content)) {
       LogFullScreenDenied(true, "FullScreenDeniedFocusedPlugin", this);
       return false;
     }
   }
   return true;
 }
 
-struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
-{
-  FullscreenRequest(Element* aElement, const FullScreenOptions& aOptions)
-    : mElement(aElement), mOptions(aOptions)
-    { MOZ_COUNT_CTOR(FullscreenRequest); }
-  ~FullscreenRequest() { MOZ_COUNT_DTOR(FullscreenRequest); }
-
-  nsRefPtr<Element> mElement;
-  FullScreenOptions mOptions;
-};
+FullscreenRequest::FullscreenRequest(Element* aElement)
+  : mElement(aElement)
+  , mDocument(static_cast<nsDocument*>(aElement->OwnerDoc()))
+{
+  MOZ_COUNT_CTOR(FullscreenRequest);
+  mDocument->mPendingFullscreenRequests++;
+  if (MOZ_UNLIKELY(!mDocument->mPendingFullscreenRequests)) {
+    NS_WARNING("Pending fullscreen request counter overflow");
+  }
+}
+
+static void RedispatchPendingPointerLockRequest(nsIDocument* aDocument);
+
+FullscreenRequest::~FullscreenRequest()
+{
+  MOZ_COUNT_DTOR(FullscreenRequest);
+  if (MOZ_UNLIKELY(!mDocument->mPendingFullscreenRequests)) {
+    NS_WARNING("Pending fullscreen request counter underflow");
+    return;
+  }
+  mDocument->mPendingFullscreenRequests--;
+  if (!mDocument->mPendingFullscreenRequests) {
+    // There may be pointer lock request be blocked because of pending
+    // fullscreen requests. Re-dispatch it to ensure it gets handled.
+    RedispatchPendingPointerLockRequest(mDocument);
+  }
+}
 
 // Any fullscreen request waiting for the widget to finish being full-
 // screen is queued here. This is declared static instead of a member
 // of nsDocument because in the majority of time, there would be at most
 // one document requesting fullscreen. We shouldn't waste the space to
 // hold for it in every document.
 static LinkedList<FullscreenRequest> sPendingFullscreenRequests;
 
@@ -11628,71 +11624,70 @@ GetRootWindow(nsIDocument* aDoc)
     return nullptr;
   }
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   docShell->GetRootTreeItem(getter_AddRefs(rootItem));
   return rootItem ? rootItem->GetWindow() : nullptr;
 }
 
 void
-nsDocument::RequestFullScreen(Element* aElement,
-                              const FullScreenOptions& aOptions)
+nsDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
 {
   nsCOMPtr<nsPIDOMWindow> rootWin = GetRootWindow(this);
   if (!rootWin) {
     return;
   }
 
   // If we have been in fullscreen, apply the new state directly.
   // Note that we should check both condition, because if we are in
   // child process, our window may not report to be in fullscreen.
   if (static_cast<nsGlobalWindow*>(rootWin.get())->FullScreen() ||
       nsContentUtils::GetRootDocument(this)->IsFullScreenDoc()) {
-    ApplyFullscreen(aElement, aOptions);
+    ApplyFullscreen(*aRequest);
     return;
   }
 
   // We don't need to check element ready before this point, because
   // if we called ApplyFullscreen, it would check that for us.
-  if (!FullscreenElementReadyCheck(aElement, aOptions.mIsCallerChrome)) {
-    return;
-  }
-
-  sPendingFullscreenRequests.insertBack(
-    new FullscreenRequest(aElement, aOptions));
+  Element* elem = aRequest->GetElement();
+  if (!FullscreenElementReadyCheck(elem, aRequest->mIsCallerChrome)) {
+    return;
+  }
+
+  sPendingFullscreenRequests.insertBack(aRequest.release());
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     // If we are not the top level process, dispatch an event to make
     // our parent process go fullscreen first.
     (new AsyncEventDispatcher(
        this, NS_LITERAL_STRING("MozDOMFullscreen:Request"),
        /* Bubbles */ true, /* ChromeOnly */ true))->PostDOMEvent();
   } else {
     // Make the window fullscreen.
-    SetWindowFullScreen(this, true, aOptions.mVRHMDDevice);
+    FullscreenRequest* lastRequest = sPendingFullscreenRequests.getLast();
+    SetWindowFullScreen(this, true, lastRequest->mVRHMDDevice);
   }
 }
 
 /* static */ bool
 nsIDocument::HandlePendingFullscreenRequest(const FullscreenRequest& aRequest,
                                             nsIDocShellTreeItem* aRootShell,
                                             bool* aHandled)
 {
-  nsRefPtr<nsDocument> doc =
-    static_cast<nsDocument*>(aRequest.mElement->OwnerDoc());
+  nsDocument* doc = aRequest.GetDocument();
   nsIDocShellTreeItem* shell = doc->GetDocShell();
   if (!shell) {
     return true;
   }
   nsCOMPtr<nsIDocShellTreeItem> rootShell;
   shell->GetRootTreeItem(getter_AddRefs(rootShell));
   if (rootShell != aRootShell) {
     return false;
   }
 
-  doc->ApplyFullscreen(aRequest.mElement, aRequest.mOptions);
+  doc->ApplyFullscreen(aRequest);
   *aHandled = true;
   return true;
 }
 
 /* static */ bool
 nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc)
 {
   if (sPendingFullscreenRequests.isEmpty()) {
@@ -11716,20 +11711,20 @@ nsIDocument::HandlePendingFullscreenRequ
     } else {
       request = request->getNext();
     }
   }
   return handled;
 }
 
 void
-nsDocument::ApplyFullscreen(Element* aElement,
-                            const FullScreenOptions& aOptions)
-{
-  if (!FullscreenElementReadyCheck(aElement, aOptions.mIsCallerChrome)) {
+nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
+{
+  Element* elem = aRequest.GetElement();
+  if (!FullscreenElementReadyCheck(elem, aRequest.mIsCallerChrome)) {
     return;
   }
 
   // Stash a reference to any existing fullscreen doc, we'll use this later
   // to detect if the origin which is fullscreen has changed.
   nsCOMPtr<nsIDocument> previousFullscreenDoc = GetFullscreenLeaf(this);
 
   AddFullscreenApprovedObserver();
@@ -11745,27 +11740,26 @@ nsDocument::ApplyFullscreen(Element* aEl
   // we can reset full-screen state in the remaining visible full-screen documents.
   nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
 
   // If a document is already in fullscreen, then unlock the mouse pointer
   // before setting a new document to fullscreen
   UnlockPointer();
 
   // Process options -- in this case, just HMD
-  if (aOptions.mVRHMDDevice) {
-    nsRefPtr<gfx::VRHMDInfo> hmdRef = aOptions.mVRHMDDevice;
-    aElement->SetProperty(nsGkAtoms::vr_state, hmdRef.forget().take(),
-                          ReleaseHMDInfoRef,
-                          true);
+  if (aRequest.mVRHMDDevice) {
+    nsRefPtr<gfx::VRHMDInfo> hmdRef = aRequest.mVRHMDDevice;
+    elem->SetProperty(nsGkAtoms::vr_state, hmdRef.forget().take(),
+                      ReleaseHMDInfoRef, true);
   }
 
   // Set the full-screen element. This sets the full-screen style on the
   // element, and the full-screen-ancestor styles on ancestors of the element
   // in this document.
-  DebugOnly<bool> x = FullScreenStackPush(aElement);
+  DebugOnly<bool> x = FullScreenStackPush(elem);
   NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
   changed.AppendElement(this);
 
   // Propagate up the document hierarchy, setting the full-screen element as
   // the element's container in ancestor documents. This also sets the
   // appropriate css styles as well. Note we don't propagate down the
   // document hierarchy, the full-screen element (or its container) is not
   // visible there. Stop when we reach the root document.
@@ -11813,29 +11807,29 @@ nsDocument::ApplyFullscreen(Element* aEl
   // If it is the first entry of the fullscreen, trigger an event so
   // that the UI can response to this change, e.g. hide chrome, or
   // notifying parent process to enter fullscreen. Note that chrome
   // code may also want to listen to MozDOMFullscreen:NewOrigin event
   // to pop up warning/approval UI.
   if (!previousFullscreenDoc) {
     nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(
-        aElement, NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
+        elem, NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
         /* Bubbles */ true, /* ChromeOnly */ true);
     asyncDispatcher->PostDOMEvent();
   }
 
   // The origin which is fullscreen gets changed. Trigger an event so
   // that the chrome knows to pop up a warning/approval UI. Note that
   // previousFullscreenDoc == nullptr upon first entry, so we always
   // take this path on the first entry. Also note that, in a multi-
   // process browser, the code in content process is responsible for
   // sending message with the origin to its parent, and the parent
   // shouldn't rely on this event itself.
-  if (aOptions.mShouldNotifyNewOrigin &&
+  if (aRequest.mShouldNotifyNewOrigin &&
       !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
     nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(
         this, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
         /* Bubbles */ true, /* ChromeOnly */ true);
     asyncDispatcher->PostDOMEvent();
   }
 
@@ -11971,16 +11965,19 @@ DispatchPointerLockError(nsIDocument* aT
   nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(aTarget,
                              NS_LITERAL_STRING("mozpointerlockerror"),
                              true,
                              false);
   asyncDispatcher->PostDOMEvent();
 }
 
+static const uint8_t kPointerLockRequestLimit = 2;
+
+class nsPointerLockPermissionRequest;
 mozilla::StaticRefPtr<nsPointerLockPermissionRequest> gPendingPointerLockRequest;
 
 class nsPointerLockPermissionRequest : public nsRunnable,
                                        public nsIContentPermissionRequest
 {
 public:
   nsPointerLockPermissionRequest(Element* aElement, bool aUserInputOrChromeCaller)
   : mElement(do_GetWeakReference(aElement)),
@@ -12004,31 +12001,31 @@ public:
         e->GetUncomposedDoc() != d) {
       Handled();
       DispatchPointerLockError(d);
       return NS_OK;
     }
 
     // We're about to enter fullscreen mode.
     nsDocument* doc = static_cast<nsDocument*>(d.get());
-    if (doc->mAsyncFullscreenPending ||
+    if (doc->mPendingFullscreenRequests > 0 ||
         (doc->mHasFullscreenApprovedObserver && !doc->mIsApprovedForFullscreen)) {
       // We're still waiting for approval.
       return NS_OK;
     }
 
     if (doc->mIsApprovedForFullscreen || doc->mAllowRelocking) {
       Allow(JS::UndefinedHandleValue);
       return NS_OK;
     }
 
     // In non-fullscreen mode user input (or chrome caller) is required!
     // Also, don't let the page to try to get the permission too many times.
     if (!mUserInputOrChromeCaller ||
-        doc->mCancelledPointerLockRequests > 2) {
+        doc->mCancelledPointerLockRequests > kPointerLockRequestLimit) {
       Handled();
       DispatchPointerLockError(d);
       return NS_OK;
     }
 
     // Handling a request from user input in non-fullscreen mode.
     // Do a normal permission check.
     nsCOMPtr<nsPIDOMWindow> window = doc->GetInnerWindow();
@@ -12097,17 +12094,20 @@ nsPointerLockPermissionRequest::GetEleme
 }
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::Cancel()
 {
   nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
   Handled();
   if (d) {
-    static_cast<nsDocument*>(d.get())->mCancelledPointerLockRequests++;
+    auto doc = static_cast<nsDocument*>(d.get());
+    if (doc->mCancelledPointerLockRequests <= kPointerLockRequestLimit) {
+      doc->mCancelledPointerLockRequests++;
+    }
     DispatchPointerLockError(d);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::Allow(JS::HandleValue aChoices)
 {
@@ -12168,45 +12168,57 @@ nsPointerLockPermissionRequest::GetReque
 }
 
 void
 nsDocument::SetApprovedForFullscreen(bool aIsApproved)
 {
   mIsApprovedForFullscreen = aIsApproved;
 }
 
+static void
+RedispatchPendingPointerLockRequest(nsIDocument* aDocument)
+{
+  if (!gPendingPointerLockRequest) {
+    return;
+  }
+  nsCOMPtr<nsIDocument> doc =
+    do_QueryReferent(gPendingPointerLockRequest->mDocument);
+  if (doc != aDocument) {
+    return;
+  }
+  nsCOMPtr<Element> elem =
+    do_QueryReferent(gPendingPointerLockRequest->mElement);
+  if (!elem || elem->GetUncomposedDoc() != aDocument) {
+    gPendingPointerLockRequest->Handled();
+    return;
+  }
+
+  // We have a request pending on the document which may previously be
+  // blocked for fullscreen approval. Create a clone and re-dispatch it
+  // to guarantee that Run() method gets called again.
+  bool userInputOrChromeCaller =
+    gPendingPointerLockRequest->mUserInputOrChromeCaller;
+  gPendingPointerLockRequest->Handled();
+  gPendingPointerLockRequest =
+    new nsPointerLockPermissionRequest(elem, userInputOrChromeCaller);
+  NS_DispatchToMainThread(gPendingPointerLockRequest);
+}
+
 nsresult
 nsDocument::Observe(nsISupports *aSubject,
                     const char *aTopic,
                     const char16_t *aData)
 {
   if (strcmp("fullscreen-approved", aTopic) == 0) {
     nsCOMPtr<nsIDocument> subject(do_QueryInterface(aSubject));
     if (subject != this) {
       return NS_OK;
     }
     SetApprovedForFullscreen(true);
-    if (gPendingPointerLockRequest) {
-      // We have a request pending. Create a clone of it and re-dispatch so that
-      // Run() method gets called again.
-      nsCOMPtr<Element> el =
-        do_QueryReferent(gPendingPointerLockRequest->mElement);
-      nsCOMPtr<nsIDocument> doc =
-        do_QueryReferent(gPendingPointerLockRequest->mDocument);
-      bool userInputOrChromeCaller =
-        gPendingPointerLockRequest->mUserInputOrChromeCaller;
-      gPendingPointerLockRequest->Handled();
-      if (doc == this && el && el->GetUncomposedDoc() == doc) {
-        nsPointerLockPermissionRequest* clone =
-          new nsPointerLockPermissionRequest(el, userInputOrChromeCaller);
-        gPendingPointerLockRequest = clone;
-        nsCOMPtr<nsIRunnable> r = gPendingPointerLockRequest.get();
-        NS_DispatchToMainThread(r);
-      }
-    }
+    RedispatchPendingPointerLockRequest(this);
   } else if (strcmp("app-theme-changed", aTopic) == 0) {
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()) &&
         !IsUnstyledDocument()) {
       // We don't want to style the chrome window, only app ones.
       OnAppThemeChanged();
     }
   }
   return NS_OK;
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -62,16 +62,17 @@
 #include "mozilla/dom/DOMImplementation.h"
 #include "mozilla/dom/StyleSheetList.h"
 #include "nsDataHashtable.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
 #include "nsIDOMXPathEvaluator.h"
 #include "jsfriendapi.h"
 #include "ImportManager.h"
+#include "mozilla/LinkedList.h"
 
 #define XML_DECLARATION_BITS_DECLARATION_EXISTS   (1 << 0)
 #define XML_DECLARATION_BITS_ENCODING_EXISTS      (1 << 1)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS    (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES       (1 << 3)
 
 
 class nsDOMStyleSheetSetList;
@@ -80,29 +81,54 @@ class nsIRadioVisitor;
 class nsIFormControl;
 struct nsRadioGroupStruct;
 class nsOnloadBlocker;
 class nsUnblockOnloadEvent;
 class nsDOMNavigationTiming;
 class nsWindowSizes;
 class nsHtml5TreeOpExecutor;
 class nsDocumentOnStack;
-class nsPointerLockPermissionRequest;
 class nsISecurityConsoleMessage;
 class nsPIBoxObject;
 
 namespace mozilla {
 class EventChainPreVisitor;
 namespace dom {
 class BoxObject;
 class UndoManager;
 struct LifecycleCallbacks;
 class CallbackFunction;
-}
-}
+
+struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
+{
+  explicit FullscreenRequest(Element* aElement);
+  ~FullscreenRequest();
+
+  Element* GetElement() const { return mElement; }
+  nsDocument* GetDocument() const { return mDocument; }
+
+private:
+  nsRefPtr<Element> mElement;
+  nsRefPtr<nsDocument> mDocument;
+
+public:
+  nsRefPtr<gfx::VRHMDInfo> mVRHMDDevice;
+  // This value should be true if the fullscreen request is
+  // originated from chrome code.
+  bool mIsCallerChrome = false;
+  // This value denotes whether we should trigger a NewOrigin event if
+  // requesting fullscreen in its document causes the origin which is
+  // fullscreen to change. We may want *not* to trigger that event if
+  // we're calling RequestFullScreen() as part of a continuation of a
+  // request in a subdocument in different process, whereupon the caller
+  // need to send some notification itself with the real origin.
+  bool mShouldNotifyNewOrigin = true;
+};
+} // namespace dom
+} // namespace mozilla
 
 /**
  * Right now our identifier map entries contain information for 'name'
  * and 'id' mappings of a given string. This is so that
  * nsHTMLDocument::ResolveName only has to do one hash lookup instead
  * of two. It's not clear whether this still matters for performance.
  * 
  * We also store the document.all result list here. This is mainly so that
@@ -1170,18 +1196,18 @@ public:
   virtual nsresult GetStateObject(nsIVariant** aResult) override;
 
   virtual nsDOMNavigationTiming* GetNavigationTiming() const override;
   virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) override;
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName) override;
 
   virtual Element* GetFullScreenElement() override;
-  virtual void AsyncRequestFullScreen(Element* aElement,
-                                      mozilla::dom::FullScreenOptions& aOptions) override;
+  virtual void AsyncRequestFullScreen(
+    mozilla::UniquePtr<FullscreenRequest>&& aRequest) override;
   virtual void RestorePreviousFullScreenState() override;
   virtual bool IsFullscreenLeaf() override;
   virtual bool IsFullScreenDoc() override;
   virtual void SetApprovedForFullscreen(bool aIsApproved) override;
   virtual nsresult
     RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) override;
 
   virtual nsresult RemoteFrameFullscreenReverted() override;
@@ -1217,18 +1243,17 @@ public:
   static void ExitFullscreen(nsIDocument* aDoc);
 
   // Do the "fullscreen element ready check" from the fullscreen spec.
   // It returns true if the given element is allowed to go into fullscreen.
   bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome);
 
   // This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
   // to move this document into full-screen mode if allowed.
-  void RequestFullScreen(Element* aElement,
-                         const mozilla::dom::FullScreenOptions& aOptions);
+  void RequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&& aRequest);
 
   // Removes all elements from the full-screen stack, removing full-scren
   // styles from the top element in the stack.
   void CleanupFullscreenState();
 
   // Add/remove "fullscreen-approved" observer service notification listener.
   // Chrome sends us a notification when fullscreen is approved for a
   // document, with the notification subject as the document that was approved.
@@ -1508,18 +1533,17 @@ protected:
   explicit nsDocument(const char* aContentType);
   virtual ~nsDocument();
 
   void EnsureOnloadBlocker();
 
   void NotifyStyleSheetApplicableStateChanged();
 
   // Apply the fullscreen state to the document, and trigger related events.
-  void ApplyFullscreen(Element* aElement,
-                       const mozilla::dom::FullScreenOptions& aOptions);
+  void ApplyFullscreen(const FullscreenRequest& aRequest);
 
   nsTArray<nsIObserver*> mCharSetObservers;
 
   PLDHashTable *mSubDocuments;
 
   // Array of owning references to all children
   nsAttrAndChildArray mChildren;
 
@@ -1658,29 +1682,31 @@ public:
   bool mHasFullscreenApprovedObserver:1;
 
   friend class nsPointerLockPermissionRequest;
   friend class nsCallRequestFullScreen;
   // When set, trying to lock the pointer doesn't require permission from the
   // user.
   bool mAllowRelocking:1;
 
-  bool mAsyncFullscreenPending:1;
-
   // Whether we're observing the "app-theme-changed" observer service
   // notification.  We need to keep track of this because we might get multiple
   // OnPageShow notifications in a row without an OnPageHide in between, if
   // we're getting document.open()/close() called on us.
   bool mObservingAppThemeChanged:1;
 
   // Keeps track of whether we have a pending
   // 'style-sheet-applicable-state-changed' notification.
   bool mSSApplicableStateNotificationPending:1;
 
-  uint32_t mCancelledPointerLockRequests;
+  // The number of pointer lock requests which are cancelled by the user.
+  // The value is saturated to kPointerLockRequestLimit+1 = 3.
+  uint8_t mCancelledPointerLockRequests:2;
+
+  uint8_t mPendingFullscreenRequests;
 
   uint8_t mXMLDeclarationBits;
 
   nsInterfaceHashtable<nsPtrHashKey<nsIContent>, nsPIBoxObject> *mBoxObjectTable;
 
   // A document "without a browsing context" that owns the content of
   // HTMLTemplateElement.
   nsCOMPtr<nsIDocument> mTemplateContentsOwner;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -25,16 +25,17 @@
 #include "mozilla/net/ReferrerPolicy.h"  // for member
 #include "nsWeakReference.h"
 #include "mozilla/dom/DocumentBinding.h"
 #include "mozilla/WeakPtr.h"
 #include "Units.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "prclist.h"
+#include "mozilla/UniquePtr.h"
 #include <bitset>                        // for member
 
 class imgIRequest;
 class nsAString;
 class nsBindingManager;
 class nsIDocShell;
 class nsDocShell;
 class nsDOMNavigationTiming;
@@ -79,17 +80,16 @@ class nsScriptLoader;
 class nsSMILAnimationController;
 class nsStyleSet;
 class nsTextNode;
 class nsWindowSizes;
 class nsDOMCaretPosition;
 class nsViewportInfo;
 class nsIGlobalObject;
 struct nsCSSSelectorList;
-struct FullscreenRequest; // For nsIDocument::HandlePendingFullscreenRequest
 
 namespace mozilla {
 class CSSStyleSheet;
 class ErrorResult;
 class EventStates;
 class PendingAnimationTracker;
 class SVGAttrAnimationRuleProcessor;
 
@@ -115,16 +115,17 @@ class DocumentType;
 class DOMImplementation;
 class DOMStringList;
 class Element;
 struct ElementRegistrationOptions;
 class Event;
 class EventTarget;
 class FontFaceSet;
 class FrameRequestCallback;
+struct FullscreenRequest;
 class ImportManager;
 class HTMLBodyElement;
 struct LifecycleCallbackArgs;
 class Link;
 class MediaQueryList;
 class GlobalObject;
 class NodeFilter;
 class NodeIterator;
@@ -140,37 +141,22 @@ class XPathExpression;
 class XPathNSResolver;
 class XPathResult;
 template<typename> class OwningNonNull;
 template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
-struct FullScreenOptions {
-  FullScreenOptions();
-  nsRefPtr<gfx::VRHMDInfo> mVRHMDDevice;
-  // This value should be true if the fullscreen request is
-  // originated from chrome code.
-  bool mIsCallerChrome = false;
-  // This value denotes whether we should trigger a NewOrigin event if
-  // requesting fullscreen in its document causes the origin which is
-  // fullscreen to change. We may want *not* to trigger that event if
-  // we're calling RequestFullScreen() as part of a continuation of a
-  // request in a subdocument in different process, whereupon the caller
-  // need to send some notification itself with the real origin.
-  bool mShouldNotifyNewOrigin = true;
-};
-
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0xdcfa30f2, 0x2197, 0x421f, \
-  { 0xa7, 0x5a, 0x3e, 0x70, 0x18, 0x08, 0xde, 0x81 } }
+{ 0x24fbaa06, 0x322b, 0x495f, \
+  {0x89, 0xcb, 0x33, 0xbc, 0x6f, 0x2d, 0xf6, 0x93} }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
   DocumentFlavorSVG, // SVGDocument
   DocumentFlavorPlain, // Just a Document
 };
@@ -192,19 +178,21 @@ NS_GetContentList(nsINode* aRootNode,
 
 //----------------------------------------------------------------------
 
 // Document interface.  This is implemented by all document objects in
 // Gecko.
 class nsIDocument : public nsINode
 {
   typedef mozilla::dom::GlobalObject GlobalObject;
+
 public:
   typedef mozilla::net::ReferrerPolicy ReferrerPolicyEnum;
   typedef mozilla::dom::Element Element;
+  typedef mozilla::dom::FullscreenRequest FullscreenRequest;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_IID)
   NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
 
 #ifdef MOZILLA_INTERNAL_API
   nsIDocument();
 #endif
 
@@ -1095,18 +1083,18 @@ public:
    * (if any) is pushed onto the fullscreen element stack, and it can be
    * returned to fullscreen status by calling RestorePreviousFullScreenState().
    *
    * Note that requesting fullscreen in a document also makes the element which
    * contains this document in this document's parent document fullscreen. i.e.
    * the <iframe> or <browser> that contains this document is also mode
    * fullscreen. This happens recursively in all ancestor documents.
    */
-  virtual void AsyncRequestFullScreen(Element* aElement,
-                                      mozilla::dom::FullScreenOptions& aOptions) = 0;
+  virtual void AsyncRequestFullScreen(
+    mozilla::UniquePtr<FullscreenRequest>&& aRequest) = 0;
 
   /**
    * Called when a frame in a child process has entered fullscreen or when a
    * fullscreen frame in a child process changes to another origin.
    * aFrameElement is the frame element which contains the child-process
    * fullscreen document.
    */
   virtual nsresult
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -173,16 +173,29 @@ CameraControlImpl::OnTakePictureComplete
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     l->OnTakePictureComplete(aData, aLength, aMimeType);
   }
 }
 
 void
+CameraControlImpl::OnPoster(dom::BlobImpl* aBlobImpl)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnPoster(aBlobImpl);
+  }
+}
+
+void
 CameraControlImpl::OnShutter()
 {
   // This callback can run on threads other than the Main Thread and
   //  the Camera Thread. On Gonk, it is called from the camera driver's
   //  preview thread.
   RwLockAutoEnterRead lock(mListenerLock);
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -15,16 +15,20 @@
 #include "ICameraControl.h"
 #include "CameraCommon.h"
 #include "DeviceStorage.h"
 #include "DeviceStorageFileDescriptor.h"
 #include "CameraControlListener.h"
 
 namespace mozilla {
 
+namespace dom {
+  class BlobImpl;
+}
+
 namespace layers {
   class Image;
 }
 
 class CameraControlImpl : public ICameraControl
 {
 public:
   explicit CameraControlImpl();
@@ -52,16 +56,17 @@ public:
   void OnSystemError(CameraControlListener::SystemContext aContext, nsresult aError);
   void OnAutoFocusMoving(bool aIsMoving);
 
 protected:
   // Event handlers.
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnFacesDetected(const nsTArray<Face>& aFaces);
   void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
+  void OnPoster(dom::BlobImpl* aBlobImpl);
 
   void OnRateLimitPreview(bool aLimit);
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState,
                              int32_t aStatus = -1, int32_t aTrackNumber = -1);
   void OnPreviewStateChange(CameraControlListener::PreviewState aState);
   void OnHardwareStateChange(CameraControlListener::HardwareState aState,
                              nsresult aReason);
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -5,16 +5,20 @@
 #ifndef DOM_CAMERA_CAMERACONTROLLISTENER_H
 #define DOM_CAMERA_CAMERACONTROLLISTENER_H
 
 #include <stdint.h>
 #include "ICameraControl.h"
 
 namespace mozilla {
 
+namespace dom {
+  class BlobImpl;
+}
+
 namespace layers {
   class Image;
 }
 
 class CameraControlListener
 {
 public:
   CameraControlListener()
@@ -55,16 +59,18 @@ public:
     kPreviewStarted
   };
   virtual void OnPreviewStateChange(PreviewState aState) { }
 
   enum RecorderState
   {
     kRecorderStopped,
     kRecorderStarted,
+    kPosterCreated,
+    kPosterFailed,
 #ifdef MOZ_B2G_CAMERA
     kFileSizeLimitReached,
     kVideoLengthLimitReached,
     kTrackCompleted,
     kTrackFailed,
     kMediaRecorderFailed,
     kMediaServerFailed
 #endif
@@ -86,16 +92,17 @@ public:
     uint32_t mMaxFocusAreas;
   };
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
 
   virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
   virtual void OnAutoFocusMoving(bool aIsMoving) { }
   virtual void OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
   virtual void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces) { }
+  virtual void OnPoster(dom::BlobImpl* aBlobImpl) { }
 
   enum UserContext
   {
     kInStartCamera,
     kInStopCamera,
     kInAutoFocus,
     kInStartFaceDetection,
     kInStopFaceDetection,
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -77,50 +77,103 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOM
 
 /* static */
 bool
 nsDOMCameraControl::HasSupport(JSContext* aCx, JSObject* aGlobal)
 {
   return Navigator::HasCameraSupport(aCx, aGlobal);
 }
 
+static nsresult
+RegisterStorageRequestEvents(DOMRequest* aRequest, nsIDOMEventListener* aListener)
+{
+  EventListenerManager* elm = aRequest->GetOrCreateListenerManager();
+  if (NS_WARN_IF(!elm)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  elm->AddEventListener(NS_LITERAL_STRING("success"), aListener, false, false);
+  elm->AddEventListener(NS_LITERAL_STRING("error"), aListener, false, false);
+  return NS_OK;
+}
+
 class mozilla::StartRecordingHelper : public nsIDOMEventListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
   explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
     : mDOMCameraControl(aDOMCameraControl)
+    , mState(false)
   {
     MOZ_COUNT_CTOR(StartRecordingHelper);
   }
 
 protected:
   virtual ~StartRecordingHelper()
   {
     MOZ_COUNT_DTOR(StartRecordingHelper);
+    mDOMCameraControl->OnCreatedFileDescriptor(mState);
   }
 
 protected:
   nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
+  bool mState;
 };
 
 NS_IMETHODIMP
 StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsString eventType;
   aEvent->GetType(eventType);
-
-  mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success"));
+  mState = eventType.EqualsLiteral("success");
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, nsIDOMEventListener)
 
+class mozilla::RecorderPosterHelper : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  explicit RecorderPosterHelper(nsDOMCameraControl* aDOMCameraControl)
+    : mDOMCameraControl(aDOMCameraControl)
+    , mState(CameraControlListener::kPosterFailed)
+  {
+    MOZ_COUNT_CTOR(RecorderPosterHelper);
+  }
+
+protected:
+  virtual ~RecorderPosterHelper()
+  {
+    MOZ_COUNT_DTOR(RecorderPosterHelper);
+    mDOMCameraControl->OnRecorderStateChange(mState, 0, 0);
+  }
+
+protected:
+  nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
+  CameraControlListener::RecorderState mState;
+};
+
+NS_IMETHODIMP
+RecorderPosterHelper::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsString eventType;
+  aEvent->GetType(eventType);
+  if (eventType.EqualsLiteral("success")) {
+    mState = CameraControlListener::kPosterCreated;
+  }
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(mozilla::RecorderPosterHelper, nsIDOMEventListener)
+
 nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration()
   : CameraConfiguration()
   , mMaxFocusAreas(0)
   , mMaxMeteringAreas(0)
 {
   MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
 }
 
@@ -197,16 +250,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
                                        nsPIDOMWindow* aWindow)
   : DOMMediaStream()
   , mCameraControl(nullptr)
   , mAudioChannelAgent(nullptr)
   , mGetCameraPromise(aPromise)
   , mWindow(aWindow)
   , mPreviewState(CameraControlListener::kPreviewStopped)
   , mRecording(false)
+  , mRecordingStoppedDeferred(false)
   , mSetInitialConfig(false)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mInput = new CameraPreviewMediaStream(this);
 
   BindToOwner(aWindow);
 
   nsRefPtr<DOMCameraConfiguration> initialConfig =
@@ -738,17 +792,28 @@ nsDOMCameraControl::StartRecording(const
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 
   nsRefPtr<Promise> promise = CreatePromise(aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  if (mStartRecordingPromise || mRecording) {
+  // Must supply both the poster path and storage area or neither
+  if (aOptions.mPosterFilepath.IsEmpty() ==
+      static_cast<bool>(aOptions.mPosterStorageArea.get())) {
+    promise->MaybeReject(NS_ERROR_ILLEGAL_VALUE);
+    return promise.forget();
+  }
+
+  // If we are trying to start recording, already recording or are still
+  // waiting for a poster to be created/fail, we need to wait
+  if (mStartRecordingPromise || mRecording ||
+      mRecordingStoppedDeferred ||
+      !mOptions.mPosterFilepath.IsEmpty()) {
     promise->MaybeReject(NS_ERROR_IN_PROGRESS);
     return promise.forget();
   }
 
   aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
   if (aRv.Failed()) {
     return nullptr;
   }
@@ -757,51 +822,50 @@ nsDOMCameraControl::StartRecording(const
   nsRefPtr<DOMRequest> request = aStorageArea.CreateFileDescriptor(aFilename,
                                                                    mDSFileDescriptor.get(),
                                                                    aRv);
   if (aRv.Failed()) {
     NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
     return nullptr;
   }
 
-  mStartRecordingPromise = promise;
-  mOptions = aOptions;
-
-  EventListenerManager* elm = request->GetOrCreateListenerManager();
-  if (!elm) {
+  nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
+  aRv = RegisterStorageRequestEvents(request, listener);
+  if (aRv.Failed()) {
     NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
-    aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  mStartRecordingPromise = promise;
+  mOptions = aOptions;
   mRecording = true;
-  nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
-  elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false);
-  elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false);
   return promise.forget();
 }
 
 void
 nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   if (!mCameraControl) {
     rv = NS_ERROR_NOT_AVAILABLE;
   } else if (!mRecording) {
     // Race condition where StopRecording comes in before we issue
     // the start recording request to Gonk
     rv = NS_ERROR_ABORT;
+    mOptions.mPosterFilepath.Truncate();
+    mOptions.mPosterStorageArea = nullptr;
   } else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
     ICameraControl::StartRecordingOptions o;
 
     o.rotation = mOptions.mRotation;
     o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
     o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
     o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch;
+    o.createPoster = !mOptions.mPosterFilepath.IsEmpty();
     rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
     if (NS_SUCCEEDED(rv)) {
       return;
     }
   }
 
   OnUserError(CameraControlListener::kInStartRecording, rv);
 
@@ -1250,16 +1314,48 @@ nsDOMCameraControl::OnPreviewStateChange
       state = NS_LITERAL_STRING("stopped");
       break;
   }
 
   DispatchPreviewStateEvent(aState);
 }
 
 void
+nsDOMCameraControl::OnPoster(BlobImpl* aPoster)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mOptions.mPosterFilepath.IsEmpty());
+
+  // Destructor will trigger an error notification if any step fails
+  nsRefPtr<RecorderPosterHelper> listener = new RecorderPosterHelper(this);
+  if (NS_WARN_IF(!aPoster)) {
+    return;
+  }
+
+  nsRefPtr<Blob> blob = Blob::Create(GetParentObject(), aPoster);
+  if (NS_WARN_IF(!blob)) {
+    return;
+  }
+
+  if (NS_WARN_IF(!mOptions.mPosterStorageArea)) {
+    return;
+  }
+
+  ErrorResult rv;
+  nsRefPtr<DOMRequest> request =
+    mOptions.mPosterStorageArea->AddNamed(blob, mOptions.mPosterFilepath, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return;
+  }
+
+  RegisterStorageRequestEvents(request, listener);
+}
+
+void
 nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
                                           int32_t aArg, int32_t aTrackNum)
 {
   // For now, we do nothing with 'aStatus' and 'aTrackNum'.
   DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState);
   MOZ_ASSERT(NS_IsMainThread());
 
   ErrorResult ignored;
@@ -1273,20 +1369,37 @@ nsDOMCameraControl::OnRecorderStateChang
           promise->MaybeResolve(JS::UndefinedHandleValue);
         }
 
         state = NS_LITERAL_STRING("Started");
       }
       break;
 
     case CameraControlListener::kRecorderStopped:
+      if (!mOptions.mPosterFilepath.IsEmpty()) {
+        mRecordingStoppedDeferred = true;
+        return;
+      }
+
       NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
       state = NS_LITERAL_STRING("Stopped");
       break;
 
+    case CameraControlListener::kPosterCreated:
+      state = NS_LITERAL_STRING("PosterCreated");
+      mOptions.mPosterFilepath.Truncate();
+      mOptions.mPosterStorageArea = nullptr;
+      break;
+
+    case CameraControlListener::kPosterFailed:
+      state = NS_LITERAL_STRING("PosterFailed");
+      mOptions.mPosterFilepath.Truncate();
+      mOptions.mPosterStorageArea = nullptr;
+      break;
+
 #ifdef MOZ_B2G_CAMERA
     case CameraControlListener::kFileSizeLimitReached:
       state = NS_LITERAL_STRING("FileSizeLimitReached");
       break;
 
     case CameraControlListener::kVideoLengthLimitReached:
       state = NS_LITERAL_STRING("VideoLengthLimitReached");
       break;
@@ -1309,16 +1422,21 @@ nsDOMCameraControl::OnRecorderStateChang
 #endif
 
     default:
       MOZ_ASSERT_UNREACHABLE("Unanticipated video recorder error");
       return;
   }
 
   DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state);
+
+  if (mRecordingStoppedDeferred && mOptions.mPosterFilepath.IsEmpty()) {
+    mRecordingStoppedDeferred = false;
+    OnRecorderStateChange(CameraControlListener::kRecorderStopped, 0, 0);
+  }
 }
 
 void
 nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aConfiguration != nullptr);
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -34,16 +34,17 @@ namespace dom {
   struct CameraPictureOptions;
   struct CameraStartRecordingOptions;
   struct CameraRegion;
   struct CameraSize;
   template<typename T> class Optional;
 }
 class ErrorResult;
 class StartRecordingHelper;
+class RecorderPosterHelper;
 
 #define NS_DOM_CAMERA_CONTROL_CID \
 { 0x3700c096, 0xf920, 0x438d, \
   { 0x8b, 0x3f, 0x15, 0xb3, 0xc9, 0x96, 0x23, 0x62 } }
 
 // Main camera control.
 class nsDOMCameraControl final : public DOMMediaStream
                                , public nsSupportsWeakReference
@@ -158,23 +159,25 @@ protected:
 
   private:
     // Private destructor, to discourage deletion outside of Release():
     ~DOMCameraConfiguration();
   };
 
   friend class DOMCameraControlListener;
   friend class mozilla::StartRecordingHelper;
+  friend class mozilla::RecorderPosterHelper;
 
   void OnCreatedFileDescriptor(bool aSucceeded);
 
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnAutoFocusMoving(bool aIsMoving);
   void OnTakePictureComplete(nsIDOMBlob* aPicture);
   void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
+  void OnPoster(dom::BlobImpl* aPoster);
 
   void OnGetCameraComplete();
   void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason);
   void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
   void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
   void OnShutter();
   void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
@@ -220,16 +223,17 @@ protected:
 
   // set once when this object is created
   nsCOMPtr<nsPIDOMWindow>   mWindow;
 
   dom::CameraStartRecordingOptions mOptions;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
   DOMCameraControlListener::PreviewState mPreviewState;
   bool mRecording;
+  bool mRecordingStoppedDeferred;
   bool mSetInitialConfig;
 
 #ifdef MOZ_WIDGET_GONK
   // cached camera control, to improve start-up time
   static StaticRefPtr<ICameraControl> sCachedCameraControl;
   static nsresult sCachedCameraControlStartResult;
   static nsCOMPtr<nsITimer> sDiscardCachedCameraControlTimer;
 #endif
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -405,8 +405,32 @@ DOMCameraControlListener::OnUserError(Us
 
   protected:
     UserContext mContext;
     nsresult mError;
   };
 
   NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError));
 }
+
+void
+DOMCameraControlListener::OnPoster(BlobImpl* aBlobImpl)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsISupports> aDOMCameraControl, BlobImpl* aBlobImpl)
+      : DOMCallback(aDOMCameraControl)
+      , mBlobImpl(aBlobImpl)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) override
+    {
+      aDOMCameraControl->OnPoster(mBlobImpl);
+    }
+
+  protected:
+    nsRefPtr<BlobImpl> mBlobImpl;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aBlobImpl));
+}
--- a/dom/camera/DOMCameraControlListener.h
+++ b/dom/camera/DOMCameraControlListener.h
@@ -26,16 +26,17 @@ public:
   virtual void OnHardwareStateChange(HardwareState aState, nsresult aReason) override;
   virtual void OnPreviewStateChange(PreviewState aState) override;
   virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) override;
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) override;
   virtual void OnShutter() override;
   virtual void OnRateLimitPreview(bool aLimit) override;
   virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) override;
   virtual void OnUserError(UserContext aContext, nsresult aError) override;
+  virtual void OnPoster(dom::BlobImpl* aBlobImpl) override;
 
 protected:
   virtual ~DOMCameraControlListener();
 
   nsMainThreadPtrHandle<nsISupports> mDOMCameraControl;
   CameraPreviewMediaStream* mStream;
 
   class DOMCallback;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -22,35 +22,40 @@
 #include <errno.h>
 #include <libgen.h>
 #include "base/basictypes.h"
 #include "Layers.h"
 #ifdef MOZ_WIDGET_GONK
 #include <media/mediaplayer.h>
 #include <media/MediaProfiles.h>
 #include "GrallocImages.h"
+#include "imgIEncoder.h"
+#include "libyuv.h"
+#include "nsNetUtil.h" // for NS_ReadInputStreamToBuffer
 #endif
+#include "nsNetCID.h" // for NS_STREAMTRANSPORTSERVICE_CONTRACTID
+#include "nsAutoPtr.h" // for nsAutoArrayPtr
 #include "nsCOMPtr.h"
 #include "nsMemory.h"
 #include "nsThread.h"
 #include "nsITimer.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "nsAlgorithm.h"
 #include "nsPrintfCString.h"
-#include "AutoRwLock.h"
 #include "GonkCameraHwMgr.h"
 #include "GonkRecorderProfiles.h"
 #include "CameraCommon.h"
 #include "GonkCameraParameters.h"
 #include "DeviceStorageFileDescriptor.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 using namespace mozilla::ipc;
 using namespace android;
 
 #define RETURN_IF_NO_CAMERA_HW()                                          \
   do {                                                                    \
     if (!mCameraHw.get()) {                                               \
@@ -74,16 +79,17 @@ nsGonkCameraControl::nsGonkCameraControl
   , mAutoFlashModeOverridden(false)
   , mSeparateVideoAndPreviewSizesSupported(false)
   , mDeferConfigUpdate(0)
 #ifdef MOZ_WIDGET_GONK
   , mRecorder(nullptr)
 #endif
   , mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
   , mVideoFile(nullptr)
+  , mCapturePoster(false)
   , mAutoFocusPending(false)
   , mAutoFocusCompleteExpired(0)
   , mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mImageContainer = LayerManager::CreateImageContainer();
 
@@ -1247,16 +1253,17 @@ nsGonkCameraControl::StartRecordingImpl(
     if (mAutoFlashModeOverridden) {
       SetAndPush(CAMERA_PARAM_FLASHMODE, NS_LITERAL_STRING("auto"));
     }
     return NS_ERROR_FAILURE;
   }
 #endif
 
   OnRecorderStateChange(CameraControlListener::kRecorderStarted);
+  mCapturePoster = aOptions->createPoster;
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::StopRecordingImpl()
 {
   class RecordingComplete : public nsRunnable
   {
@@ -1291,16 +1298,19 @@ nsGonkCameraControl::StopRecordingImpl()
 
   mRecorder->stop();
   mRecorder = nullptr;
 #else
   if (!mVideoFile) {
     return NS_OK;
   }
 #endif
+  if (mCapturePoster.exchange(false)) {
+    OnPoster(nullptr, 0);
+  }
   OnRecorderStateChange(CameraControlListener::kRecorderStopped);
 
   {
     ICameraControlParameterSetAutoEnter set(this);
 
     if (mAutoFlashModeOverridden) {
       nsresult rv = Set(CAMERA_PARAM_FLASHMODE, NS_LITERAL_STRING("auto"));
       if (NS_FAILED(rv)) {
@@ -2013,21 +2023,23 @@ nsGonkCameraControl::SetupRecording(int 
     DOM_CAMERA_LOGW("maxFileSizeBytes capped to INT64_MAX\n");
     aMaxFileSizeBytes = INT64_MAX;
   }
   snprintf(buffer, SIZE, "max-filesize=%lld", aMaxFileSizeBytes);
   CHECK_SETARG_RETURN(mRecorder->setParameters(String8(buffer)),
                       NS_ERROR_INVALID_ARG);
 
   // adjust rotation by camera sensor offset
-  int r = aRotation;
-  r += mCameraHw->GetSensorOrientation();
-  r = RationalizeRotation(r);
-  DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n", r, aRotation);
-  snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", r);
+  mVideoRotation = aRotation;
+  mVideoRotation += mCameraHw->GetSensorOrientation();
+  mVideoRotation = RationalizeRotation(mVideoRotation);
+  DOM_CAMERA_LOGI("setting video rotation to %d degrees (mapped from %d)\n",
+                  mVideoRotation, aRotation);
+  snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d",
+           mVideoRotation);
   CHECK_SETARG_RETURN(mRecorder->setParameters(String8(buffer)),
                       NS_ERROR_INVALID_ARG);
 
   CHECK_SETARG_RETURN(mRecorder->setListener(new GonkRecorderListener(this)),
                       NS_ERROR_FAILURE);
 
   // recording API needs file descriptor of output file
   CHECK_SETARG_RETURN(mRecorder->setOutputFile(aFd, 0, 0), NS_ERROR_FAILURE);
@@ -2161,29 +2173,168 @@ nsGonkCameraControl::GetProfileInfo(cons
 
 void
 nsGonkCameraControl::OnRateLimitPreview(bool aLimit)
 {
   CameraControlImpl::OnRateLimitPreview(aLimit);
 }
 
 void
+nsGonkCameraControl::CreatePoster(Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation)
+{
+  class PosterRunnable : public nsRunnable {
+  public:
+    PosterRunnable(nsGonkCameraControl* aTarget, Image* aImage,
+                   uint32_t aWidth, uint32_t aHeight, int32_t aRotation)
+      : mTarget(aTarget)
+      , mImage(aImage)
+      , mWidth(aWidth)
+      , mHeight(aHeight)
+      , mRotation(aRotation)
+      , mDst(nullptr)
+      , mDstLength(0)
+    { }
+
+    virtual ~PosterRunnable()
+    {
+      mTarget->OnPoster(mDst, mDstLength);
+    }
+
+    NS_IMETHODIMP Run() override
+    {
+#ifdef MOZ_WIDGET_GONK
+      // NV21 (yuv420sp) is 12 bits / pixel
+      size_t srcLength = (mWidth * mHeight * 3 + 1) / 2;
+
+      // ARGB is 32 bits / pixel
+      size_t tmpLength = mWidth * mHeight * sizeof(uint32_t);
+      nsAutoArrayPtr<uint8_t> tmp;
+      tmp = new uint8_t[tmpLength];
+
+      GrallocImage* nativeImage = static_cast<GrallocImage*>(mImage.get());
+      android::sp<GraphicBuffer> graphicBuffer = nativeImage->GetGraphicBuffer();
+
+      void* graphicSrc = nullptr;
+      graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &graphicSrc);
+
+      uint32_t stride = mWidth * 4;
+      int err = libyuv::ConvertToARGB(static_cast<uint8_t*>(graphicSrc),
+                                      srcLength, tmp, stride, 0, 0,
+                                      mWidth, mHeight, mWidth, mHeight,
+                                      libyuv::kRotate0, libyuv::FOURCC_NV21);
+
+      graphicBuffer->unlock();
+      graphicSrc = nullptr;
+      graphicBuffer.clear();
+      nativeImage = nullptr;
+      mImage = nullptr;
+
+      if (NS_WARN_IF(err < 0)) {
+        DOM_CAMERA_LOGE("CreatePoster: to ARGB failed (%d)\n", err);
+        return NS_OK;
+      }
+
+      nsCOMPtr<imgIEncoder> encoder =
+        do_CreateInstance("@mozilla.org/image/encoder;2?type=image/jpeg");
+      if (NS_WARN_IF(!encoder)) {
+        DOM_CAMERA_LOGE("CreatePoster: no JPEG encoder\n");
+        return NS_OK;
+      }
+
+      nsString opt;
+      nsresult rv = encoder->InitFromData(tmp, tmpLength, mWidth,
+                                          mHeight, stride,
+                                          imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                                          opt);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        DOM_CAMERA_LOGE("CreatePoster: encoder init failed (0x%x)\n",
+                        rv);
+        return NS_OK;
+      }
+
+      nsCOMPtr<nsIInputStream> stream = do_QueryInterface(encoder);
+      if (NS_WARN_IF(!stream)) {
+        DOM_CAMERA_LOGE("CreatePoster: to input stream failed\n");
+        return NS_OK;
+      }
+
+      uint64_t length = 0;
+      rv = stream->Available(&length);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        DOM_CAMERA_LOGE("CreatePoster: get length failed (0x%x)\n",
+                        rv);
+        return NS_OK;
+      }
+
+      rv = NS_ReadInputStreamToBuffer(stream, &mDst, length);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        DOM_CAMERA_LOGE("CreatePoster: read failed (0x%x)\n", rv);
+        mDst = nullptr;
+        return NS_OK;
+      }
+
+      mDstLength = length;
+#endif
+      return NS_OK;
+    }
+
+  private:
+    nsRefPtr<nsGonkCameraControl> mTarget;
+    nsRefPtr<Image> mImage;
+    int32_t mWidth;
+    int32_t mHeight;
+    int32_t mRotation;
+    void* mDst;
+    size_t mDstLength;
+  };
+
+  nsCOMPtr<nsIRunnable> event = new PosterRunnable(this, aImage,
+                                                   aWidth,
+                                                   aHeight,
+                                                   aRotation);
+
+  nsCOMPtr<nsIEventTarget> target
+    = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+
+  target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+nsGonkCameraControl::OnPoster(void* aData, uint32_t aLength)
+{
+  nsRefPtr<BlobImpl> blobImpl;
+  if (aData) {
+    blobImpl = new BlobImplMemory(aData, aLength, NS_LITERAL_STRING("image/jpeg"));
+  }
+  CameraControlImpl::OnPoster(blobImpl);
+}
+
+void
 nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer)
 {
 #ifdef MOZ_WIDGET_GONK
   nsRefPtr<Image> frame = mImageContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
 
   GrallocImage* videoImage = static_cast<GrallocImage*>(frame.get());
 
   GrallocImage::GrallocData data;
   data.mGraphicBuffer = aBuffer;
   data.mPicSize = IntSize(mCurrentConfiguration.mPreviewSize.width,
                           mCurrentConfiguration.mPreviewSize.height);
   videoImage->SetData(data);
 
+  if (mCapturePoster.exchange(false)) {
+    CreatePoster(frame,
+                 mCurrentConfiguration.mPreviewSize.width,
+                 mCurrentConfiguration.mPreviewSize.height,
+                 mVideoRotation);
+    return;
+  }
+
   OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width,
                     mCurrentConfiguration.mPreviewSize.height);
 #endif
 }
 
 void
 nsGonkCameraControl::OnSystemError(CameraControlListener::SystemContext aWhere,
                                    nsresult aError)
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -42,29 +42,31 @@ namespace android {
   class GonkCameraSource;
 }
 
 namespace mozilla {
 
 namespace layers {
   class TextureClient;
   class ImageContainer;
+  class Image;
 }
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId);
 
   void OnAutoFocusMoving(bool aIsMoving);
   void OnAutoFocusComplete(bool aSuccess, bool aExpired);
   void OnFacesDetected(camera_frame_metadata_t* aMetaData);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
   void OnTakePictureError();
   void OnRateLimitPreview(bool aLimit);
+  void OnPoster(void* aData, uint32_t aLength);
   void OnNewPreviewFrame(layers::TextureClient* aBuffer);
 #ifdef MOZ_WIDGET_GONK
   void OnRecorderEvent(int msg, int ext1, int ext2);
 #endif
   void OnSystemError(CameraControlListener::SystemContext aWhere, nsresult aError);
 
   // See ICameraControl.h for getter/setter return values.
   virtual nsresult Set(uint32_t aKey, const nsAString& aValue) override;
@@ -144,16 +146,18 @@ protected:
                           uint64_t aMaxVideoLengthMs);
   nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch);
   nsresult SelectCaptureAndPreviewSize(const Size& aPreviewSize, const Size& aCaptureSize,
                                        const Size& aMaxSize, uint32_t aCaptureSizeKey);
   nsresult MaybeAdjustVideoSize();
   nsresult PausePreview();
   nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
 
+  void CreatePoster(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight, int32_t aRotation);
+
   nsresult LoadRecorderProfiles();
   static PLDHashOperator Enumerate(const nsAString& aProfileName,
                                    RecorderProfile* aProfile,
                                    void* aUserArg);
 
   friend class SetPictureSize;
   friend class SetThumbnailSize;
   nsresult SetPictureSize(const Size& aSize);
@@ -193,16 +197,19 @@ protected:
   ReentrantMonitor          mRecorderMonitor;
 
   // Supported recorder profiles
   nsRefPtrHashtable<nsStringHashKey, RecorderProfile> mRecorderProfiles;
 
   nsRefPtr<DeviceStorageFile> mVideoFile;
   nsString                  mFileFormat;
 
+  Atomic<bool>              mCapturePoster;
+  int32_t                   mVideoRotation;
+
   bool                      mAutoFocusPending;
   nsCOMPtr<nsITimer>        mAutoFocusCompleteTimer;
   int32_t                   mAutoFocusCompleteExpired;
 
   // Guards against calling StartPreviewImpl() while in OnTakePictureComplete().
   ReentrantMonitor          mReentrantMonitor;
 
 private:
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -48,16 +48,17 @@ GonkCameraHardware::GonkCameraHardware(m
   , mClosing(false)
   , mNumFrames(0)
 #ifdef MOZ_WIDGET_GONK
   , mCamera(aCamera)
 #endif
   , mTarget(aTarget)
   , mRawSensorOrientation(0)
   , mSensorOrientation(0)
+  , mEmulated(false)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n", __func__, __LINE__, (void*)this, (void*)aTarget);
 }
 
 void
 GonkCameraHardware::OnRateLimitPreview(bool aLimit)
 {
   ::OnRateLimitPreview(mTarget, aLimit);
@@ -189,16 +190,21 @@ GonkCameraHardware::Init()
   snprintf(propname, sizeof(propname), "ro.moz.cam.%d.sensor_offset", mCameraId);
   if (__system_property_get(propname, prop) > 0) {
     offset = clamped(atoi(prop), 0, 270);
     mSensorOrientation += offset;
     mSensorOrientation %= 360;
   }
   DOM_CAMERA_LOGI("Sensor orientation: base=%d, offset=%d, final=%d\n", info.orientation, offset, mSensorOrientation);
 
+  if (__system_property_get("ro.kernel.qemu", prop) > 0 && atoi(prop)) {
+    DOM_CAMERA_LOGI("Using emulated camera\n");
+    mEmulated = true;
+  }
+
   // Disable shutter sound in android CameraService because gaia camera app will play it
   mCamera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, 0, 0);
 
 #if ANDROID_VERSION >= 21
   sp<IGraphicBufferProducer> producer;
   sp<IGonkGraphicBufferConsumer> consumer;
   GonkBufferQueue::createBufferQueue(&producer, &consumer);
   static_cast<GonkBufferQueueProducer*>(producer.get())->setSynchronousMode(false);
@@ -317,16 +323,22 @@ GonkCameraHardware::GetSensorOrientation
       return mRawSensorOrientation;
 
     default:
       DOM_CAMERA_LOGE("%s:%d : unknown aType=%d\n", __func__, __LINE__, aType);
       return 0;
   }
 }
 
+bool
+GonkCameraHardware::IsEmulated()
+{
+  return mEmulated;
+}
+
 int
 GonkCameraHardware::AutoFocus()
 {
   DOM_CAMERA_LOGI("%s\n", __func__);
   if (NS_WARN_IF(mClosing)) {
     return DEAD_OBJECT;
   }
   return mCamera->autoFocus();
--- a/dom/camera/GonkCameraHwMgr.h
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -94,16 +94,18 @@ public:
    * orientation.
    */
   enum {
     RAW_SENSOR_ORIENTATION,
     OFFSET_SENSOR_ORIENTATION
   };
   virtual int      GetSensorOrientation(uint32_t aType = RAW_SENSOR_ORIENTATION);
 
+  virtual bool     IsEmulated();
+
   /**
    * MIN_UNDEQUEUED_BUFFERS has increased to 4 since Android JB. For FFOS, more
    * than 3 gralloc buffers are necessary between ImageHost and GonkBufferQueue
    * for consuming preview stream. To keep the stability for older platform, we
    * set MIN_UNDEQUEUED_BUFFERS to 4 only in Android KK base.
    * See also bug 988704.
    */
   enum { MIN_UNDEQUEUED_BUFFERS = 4};
@@ -135,16 +137,17 @@ protected:
   sp<Camera>                    mCamera;
   mozilla::nsGonkCameraControl* mTarget;
 #ifdef MOZ_WIDGET_GONK
   sp<GonkNativeWindow>          mNativeWindow;
   sp<GonkCameraListener>        mListener;
 #endif
   int                           mRawSensorOrientation;
   int                           mSensorOrientation;
+  bool                          mEmulated;
 
 private:
   GonkCameraHardware(const GonkCameraHardware&) = delete;
   GonkCameraHardware& operator=(const GonkCameraHardware&) = delete;
 };
 
 } // namespace android
 
--- a/dom/camera/GonkRecorder.cpp
+++ b/dom/camera/GonkRecorder.cpp
@@ -1267,17 +1267,19 @@ status_t GonkRecorder::setupVideoEncoder
     }
 
     // OMXClient::connect() always returns OK and abort's fatally if
     // it can't connect.
     OMXClient client;
     // CHECK_EQ causes an abort if the given condition fails.
     CHECK_EQ(client.connect(), (status_t)OK);
 
-    uint32_t encoder_flags = OMXCodec::kHardwareCodecsOnly;
+    uint32_t encoder_flags = mCameraHw->IsEmulated()
+                             ? 0
+                             : OMXCodec::kHardwareCodecsOnly;
     if (mIsMetaDataStoredInVideoBuffers) {
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
         encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers;
 #else
         encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers;
         encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime;
 #endif
     }
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -133,16 +133,17 @@ public:
     double    timestamp;
   };
 
   struct StartRecordingOptions {
     uint32_t  rotation;
     uint64_t  maxFileSizeBytes;
     uint64_t  maxVideoLengthMs;
     bool      autoEnableLowLightTorch;
+    bool      createPoster;
   };
 
   struct Configuration {
     Mode      mMode;
     Size      mPreviewSize;
     Size      mPictureSize;
     nsString  mRecorderProfile;
   };
--- a/dom/camera/moz.build
+++ b/dom/camera/moz.build
@@ -60,16 +60,17 @@ else:
         'FallbackCameraControl.cpp',
         'FallbackCameraManager.cpp',
     ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../base',
+    '/media/libyuv/include',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 # Suppress some GCC warnings being treated as errors:
 #  - about attributes on forward declarations for types that are already
 #    defined, which complains about an important MOZ_EXPORT for android::AString
 if CONFIG['GNU_CC']:
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -82,16 +82,17 @@
 #include "mozilla/Endian.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/gfx/PatternHelpers.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
 #include "mozilla/ipc/PDocumentRendererParent.h"
+#include "mozilla/layers/PersistentBufferProvider.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/unused.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsWrapperCacheInlines.h"
@@ -815,16 +816,17 @@ public:
       static_cast<CanvasRenderingContext2DUserData*>(aData);
     CanvasRenderingContext2D* context = self->mContext;
     if (!context || !context->mTarget)
       return;
 
     // Since SkiaGL default to store drawing command until flush
     // We will have to flush it before present.
     context->mTarget->Flush();
+    context->ReturnTarget();
   }
 
   static void DidTransactionCallback(void* aData)
   {
     CanvasRenderingContext2DUserData* self =
       static_cast<CanvasRenderingContext2DUserData*>(aData);
     if (self->mContext) {
       self->mContext->MarkContextClean();
@@ -1029,17 +1031,19 @@ CanvasRenderingContext2D::Reset()
   }
 
   // only do this for non-docshell created contexts,
   // since those are the ones that we created a surface for
   if (mTarget && IsTargetValid() && !mDocShell) {
     gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
   }
 
+  ReturnTarget();
   mTarget = nullptr;
+  mBufferProvider = nullptr;
 
   // reset hit regions
   mHitRegionsOptions.ClearAndRetainStorage();
 
   // Since the target changes the backing texture will change, and this will
   // no longer be valid.
   mIsEntireFrameInvalid = false;
   mPredictManyRedrawCalls = false;
@@ -1181,19 +1185,28 @@ bool CanvasRenderingContext2D::SwitchRen
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
     }
 	  mCurrentVideoSize.width = 0;
 	  mCurrentVideoSize.height = 0;
   }
 #endif
 
-  RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
-  RefPtr<DrawTarget> oldTarget = mTarget;
+  RefPtr<SourceSurface> snapshot;
+  Matrix transform;
+
+  if (mTarget) {
+    snapshot = mTarget->Snapshot();
+    transform = mTarget->GetTransform();
+  } else {
+    MOZ_ASSERT(mBufferProvider);
+    snapshot = mBufferProvider->GetSnapshot();
+  }
   mTarget = nullptr;
+  mBufferProvider = nullptr;
   mResetLayer = true;
 
   // Recreate target using the new rendering mode
   RenderingMode attemptedMode = EnsureTarget(aRenderingMode);
   if (!IsTargetValid())
     return false;
 
   // We succeeded, so update mRenderingMode to reflect reality
@@ -1203,17 +1216,17 @@ bool CanvasRenderingContext2D::SwitchRen
   mgfx::Rect r(0, 0, mWidth, mHeight);
   mTarget->DrawSurface(snapshot, r, r);
 
   // Restore the clips and transform
   for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
     mTarget->PushClip(CurrentState().clipsPushed[i]);
   }
 
-  mTarget->SetTransform(oldTarget->GetTransform());
+  mTarget->SetTransform(transform);
 
   return true;
 }
 
 void CanvasRenderingContext2D::Demote()
 {
   if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
     RemoveDemotableContext(this);
@@ -1327,16 +1340,25 @@ CanvasRenderingContext2D::EnsureTarget(R
   MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode);
 
   RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
 
   if (mTarget && mode == mRenderingMode) {
     return mRenderingMode;
   }
 
+  if (mBufferProvider && mode == mRenderingMode) {
+    mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), IntSize(mWidth, mHeight)));
+    if (mTarget) {
+      return mRenderingMode;
+    } else {
+      mBufferProvider = nullptr;
+    }
+  }
+
    // Check that the dimensions are sane
   IntSize size(mWidth, mHeight);
   if (size.width <= gfxPrefs::MaxCanvasSize() &&
       size.height <= gfxPrefs::MaxCanvasSize() &&
       size.width >= 0 && size.height >= 0) {
     SurfaceFormat format = GetSurfaceFormat();
     nsIDocument* ownerDoc = nullptr;
     if (mCanvasElement) {
@@ -1345,46 +1367,49 @@ CanvasRenderingContext2D::EnsureTarget(R
 
     nsRefPtr<LayerManager> layerManager = nullptr;
 
     if (ownerDoc) {
       layerManager =
         nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
     }
 
-     if (layerManager) {
+    if (layerManager) {
       if (mode == RenderingMode::OpenGLBackendMode &&
           gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() &&
           CheckSizeForSkiaGL(size)) {
         DemoteOldestContextIfNecessary();
+        mBufferProvider = nullptr;
 
 #if USE_SKIA_GPU
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
 
         if (glue && glue->GetGrContext() && glue->GetGLContext()) {
           mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
           if (mTarget) {
             AddDemotableContext(this);
           } else {
             printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n");
             mode = RenderingMode::SoftwareBackendMode;
           }
         }
 #endif
-        if (!mTarget) {
-          mTarget = layerManager->CreateDrawTarget(size, format);
-        }
-      } else {
-        mTarget = layerManager->CreateDrawTarget(size, format);
-        mode = RenderingMode::SoftwareBackendMode;
+      }
+
+      if (!mBufferProvider) {
+        mBufferProvider = layerManager->CreatePersistentBufferProvider(size, format);
       }
-     } else {
-        mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
-        mode = RenderingMode::SoftwareBackendMode;
-     }
+    }
+
+    if (mBufferProvider) {
+      mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), IntSize(mWidth, mHeight)));
+    } else if (!mTarget) {
+      mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
+      mode = RenderingMode::SoftwareBackendMode;
+    }
   }
 
   if (mTarget) {
     static bool registered = false;
     if (!registered) {
       registered = true;
       RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
     }
@@ -1491,16 +1516,27 @@ CanvasRenderingContext2D::ClearTarget()
         if (wm.IsVertical() && !wm.IsSideways()) {
           state->textBaseline = TextBaseline::MIDDLE;
         }
       }
     }
   }
 }
 
+void
+CanvasRenderingContext2D::ReturnTarget()
+{
+  if (mTarget && mBufferProvider) {
+    CurrentState().transform = mTarget->GetTransform();
+    DrawTarget* oldDT = mTarget;
+    mTarget = nullptr;
+    mBufferProvider->ReturnAndUseDT(oldDT);
+  }
+}
+
 NS_IMETHODIMP
 CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell,
                                                 gfxASurface *surface,
                                                 int32_t width,
                                                 int32_t height)
 {
   RemovePostRefreshObserver();
   mDocShell = shell;
@@ -2377,19 +2413,17 @@ CanvasRenderingContext2D::UpdateFilter()
 //
 // rects
 //
 
 void
 CanvasRenderingContext2D::ClearRect(double x, double y, double w,
                                     double h)
 {
-  if (!mTarget) {
-    return;
-  }
+  EnsureTarget();
 
   mTarget->ClearRect(mgfx::Rect(x, y, w, h));
 
   RedrawUser(gfxRect(x, y, w, h));
 }
 
 void
 CanvasRenderingContext2D::FillRect(double x, double y, double w,
@@ -2540,16 +2574,17 @@ CanvasRenderingContext2D::BeginPath()
   mPathBuilder = nullptr;
   mDSPathBuilder = nullptr;
   mPathTransformWillUpdate = false;
 }
 
 void
 CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding)
 {
+  EnsureTarget();
   EnsureUserSpacePath(winding);
 
   if (!mPath) {
     return;
   }
 
   mgfx::Rect bounds;
 
@@ -2585,16 +2620,17 @@ void CanvasRenderingContext2D::Fill(cons
          DrawOptions(CurrentState().globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void
 CanvasRenderingContext2D::Stroke()
 {
+  EnsureTarget();
   EnsureUserSpacePath();
 
   if (!mPath) {
     return;
   }
 
   const ContextState &state = CurrentState();
 
@@ -2720,16 +2756,18 @@ bool CanvasRenderingContext2D::DrawCusto
   }
 
   return false;
 }
 
 void
 CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding)
 {
+  EnsureTarget();
+
   EnsureUserSpacePath(winding);
 
   if (!mPath) {
     return;
   }
 
   mTarget->PushClip(mPath);
   CurrentState().clipsPushed.push_back(mPath);
@@ -5151,16 +5189,17 @@ CanvasRenderingContext2D::PutImageData(I
 // void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight);
 
 nsresult
 CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
                                                 dom::Uint8ClampedArray* aArray,
                                                 bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
                                                 int32_t dirtyWidth, int32_t dirtyHeight)
 {
+  EnsureTarget();
   if (mDrawObserver) {
     mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData);
   }
 
   if (w == 0 || h == 0) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
@@ -5342,69 +5381,91 @@ CanvasRenderingContext2D::CreateImageDat
 }
 
 static uint8_t g2DContextLayerUserData;
 
 
 uint32_t
 CanvasRenderingContext2D::SkiaGLTex() const
 {
-    MOZ_ASSERT(IsTargetValid());
-    return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE);
+  if (!mTarget) {
+    return 0;
+  }
+  MOZ_ASSERT(IsTargetValid());
+  return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE);
 }
 
 void CanvasRenderingContext2D::RemoveDrawObserver()
 {
   if (mDrawObserver) {
     delete mDrawObserver;
     mDrawObserver = nullptr;
   }
 }
 
+PersistentBufferProvider*
+CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager)
+{
+  if (mBufferProvider) {
+    return mBufferProvider;
+  }
+
+  if (!mTarget) {
+    return nullptr;
+  }
+
+  mBufferProvider = aManager->CreatePersistentBufferProvider(mTarget->GetSize(), mTarget->GetFormat());
+
+  RefPtr<SourceSurface> surf = mTarget->Snapshot();
+
+  mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), mTarget->GetSize()));
+  mTarget->CopySurface(surf, IntRect(IntPoint(), mTarget->GetSize()), IntPoint());
+
+  return mBufferProvider;
+}
 
 already_AddRefed<CanvasLayer>
 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          CanvasLayer *aOldLayer,
                                          LayerManager *aManager)
 {
   if (mOpaque) {
     // If we're opaque then make sure we have a surface so we paint black
     // instead of transparent.
     EnsureTarget();
   }
 
   // Don't call EnsureTarget() ... if there isn't already a surface, then
   // we have nothing to paint and there is no need to create a surface just
   // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
   // layer manager which must NOT happen during a paint.
-  if (!mTarget || !IsTargetValid()) {
+  if ((!mBufferProvider && !mTarget) || !IsTargetValid()) {
     // No DidTransactionCallback will be received, so mark the context clean
     // now so future invalidations will be dispatched.
     MarkContextClean();
     return nullptr;
   }
 
-  mTarget->Flush();
-
   if (!mResetLayer && aOldLayer) {
     CanvasRenderingContext2DUserData* userData =
       static_cast<CanvasRenderingContext2DUserData*>(
         aOldLayer->GetUserData(&g2DContextLayerUserData));
 
     CanvasLayer::Data data;
 
     GLuint skiaGLTex = SkiaGLTex();
     if (skiaGLTex) {
       SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
       MOZ_ASSERT(glue);
 
       data.mGLContext = glue->GetGLContext();
       data.mFrontbufferGLTex = skiaGLTex;
     } else {
-      data.mDrawTarget = mTarget;
+      PersistentBufferProvider *provider = GetBufferProvider(aManager);
+      data.mBufferProvider = provider;
     }
 
     if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) {
       nsRefPtr<CanvasLayer> ret = aOldLayer;
       return ret.forget();
     }
   }
 
@@ -5432,28 +5493,29 @@ CanvasRenderingContext2D::GetCanvasLayer
   canvasLayer->SetDidTransactionCallback(
           CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
   canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
 
   CanvasLayer::Data data;
   data.mSize = nsIntSize(mWidth, mHeight);
   data.mHasAlpha = !mOpaque;
 
+  canvasLayer->SetPreTransactionCallback(
+          CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
+
   GLuint skiaGLTex = SkiaGLTex();
   if (skiaGLTex) {
-    canvasLayer->SetPreTransactionCallback(
-            CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
-
     SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
     MOZ_ASSERT(glue);
 
     data.mGLContext = glue->GetGLContext();
     data.mFrontbufferGLTex = skiaGLTex;
   } else {
-    data.mDrawTarget = mTarget;
+    PersistentBufferProvider *provider = GetBufferProvider(aManager);
+    data.mBufferProvider = provider;
   }
 
   canvasLayer->Initialize(data);
   uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
   canvasLayer->SetContentFlags(flags);
   canvasLayer->Updated();
 
   mResetLayer = false;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -22,16 +22,17 @@
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/2D.h"
 #include "gfx2DGlue.h"
 #include "imgIEncoder.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/EnumeratedArray.h"
 #include "FilterSupport.h"
 #include "nsSVGEffects.h"
+#include "Layers.h"
 
 class nsGlobalWindow;
 class nsXULElement;
 
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 }
@@ -451,16 +452,17 @@ public:
       *aPremultAlpha = true;
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool isOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
+  mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
   already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                CanvasLayer *aOldLayer,
                                                LayerManager *aManager) override;
   virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
   void MarkContextClean() override;
   NS_IMETHOD SetIsIPC(bool isIPC) override;
   // this rect is in canvas device space
   void Redraw(const mozilla::gfx::Rect &r);
@@ -632,21 +634,27 @@ protected:
    */
   RenderingMode EnsureTarget(RenderingMode aRenderMode = RenderingMode::DefaultBackendMode);
 
   /*
    * Disposes an old target and prepares to lazily create a new target.
    */
   void ClearTarget();
 
+  /*
+   * Returns the target to the buffer provider. i.e. this will queue a frame for
+   * rendering.
+   */
+  void ReturnTarget();
+
   /**
    * Check if the target is valid after calling EnsureTarget.
    */
   bool IsTargetValid() const {
-    return mTarget != sErrorTarget && mTarget != nullptr;
+    return (sErrorTarget == nullptr || mTarget != sErrorTarget) && (mBufferProvider != nullptr || mTarget);
   }
 
   /**
     * Returns the surface format this canvas should be allocated using. Takes
     * into account mOpaque, platform requirements, etc.
     */
   mozilla::gfx::SurfaceFormat GetSurfaceFormat() const;
 
@@ -710,16 +718,18 @@ protected:
   // If mCanvasElement is not provided, then a docshell is
   nsCOMPtr<nsIDocShell> mDocShell;
 
   // This is created lazily so it is necessary to call EnsureTarget before
   // accessing it. In the event of an error it will be equal to
   // sErrorTarget.
   mozilla::RefPtr<mozilla::gfx::DrawTarget> mTarget;
 
+  mozilla::RefPtr<mozilla::layers::PersistentBufferProvider> mBufferProvider;
+
   uint32_t SkiaGLTex() const;
 
   // This observes our draw calls at the beginning of the canvas
   // lifetime and switches to software or GPU mode depending on
   // what it thinks is best
   CanvasDrawObserver* mDrawObserver;
   void RemoveDrawObserver();
 
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -152,9 +152,9 @@ skip-if(!winWidget) pref(webgl.disable-a
 # Bug 815648
 == stroketext-shadow.html stroketext-shadow-ref.html
 
 # focus rings
 pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
 pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
-pref(canvas.capturestream.enabled,true) fails-if(B2G) skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
+pref(canvas.capturestream.enabled,true) skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(a01d5bb4-3e81-4529-b139-2f4ebb78f717)]
+[scriptable, uuid(42c529d8-54cf-4158-8b09-13ca89d7a88c)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1441,16 +1441,26 @@ interface nsIDOMWindowUtils : nsISupport
    * Set async zoom value. aRootElement should be the document element of our
    * document. The next composite will render with that zoom added to any
    * existing zoom if async scrolling is enabled, and then the zoom will be
    * removed. Only call this while test-controlled refreshes is enabled.
    */
   void setAsyncZoom(in nsIDOMNode aRootElement, in float aValue);
 
   /**
+   * Do a round-trip to the compositor to ensure any pending APZ repaint requests
+   * get flushed to the main thread. If the function returns true, the flush was
+   * triggered and an "apz-repaints-flushed" notification will be dispatched via
+   * the observer service once the flush is complete. If the function returns
+   * false, an error occurred or a flush is not needed, and the notification
+   * will not fire. This is intended to be used by test code only!
+   */
+  bool flushApzRepaints();
+
+  /**
    * Method for testing StyleAnimationValue::ComputeDistance.
    *
    * Returns the distance between the two values as reported by
    * StyleAnimationValue::ComputeDistance for the given element and
    * property.
    */
   double computeAnimationDistance(in nsIDOMElement element,
                                   in AString property,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/PCompositorChild.h"
 #include "mozilla/layers/SharedBufferManagerChild.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 #include "mozilla/widget/WidgetMessageUtils.h"
+#include "mozilla/media/MediaChild.h"
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #if defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
 #elif defined(XP_LINUX)
 #include "mozilla/Sandbox.h"
 #include "mozilla/SandboxInfo.h"
@@ -199,16 +200,17 @@ using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::cellbroadcast;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::icc;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::mobileconnection;
 using namespace mozilla::dom::mobilemessage;
 using namespace mozilla::dom::telephony;
 using namespace mozilla::dom::voicemail;
+using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
@@ -1745,16 +1747,28 @@ ContentChild::SendPVoicemailConstructor(
 
 bool
 ContentChild::DeallocPVoicemailChild(PVoicemailChild* aActor)
 {
     static_cast<VoicemailIPCService*>(aActor)->Release();
     return true;
 }
 
+media::PMediaChild*
+ContentChild::AllocPMediaChild()
+{
+  return media::AllocPMediaChild();
+}
+
+bool
+ContentChild::DeallocPMediaChild(media::PMediaChild *aActor)
+{
+  return media::DeallocPMediaChild(aActor);
+}
+
 PStorageChild*
 ContentChild::AllocPStorageChild()
 {
     NS_NOTREACHED("We should never be manually allocating PStorageChild actors");
     return nullptr;
 }
 
 bool
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -257,16 +257,19 @@ public:
 
     virtual PTelephonyChild* AllocPTelephonyChild() override;
     virtual bool DeallocPTelephonyChild(PTelephonyChild*) override;
 
     virtual PVoicemailChild* AllocPVoicemailChild() override;
     PVoicemailChild* SendPVoicemailConstructor(PVoicemailChild* aActor);
     virtual bool DeallocPVoicemailChild(PVoicemailChild*) override;
 
+    virtual PMediaChild* AllocPMediaChild() override;
+    virtual bool DeallocPMediaChild(PMediaChild* aActor) override;
+
     virtual PStorageChild* AllocPStorageChild() override;
     virtual bool DeallocPStorageChild(PStorageChild* aActor) override;
 
     virtual PBluetoothChild* AllocPBluetoothChild() override;
     virtual bool DeallocPBluetoothChild(PBluetoothChild* aActor) override;
 
     virtual PFMRadioChild* AllocPFMRadioChild() override;
     virtual bool DeallocPFMRadioChild(PFMRadioChild* aActor) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -69,16 +69,17 @@
 #include "mozilla/ipc/PFileDescriptorSetParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/SharedBufferManagerParent.h"
 #include "mozilla/LookAndFeel.h"
+#include "mozilla/media/MediaParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "mozilla/ProfileGatherer.h"
 #endif
@@ -253,16 +254,17 @@ using namespace mozilla::dom::cellbroadc
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::icc;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::dom::power;
 using namespace mozilla::dom::mobileconnection;
 using namespace mozilla::dom::mobilemessage;
 using namespace mozilla::dom::telephony;
 using namespace mozilla::dom::voicemail;
+using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
@@ -3802,16 +3804,28 @@ ContentParent::RecvPVoicemailConstructor
 
 bool
 ContentParent::DeallocPVoicemailParent(PVoicemailParent* aActor)
 {
     static_cast<VoicemailParent*>(aActor)->Release();
     return true;
 }
 
+media::PMediaParent*
+ContentParent::AllocPMediaParent()
+{
+  return media::AllocPMediaParent();
+}
+
+bool
+ContentParent::DeallocPMediaParent(media::PMediaParent *aActor)
+{
+  return media::DeallocPMediaParent(aActor);
+}
+
 PStorageParent*
 ContentParent::AllocPStorageParent()
 {
     return new DOMStorageDBParent();
 }
 
 bool
 ContentParent::DeallocPStorageParent(PStorageParent* aActor)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -641,16 +641,19 @@ private:
 
     virtual PTelephonyParent* AllocPTelephonyParent() override;
     virtual bool DeallocPTelephonyParent(PTelephonyParent*) override;
 
     virtual PVoicemailParent* AllocPVoicemailParent() override;
     virtual bool RecvPVoicemailConstructor(PVoicemailParent* aActor) override;
     virtual bool DeallocPVoicemailParent(PVoicemailParent* aActor) override;
 
+    virtual PMediaParent* AllocPMediaParent() override;
+    virtual bool DeallocPMediaParent(PMediaParent* aActor) override;
+
     virtual bool DeallocPStorageParent(PStorageParent* aActor) override;
 
     virtual PBluetoothParent* AllocPBluetoothParent() override;
     virtual bool DeallocPBluetoothParent(PBluetoothParent* aActor) override;
     virtual bool RecvPBluetoothConstructor(PBluetoothParent* aActor) override;
 
     virtual PFMRadioParent* AllocPFMRadioParent() override;
     virtual bool DeallocPFMRadioParent(PFMRadioParent* aActor) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -559,16 +559,17 @@ child:
     // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
     // in that file for these functions.
     RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
     AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
     HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
     HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
     HandleLongTap(CSSPoint point, Modifiers aModifiers, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
+    NotifyFlushComplete();
 
 
     /**
      * Sending an activate message moves focus to the child.
      */
     Activate();
 
     Deactivate();
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -20,16 +20,17 @@ include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PIcc;
 include protocol PProcessHangMonitor;
 include protocol PImageBridge;
+include protocol PMedia;
 include protocol PMemoryReportRequest;
 include protocol PMobileConnection;
 include protocol PNecko;
 // FIXME This is pretty ridiculous, but we have to keep the order of the
 //       following 4 includes, or the parser is confused about PGMPContent
 //       bridging PContent and PGMP. As soon as it registers the bridge between
 //       PContent and PPluginModule it seems to think that PContent's parent and
 //       child live in the same process!
@@ -424,16 +425,17 @@ prio(normal upto urgent) sync protocol P
     manages PDeviceStorageRequest;
     manages PFileSystemRequest;
     manages PPSMContentDownloader;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
     manages PHal;
     manages PIcc;
+    manages PMedia;
     manages PMemoryReportRequest;
     manages PMobileConnection;
     manages PNecko;
     manages POfflineCacheUpdate;
     manages PPrinting;
     manages PScreenManager;
     manages PSms;
     manages PSpeechSynthesis;
@@ -759,16 +761,18 @@ parent:
     PSpeechSynthesis();
 
     prio(urgent) async PStorage();
 
     PTelephony();
 
     PVoicemail();
 
+    PMedia();
+
     PBluetooth();
 
     PFMRadio();
 
     PAsmJSCacheEntry(OpenMode openMode, WriteParams write, Principal principal);
 
     PWebrtcGlobal();
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2145,16 +2145,23 @@ TabChild::RecvNotifyAPZStateChange(const
                                    const APZStateChange& aChange,
                                    const int& aArg)
 {
   mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg);
   return true;
 }
 
 bool
+TabChild::RecvNotifyFlushComplete()
+{
+  APZCCallbackHelper::NotifyFlushComplete();
+  return true;
+}
+
+bool
 TabChild::RecvActivate()
 {
   nsCOMPtr<nsIWebBrowserFocus> browser = do_QueryInterface(WebNavigation());
   browser->Activate();
   return true;
 }
 
 bool TabChild::RecvDeactivate()
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -339,16 +339,17 @@ public:
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) override;
     virtual bool RecvHandleLongTap(const CSSPoint& aPoint,
                                    const Modifiers& aModifiers,
                                    const mozilla::layers::ScrollableLayerGuid& aGuid,
                                    const uint64_t& aInputBlockId) override;
     virtual bool RecvNotifyAPZStateChange(const ViewID& aViewId,
                                           const APZStateChange& aChange,
                                           const int& aArg) override;
+    virtual bool RecvNotifyFlushComplete() override;
     virtual bool RecvActivate() override;
     virtual bool RecvDeactivate() override;
     virtual bool RecvMouseEvent(const nsString& aType,
                                 const float&    aX,
                                 const float&    aY,
                                 const int32_t&  aButton,
                                 const int32_t&  aClickCount,
                                 const int32_t&  aModifiers,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1094,16 +1094,24 @@ void
 TabParent::NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent)
 {
   if (!mIsDestroyed) {
     unused << SendMouseScrollTestEvent(aScrollId, aEvent);
   }
 }
 
 void
+TabParent::NotifyFlushComplete()
+{
+  if (!mIsDestroyed) {
+    unused << SendNotifyFlushComplete();
+  }
+}
+
+void
 TabParent::Activate()
 {
   if (!mIsDestroyed) {
     unused << SendActivate();
   }
 }
 
 void
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -267,16 +267,17 @@ public:
     void HandleLongTap(const CSSPoint& aPoint,
                        Modifiers aModifiers,
                        const ScrollableLayerGuid& aGuid,
                        uint64_t aInputBlockId);
     void NotifyAPZStateChange(ViewID aViewId,
                               APZStateChange aChange,
                               int aArg);
     void NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent);
+    void NotifyFlushComplete();
     void Activate();
     void Deactivate();
 
     bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
     void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                             mozilla::WidgetEvent* aEvent);
     LayoutDeviceToCSSScale GetLayoutDeviceToCSSScale();
 
--- a/dom/media/Intervals.h
+++ b/dom/media/Intervals.h
@@ -604,16 +604,37 @@ public:
     for (IndexType i = 0; i < mIntervals.Length(); i++) {
       if (mIntervals[i].Contains(aValue)) {
         return i;
       }
     }
     return NoIndex;
   }
 
+  // Methods for range-based for loops.
+  typename ContainerType::iterator begin()
+  {
+    return mIntervals.begin();
+  }
+
+  typename ContainerType::const_iterator begin() const
+  {
+    return mIntervals.begin();
+  }
+
+  typename ContainerType::iterator end()
+  {
+    return mIntervals.end();
+  }
+
+  typename ContainerType::const_iterator end() const
+  {
+    return mIntervals.end();
+  }
+
 protected:
   ContainerType mIntervals;
 
 private:
   void Normalize()
   {
     if (mIntervals.Length() >= 2) {
       ContainerType normalized;
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -523,16 +523,17 @@ MediaRawData::Clone() const
   nsRefPtr<MediaRawData> s = new MediaRawData;
   s->mTimecode = mTimecode;
   s->mTime = mTime;
   s->mDuration = mDuration;
   s->mOffset = mOffset;
   s->mKeyframe = mKeyframe;
   s->mExtraData = mExtraData;
   s->mCryptoInternal = mCryptoInternal;
+  s->mTrackInfo = mTrackInfo;
   if (mSize) {
     if (!s->EnsureCapacity(mSize)) {
       return nullptr;
     }
 
     // We ensure sufficient capacity above so this shouldn't fail.
     MOZ_ALWAYS_TRUE(s->mBuffer->AppendElements(mData, mSize, fallible));
     MOZ_ALWAYS_TRUE(s->mBuffer->AppendElements(RAW_DATA_ALIGNMENT, fallible));
@@ -576,19 +577,16 @@ MediaRawData::~MediaRawData()
 {
 }
 
 size_t
 MediaRawData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t size = aMallocSizeOf(this);
 
-  if (mExtraData) {
-    size += mExtraData->SizeOfIncludingThis(aMallocSizeOf);
-  }
   size += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
   return size;
 }
 
 MediaRawDataWriter*
 MediaRawData::CreateWriter()
 {
   return new MediaRawDataWriter(this);
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -18,16 +18,17 @@
 namespace mozilla {
 
 namespace layers {
 class Image;
 class ImageContainer;
 }
 
 class MediaByteBuffer;
+class SharedTrackInfo;
 
 // Container that holds media samples.
 class MediaData {
 public:
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaData)
 
   enum Type {
@@ -390,16 +391,18 @@ public:
   // Pointer to data or null if not-yet allocated
   const uint8_t* mData;
   // Size of buffer.
   size_t mSize;
 
   const CryptoSample& mCrypto;
   nsRefPtr<MediaByteBuffer> mExtraData;
 
+  nsRefPtr<SharedTrackInfo> mTrackInfo;
+
   // Return a deep copy or nullptr if out of memory.
   virtual already_AddRefed<MediaRawData> Clone() const;
   // Create a MediaRawDataWriter for this MediaRawData. The caller must
   // delete the writer once done. The writer is not thread-safe.
   virtual MediaRawDataWriter* CreateWriter();
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
 protected:
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -103,16 +103,19 @@ MediaFormatReader::Shutdown()
   MOZ_ASSERT(OnTaskQueue());
 
   mDemuxerInitRequest.DisconnectIfExists();
   mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
   mSkipRequest.DisconnectIfExists();
 
   if (mAudio.mDecoder) {
     Flush(TrackInfo::kAudioTrack);
+    if (mAudio.HasPromise()) {
+      mAudio.RejectPromise(CANCELED, __func__);
+    }
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
   }
   if (mAudio.mTrackDemuxer) {
     mAudio.ResetDemuxer();
     mAudio.mTrackDemuxer->BreakCycles();
     mAudio.mTrackDemuxer = nullptr;
   }
@@ -120,16 +123,19 @@ MediaFormatReader::Shutdown()
     mAudio.mTaskQueue->BeginShutdown();
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
   MOZ_ASSERT(mAudio.mPromise.IsEmpty());
 
   if (mVideo.mDecoder) {
     Flush(TrackInfo::kVideoTrack);
+    if (mVideo.HasPromise()) {
+      mVideo.RejectPromise(CANCELED, __func__);
+    }
     mVideo.mDecoder->Shutdown();
     mVideo.mDecoder = nullptr;
   }
   if (mVideo.mTrackDemuxer) {
     mVideo.ResetDemuxer();
     mVideo.mTrackDemuxer->BreakCycles();
     mVideo.mTrackDemuxer = nullptr;
   }
@@ -426,40 +432,46 @@ MediaFormatReader::EnsureDecodersSetup()
 
   MOZ_ASSERT(mPlatform);
 
   if (HasAudio() && !mAudio.mDecoder) {
     NS_ENSURE_TRUE(IsSupportedAudioMimeType(mInfo.mAudio.mMimeType),
                    false);
 
     mAudio.mDecoder =
-      mPlatform->CreateDecoder(mInfo.mAudio,
+      mPlatform->CreateDecoder(mAudio.mInfo ?
+                                 *mAudio.mInfo->GetAsAudioInfo() :
+                                 mInfo.mAudio,
                                mAudio.mTaskQueue,
                                mAudio.mCallback);
     NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, false);
     nsresult rv = mAudio.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, false);
   }
 
   if (HasVideo() && !mVideo.mDecoder) {
     NS_ENSURE_TRUE(IsSupportedVideoMimeType(mInfo.mVideo.mMimeType),
                    false);
 
     if (mSharedDecoderManager &&
         mPlatform->SupportsSharedDecoders(mInfo.mVideo)) {
       mVideo.mDecoder =
         mSharedDecoderManager->CreateVideoDecoder(mPlatform,
-                                                  mInfo.mVideo,
+                                                  mVideo.mInfo ?
+                                                    *mVideo.mInfo->GetAsVideoInfo() :
+                                                    mInfo.mVideo,
                                                   mLayersBackendType,
                                                   mDecoder->GetImageContainer(),
                                                   mVideo.mTaskQueue,
                                                   mVideo.mCallback);
     } else {
       mVideo.mDecoder =
-        mPlatform->CreateDecoder(mInfo.mVideo,
+        mPlatform->CreateDecoder(mVideo.mInfo ?
+                                   *mVideo.mInfo->GetAsVideoInfo() :
+                                   mInfo.mVideo,
                                  mVideo.mTaskQueue,
                                  mVideo.mCallback,
                                  mLayersBackendType,
                                  mDecoder->GetImageContainer());
     }
     NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, false);
     nsresult rv = mVideo.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, false);
@@ -503,28 +515,27 @@ bool
 MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(HasVideo());
   media::TimeUnit nextKeyframe;
   nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
   if (NS_FAILED(rv)) {
     return aSkipToNextKeyframe;
   }
-  return nextKeyframe < aTimeThreshold;
+  return nextKeyframe < aTimeThreshold && nextKeyframe.ToMicroseconds() >= 0;
 }
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
                                      int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
-  MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
-  MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
+  MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists());
   MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
 
   if (!HasVideo()) {
     LOG("called with no video track");
     return VideoDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
@@ -558,24 +569,23 @@ MediaFormatReader::RequestVideoData(bool
   ScheduleUpdate(TrackInfo::kVideoTrack);
   return p;
 }
 
 void
 MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
-  LOG("Failed to demux %s, failure = %d",
+  LOG("Failed to demux %s, failure:%d",
       aTrack == TrackType::kVideoTrack ? "video" : "audio", aFailure);
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxRequest.Complete();
   switch (aFailure) {
     case DemuxerFailureReason::END_OF_STREAM:
-      decoder.mDemuxEOS = true;
-      ScheduleUpdate(aTrack);
+      NotifyEndOfStream(aTrack);
       break;
     case DemuxerFailureReason::DEMUXER_ERROR:
       NotifyError(aTrack);
       break;
     case DemuxerFailureReason::WAITING_FOR_DATA:
       NotifyWaitingForData(aTrack);
       break;
     case DemuxerFailureReason::CANCELED:
@@ -598,29 +608,30 @@ MediaFormatReader::DoDemuxVideo()
                       ->Then(TaskQueue(), __func__, this,
                              &MediaFormatReader::OnVideoDemuxCompleted,
                              &MediaFormatReader::OnVideoDemuxFailed));
 }
 
 void
 MediaFormatReader::OnVideoDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
-  LOGV("%d video samples demuxed", aSamples->mSamples.Length());
+  LOGV("%d video samples demuxed (sid:%d)",
+       aSamples->mSamples.Length(),
+       aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
   mVideo.mDemuxRequest.Complete();
   mVideo.mQueuedSamples.AppendElements(aSamples->mSamples);
   ScheduleUpdate(TrackInfo::kVideoTrack);
 }
 
 nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaFormatReader::RequestAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
-  MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
-  MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
+  MOZ_DIAGNOSTIC_ASSERT(!mAudio.mSeekRequest.Exists());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("");
 
   if (!HasAudio()) {
     LOG("called with no audio track");
     return AudioDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
@@ -653,17 +664,19 @@ MediaFormatReader::DoDemuxAudio()
                       ->Then(TaskQueue(), __func__, this,
                              &MediaFormatReader::OnAudioDemuxCompleted,
                              &MediaFormatReader::OnAudioDemuxFailed));
 }
 
 void
 MediaFormatReader::OnAudioDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
-  LOGV("%d audio samples demuxed", aSamples->mSamples.Length());
+  LOGV("%d audio samples demuxed (sid:%d)",
+       aSamples->mSamples.Length(),
+       aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
   mAudio.mDemuxRequest.Complete();
   mAudio.mQueuedSamples.AppendElements(aSamples->mSamples);
   ScheduleUpdate(TrackInfo::kAudioTrack);
 }
 
 void
 MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample)
 {
@@ -717,31 +730,41 @@ void
 MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mWaitingForData = true;
   ScheduleUpdate(aTrack);
 }
 
+void
+MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  decoder.mDemuxEOS = true;
+  ScheduleUpdate(aTrack);
+}
+
 bool
 MediaFormatReader::NeedInput(DecoderData& aDecoder)
 {
   MOZ_ASSERT(OnTaskQueue());
   // We try to keep a few more compressed samples input than decoded samples
   // have been output, provided the state machine has requested we send it a
   // decoded sample. To account for H.264 streams which may require a longer
   // run of input than we input, decoders fire an "input exhausted" callback,
   // which overrides our "few more samples" threshold.
   return
     !aDecoder.mError &&
     aDecoder.HasPromise() &&
     !aDecoder.mDemuxRequest.Exists() &&
     aDecoder.mOutput.IsEmpty() &&
     (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
+     aDecoder.mTimeThreshold.isSome() ||
      aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
 }
 
 void
 MediaFormatReader::ScheduleUpdate(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mShutdown) {
@@ -789,17 +812,17 @@ MediaFormatReader::UpdateReceivedNewData
   if (decoder.HasWaitingPromise()) {
     MOZ_ASSERT(!decoder.HasPromise());
     LOG("We have new data. Resolving WaitingPromise");
     decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
     return true;
   }
   if (!mSeekPromise.IsEmpty()) {
     MOZ_ASSERT(!decoder.HasPromise());
-    if (mVideoSeekRequest.Exists() || mAudioSeekRequest.Exists()) {
+    if (mVideo.mSeekRequest.Exists() || mAudio.mSeekRequest.Exists()) {
       // Already waiting for a seek to complete. Nothing more to do.
       return true;
     }
     LOG("Attempting Seek");
     AttemptSeek();
     return true;
   }
   return false;
@@ -835,29 +858,98 @@ MediaFormatReader::DecodeDemuxedSamples(
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mQueuedSamples.IsEmpty()) {
     return;
   }
+
   LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
-  decoder.mOutputRequested = true;
 
   // Decode all our demuxed frames.
-  for (auto& samples : decoder.mQueuedSamples) {
-    decoder.mDecoder->Input(samples);
+  bool samplesPending = false;
+  while (decoder.mQueuedSamples.Length()) {
+    nsRefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
+    nsRefPtr<SharedTrackInfo> info = sample->mTrackInfo;
+
+    if (info && decoder.mLastStreamSourceID != info->GetID()) {
+      if (samplesPending) {
+        // Let existing samples complete their decoding. We'll resume later.
+        return;
+      }
+
+      LOG("%s stream id has changed from:%d to:%d, recreating decoder.",
+          TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
+          info->GetID());
+      decoder.mInfo = info;
+      decoder.mLastStreamSourceID = info->GetID();
+      // Flush will clear our array of queued samples. So make a copy now.
+      nsTArray<nsRefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
+      Flush(aTrack);
+      decoder.mDecoder->Shutdown();
+      decoder.mDecoder = nullptr;
+      if (!EnsureDecodersSetup()) {
+        LOG("Unable to re-create decoder, aborting");
+        NotifyError(aTrack);
+        return;
+      }
+      LOGV("%s decoder:%p created for sid:%u",
+           TrackTypeToStr(aTrack), decoder.mDecoder.get(), info->GetID());
+      if (sample->mKeyframe) {
+        decoder.mQueuedSamples.MoveElementsFrom(samples);
+      } else {
+        MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
+        LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
+            sample->mTime);
+        decoder.mTimeThreshold = Some(TimeUnit::FromMicroseconds(sample->mTime));
+        nsRefPtr<MediaFormatReader> self = this;
+        decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref())
+                   ->Then(TaskQueue(), __func__,
+                          [self, aTrack] (media::TimeUnit aTime) {
+                            auto& decoder = self->GetDecoderData(aTrack);
+                            decoder.mSeekRequest.Complete();
+                            self->ScheduleUpdate(aTrack);
+                          },
+                          [self, aTrack] (DemuxerFailureReason aResult) {
+                            auto& decoder = self->GetDecoderData(aTrack);
+                            decoder.mSeekRequest.Complete();
+                            switch (aResult) {
+                              case DemuxerFailureReason::WAITING_FOR_DATA:
+                                self->NotifyWaitingForData(aTrack);
+                                break;
+                              case DemuxerFailureReason::END_OF_STREAM:
+                                self->NotifyEndOfStream(aTrack);
+                                break;
+                              case DemuxerFailureReason::CANCELED:
+                              case DemuxerFailureReason::SHUTDOWN:
+                                break;
+                              default:
+                                self->NotifyError(aTrack);
+                                break;
+                            }
+                            decoder.mTimeThreshold.reset();
+                          }));
+        return;
+      }
+    }
+
+    LOGV("Input:%lld (dts:%lld kf:%d)",
+         sample->mTime, sample->mTimecode, sample->mKeyframe);
+    decoder.mOutputRequested = true;
+    decoder.mNumSamplesInput++;
+    decoder.mSizeOfQueue++;
+    if (aTrack == TrackInfo::kVideoTrack) {
+      aA.mParsed++;
+    }
+    decoder.mDecoder->Input(sample);
+    decoder.mQueuedSamples.RemoveElementAt(0);
+    samplesPending = true;
   }
-  decoder.mNumSamplesInput += decoder.mQueuedSamples.Length();
-  decoder.mSizeOfQueue += decoder.mQueuedSamples.Length();
-  if (aTrack == TrackInfo::kVideoTrack) {
-    aA.mParsed += decoder.mQueuedSamples.Length();
-  }
-  decoder.mQueuedSamples.Clear();
 
   // We have serviced the decoder's request for more data.
   decoder.mInputExhausted = false;
 }
 
 void
 MediaFormatReader::Update(TrackType aTrack)
 {
@@ -908,17 +1000,26 @@ MediaFormatReader::Update(TrackType aTra
   }
   if (decoder.HasPromise()) {
     needOutput = true;
     if (!decoder.mOutput.IsEmpty()) {
       // We have a decoded sample ready to be returned.
       nsRefPtr<MediaData> output = decoder.mOutput[0];
       decoder.mOutput.RemoveElementAt(0);
       decoder.mSizeOfQueue -= 1;
-      ReturnOutput(output, aTrack);
+      if (decoder.mTimeThreshold.isNothing() ||
+          media::TimeUnit::FromMicroseconds(output->mTime) >= decoder.mTimeThreshold.ref()) {
+        ReturnOutput(output, aTrack);
+        decoder.mTimeThreshold.reset();
+      } else {
+        LOGV("Internal Seeking: Dropping frame time:%f wanted:%f (kf:%d)",
+             media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
+             decoder.mTimeThreshold.ref().ToSeconds(),
+             output->mKeyframe);
+      }
     } else if (decoder.mDrainComplete) {
       decoder.RejectPromise(END_OF_STREAM, __func__);
       decoder.mDrainComplete = false;
     }
   }
 
   if (decoder.mDemuxEOS && !decoder.mDemuxEOSServiced) {
     decoder.mOutputRequested = true;
@@ -930,20 +1031,20 @@ MediaFormatReader::Update(TrackType aTra
 
   if (!NeedInput(decoder)) {
     LOGV("No need for additional input");
     return;
   }
 
   needInput = true;
 
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d",
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d sid:%d",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
-       size_t(decoder.mSizeOfQueue));
+       size_t(decoder.mSizeOfQueue), decoder.mLastStreamSourceID);
 
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
   // Decode all pending demuxed samples.
   DecodeDemuxedSamples(aTrack, a);
 }
 
 void
@@ -1009,35 +1110,41 @@ MediaFormatReader::WaitForData(MediaData
 nsresult
 MediaFormatReader::ResetDecode()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   LOGV("");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-  mAudioSeekRequest.DisconnectIfExists();
-  mVideoSeekRequest.DisconnectIfExists();
+  mAudio.mSeekRequest.DisconnectIfExists();
+  mVideo.mSeekRequest.DisconnectIfExists();
   mSeekPromise.RejectIfExists(NS_OK, __func__);
   mSkipRequest.DisconnectIfExists();
 
   // Do the same for any data wait promises.
   mAudio.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::CANCELED), __func__);
   mVideo.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::CANCELED), __func__);
 
   // Reset miscellaneous seeking state.
   mPendingSeekTime.reset();
 
   if (HasVideo()) {
     mVideo.ResetDemuxer();
     Flush(TrackInfo::kVideoTrack);
+    if (mVideo.HasPromise()) {
+      mVideo.RejectPromise(CANCELED, __func__);
+    }
   }
   if (HasAudio()) {
     mAudio.ResetDemuxer();
     Flush(TrackInfo::kAudioTrack);
+    if (mAudio.HasPromise()) {
+      mAudio.RejectPromise(CANCELED, __func__);
+    }
   }
   return MediaDecoderReader::ResetDecode();
 }
 
 void
 MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
 {
   LOGV("Decoded %s sample time=%lld dur=%lld",
@@ -1093,19 +1200,16 @@ MediaFormatReader::Flush(TrackType aTrac
     return;
   }
 
   decoder.mDecoder->Flush();
   // Purge the current decoder's state.
   // ResetState clears mOutputRequested flag so that we ignore all output until
   // the next request for more data.
   decoder.ResetState();
-  if (decoder.HasPromise()) {
-    decoder.RejectPromise(CANCELED, __func__);
-  }
   LOG("Flush(%s) END", TrackTypeToStr(aTrack));
 }
 
 void
 MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -1140,39 +1244,48 @@ MediaFormatReader::OnVideoSkipCompleted(
 void
 MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
   mSkipRequest.Complete();
   mDecoder->NotifyDecodedFrames(aFailure.mSkipped, 0, aFailure.mSkipped);
   MOZ_ASSERT(mVideo.HasPromise());
-  if (aFailure.mFailure == DemuxerFailureReason::END_OF_STREAM) {
-    mVideo.mDemuxEOS = true;
-    ScheduleUpdate(TrackType::kVideoTrack);
-    mVideo.RejectPromise(END_OF_STREAM, __func__);
-  } else if (aFailure.mFailure == DemuxerFailureReason::WAITING_FOR_DATA) {
-    NotifyWaitingForData(TrackType::kVideoTrack);
-    mVideo.RejectPromise(WAITING_FOR_DATA, __func__);
-  } else {
-    mVideo.RejectPromise(DECODE_ERROR, __func__);
+  switch (aFailure.mFailure) {
+    case DemuxerFailureReason::END_OF_STREAM:
+      NotifyEndOfStream(TrackType::kVideoTrack);
+      mVideo.RejectPromise(END_OF_STREAM, __func__);
+      break;
+    case DemuxerFailureReason::WAITING_FOR_DATA:
+      NotifyWaitingForData(TrackType::kVideoTrack);
+      mVideo.RejectPromise(WAITING_FOR_DATA, __func__);
+      break;
+    case DemuxerFailureReason::CANCELED:
+    case DemuxerFailureReason::SHUTDOWN:
+      break;
+    default:
+      NotifyError(TrackType::kVideoTrack);
+      mVideo.RejectPromise(DECODE_ERROR, __func__);
+      break;
   }
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaFormatReader::Seek(int64_t aTime, int64_t aUnused)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   LOG("aTime=(%lld)", aTime);
 
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty());
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(mPendingSeekTime.isNothing());
+  MOZ_DIAGNOSTIC_ASSERT(mVideo.mTimeThreshold.isNothing());
+  MOZ_DIAGNOSTIC_ASSERT(mAudio.mTimeThreshold.isNothing());
 
   if (!mSeekable) {
     LOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   if (mShutdown) {
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
@@ -1199,79 +1312,78 @@ MediaFormatReader::AttemptSeek()
     MOZ_CRASH();
   }
 }
 
 void
 MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult)
 {
   MOZ_ASSERT(OnTaskQueue());
-  LOGV("%s failure = %d", TrackTypeToStr(aTrack), aResult);
+  LOGV("%s failure:%d", TrackTypeToStr(aTrack), aResult);
   if (aTrack == TrackType::kVideoTrack) {
-    mVideoSeekRequest.Complete();
+    mVideo.mSeekRequest.Complete();
   } else {
-    mAudioSeekRequest.Complete();
+    mAudio.mSeekRequest.Complete();
   }
 
   if (aResult == DemuxerFailureReason::WAITING_FOR_DATA) {
     NotifyWaitingForData(aTrack);
     return;
   }
-  MOZ_ASSERT(!mVideoSeekRequest.Exists() && !mAudioSeekRequest.Exists());
+  MOZ_ASSERT(!mVideo.mSeekRequest.Exists() && !mAudio.mSeekRequest.Exists());
   mPendingSeekTime.reset();
   mSeekPromise.Reject(NS_ERROR_FAILURE, __func__);
 }
 
 void
 MediaFormatReader::DoVideoSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking video to %lld", mPendingSeekTime.ref().ToMicroseconds());
   media::TimeUnit seekTime = mPendingSeekTime.ref();
-  mVideoSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
+  mVideo.mSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
                           ->Then(TaskQueue(), __func__, this,
                                  &MediaFormatReader::OnVideoSeekCompleted,
                                  &MediaFormatReader::OnVideoSeekFailed));
 }
 
 void
 MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Video seeked to %lld", aTime.ToMicroseconds());
-  mVideoSeekRequest.Complete();
+  mVideo.mSeekRequest.Complete();
 
   if (HasAudio()) {
     MOZ_ASSERT(mPendingSeekTime.isSome());
-    mPendingSeekTime.ref() = aTime;
     DoAudioSeek();
   } else {
     mPendingSeekTime.reset();
     mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__);
   }
 }
 
 void
 MediaFormatReader::DoAudioSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds());
   media::TimeUnit seekTime = mPendingSeekTime.ref();
-  mAudioSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
+  mAudio.mSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
                          ->Then(TaskQueue(), __func__, this,
                                 &MediaFormatReader::OnAudioSeekCompleted,
                                 &MediaFormatReader::OnAudioSeekFailed));
 }
 
 void
 MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Audio seeked to %lld", aTime.ToMicroseconds());
-  mAudioSeekRequest.Complete();
+  mAudio.mSeekRequest.Complete();
   mPendingSeekTime.reset();
   mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__);
 }
 
 int64_t
 MediaFormatReader::GetEvictionOffset(double aTime)
 {
   int64_t audioOffset;
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -126,16 +126,17 @@ private:
   // Decode any pending already demuxed samples.
   void DecodeDemuxedSamples(TrackType aTrack,
                             AbstractMediaDecoder::AutoNotifyDecoded& aA);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack);
   void NotifyWaitingForData(TrackType aTrack);
+  void NotifyEndOfStream(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
@@ -200,16 +201,17 @@ private:
       , mDiscontinuity(true)
       , mOutputRequested(false)
       , mInputExhausted(false)
       , mError(false)
       , mDrainComplete(false)
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mSizeOfQueue(0)
+      , mLastStreamSourceID(UINT32_MAX)
       , mMonitor(aType == MediaData::AUDIO_DATA ? "audio decoder data"
                                                 : "video decoder data")
     {}
 
     MediaFormatReader* mOwner;
     // Disambiguate Audio vs Video.
     MediaData::Type mType;
     nsRefPtr<MediaTrackDemuxer> mTrackDemuxer;
@@ -225,31 +227,38 @@ private:
     uint32_t mDecodeAhead;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mDemuxEOSServiced;
     bool mWaitingForData;
     bool mReceivedNewData;
     bool mDiscontinuity;
 
+    // Pending seek.
+    MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
+
     // Queued demux samples waiting to be decoded.
     nsTArray<nsRefPtr<MediaRawData>> mQueuedSamples;
     MediaPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
     MediaPromiseHolder<WaitForDataPromise> mWaitingPromise;
     bool HasWaitingPromise()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       return !mWaitingPromise.IsEmpty();
     }
 
     // MediaDataDecoder handler's variables.
     bool mOutputRequested;
     bool mInputExhausted;
     bool mError;
     bool mDrainComplete;
+    // If set, all decoded samples prior mTimeThreshold will be dropped.
+    // Used for internal seeking when a change of stream is detected.
+    Maybe<media::TimeUnit> mTimeThreshold;
+
     // Decoded samples returned my mDecoder awaiting being returned to
     // state machine upon request.
     nsTArray<nsRefPtr<MediaData>> mOutput;
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
 
     // These get overriden in the templated concrete class.
     // Indicate if we have a pending promise for decoded frame.
@@ -271,27 +280,31 @@ private:
       mDemuxEOSServiced = false;
       mWaitingForData = false;
       mReceivedNewData = false;
       mDiscontinuity = true;
       mQueuedSamples.Clear();
       mOutputRequested = false;
       mInputExhausted = false;
       mDrainComplete = false;
+      mTimeThreshold.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
     }
 
     // Used by the MDSM for logging purposes.
     Atomic<size_t> mSizeOfQueue;
+    // Sample format monitoring.
+    uint32_t mLastStreamSourceID;
     // Monitor that protects all non-threadsafe state; the primitives
     // that follow.
     Monitor mMonitor;
     media::TimeIntervals mTimeRanges;
+    nsRefPtr<SharedTrackInfo> mInfo;
   };
 
   template<typename PromiseType>
   struct DecoderDataWithPromise : public DecoderData {
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
                            uint32_t aDecodeAhead) :
       DecoderData(aOwner, aType, aDecodeAhead)
@@ -388,18 +401,16 @@ private:
   void DoAudioSeek();
   void OnAudioSeekCompleted(media::TimeUnit aTime);
   void OnAudioSeekFailed(DemuxerFailureReason aFailure)
   {
     OnSeekFailed(TrackType::kAudioTrack, aFailure);
   }
   // Temporary seek information while we wait for the data
   Maybe<media::TimeUnit> mPendingSeekTime;
-  MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mVideoSeekRequest;
-  MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mAudioSeekRequest;
   MediaPromiseHolder<SeekPromise> mSeekPromise;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
   nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
 
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -355,11 +355,62 @@ public:
   // The Ogg reader tries to kinda-sorta compute the duration by seeking to the
   // end and determining the timestamp of the last frame. This isn't useful as
   // a duration until we know the start time, so we need to track it separately.
   media::NullableTimeUnit mUnadjustedMetadataEndTime;
 
   EncryptionInfo mCrypto;
 };
 
+class SharedTrackInfo {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedTrackInfo)
+public:
+  SharedTrackInfo(const TrackInfo& aOriginal, uint32_t aStreamID)
+    : mInfo(aOriginal.Clone())
+    , mStreamSourceID(aStreamID)
+    , mMimeType(mInfo->mMimeType)
+  {
+  }
+
+  uint32_t GetID() const
+  {
+    return mStreamSourceID;
+  }
+
+  const TrackInfo* operator*() const
+  {
+    return mInfo.get();
+  }
+
+  const TrackInfo* operator->() const
+  {
+    MOZ_ASSERT(mInfo.get(), "dereferencing a UniquePtr containing nullptr");
+    return mInfo.get();
+  }
+
+  const AudioInfo* GetAsAudioInfo() const
+  {
+    return mInfo ? mInfo->GetAsAudioInfo() : nullptr;
+  }
+
+  const VideoInfo* GetAsVideoInfo() const
+  {
+    return mInfo ? mInfo->GetAsVideoInfo() : nullptr;
+  }
+
+  const TextInfo* GetAsTextInfo() const
+  {
+    return mInfo ? mInfo->GetAsTextInfo() : nullptr;
+  }
+
+private:
+  ~SharedTrackInfo() {};
+  UniquePtr<TrackInfo> mInfo;
+  // A unique ID, guaranteed to change when changing streams.
+  uint32_t mStreamSourceID;
+
+public:
+  const nsAutoCString& mMimeType;
+};
+
 } // namespace mozilla
 
 #endif // MediaInfo_h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -118,16 +118,19 @@ using dom::MediaStreamConstraints;
 using dom::MediaTrackConstraintSet;
 using dom::MediaTrackConstraints;
 using dom::MediaStreamError;
 using dom::GetUserMediaRequest;
 using dom::Sequence;
 using dom::OwningBooleanOrMediaTrackConstraints;
 using dom::SupportedAudioConstraints;
 using dom::SupportedVideoConstraints;
+using media::Pledge;
+using media::NewRunnableFrom;
+using media::NewTaskFrom;
 
 static Atomic<bool> sInShutdown;
 
 static bool
 HostInDomain(const nsCString &aHost, const nsCString &aPattern)
 {
   int32_t patternOffset = 0;
   int32_t hostOffset = 0;
@@ -262,160 +265,16 @@ private:
 
   nsCOMPtr<SuccessCallbackType> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   nsRefPtr<MediaMgrError> mError;
   uint64_t mWindowID;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
-/**
- * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable
- * so that it may be called on the main thread. The error callback is also
- * passed so it can be released correctly.
- */
-class DeviceSuccessCallbackRunnable : public nsRunnable
-{
-public:
-  DeviceSuccessCallbackRunnable(
-    uint64_t aWindowID,
-    nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aOnSuccess,
-    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
-    nsTArray<nsRefPtr<MediaDevice>>* aDevices,
-    bool aIsGum)
-    : mDevices(aDevices)
-    , mWindowID(aWindowID)
-    , mIsGum(aIsGum)
-    , mManager(MediaManager::GetInstance())
-  {
-    mOnSuccess.swap(aOnSuccess);
-    mOnFailure.swap(aOnFailure);
-  }
-
-  ~DeviceSuccessCallbackRunnable()
-  {
-    if (!NS_IsMainThread()) {
-      // This can happen if the main thread processes the runnable before
-      // GetUserMediaDevicesTask::Run returns.
-      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-
-      NS_ProxyRelease(mainThread, mOnSuccess);
-      NS_ProxyRelease(mainThread, mOnFailure);
-    }
-  }
-
-  nsresult
-  AnonymizeId(nsAString& aId, const nsACString& aOriginKey)
-  {
-    nsresult rv;
-    nsCOMPtr<nsIKeyObjectFactory> factory =
-      do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    nsCString rawKey;
-    rv = Base64Decode(aOriginKey, rawKey);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    nsCOMPtr<nsIKeyObject> key;
-    rv = factory->KeyFromString(nsIKeyObject::HMAC, rawKey, getter_AddRefs(key));
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    nsCOMPtr<nsICryptoHMAC> hasher =
-      do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    rv = hasher->Init(nsICryptoHMAC::SHA256, key);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    NS_ConvertUTF16toUTF8 id(aId);
-    rv = hasher->Update(reinterpret_cast<const uint8_t*> (id.get()), id.Length());
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    nsCString mac;
-    rv = hasher->Finish(true, mac);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    aId = NS_ConvertUTF8toUTF16(mac);
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
-    // Only run if window is still on our active list.
-    if (!mManager->IsWindowStillActive(mWindowID)) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIWritableVariant> devices =
-      do_CreateInstance("@mozilla.org/variant;1");
-
-    size_t len = mDevices->Length();
-
-    if (!len) {
-      if (mIsGum) { // gUM fails on 0 devices whereas enumerateDevices doesn't.
-        // XXX
-        // We should in the future return an empty array, and dynamically add
-        // devices to the dropdowns if things are hotplugged while the
-        // requester is up.
-        nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
-        if (window) {
-          nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
-              NS_LITERAL_STRING("NotFoundError"));
-          mOnFailure->OnError(error);
-        }
-        return NS_OK;
-      }
-      devices->SetAsEmptyArray(); // SetAsArray() fails on zero length arrays.
-    } else {
-      nsTArray<nsIMediaDevice*> tmp(len);
-      for (auto& device : *mDevices) {
-        if (!mOriginKey.IsEmpty()) {
-          nsString id;
-          device->GetId(id);
-          AnonymizeId(id, mOriginKey);
-          device->SetId(id);
-        }
-        tmp.AppendElement(device);
-      }
-      nsresult rv = devices->SetAsArray(nsIDataType::VTYPE_INTERFACE,
-                                        &NS_GET_IID(nsIMediaDevice),
-                                        mDevices->Length(),
-                                        const_cast<void*>(
-                                          static_cast<const void*>(tmp.Elements())
-                                        ));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-    mOnSuccess->OnSuccess(devices);
-    return NS_OK;
-  }
-
-  nsCString mOriginKey;
-private:
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mOnSuccess;
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-  nsAutoPtr<nsTArray<nsRefPtr<MediaDevice>>> mDevices;
-  uint64_t mWindowID;
-  bool mIsGum;
-  nsRefPtr<MediaManager> mManager;
-};
-
 // Handle removing GetUserMediaCallbackMediaStreamListener from main thread
 class GetUserMediaListenerRemove: public nsRunnable
 {
 public:
   GetUserMediaListenerRemove(uint64_t aWindowID,
     GetUserMediaCallbackMediaStreamListener *aListener)
     : mWindowID(aWindowID)
     , mListener(aListener) {}
@@ -435,125 +294,49 @@ protected:
 };
 
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(MediaEngineSource* aSource)
-  : mHasFacingMode(false)
-  , mSource(aSource) {
+  : mSource(aSource) {
   mSource->GetName(mName);
-  mSource->GetUUID(mID);
+  nsCString id;
+  mSource->GetUUID(id);
+  CopyUTF8toUTF16(id, mID);
 }
 
 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
   : MediaDevice(aSource) {
-#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
-  if (mName.EqualsLiteral("back")) {
-    mHasFacingMode = true;
-    mFacingMode = dom::VideoFacingModeEnum::Environment;
-  } else if (mName.EqualsLiteral("front")) {
-    mHasFacingMode = true;
-    mFacingMode = dom::VideoFacingModeEnum::User;
-  }
-#endif // MOZ_B2G_CAMERA
-#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
-  // Names are generated. Example: "Camera 0, Facing back, Orientation 90"
-  //
-  // See media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/
-  // webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java
-
-  if (mName.Find(NS_LITERAL_STRING("Facing back")) != kNotFound) {
-    mHasFacingMode = true;
-    mFacingMode = dom::VideoFacingModeEnum::Environment;
-  } else if (mName.Find(NS_LITERAL_STRING("Facing front")) != kNotFound) {
-    mHasFacingMode = true;
-    mFacingMode = dom::VideoFacingModeEnum::User;
-  }
-#endif // ANDROID
-#ifdef XP_MACOSX
-  // Kludge to test user-facing cameras on OSX.
-  if (mName.Find(NS_LITERAL_STRING("Face")) != -1) {
-    mHasFacingMode = true;
-    mFacingMode = dom::VideoFacingModeEnum::User;
-  }
-#endif
   mMediaSource = aSource->GetMediaSource();
 }
 
 /**
  * Helper functions that implement the constraints algorithm from
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
  */
 
 // Reminder: add handling for new constraints both here and in GetSources below!
 
 uint32_t
 VideoDevice::GetBestFitnessDistance(
     const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
 {
-  // TODO: Minimal kludge to fix plain and ideal facingMode regression, for
-  // smooth landing and uplift. Proper cleanup is forthcoming (1037389).
-  uint64_t distance = 0;
-
   // Interrogate device-inherent properties first.
-  for (size_t i = 0; i < aConstraintSets.Length(); i++) {
-    auto& c = *aConstraintSets[i];
-    if (!c.mFacingMode.IsConstrainDOMStringParameters() ||
-        c.mFacingMode.GetAsConstrainDOMStringParameters().mIdeal.WasPassed() ||
-        c.mFacingMode.GetAsConstrainDOMStringParameters().mExact.WasPassed()) {
-      nsString deviceFacingMode;
-      GetFacingMode(deviceFacingMode);
-      if (c.mFacingMode.IsString()) {
-        if (c.mFacingMode.GetAsString() != deviceFacingMode) {
-          if (i == 0) {
-            distance = 1000;
-          }
-        }
-      } else if (c.mFacingMode.IsStringSequence()) {
-        if (!c.mFacingMode.GetAsStringSequence().Contains(deviceFacingMode)) {
-          if (i == 0) {
-            distance = 1000;
-          }
-        }
-      } else if (c.mFacingMode.GetAsConstrainDOMStringParameters().mExact.WasPassed()) {
-        auto& exact = c.mFacingMode.GetAsConstrainDOMStringParameters().mExact.Value();
-        if (exact.IsString()) {
-          if (exact.GetAsString() != deviceFacingMode) {
-            return UINT32_MAX;
-          }
-        } else if (!exact.GetAsStringSequence().Contains(deviceFacingMode)) {
-          return UINT32_MAX;
-        }
-      } else if (c.mFacingMode.GetAsConstrainDOMStringParameters().mIdeal.WasPassed()) {
-        auto& ideal = c.mFacingMode.GetAsConstrainDOMStringParameters().mIdeal.Value();
-        if (ideal.IsString()) {
-          if (ideal.GetAsString() != deviceFacingMode) {
-            if (i == 0) {
-              distance = 1000;
-            }
-          }
-        } else if (!ideal.GetAsStringSequence().Contains(deviceFacingMode)) {
-          if (i == 0) {
-            distance = 1000;
-          }
-        }
-      }
-    }
+  for (const auto& constraint : aConstraintSets) {
     nsString s;
     GetMediaSource(s);
-    if (s != c.mMediaSource) {
+    if (s != constraint->mMediaSource) {
       return UINT32_MAX;
     }
   }
   // Forward request to underlying object to interrogate per-mode capabilities.
-  distance += uint64_t(GetSource()->GetBestFitnessDistance(aConstraintSets));
-  return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+  return GetSource()->GetBestFitnessDistance(aConstraintSets);
 }
 
 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
   : MediaDevice(aSource) {}
 
 uint32_t
 AudioDevice::GetBestFitnessDistance(
     const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
@@ -598,28 +381,16 @@ MediaDevice::GetId(nsAString& aID)
 
 void
 MediaDevice::SetId(const nsAString& aID)
 {
   mID.Assign(aID);
 }
 
 NS_IMETHODIMP
-MediaDevice::GetFacingMode(nsAString& aFacingMode)
-{
-  if (mHasFacingMode) {
-    aFacingMode.Assign(NS_ConvertUTF8toUTF16(
-        dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value));
-  } else {
-    aFacingMode.Truncate(0);
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 MediaDevice::GetMediaSource(nsAString& aMediaSource)
 {
   if (mMediaSource == dom::MediaSourceEnum::Microphone) {
     aMediaSource.Assign(NS_LITERAL_STRING("microphone"));
   } else if (mMediaSource == dom::MediaSourceEnum::Window) { // this will go away
     aMediaSource.Assign(NS_LITERAL_STRING("window"));
   } else { // all the rest are shared
     aMediaSource.Assign(NS_ConvertUTF8toUTF16(
@@ -1434,145 +1205,95 @@ public:
     return NS_OK;
   }
 
 private:
   nsAutoPtr<GetUserMediaTask> mTask;
 };
 #endif
 
-class SanitizeDeviceIdsTask : public Task
-{
-public:
-  explicit SanitizeDeviceIdsTask(int64_t aSinceWhen)
-  : mSinceWhen(aSinceWhen) {}
+// TODO: Remove once upgraded to GCC 4.8+ on linux. Bogus error on static func:
+// error: 'this' was not captured for this lambda function
 
-  void // NS_IMETHOD
-  Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    nsRefPtr<media::ChildPledge<bool>> p =
-        mozilla::media::SanitizeOriginKeys(mSinceWhen); // we fire and forget
-  }
-private:
-  int64_t mSinceWhen;
-};
+static auto& MediaManager_GetInstance = MediaManager::GetInstance;
+static auto& MediaManager_ToJSArray = MediaManager::ToJSArray;
+static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices;
 
 /**
- * Similar to GetUserMediaTask, but used for the chrome-only
- * GetUserMediaDevices function. Enumerates a list of audio & video devices,
- * wraps them up in nsIMediaDevice objects and returns it to the success
- * callback.
- *
- * All code in this class runs on the MediaManager thread.
+ * EnumerateRawDevices - Enumerate a list of audio & video devices that
+ * satisfy passed-in constraints. List contains raw id's.
  */
-class GetUserMediaDevicesTask : public Task
+
+already_AddRefed<MediaManager::PledgeSourceSet>
+MediaManager::EnumerateRawDevices(uint64_t aWindowId,
+                                  const MediaStreamConstraints& aConstraints)
 {
-public:
-  GetUserMediaDevicesTask(
-    const MediaStreamConstraints& aConstraints,
-    already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aOnSuccess,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
-    uint64_t aWindowId, nsACString& aAudioLoopbackDev,
-    nsACString& aVideoLoopbackDev, bool aPrivileged, const nsACString& aOrigin,
-    bool aInPrivateBrowsing, bool aUseFakeDevices)
-    : mConstraints(aConstraints)
-    , mOnSuccess(aOnSuccess)
-    , mOnFailure(aOnFailure)
-    , mManager(MediaManager::GetInstance())
-    , mWindowId(aWindowId)
-    , mLoopbackAudioDevice(aAudioLoopbackDev)
-    , mLoopbackVideoDevice(aVideoLoopbackDev)
-    , mPrivileged(aPrivileged)
-    , mOrigin(aOrigin)
-    , mInPrivateBrowsing(aInPrivateBrowsing)
-    , mUseFakeDevices(aUseFakeDevices) {}
+  MOZ_ASSERT(NS_IsMainThread());
+  nsRefPtr<PledgeSourceSet> p = new PledgeSourceSet();
+  uint32_t id = mOutstandingPledges.Append(*p);
+
+  // Check if the preference for using audio/video loopback devices is
+  // enabled. This is currently used for automated media tests only.
+  auto audioLoopDev = Preferences::GetCString("media.audio_loopback_dev");
+  auto videoLoopDev = Preferences::GetCString("media.video_loopback_dev");
+  bool fake = Preferences::GetBool("media.navigator.streams.fake", false);
 
-  void // NS_IMETHOD
-  Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
+  MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aConstraints, aWindowId,
+                                                 audioLoopDev,  videoLoopDev,
+                                                 fake]() mutable {
     nsRefPtr<MediaEngine> backend;
-    if (mConstraints.mFake || mUseFakeDevices)
-      backend = new MediaEngineDefault(mConstraints.mFakeTracks);
-    else
-      backend = mManager->GetBackend(mWindowId);
-
-    typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
+    if (aConstraints.mFake || fake) {
+      backend = new MediaEngineDefault(aConstraints.mFakeTracks);
+    } else {
+      nsRefPtr<MediaManager> manager = MediaManager_GetInstance();
+      backend = manager->GetBackend(aWindowId);
+    }
 
     ScopedDeletePtr<SourceSet> result(new SourceSet);
-    if (IsOn(mConstraints.mVideo)) {
+    if (IsOn(aConstraints.mVideo)) {
       nsTArray<nsRefPtr<VideoDevice>> sources;
-      GetSources(backend, GetInvariant(mConstraints.mVideo),
-                 &MediaEngine::EnumerateVideoDevices, sources,
-                 mLoopbackVideoDevice.get());
+      GetSources(backend, GetInvariant(aConstraints.mVideo),
+                 &MediaEngine::EnumerateVideoDevices, sources, videoLoopDev);
       for (auto& source : sources) {
         result->AppendElement(source);
       }
     }
-    if (IsOn(mConstraints.mAudio)) {
+    if (IsOn(aConstraints.mAudio)) {
       nsTArray<nsRefPtr<AudioDevice>> sources;
-      GetSources(backend, GetInvariant(mConstraints.mAudio),
-                 &MediaEngine::EnumerateAudioDevices, sources,
-                 mLoopbackAudioDevice.get());
+      GetSources(backend, GetInvariant(aConstraints.mAudio),
+                 &MediaEngine::EnumerateAudioDevices, sources, audioLoopDev);
       for (auto& source : sources) {
         result->AppendElement(source);
       }
     }
-    nsRefPtr<DeviceSuccessCallbackRunnable> runnable =
-        new DeviceSuccessCallbackRunnable(mWindowId, mOnSuccess, mOnFailure,
-                                          result.forget(), mPrivileged);
-    if (mPrivileged) {
-      NS_DispatchToMainThread(runnable);
-    } else {
-      // Get persistent origin-unique uuid to anonymize deviceIds back on main.
-      //
-      // GetOriginKey is an async API that returns a pledge (as promise-like
-      // pattern). We use .Then() to pass in a lambda to run back on this
-      // thread once GetOriginKey resolves asynchronously . The "runnable"
-      // pointer is "captured" (passed by value) into the lambda.
-      nsRefPtr<media::ChildPledge<nsCString>> p =
-          media::GetOriginKey(mOrigin, mInPrivateBrowsing);
-      p->Then([runnable](nsCString result) mutable {
-        runnable->mOriginKey = result;
-        NS_DispatchToMainThread(runnable);
-      });
-    }
-    // One of the Runnables have taken these.
-    MOZ_ASSERT(!mOnSuccess && !mOnFailure);
-  }
-
-private:
-  MediaStreamConstraints mConstraints;
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mOnSuccess;
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-  nsRefPtr<MediaManager> mManager;
-  uint64_t mWindowId;
-  const nsString mCallId;
-  // Audio & Video loopback devices to be used based on
-  // the preference settings. This is currently used for
-  // automated media tests only.
-  nsCString mLoopbackAudioDevice;
-  nsCString mLoopbackVideoDevice;
-  bool mPrivileged;
-  nsCString mOrigin;
-  bool mInPrivateBrowsing;
-  bool mUseFakeDevices;
-};
+    SourceSet* handoff = result.forget();
+    NS_DispatchToMainThread(NewRunnableFrom([id, handoff]() mutable {
+      ScopedDeletePtr<SourceSet> result(handoff);
+      nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+      if (!mgr) {
+        return NS_OK;
+      }
+      nsRefPtr<PledgeSourceSet> p = mgr->mOutstandingPledges.Remove(id);
+      if (p) {
+        p->Resolve(result.forget());
+      }
+      return NS_OK;
+    }));
+  }));
+  return p.forget();
+}
 
 MediaManager::MediaManager()
   : mMediaThread(nullptr)
   , mMutex("mozilla::MediaManager")
   , mBackend(nullptr) {
   mPrefs.mWidth  = 0; // adaptive default
   mPrefs.mHeight = 0; // adaptive default
   mPrefs.mFPS    = MediaEngine::DEFAULT_VIDEO_FPS;
   mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS;
-
   nsresult rv;
   nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
     if (branch) {
       GetPrefs(branch, nullptr);
     }
   }
@@ -1649,16 +1370,25 @@ MediaManager::GetIfExists() {
 /* static */ already_AddRefed<MediaManager>
 MediaManager::GetInstance()
 {
   // so we can have non-refcounted getters
   nsRefPtr<MediaManager> service = MediaManager::Get();
   return service.forget();
 }
 
+media::Parent<media::NonE10s>*
+MediaManager::GetNonE10sParent()
+{
+  if (!mNonE10sParent) {
+    mNonE10sParent = new media::Parent<media::NonE10s>(true);
+  }
+  return mNonE10sParent;
+}
+
 /* static */
 void
 MediaManager::PostTask(const tracked_objects::Location& from_here, Task* task)
 {
   if (sInShutdown) {
     // Can't safely delete task here since it may have items with specific
     // thread-release requirements.
     return;
@@ -1731,32 +1461,30 @@ MediaManager::NotifyRecordingStatusChang
 }
 
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
-MediaManager::GetUserMedia(
-  nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints,
-  nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
-  nsIDOMGetUserMediaErrorCallback* aOnFailure)
+MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
+                           const MediaStreamConstraints& aConstraints,
+                           nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
+                           nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
-  NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_TRUE(aOnFailure, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aOnFailure);
+  MOZ_ASSERT(aOnSuccess);
+  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
 
   bool privileged = nsContentUtils::IsCallerChrome();
 
-  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
-
   MediaStreamConstraints c(aConstraints); // copy
 
   static bool created = false;
   if (!created) {
     // Force MediaManager to startup before we try to access it from other threads
     // Hack: should init singleton earlier unless it's expensive (mem or CPU)
     (void) MediaManager::Get();
 #ifdef MOZ_B2G
@@ -1976,72 +1704,223 @@ MediaManager::GetUserMedia(
 
 #ifdef MOZ_WEBRTC
   EnableWebRtcLog();
 #endif
 
   return NS_OK;
 }
 
-nsresult
-MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
-  const MediaStreamConstraints& aConstraints,
-  nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
-  nsIDOMGetUserMediaErrorCallback* aOnFailure,
-  uint64_t aInnerWindowID,
-  bool aPrivileged)
+/* static */ void
+MediaManager::AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey)
+{
+  if (!aOriginKey.IsEmpty()) {
+    for (auto& device : aDevices) {
+      nsString id;
+      device->GetId(id);
+      AnonymizeId(id, aOriginKey);
+      device->SetId(id);
+    }
+  }
+}
+
+/* static */ nsresult
+MediaManager::AnonymizeId(nsAString& aId, const nsACString& aOriginKey)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv;
+  nsCOMPtr<nsIKeyObjectFactory> factory =
+    do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCString rawKey;
+  rv = Base64Decode(aOriginKey, rawKey);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCOMPtr<nsIKeyObject> key;
+  rv = factory->KeyFromString(nsIKeyObject::HMAC, rawKey, getter_AddRefs(key));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
-  NS_ENSURE_TRUE(aOnFailure, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
+  nsCOMPtr<nsICryptoHMAC> hasher =
+    do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = hasher->Init(nsICryptoHMAC::SHA256, key);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  NS_ConvertUTF16toUTF8 id(aId);
+  rv = hasher->Update(reinterpret_cast<const uint8_t*> (id.get()), id.Length());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCString mac;
+  rv = hasher->Finish(true, mac);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
+  aId = NS_ConvertUTF8toUTF16(mac);
+  return NS_OK;
+}
 
-  // Check if the preference for using loopback devices is enabled.
-  nsAdoptingCString loopbackAudioDevice =
-    Preferences::GetCString("media.audio_loopback_dev");
-  nsAdoptingCString loopbackVideoDevice =
-    Preferences::GetCString("media.video_loopback_dev");
-  bool useFakeStreams =
-    Preferences::GetBool("media.navigator.streams.fake", false);
+/* static */
+already_AddRefed<nsIWritableVariant>
+MediaManager::ToJSArray(SourceSet& aDevices)
+{
+  nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
+  size_t len = aDevices.Length();
+  if (len) {
+    nsTArray<nsIMediaDevice*> tmp(len);
+    for (auto& device : aDevices) {
+      tmp.AppendElement(device);
+    }
+    auto* elements = static_cast<const void*>(tmp.Elements());
+    nsresult rv = var->SetAsArray(nsIDataType::VTYPE_INTERFACE,
+                                  &NS_GET_IID(nsIMediaDevice), len,
+                                  const_cast<void*>(elements));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+  } else {
+    var->SetAsEmptyArray(); // because SetAsArray() fails on zero length arrays.
+  }
+  return var.forget();
+}
+
+already_AddRefed<MediaManager::PledgeSourceSet>
+MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
+                                   const MediaStreamConstraints& aConstraints)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // This function returns a pledge, a promise-like object with the future result
+  nsRefPtr<PledgeSourceSet> pledge = new PledgeSourceSet();
+  uint32_t id = mOutstandingPledges.Append(*pledge);
+
+  // To get a device list anonymized for a particular origin, we must:
+  // 1. Get an origin-key (for either regular or private browsing)
+  // 2. Get the raw devices list
+  // 3. Anonymize the raw list with the origin-key.
 
   nsCString origin;
-  nsPrincipal::GetOriginForURI(aWindow->GetDocumentURI(), origin);
-  bool inPrivateBrowsing;
+  bool privateBrowsing;
   {
-    nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
+    nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
+        (nsGlobalWindow::GetInnerWindowWithId(aWindowId));
+    nsPrincipal::GetOriginForURI(window->GetDocumentURI(), origin);
+
+    nsCOMPtr<nsIDocument> doc = window->GetDoc();
     nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
-    inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
+    privateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
   }
-  MediaManager::PostTask(FROM_HERE,
-    new GetUserMediaDevicesTask(
-      aConstraints, onSuccess.forget(), onFailure.forget(),
-      (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
-      loopbackAudioDevice, loopbackVideoDevice, aPrivileged, origin,
-      inPrivateBrowsing, useFakeStreams));
+  // GetOriginKey is an async API that returns a pledge (a promise-like
+  // pattern). We use .Then() to pass in a lambda to run back on this same
+  // thread later once GetOriginKey resolves. Needed variables are "captured"
+  // (passed by value) safely into the lambda.
+
+  nsRefPtr<Pledge<nsCString>> p = media::GetOriginKey(origin, privateBrowsing);
+  p->Then([id, aWindowId, aConstraints](const nsCString& aOriginKey) mutable {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+
+    nsRefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aConstraints);
+    p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable {
+      ScopedDeletePtr<SourceSet> devices(aDevices); // secondary result
 
-  return NS_OK;
+      // Only run if window is still on our active list.
+      nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+      if (!mgr) {
+        return NS_OK;
+      }
+      nsRefPtr<PledgeSourceSet> p = mgr->mOutstandingPledges.Remove(id);
+      if (!p || !mgr->IsWindowStillActive(aWindowId)) {
+        return NS_OK;
+      }
+      MediaManager_AnonymizeDevices(*devices, aOriginKey);
+      p->Resolve(devices.forget());
+      return NS_OK;
+    });
+  });
+  return pledge.forget();
 }
 
 nsresult
 MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
                                nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
                                nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
+  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
+  uint64_t windowId = aWindow->WindowID();
+
+  AddWindowID(windowId);
 
   MediaStreamConstraints c;
   c.mVideo.SetAsBoolean() = true;
   c.mAudio.SetAsBoolean() = true;
 
-  AddWindowID(aWindow->WindowID());
-  return GetUserMediaDevices(aWindow, c, aOnSuccess, aOnFailure, 0, false);
+  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId, c);
+  p->Then([onSuccess](SourceSet*& aDevices) mutable {
+    ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
+    nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
+    onSuccess->OnSuccess(array);
+  }, [onFailure](MediaStreamError& reason) mutable {
+    onFailure->OnError(&reason);
+  });
+  return NS_OK;
+}
+
+/*
+ * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
+ */
+
+nsresult
+MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
+                                  const MediaStreamConstraints& aConstraints,
+                                  nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
+                                  nsIDOMGetUserMediaErrorCallback* aOnFailure,
+                                  uint64_t aWindowId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
+  if (!aWindowId) {
+    aWindowId = aWindow->WindowID();
+  }
+
+  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(aWindowId, aConstraints);
+  p->Then([aWindowId, onSuccess, onFailure](SourceSet*& aDevices) mutable {
+    ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
+
+    if (devices->Length()) {
+      nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
+      onSuccess->OnSuccess(array);
+    } else {
+      nsRefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(aWindowId);
+      if (!window) {
+        return NS_ERROR_UNEXPECTED;
+      }
+      nsRefPtr<MediaStreamError> reason =
+          new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
+      onFailure->OnError(reason);
+    }
+    return NS_OK;
+  }, [onFailure](MediaStreamError& reason) mutable {
+    onFailure->OnError(&reason);
+  });
+  return NS_OK;
 }
 
 MediaEngine*
 MediaManager::GetBackend(uint64_t aWindowId)
 {
   // Plugin backends as appropriate. The default engine also currently
   // includes picture support for Android.
   // This IS called off main-thread.
@@ -2545,17 +2424,17 @@ MediaManager::MediaCaptureWindowState(ns
 }
 
 NS_IMETHODIMP
 MediaManager::SanitizeDeviceIds(int64_t aSinceWhen)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   LOG(("%s: sinceWhen = %llu", __FUNCTION__, aSinceWhen));
 
-  MediaManager::PostTask(FROM_HERE, new SanitizeDeviceIdsTask(aSinceWhen));
+  media::SanitizeOriginKeys(aSinceWhen); // we fire and forget
   return NS_OK;
 }
 
 static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
                           StreamListeners *aListeners,
                           void *aData)
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -22,16 +22,18 @@
 #include "nsIDOMNavigatorUserMedia.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/MediaStreamError.h"
+#include "mozilla/media/MediaChild.h"
+#include "mozilla/media/MediaParent.h"
 #include "mozilla/Logging.h"
 #include "DOMMediaStream.h"
 
 #ifdef MOZ_WEBRTC
 #include "mtransport/runnable_utils.h"
 #endif
 
 // Note, these suck in Windows headers, unfortunately.
@@ -463,18 +465,16 @@ public:
   NS_DECL_NSIMEDIADEVICE
 
   void SetId(const nsAString& aID);
 protected:
   virtual ~MediaDevice() {}
   explicit MediaDevice(MediaEngineSource* aSource);
   nsString mName;
   nsString mID;
-  bool mHasFacingMode;
-  dom::VideoFacingModeEnum mFacingMode;
   dom::MediaSourceEnum mMediaSource;
   nsRefPtr<MediaEngineSource> mSource;
 };
 
 class VideoDevice : public MediaDevice
 {
 public:
   typedef MediaEngineVideoSource Source;
@@ -529,16 +529,17 @@ public:
                                               const nsString& aMsg,
                                               const bool& aIsAudio,
                                               const bool& aIsVideo);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEDIAMANAGERSERVICE
 
+  media::Parent<media::NonE10s>* GetNonE10sParent();
   MediaEngine* GetBackend(uint64_t aWindowId = 0);
   StreamListeners *GetWindowListeners(uint64_t aWindowId) {
     NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
 
     return mActiveWindows.Get(aWindowId);
   }
   void RemoveWindowID(uint64_t aWindowId);
   bool IsWindowStillActive(uint64_t aWindowId) {
@@ -550,33 +551,47 @@ public:
 
   nsresult GetUserMedia(
     nsPIDOMWindow* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError);
 
   nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow,
-    const dom::MediaStreamConstraints& aConstraints,
-    nsIGetUserMediaDevicesSuccessCallback* onSuccess,
-    nsIDOMGetUserMediaErrorCallback* onError,
-    uint64_t aInnerWindowID = 0,
-    bool aPrivileged = true);
+                               const dom::MediaStreamConstraints& aConstraints,
+                               nsIGetUserMediaDevicesSuccessCallback* onSuccess,
+                               nsIDOMGetUserMediaErrorCallback* onError,
+                               uint64_t aInnerWindowID = 0);
 
   nsresult EnumerateDevices(nsPIDOMWindow* aWindow,
                             nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
                             nsIDOMGetUserMediaErrorCallback* aOnFailure);
 
   nsresult EnumerateDevices(nsPIDOMWindow* aWindow, dom::Promise& aPromise);
   void OnNavigation(uint64_t aWindowID);
   bool IsWindowActivelyCapturing(uint64_t aWindowId);
 
   MediaEnginePrefs mPrefs;
 
 private:
+  typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
+  typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet;
+
+  static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
+public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
+  static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
+  static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
+private:
+  already_AddRefed<PledgeSourceSet>
+  EnumerateRawDevices(uint64_t aWindowId,
+                      const dom::MediaStreamConstraints& aConstraints);
+  already_AddRefed<PledgeSourceSet>
+  EnumerateDevicesImpl(uint64_t aWindowId,
+                       const dom::MediaStreamConstraints& aConstraints);
+
   StreamListeners* AddWindowID(uint64_t aWindowId);
   WindowTable *GetActiveWindows() {
     NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
     return &mActiveWindows;
   }
 
   void GetPref(nsIPrefBranch *aBranch, const char *aPref,
                const char *aData, int32_t *aVal);
@@ -605,16 +620,20 @@ private:
   nsAutoPtr<base::Thread> mMediaThread;
 
   Mutex mMutex;
   // protected with mMutex:
   RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
+  media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
 #if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
   nsRefPtr<nsDOMCameraManager> mCameraManager;
 #endif
+public:
+  media::CoatCheck<media::Pledge<nsCString>> mGetOriginKeyPledges;
+  ScopedDeletePtr<media::Parent<media::NonE10s>> mNonE10sParent;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_MEDIAMANAGER_H
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -999,16 +999,17 @@ RTCPeerConnection.prototype = {
       dict.maxRetransmits = dict.maxRetransmitNum;
       this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0);
     }
     if (dict.outOfOrderAllowed != undefined) {
       dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
                                               // the name change
       this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0);
     }
+
     if (dict.preset != undefined) {
       dict.negotiated = dict.preset;
       this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!", null, 0);
     }
     if (dict.stream != undefined) {
       dict.id = dict.stream;
       this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!", null, 0);
     }
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -599,17 +599,17 @@ MP4Reader::ShouldSkip(bool aSkipToNextKe
   // if the time threshold (the current playback position) is after the next
   // keyframe in the stream. This means we'll only skip frames that we have
   // no hope of ever playing.
   Microseconds nextKeyframe = -1;
   if (!sDemuxSkipToNextKeyframe ||
       (nextKeyframe = GetNextKeyframeTime()) == -1) {
     return aSkipToNextKeyframe;
   }
-  return nextKeyframe < aTimeThreshold;
+  return nextKeyframe < aTimeThreshold && nextKeyframe >= 0;
 }
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
                             int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   VLOG("skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -9,29 +9,32 @@
 #include "WebMBufferedParser.h"
 #include "mozilla/Endian.h"
 #include "mozilla/ErrorResult.h"
 #include "mp4_demuxer/MoofParser.h"
 #include "mozilla/Logging.h"
 #include "MediaData.h"
 #ifdef MOZ_FMP4
 #include "MP4Stream.h"
+#include "mp4_demuxer/AtomType.h"
+#include "mp4_demuxer/ByteReader.h"
 #endif
 #include "SourceBufferResource.h"
 
 extern PRLogModuleInfo* GetMediaSourceLog();
 
 /* Polyfill __func__ on MSVC to pass to the log. */
 #ifdef _MSC_VER
 #define __func__ __FUNCTION__
 #endif
 
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
 #define MSE_DEBUG(name, arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUGV(name, arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
 ContainerParser::ContainerParser(const nsACString& aType)
   : mHasInitData(false)
   , mType(aType)
 {
 }
@@ -253,54 +256,63 @@ private:
 #ifdef MOZ_FMP4
 class MP4ContainerParser : public ContainerParser {
 public:
   explicit MP4ContainerParser(const nsACString& aType)
     : ContainerParser(aType)
     , mMonitor("MP4ContainerParser Index Monitor")
   {}
 
+  bool HasAtom(const mp4_demuxer::AtomType& aAtom, const MediaByteBuffer* aData) {
+    mp4_demuxer::ByteReader reader(aData);
+
+    while (reader.Remaining() >= 8) {
+      uint64_t size = reader.ReadU32();
+      const uint8_t* typec = reader.Peek(4);
+      uint32_t type = reader.ReadU32();
+      MSE_DEBUGV(MP4ContainerParser ,"Checking atom:'%c%c%c%c'",
+                typec[0], typec[1], typec[2], typec[3]);
+      if (mp4_demuxer::AtomType(type) == aAtom) {
+        reader.DiscardRemaining();
+        return true;
+      }
+      if (size == 1) {
+        // 64 bits size.
+        if (!reader.CanReadType<uint64_t>()) {
+          break;
+        }
+        size = reader.ReadU64();
+      } else if (size == 0) {
+        // Atom extends to the end of the buffer, it can't have what we're
+        // looking for.
+        break;
+      }
+      if (reader.Remaining() < size - 8) {
+        // Incomplete atom.
+        break;
+      }
+      reader.Read(size - 8);
+    }
+    reader.DiscardRemaining();
+    return false;
+  }
+
   bool IsInitSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsInitSegmentPresent(aData);
     // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
     // file is the 'ftyp' atom followed by a file type. We just check for a
     // vaguely valid 'ftyp' atom.
-
-    if (aData->Length() < 8) {
-      return false;
-    }
-
-    uint32_t chunk_size = BigEndian::readUint32(aData->Elements());
-    if (chunk_size < 8) {
-      return false;
-    }
-
-    return (*aData)[4] == 'f' && (*aData)[5] == 't' && (*aData)[6] == 'y' &&
-           (*aData)[7] == 'p';
+    return HasAtom(mp4_demuxer::AtomType("ftyp"), aData);
   }
 
   bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsMediaSegmentPresent(aData);
-    if (aData->Length() < 8) {
-      return false;
-    }
-
-    uint32_t chunk_size = BigEndian::readUint32(aData->Elements());
-    if (chunk_size < 8) {
-      return false;
-    }
-
-    return ((*aData)[4] == 'm' && (*aData)[5] == 'o' && (*aData)[6] == 'o' &&
-            (*aData)[7] == 'f') ||
-           ((*aData)[4] == 's' && (*aData)[5] == 't' && (*aData)[6] == 'y' &&
-            (*aData)[7] == 'p') ||
-           ((*aData)[4] == 's' && (*aData)[5] == 'i' && (*aData)[6] == 'd' &&
-            (*aData)[7] == 'x');
+    return HasAtom(mp4_demuxer::AtomType("moof"), aData);
   }
 
   bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
                                   int64_t& aStart, int64_t& aEnd) override
   {
     MonitorAutoLock mon(mMonitor); // We're not actually racing against anything,
                                    // but mParser requires us to hold a monitor.
     bool initSegment = IsInitSegmentPresent(aData);
@@ -343,17 +355,19 @@ public:
     }
 
     mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
       mParser->GetCompositionRange(byteRanges);
 
     mCompleteMediaHeaderRange = mParser->FirstCompleteMediaHeader();
     mCompleteMediaSegmentRange = mParser->FirstCompleteMediaSegment();
     ErrorResult rv;
-    mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
+    if (HasCompleteInitData()) {
+      mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
+    }
     if (NS_WARN_IF(rv.Failed())) {
       rv.SuppressException();
       return false;
     }
 
     if (compositionRange.IsNull()) {
       return false;
     }
@@ -389,10 +403,11 @@ ContainerParser::CreateForMIMEType(const
   if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
     return new MP4ContainerParser(aType);
   }
 #endif
   return new ContainerParser(aType);
 }
 
 #undef MSE_DEBUG
+#undef MSE_DEBUGV
 
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -80,16 +80,20 @@ IsTypeSupported(const nsAString& aType)
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   nsresult rv = parser.GetType(mimeType);
   if (NS_FAILED(rv)) {
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
+  if (Preferences::GetBool("media.mediasource.format-reader", false) &&
+      !mimeType.EqualsASCII("video/mp4") && !mimeType.EqualsASCII("audio/mp4")) {
+    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  }
   bool found = false;
   for (uint32_t i = 0; gMediaSourceTypes[i]; ++i) {
     if (mimeType.EqualsASCII(gMediaSourceTypes[i])) {
       if ((mimeType.EqualsASCII("video/mp4") ||
            mimeType.EqualsASCII("audio/mp4")) &&
           (!Preferences::GetBool("media.mediasource.mp4.enabled", false)
 #ifdef MOZ_WIDGET_ANDROID
           // MP4 won't work unless we have JellyBean+
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -12,16 +12,21 @@
 #include "SourceBufferList.h"
 
 namespace mozilla {
 
 typedef TrackInfo::TrackType TrackType;
 using media::TimeUnit;
 using media::TimeIntervals;
 
+// Gap allowed between frames. Due to inaccuracies in determining buffer end
+// frames (Bug 1065207). This value is based on the end of frame
+// default value used in Blink, kDefaultBufferDurationInMs.
+#define EOS_FUZZ_US 125000
+
 MediaSourceDemuxer::MediaSourceDemuxer()
   : mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
                                   /* aSupportsTailDispatch = */ true))
   , mMonitor("MediaSourceDemuxer")
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
@@ -220,19 +225,25 @@ MediaSourceDemuxer::~MediaSourceDemuxer(
 }
 
 MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
                                                  TrackInfo::TrackType aType,
                                                  TrackBuffersManager* aManager)
   : mParent(aParent)
   , mManager(aManager)
   , mType(aType)
-  , mNextSampleIndex(0)
   , mMonitor("MediaSourceTrackDemuxer")
 {
+  // Force refresh of our buffered ranges.
+  nsRefPtr<MediaSourceTrackDemuxer> self = this;
+  nsCOMPtr<nsIRunnable> task =
+    NS_NewRunnableFunction([self] () {
+      self->NotifyTimeRangesChanged();
+    });
+  mParent->GetTaskQueue()->Dispatch(task.forget());
 }
 
 UniquePtr<TrackInfo>
 MediaSourceTrackDemuxer::GetInfo() const
 {
   return mParent->GetTrackInfo(mType)->Clone();
 }
 
@@ -254,21 +265,21 @@ MediaSourceTrackDemuxer::GetSamples(int3
 
 void
 MediaSourceTrackDemuxer::Reset()
 {
   MOZ_ASSERT(mParent, "Called after BreackCycle()");
   nsRefPtr<MediaSourceTrackDemuxer> self = this;
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self] () {
-      self->mNextSampleTime = TimeUnit();
-      self->mNextSampleIndex = 0;
+      self->mManager->Seek(self->mType, TimeUnit());
       {
         MonitorAutoLock mon(self->mMonitor);
-        self->mNextRandomAccessPoint = self->GetNextRandomAccessPoint();
+        self->mNextRandomAccessPoint =
+          self->mManager->GetNextRandomAccessPoint(self->mType);
       }
     });
   mParent->GetTaskQueue()->Dispatch(task.forget());
 }
 
 nsresult
 MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
 {
@@ -305,155 +316,72 @@ MediaSourceTrackDemuxer::BreakCycles()
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self]() { self->mParent = nullptr; } );
   mParent->GetTaskQueue()->Dispatch(task.forget());
 }
 
 nsRefPtr<MediaSourceTrackDemuxer::SeekPromise>
 MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
 {
-  if (aTime.ToMicroseconds() && !mManager->Buffered(mType).Contains(aTime)) {
+  if (aTime.ToMicroseconds() && !mBufferedRanges.Contains(aTime)) {
     // We don't have the data to seek to.
     return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
                                         __func__);
   }
-  const TrackBuffersManager::TrackBuffer& track =
-    mManager->GetTrackBuffer(mType);
-  TimeUnit lastKeyFrameTime;
-  uint32_t lastKeyFrameIndex = 0;
-  for (uint32_t i = 0; i < track.Length(); i++) {
-    const nsRefPtr<MediaRawData>& sample = track[i];
-    if (sample->mKeyframe) {
-      lastKeyFrameTime = TimeUnit::FromMicroseconds(sample->mTime);
-      lastKeyFrameIndex = i;
-    }
-    if (sample->mTime >= aTime.ToMicroseconds()) {
-      break;
-    }
-  }
-  mNextSampleIndex = lastKeyFrameIndex;
-  mNextSampleTime = lastKeyFrameTime;
+  TimeUnit seekTime = mManager->Seek(mType, aTime);
   {
     MonitorAutoLock mon(mMonitor);
-    mNextRandomAccessPoint = GetNextRandomAccessPoint();
+    mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType);
   }
-  return SeekPromise::CreateAndResolve(mNextSampleTime, __func__);
+  return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
 nsRefPtr<MediaSourceTrackDemuxer::SamplesPromise>
 MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
 {
-  DemuxerFailureReason failure;
-  nsRefPtr<MediaRawData> sample = GetSample(failure);
+  bool error;
+  nsRefPtr<MediaRawData> sample = mManager->GetSample(mType,
+                                                      TimeUnit::FromMicroseconds(EOS_FUZZ_US),
+                                                      error);
   if (!sample) {
-    return SamplesPromise::CreateAndReject(failure, __func__);
+    if (error) {
+      return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+    }
+    return SamplesPromise::CreateAndReject(
+      mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
+                            DemuxerFailureReason::WAITING_FOR_DATA, __func__);
   }
   nsRefPtr<SamplesHolder> samples = new SamplesHolder;
   samples->mSamples.AppendElement(sample);
   if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
     MonitorAutoLock mon(mMonitor);
-    mNextRandomAccessPoint = GetNextRandomAccessPoint();
+    mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType);
   }
   return SamplesPromise::CreateAndResolve(samples, __func__);
 }
 
 nsRefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
 MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold)
 {
-  bool found = false;
-  int32_t parsed = 0;
-  const TrackBuffersManager::TrackBuffer& track =
-    mManager->GetTrackBuffer(mType);
-  for (uint32_t i = mNextSampleIndex; i < track.Length(); i++) {
-    const nsRefPtr<MediaRawData>& sample = track[i];
-    if (sample->mKeyframe &&
-        sample->mTime >= aTimeThreadshold.ToMicroseconds()) {
-      mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
-      found = true;
-      break;
-    }
-    parsed++;
-  }
-
+  bool found;
+  uint32_t parsed =
+    mManager->SkipToNextRandomAccessPoint(mType, aTimeThreadshold, found);
   if (found) {
     return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
   }
   SkipFailureHolder holder(
     mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
                           DemuxerFailureReason::WAITING_FOR_DATA, parsed);
   return SkipAccessPointPromise::CreateAndReject(holder, __func__);
 }
 
-already_AddRefed<MediaRawData>
-MediaSourceTrackDemuxer::GetSample(DemuxerFailureReason& aFailure)
-{
-  const TrackBuffersManager::TrackBuffer& track =
-    mManager->GetTrackBuffer(mType);
-  const TimeIntervals& ranges = mManager->Buffered(mType);
-  if (mNextSampleTime >= ranges.GetEnd()) {
-    if (mManager->IsEnded()) {
-      aFailure = DemuxerFailureReason::END_OF_STREAM;
-    } else {
-      aFailure = DemuxerFailureReason::WAITING_FOR_DATA;
-    }
-    return nullptr;
-  }
-  if (mNextSampleTime == TimeUnit()) {
-    // First demux, get first sample time.
-    mNextSampleTime = ranges.GetStart();
-  }
-  if (!ranges.ContainsWithStrictEnd(mNextSampleTime)) {
-    aFailure = DemuxerFailureReason::WAITING_FOR_DATA;
-    return nullptr;
-  }
-
-  if (mNextSampleIndex) {
-    const nsRefPtr<MediaRawData>& sample = track[mNextSampleIndex];
-    nsRefPtr<MediaRawData> p = sample->Clone();
-    if (!p) {
-      aFailure = DemuxerFailureReason::DEMUXER_ERROR;
-      return nullptr;
-    }
-    mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
-    mNextSampleIndex++;
-    return p.forget();
-  }
-  for (uint32_t i = 0; i < track.Length(); i++) {
-    const nsRefPtr<MediaRawData>& sample = track[i];
-    if (sample->mTime >= mNextSampleTime.ToMicroseconds()) {
-      nsRefPtr<MediaRawData> p = sample->Clone();
-      mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
-      mNextSampleIndex = i+1;
-      if (!p) {
-        // OOM
-        break;
-      }
-      return p.forget();
-    }
-  }
-  aFailure = DemuxerFailureReason::DEMUXER_ERROR;
-  return nullptr;
-}
-
-TimeUnit
-MediaSourceTrackDemuxer::GetNextRandomAccessPoint()
-{
-  const TrackBuffersManager::TrackBuffer& track = mManager->GetTrackBuffer(mType);
-  for (uint32_t i = mNextSampleIndex; i < track.Length(); i++) {
-    const nsRefPtr<MediaRawData>& sample = track[i];
-    if (sample->mKeyframe) {
-      return TimeUnit::FromMicroseconds(sample->mTime);
-    }
-  }
-  return media::TimeUnit::FromInfinity();
-}
-
 void
 MediaSourceTrackDemuxer::NotifyTimeRangesChanged()
 {
   if (!mParent) {
     return;
   }
   MOZ_ASSERT(mParent->OnTaskQueue());
-  mNextSampleIndex = 0;
+  mBufferedRanges = mManager->Buffered(mType);
+  mBufferedRanges.SetFuzz(TimeUnit::FromMicroseconds(EOS_FUZZ_US));
 }
 
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -122,18 +122,17 @@ private:
   nsRefPtr<SkipAccessPointPromise> DoSkipToNextRandomAccessPoint(TimeUnit aTimeThreadshold);
   already_AddRefed<MediaRawData> GetSample(DemuxerFailureReason& aFailure);
   // Return the timestamp of the next keyframe after mLastSampleIndex.
   TimeUnit GetNextRandomAccessPoint();
 
   nsRefPtr<MediaSourceDemuxer> mParent;
   nsRefPtr<TrackBuffersManager> mManager;
   TrackInfo::TrackType mType;
-  uint32_t mNextSampleIndex;
-  media::TimeUnit mNextSampleTime;
+  media::TimeIntervals mBufferedRanges;
   // Monitor protecting members below accessed from multiple threads.
   Monitor mMonitor;
   media::TimeUnit mNextRandomAccessPoint;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -40,16 +40,17 @@ extern PRLogModuleInfo* GetMediaSourceLo
 #define EOS_FUZZ_US 125000
 
 namespace mozilla {
 
 MediaSourceReader::MediaSourceReader(MediaSourceDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
   , mLastAudioTime(0)
   , mLastVideoTime(0)
+  , mOriginalSeekTime(-1)
   , mPendingSeekTime(-1)
   , mWaitingForSeekData(false)
   , mSeekToEnd(false)
   , mTimeThreshold(0)
   , mDropAudioBeforeThreshold(false)
   , mDropVideoBeforeThreshold(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
@@ -832,16 +833,17 @@ MediaSourceReader::Seek(int64_t aTime, i
 
   if (IsShutdown()) {
     mSeekPromise.Reject(NS_ERROR_FAILURE, __func__);
     return p;
   }
 
   // Store pending seek target in case the track buffers don't contain
   // the desired time and we delay doing the seek.
+  mOriginalSeekTime = aTime;
   mPendingSeekTime = aTime;
 
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mWaitingForSeekData = true;
     mDropAudioBeforeThreshold = false;
     mDropVideoBeforeThreshold = false;
     mTimeThreshold = 0;
@@ -916,17 +918,18 @@ MediaSourceReader::OnVideoSeekFailed(nsr
 
 void
 MediaSourceReader::DoAudioSeek()
 {
   int64_t seekTime = mPendingSeekTime;
   if (mSeekToEnd) {
     seekTime = LastSampleTime(MediaData::AUDIO_DATA);
   }
-  if (SwitchAudioSource(&seekTime) == SOURCE_NONE) {
+  if (SwitchAudioSource(&seekTime) == SOURCE_NONE &&
+      SwitchAudioSource(&mOriginalSeekTime) == SOURCE_NONE) {
     // Data we need got evicted since the last time we checked for data
     // availability. Abort current seek attempt.
     mWaitingForSeekData = true;
     return;
   }
   GetAudioReader()->ResetDecode();
   mAudioSeekRequest.Begin(GetAudioReader()->Seek(GetReaderAudioTime(seekTime), 0)
                          ->Then(TaskQueue(), __func__, this,
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -262,16 +262,17 @@ private:
   int64_t mLastVideoTime;
 
   MediaPromiseRequestHolder<SeekPromise> mAudioSeekRequest;
   MediaPromiseRequestHolder<SeekPromise> mVideoSeekRequest;
   MediaPromiseHolder<SeekPromise> mSeekPromise;
 
   // Temporary seek information while we wait for the data
   // to be added to the track buffer.
+  int64_t mOriginalSeekTime;
   int64_t mPendingSeekTime;
   bool mWaitingForSeekData;
   bool mSeekToEnd;
 
   int64_t mTimeThreshold;
   bool mDropAudioBeforeThreshold;
   bool mDropVideoBeforeThreshold;
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -436,17 +436,17 @@ SourceBuffer::AppendData(const uint8_t* 
   nsRefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv);
   if (!data) {
     return;
   }
   mContentManager->AppendData(data, mTimestampOffset);
 
   StartUpdating();
 
-  MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments,
+  MOZ_ASSERT(mIsUsingFormatReader || mAppendMode == SourceBufferAppendMode::Segments,
              "We don't handle timestampOffset for sequence mode yet");
   nsCOMPtr<nsIRunnable> task = new BufferAppendRunnable(this, mUpdateID);
   NS_DispatchToMainThread(task);
 }
 
 void
 SourceBuffer::BufferAppend(uint32_t aUpdateID)
 {
@@ -482,16 +482,18 @@ SourceBuffer::AppendDataCompletedWithSuc
     if (!mActive) {
       mActive = true;
       mMediaSource->SourceBufferIsActive(this);
       mMediaSource->QueueInitializationEvent();
       if (mIsUsingFormatReader) {
         mMediaSource->GetDecoder()->NotifyWaitingForResourcesStatusChanged();
       }
     }
+  }
+  if (mActive) {
     // Tell our parent decoder that we have received new data.
     // The information provided do not matter much so long as it is monotonically
     // increasing.
     mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
   }
 
   CheckEndTime();
 
@@ -573,17 +575,18 @@ SourceBuffer::PrepareAppend(const uint8_
     // the current start point.
     mMediaSource->NotifyEvicted(0.0, newBufferStartTime.ToSeconds());
   }
 
   // See if we have enough free space to append our new data.
   // As we can only evict once we have playable data, we must give a chance
   // to the DASH player to provide a complete media segment.
   if (aLength > mEvictionThreshold ||
-      ((mContentManager->GetSize() > mEvictionThreshold - aLength) &&
+      ((!mIsUsingFormatReader &&
+        mContentManager->GetSize() > mEvictionThreshold - aLength) &&
        evicted != Result::CANT_EVICT)) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return nullptr;
   }
 
   nsRefPtr<MediaByteBuffer> data = new MediaByteBuffer();
   if (!data->AppendElements(aData, aLength, fallible)) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -32,16 +32,18 @@ AppendStateToStr(TrackBuffersManager::Ap
       return "PARSING_INIT_SEGMENT";
     case TrackBuffersManager::AppendState::PARSING_MEDIA_SEGMENT:
       return "PARSING_MEDIA_SEGMENT";
     default:
       return "IMPOSSIBLE";
   }
 }
 
+static Atomic<uint32_t> sStreamSourceID(0u);
+
 TrackBuffersManager::TrackBuffersManager(dom::SourceBuffer* aParent, MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mInputBuffer(new MediaByteBuffer)
   , mAppendState(AppendState::WAITING_FOR_SEGMENT)
   , mBufferFull(false)
   , mFirstInitializationSegmentReceived(false)
   , mActiveTrack(false)
   , mType(aType)
   , mParser(ContainerParser::CreateForMIMEType(aType))
@@ -59,16 +61,20 @@ TrackBuffersManager::TrackBuffersManager
   nsRefPtr<TrackBuffersManager> self = this;
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self] () {
       self->mMediaSourceDuration.Connect(self->mParentDecoder->CanonicalExplicitDuration());
     });
   GetTaskQueue()->Dispatch(task.forget());
 }
 
+TrackBuffersManager::~TrackBuffersManager()
+{
+}
+
 bool
 TrackBuffersManager::AppendData(MediaByteBuffer* aData,
                                 TimeUnit aTimestampOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("Appending %lld bytes", aData->Length());
 
   mEnded = false;
@@ -161,16 +167,20 @@ TrackBuffersManager::EvictData(TimeUnit 
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("");
 
   int64_t toEvict = GetSize() - aThreshold;
   if (toEvict <= 0) {
     return EvictDataResult::NO_DATA_EVICTED;
   }
+  if (toEvict <= 512*1024) {
+    // Don't bother evicting less than 512KB.
+    return EvictDataResult::CANT_EVICT;
+  }
   MSE_DEBUG("Reaching our size limit, schedule eviction of %lld bytes", toEvict);
 
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableMethodWithArgs<TimeUnit, uint32_t>(
       this, &TrackBuffersManager::DoEvictData,
       aPlaybackTime, toEvict);
   GetTaskQueue()->Dispatch(task.forget());
 
@@ -283,57 +293,55 @@ TrackBuffersManager::FinishCodedFramePro
 
 void
 TrackBuffersManager::CompleteResetParserState()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(!mAppendRunning);
   MSE_DEBUG("");
 
-  for (auto track : GetTracksList()) {
+  for (auto& track : GetTracksList()) {
     // 2. Unset the last decode timestamp on all track buffers.
-    track->mLastDecodeTimestamp.reset();
     // 3. Unset the last frame duration on all track buffers.
-    track->mLastFrameDuration.reset();
     // 4. Unset the highest end timestamp on all track buffers.
-    track->mHighestEndTimestamp.reset();
     // 5. Set the need random access point flag on all track buffers to true.
-    track->mNeedRandomAccessPoint = true;
+    track->ResetAppendState();
 
     // if we have been aborted, we may have pending frames that we are going
     // to discard now.
     track->mQueuedSamples.Clear();
-    track->mLongestFrameDuration.reset();
   }
   // 6. Remove all bytes from the input buffer.
   mIncomingBuffers.Clear();
   mInputBuffer = nullptr;
   if (mCurrentInputBuffer) {
     mCurrentInputBuffer->EvictAll();
     mCurrentInputBuffer = new SourceBufferResource(mType);
   }
 
   // We could be left with a demuxer in an unusable state. It needs to be
   // recreated. We store in the InputBuffer an init segment which will be parsed
   // during the next Segment Parser Loop and a new demuxer will be created and
   // initialized.
   if (mFirstInitializationSegmentReceived) {
-    nsRefPtr<MediaByteBuffer> initData = mParser->InitData();
-    MOZ_ASSERT(initData->Length(), "we must have an init segment");
+    MOZ_ASSERT(mInitData && mInitData->Length(), "we must have an init segment");
     // The aim here is really to destroy our current demuxer.
     CreateDemuxerforMIMEType();
     // Recreate our input buffer. We can't directly assign the initData buffer
     // to mInputBuffer as it will get modified in the Segment Parser Loop.
     mInputBuffer = new MediaByteBuffer;
-    MOZ_ALWAYS_TRUE(mInputBuffer->AppendElements(*initData, fallible));
+    mInputBuffer->AppendElements(*mInitData);
   }
   RecreateParser();
 
   // 7. Set append state to WAITING_FOR_SEGMENT.
   SetAppendState(AppendState::WAITING_FOR_SEGMENT);
+
+  // Reject our promise immediately.
+  mAppendPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
 }
 
 void
 TrackBuffersManager::DoEvictData(const TimeUnit& aPlaybackTime,
                                  uint32_t aSizeToEvict)
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -383,17 +391,17 @@ TrackBuffersManager::DoEvictData(const T
       }
       partialEvict = 0;
     }
     if (frame->mTime <= upperLimit.ToMicroseconds()) {
       break;
     }
     partialEvict += sizeof(*frame) + frame->mSize;
   }
-  if (lastKeyFrameIndex < buffer.Length()) {
+  if (lastKeyFrameIndex + 1 < buffer.Length()) {
     CodedFrameRemoval(
       TimeInterval(TimeUnit::FromMicroseconds(buffer[lastKeyFrameIndex+1]->mTime),
                    TimeUnit::FromInfinity()));
   }
 }
 
 nsRefPtr<TrackBuffersManager::RangeRemovalPromise>
 TrackBuffersManager::CodedFrameRemovalWithPromise(TimeInterval aInterval)
@@ -414,21 +422,21 @@ TrackBuffersManager::CodedFrameRemoval(T
   if (mMediaSourceDuration.Ref().isNothing() ||
       IsNaN(mMediaSourceDuration.Ref().ref())) {
     MSE_DEBUG("Nothing to remove, aborting");
     return false;
   }
   TimeUnit duration{TimeUnit::FromSeconds(mMediaSourceDuration.Ref().ref())};
 
   MSE_DEBUG("duration:%.2f", duration.ToSeconds());
-  if (HasAudio()) {
+  if (HasVideo()) {
     MSE_DEBUG("before video ranges=%s",
               DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
   }
-  if (HasVideo()) {
+  if (HasAudio()) {
     MSE_DEBUG("before audio ranges=%s",
               DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
   }
 
   // 1. Let start be the starting presentation timestamp for the removal range.
   TimeUnit start = aInterval.mStart;
   // 2. Let end be the end presentation timestamp for the removal range.
   TimeUnit end = aInterval.mEnd;
@@ -447,56 +455,107 @@ TrackBuffersManager::CodedFrameRemoval(T
     if (end < track->mBufferedRanges.GetEnd()) {
       for (auto& frame : track->mBuffers.LastElement()) {
         if (frame->mKeyframe && frame->mTime >= end.ToMicroseconds()) {
           removeEndTimestamp = TimeUnit::FromMicroseconds(frame->mTime);
           break;
         }
       }
     }
+
+    bool removeCurrentCodedFrameGroup = false;
+
     // 3. Remove all media data, from this track buffer, that contain starting
     // timestamps greater than or equal to start and less than the remove end timestamp.
     TimeInterval removedInterval;
-    int32_t firstRemovedIndex = -1;
+    Maybe<uint32_t> firstRemovedIndex;
     TrackBuffer& data = track->mBuffers.LastElement();
-    for (uint32_t i = 0; i < data.Length(); i++) {
+    for (uint32_t i = 0; i < data.Length();) {
       const auto& frame = data[i];
       if (frame->mTime >= start.ToMicroseconds() &&
           frame->mTime < removeEndTimestamp.ToMicroseconds()) {
-        if (firstRemovedIndex < 0) {
+        if (firstRemovedIndex.isNothing()) {
           removedInterval =
             TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
                          TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration));
-          firstRemovedIndex = i;
+          firstRemovedIndex = Some(i);
         } else {
           removedInterval = removedInterval.Span(
             TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
                          TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration)));
         }
         track->mSizeBuffer -= sizeof(*frame) + frame->mSize;
         data.RemoveElementAt(i);
+        removeCurrentCodedFrameGroup |=
+          track->mNextInsertionIndex.isSome() &&
+          track->mNextInsertionIndex.ref() == i;
+        if (!removeCurrentCodedFrameGroup &&
+            track->mNextInsertionIndex.isSome() &&
+            track->mNextInsertionIndex.ref() > i) {
+          track->mNextInsertionIndex.ref()--;
+        }
+
+        if (track->mNextGetSampleIndex.isSome()) {
+          if (track->mNextGetSampleIndex.ref() == i) {
+            MSE_DEBUG("Next sample to be played got evicted");
+            track->mNextGetSampleIndex.reset();
+          } else if (track->mNextGetSampleIndex.ref() > i) {
+            track->mNextGetSampleIndex.ref()--;
+          }
+        }
+      } else {
+        i++;
       }
     }
     // 4. Remove decoding dependencies of the coded frames removed in the previous step:
     // Remove all coded frames between the coded frames removed in the previous step and the next random access point after those removed frames.
-    if (firstRemovedIndex >= 0) {
-      for (uint32_t i = firstRemovedIndex; i < data.Length(); i++) {
-        const auto& frame = data[i];
-        if (frame->mKeyframe) {
+    if (firstRemovedIndex.isSome()) {
+      uint32_t start = firstRemovedIndex.ref();
+      uint32_t end = start;
+      for (;end < data.Length(); end++) {
+        MediaRawData* sample = data[end].get();
+        if (sample->mKeyframe) {
           break;
         }
         removedInterval = removedInterval.Span(
-          TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
-                       TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration)));
-        track->mSizeBuffer -= sizeof(*frame) + frame->mSize;
-        data.RemoveElementAt(i);
+          TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                       TimeUnit::FromMicroseconds(sample->GetEndTime())));
+        track->mSizeBuffer -= sizeof(*sample) + sample->mSize;
+      }
+      data.RemoveElementsAt(start, end - start);
+
+      removeCurrentCodedFrameGroup |=
+        track->mNextInsertionIndex.isSome() &&
+        track->mNextInsertionIndex.ref() >= start &&
+        track->mNextInsertionIndex.ref() < end;
+      if (!removeCurrentCodedFrameGroup &&
+          track->mNextInsertionIndex.isSome() &&
+          track->mNextInsertionIndex.ref() >= end) {
+        track->mNextInsertionIndex.ref() -= end - start;
       }
+
+      if (track->mNextGetSampleIndex.isSome()) {
+        if (track->mNextGetSampleIndex.ref() >= start &&
+            track->mNextGetSampleIndex.ref() < end) {
+          MSE_DEBUG("Next sample to be played got evicted");
+          track->mNextGetSampleIndex.reset();
+        } else if (track->mNextGetSampleIndex.ref() >= end) {
+          track->mNextGetSampleIndex.ref() -= end - start;
+        }
+      }
+
+      MSE_DEBUG("Removing undecodable frames from:%u (frames:%d) ([%f, %f))",
+                start, end - start,
+                removedInterval.mStart.ToSeconds(), removedInterval.mEnd.ToSeconds());
+      track->mBufferedRanges -= removedInterval;
       dataRemoved = true;
+      if (removeCurrentCodedFrameGroup) {
+        track->ResetAppendState();
+      }
     }
-    track->mBufferedRanges -= removedInterval;
 
     // 5. If this object is in activeSourceBuffers, the current playback position
     // is greater than or equal to start and less than the remove end timestamp,
     // and HTMLMediaElement.readyState is greater than HAVE_METADATA, then set the
     // HTMLMediaElement.readyState attribute to HAVE_METADATA and stall playback.
     // This will be done by the MDSM during playback.
     // TODO properly, so it works even if paused.
   }
@@ -504,21 +563,21 @@ TrackBuffersManager::CodedFrameRemoval(T
   // TODO.
   mBufferFull = false;
   {
     MonitorAutoLock mon(mMonitor);
     mVideoBufferedRanges = mVideoTracks.mBufferedRanges;
     mAudioBufferedRanges = mAudioTracks.mBufferedRanges;
   }
 
-  if (HasAudio()) {
+  if (HasVideo()) {
     MSE_DEBUG("after video ranges=%s",
               DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
   }
-  if (HasVideo()) {
+  if (HasAudio()) {
     MSE_DEBUG("after audio ranges=%s",
               DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
   }
 
   // Update our reported total size.
   mSizeSourceBuffer = mVideoTracks.mSizeBuffer + mAudioTracks.mSizeBuffer;
 
   // Tell our demuxer that data was removed.
@@ -570,58 +629,69 @@ TrackBuffersManager::SegmentParserLoop()
     }
     // 2. If the input buffer contains bytes that violate the SourceBuffer
     // byte stream format specification, then run the append error algorithm with
     // the decode error parameter set to true and abort this algorithm.
     // TODO
 
     // 3. Remove any bytes that the byte stream format specifications say must be
     // ignored from the start of the input buffer.
-    // TODO
+    // We do not remove bytes from our input buffer. Instead we enforce that
+    // our ContainerParser is able to skip over all data that is supposed to be
+    // ignored.
 
     // 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
     // steps:
     if (mAppendState == AppendState::WAITING_FOR_SEGMENT) {
       if (mParser->IsInitSegmentPresent(mInputBuffer)) {
         SetAppendState(AppendState::PARSING_INIT_SEGMENT);
+        if (mFirstInitializationSegmentReceived) {
+          // This is a new initialization segment. Obsolete the old one.
+          mInitData = nullptr;
+          RecreateParser();
+        }
         continue;
       }
       if (mParser->IsMediaSegmentPresent(mInputBuffer)) {
         SetAppendState(AppendState::PARSING_MEDIA_SEGMENT);
         continue;
       }
       // We have neither an init segment nor a media segment, this is invalid
-      // data.
-      MSE_DEBUG("Found invalid data");
-      RejectAppend(NS_ERROR_FAILURE, __func__);
+      // data. We can ignore it.
+      MSE_DEBUG("Found invalid data, ignoring.");
+      mInputBuffer = nullptr;
+      NeedMoreData();
       return;
     }
 
     int64_t start, end;
     mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
     mProcessedInput += mInputBuffer->Length();
 
     // 5. If the append state equals PARSING_INIT_SEGMENT, then run the
     // following steps:
     if (mAppendState == AppendState::PARSING_INIT_SEGMENT) {
       if (mParser->InitSegmentRange().IsNull()) {
+        mInputBuffer = nullptr;
         NeedMoreData();
         return;
       }
       InitializationSegmentReceived();
       return;
     }
     if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
       // 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm.
       if (!mFirstInitializationSegmentReceived) {
         RejectAppend(NS_ERROR_FAILURE, __func__);
         return;
       }
       // 2. If the input buffer does not contain a complete media segment header yet, then jump to the need more data step below.
       if (mParser->MediaHeaderRange().IsNull()) {
+        mCurrentInputBuffer->AppendData(mInputBuffer);
+        mInputBuffer = nullptr;
         NeedMoreData();
         return;
       }
       // 3. If the input buffer contains one or more complete coded frames, then run the coded frame processing algorithm.
       nsRefPtr<TrackBuffersManager> self = this;
       mProcessingRequest.Begin(CodedFrameProcessing()
           ->Then(GetTaskQueue(), __func__,
                  [self] (bool aNeedMoreData) {
@@ -676,44 +746,49 @@ TrackBuffersManager::CreateDemuxerforMIM
     mVideoTracks.mDemuxer = nullptr;
   }
   if (mAudioTracks.mDemuxer) {
     mAudioTracks.mDemuxer->BreakCycles();
     mAudioTracks.mDemuxer = nullptr;
   }
   mInputDemuxer = nullptr;
   if (mType.LowerCaseEqualsLiteral("video/webm") || mType.LowerCaseEqualsLiteral("audio/webm")) {
-    MOZ_ASSERT(false, "Waiting on WebMDemuxer");
+    NS_WARNING("Waiting on WebMDemuxer");
   // mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer);
+    return;
   }
 
 #ifdef MOZ_FMP4
   if (mType.LowerCaseEqualsLiteral("video/mp4") || mType.LowerCaseEqualsLiteral("audio/mp4")) {
     mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
     return;
   }
 #endif
-  MOZ_ASSERT(false, "Not supported (yet)");
+  NS_WARNING("Not supported (yet)");
+  return;
 }
 
 void
 TrackBuffersManager::InitializationSegmentReceived()
 {
   MOZ_ASSERT(mParser->HasCompleteInitData());
+  mInitData = mParser->InitData();
   mCurrentInputBuffer = new SourceBufferResource(mType);
-  mCurrentInputBuffer->AppendData(mParser->InitData());
-  uint32_t initLength = mParser->InitSegmentRange().mEnd;
-  if (mInputBuffer->Length() == initLength) {
+  mCurrentInputBuffer->AppendData(mInitData);
+  uint32_t length =
+    mParser->InitSegmentRange().mEnd - (mProcessedInput - mInputBuffer->Length());
+  if (mInputBuffer->Length() == length) {
     mInputBuffer = nullptr;
   } else {
-    mInputBuffer->RemoveElementsAt(0, initLength);
+    mInputBuffer->RemoveElementsAt(0, length);
   }
   CreateDemuxerforMIMEType();
   if (!mInputDemuxer) {
-    MOZ_ASSERT(false, "TODO type not supported");
+    NS_WARNING("TODO type not supported");
+    RejectAppend(NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__);
     return;
   }
   mDemuxerInitRequest.Begin(mInputDemuxer->Init()
                       ->Then(GetTaskQueue(), __func__,
                              this,
                              &TrackBuffersManager::OnDemuxerInitDone,
                              &TrackBuffersManager::OnDemuxerInitFailed));
 }
@@ -789,16 +864,19 @@ TrackBuffersManager::OnDemuxerInitDone(n
 
     mVideoTracks.mLongestFrameDuration = mVideoTracks.mLastFrameDuration;
     mAudioTracks.mLongestFrameDuration = mAudioTracks.mLastFrameDuration;
   }
 
   // 4. Let active track flag equal false.
   mActiveTrack = false;
 
+  // Increase our stream id.
+  uint32_t streamID = sStreamSourceID++;
+
   // 5. If the first initialization segment received flag is false, then run the following steps:
   if (!mFirstInitializationSegmentReceived) {
     mAudioTracks.mNumTracks = numAudios;
     // TODO:
     // 1. If the initialization segment contains tracks with codecs the user agent
     // does not support, then run the append error algorithm with the decode
     // error parameter set to true and abort these steps.
 
@@ -824,17 +902,18 @@ TrackBuffersManager::OnDemuxerInitDone(n
       //     2. Set active track flag to true.
       mActiveTrack = true;
       //   8. Add new audio track to the audioTracks attribute on this SourceBuffer object.
       //   9. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object referenced by the audioTracks attribute on this SourceBuffer object.
       //   10. Add new audio track to the audioTracks attribute on the HTMLMediaElement.
       //   11. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object referenced by the audioTracks attribute on the HTMLMediaElement.
       mAudioTracks.mBuffers.AppendElement(TrackBuffer());
       // 10. Add the track description for this track to the track buffer.
-      mAudioTracks.mInfo = info.mAudio.Clone();
+      mAudioTracks.mInfo = new SharedTrackInfo(info.mAudio, streamID);
+      mAudioTracks.mLastInfo = mAudioTracks.mInfo;
     }
 
     mVideoTracks.mNumTracks = numVideos;
     // 3. For each video track in the initialization segment, run following steps:
     // for (uint32_t i = 0; i < numVideos; i++) {
     if (numVideos) {
       // 1. Let video byte stream track ID be the Track ID for the current track being processed.
       // 2. Let video language be a BCP 47 language tag for the language specified in the initialization segment for this track or an empty string if no language info is present.
@@ -855,24 +934,28 @@ TrackBuffersManager::OnDemuxerInitDone(n
       //     2. Set active track flag to true.
       mActiveTrack = true;
       //   8. Add new video track to the videoTracks attribute on this SourceBuffer object.
       //   9. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object referenced by the videoTracks attribute on this SourceBuffer object.
       //   10. Add new video track to the videoTracks attribute on the HTMLMediaElement.
       //   11. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object referenced by the videoTracks attribute on the HTMLMediaElement.
       mVideoTracks.mBuffers.AppendElement(TrackBuffer());
       // 10. Add the track description for this track to the track buffer.
-      mVideoTracks.mInfo = info.mVideo.Clone();
+      mVideoTracks.mInfo = new SharedTrackInfo(info.mVideo, streamID);
+      mVideoTracks.mLastInfo = mVideoTracks.mInfo;
     }
     // 4. For each text track in the initialization segment, run following steps:
     // 5. If active track flag equals true, then run the following steps:
     // This is handled by SourceBuffer once the promise is resolved.
 
     // 6. Set first initialization segment received flag to true.
     mFirstInitializationSegmentReceived = true;
+  } else {
+    mAudioTracks.mLastInfo = new SharedTrackInfo(info.mAudio, streamID);
+    mVideoTracks.mLastInfo = new SharedTrackInfo(info.mVideo, streamID);
   }
 
   // TODO CHECK ENCRYPTION
   UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
   if (crypto && crypto->IsEncrypted()) {
 #ifdef MOZ_EME
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
     for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
@@ -1041,25 +1124,45 @@ TrackBuffersManager::CompleteCodedFrameP
   for (auto& sample : mVideoTracks.mQueuedSamples) {
     while (true) {
       if (!ProcessFrame(sample, mVideoTracks)) {
         break;
       }
     }
   }
   mVideoTracks.mQueuedSamples.Clear();
+#if defined(DEBUG)
+  if (HasVideo()) {
+    const auto& track = mVideoTracks.mBuffers.LastElement();
+    MOZ_ASSERT(track.IsEmpty() || track[0]->mKeyframe);
+    for (uint32_t i = 1; i < track.Length(); i++) {
+      MOZ_ASSERT((track[i-1]->mTrackInfo->GetID() == track[i]->mTrackInfo->GetID() && track[i-1]->mTimecode < track[i]->mTimecode) ||
+                 track[i]->mKeyframe);
+    }
+  }
+#endif
 
   for (auto& sample : mAudioTracks.mQueuedSamples) {
     while (true) {
       if (!ProcessFrame(sample, mAudioTracks)) {
         break;
       }
     }
   }
   mAudioTracks.mQueuedSamples.Clear();
+#if defined(DEBUG)
+  if (HasAudio()) {
+    const auto& track = mAudioTracks.mBuffers.LastElement();
+    MOZ_ASSERT(track.IsEmpty() || track[0]->mKeyframe);
+    for (uint32_t i = 1; i < track.Length(); i++) {
+      MOZ_ASSERT((track[i-1]->mTrackInfo->GetID() == track[i]->mTrackInfo->GetID() && track[i-1]->mTimecode < track[i]->mTimecode) ||
+                 track[i]->mKeyframe);
+    }
+  }
+#endif
 
   {
     MonitorAutoLock mon(mMonitor);
 
     // Save our final tracks buffered ranges.
     mVideoBufferedRanges = mVideoTracks.mBufferedRanges;
     mAudioBufferedRanges = mAudioTracks.mBufferedRanges;
     if (HasAudio()) {
@@ -1125,17 +1228,16 @@ TrackBuffersManager::ResolveProcessing(b
   }
   mProcessingPromise.ResolveIfExists(aResolveValue, __func__);
 }
 
 bool
 TrackBuffersManager::ProcessFrame(MediaRawData* aSample,
                                   TrackData& aTrackData)
 {
-  TrackData* tracks[] = { &mVideoTracks, &mAudioTracks };
   TimeUnit presentationTimestamp;
   TimeUnit decodeTimestamp;
 
   if (!mParent->mGenerateTimestamp) {
     presentationTimestamp = TimeUnit::FromMicroseconds(aSample->mTime);
     decodeTimestamp = TimeUnit::FromMicroseconds(aSample->mTimecode);
   }
 
@@ -1191,44 +1293,44 @@ TrackBuffersManager::ProcessFrame(MediaR
       // Set group end timestamp to presentation timestamp.
       mGroupEndTimestamp = presentationTimestamp;
     }
     // 1b. If mode equals "sequence":
     if (mParent->mAppendMode == SourceBufferAppendMode::Sequence) {
       // Set group start timestamp equal to the group end timestamp.
       mGroupStartTimestamp = Some(mGroupEndTimestamp);
     }
-    for (auto& track : tracks) {
+    for (auto& track : GetTracksList()) {
       // 2. Unset the last decode timestamp on all track buffers.
-      track->mLastDecodeTimestamp.reset();
       // 3. Unset the last frame duration on all track buffers.
-      track->mLastFrameDuration.reset();
       // 4. Unset the highest end timestamp on all track buffers.
-      track->mHighestEndTimestamp.reset();
       // 5. Set the need random access point flag on all track buffers to true.
-      track->mNeedRandomAccessPoint = true;
+      track->ResetAppendState();
+    }
 
-      trackBuffer.mLongestFrameDuration.reset();
-    }
-    MSE_DEBUG("Detected discontinuity. Restarting process");
+    MSE_DEBUG("Discontinuity detected. Restarting process");
     // 6. Jump to the Loop Top step above to restart processing of the current coded frame.
     return true;
   }
 
   // 7. Let frame end timestamp equal the sum of presentation timestamp and frame duration.
   TimeUnit frameEndTimestamp = presentationTimestamp + frameDuration;
 
   // 8. If presentation timestamp is less than appendWindowStart, then set the need random access point flag to true, drop the coded frame, and jump to the top of the loop to start processing the next coded frame.
-  if (presentationTimestamp.ToSeconds() < mParent->mAppendWindowStart) {
-    trackBuffer.mNeedRandomAccessPoint = true;
-    return false;
-  }
+  // 9. If frame end timestamp is greater than appendWindowEnd, then set the need random access point flag to true, drop the coded frame, and jump to the top of the loop to start processing the next coded frame.
 
-  // 9. If frame end timestamp is greater than appendWindowEnd, then set the need random access point flag to true, drop the coded frame, and jump to the top of the loop to start processing the next coded frame.
-  if (frameEndTimestamp.ToSeconds() > mParent->mAppendWindowEnd) {
+  // We apply a fuzz search += mLongestFrameDuration to get around videos where
+  // the start time is negative but close to 0.
+  TimeInterval targetWindow{
+    TimeInterval(TimeUnit::FromSeconds(mParent->mAppendWindowStart),
+                 TimeUnit::FromSeconds(mParent->mAppendWindowEnd),
+                 trackBuffer.mLongestFrameDuration.valueOr(frameDuration))};
+  TimeInterval frameInterval{presentationTimestamp, frameEndTimestamp};
+
+  if (!targetWindow.Contains(frameInterval)) {
     trackBuffer.mNeedRandomAccessPoint = true;
     return false;
   }
 
   // 10. If the need random access point flag on track buffer equals true, then run the following steps:
   if (trackBuffer.mNeedRandomAccessPoint) {
     // 1. If the coded frame is not a random access point, then drop the coded frame and jump to the top of the loop to start processing the next coded frame.
     if (!aSample->mKeyframe) {
@@ -1242,100 +1344,153 @@ TrackBuffersManager::ProcessFrame(MediaR
   // 11. Let spliced audio frame be an unset variable for holding audio splice information
   // 12. Let spliced timed text frame be an unset variable for holding timed text splice information
 
   // 13. If last decode timestamp for track buffer is unset and presentation timestamp falls within the presentation interval of a coded frame in track buffer,then run the following steps:
   // For now we only handle replacing existing frames with the new ones. So we
   // skip this step.
 
   // 14. Remove existing coded frames in track buffer:
+  //   a) If highest end timestamp for track buffer is not set:
+  //      Remove all coded frames from track buffer that have a presentation timestamp greater than or equal to presentation timestamp and less than frame end timestamp.
+  //   b) If highest end timestamp for track buffer is set and less than or equal to presentation timestamp:
+  //      Remove all coded frames from track buffer that have a presentation timestamp greater than or equal to highest end timestamp and less than frame end timestamp
 
   // There is an ambiguity on how to remove frames, which was lodged with:
   // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28710, implementing as per
   // bug description.
-  int firstRemovedIndex = -1;
+  Maybe<uint32_t> firstRemovedIndex;
   TimeInterval removedInterval;
   TrackBuffer& data = trackBuffer.mBuffers.LastElement();
-  if (trackBuffer.mBufferedRanges.Contains(presentationTimestamp)) {
-    if (trackBuffer.mHighestEndTimestamp.isNothing()) {
-      for (uint32_t i = 0; i < data.Length(); i++) {
-        MediaRawData* sample = data[i].get();
-        if (sample->mTime >= presentationTimestamp.ToMicroseconds() &&
-            sample->mTime < frameEndTimestamp.ToMicroseconds()) {
-          if (firstRemovedIndex < 0) {
-            removedInterval =
-              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
-                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration));
-            firstRemovedIndex = i;
-          } else {
-            removedInterval = removedInterval.Span(
-              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
-                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
-          }
-          trackBuffer.mSizeBuffer -= sizeof(*sample) + sample->mSize;
-          data.RemoveElementAt(i);
+  if (trackBuffer.mBufferedRanges.ContainsStrict(presentationTimestamp)) {
+    TimeUnit lowerBound =
+      trackBuffer.mHighestEndTimestamp.valueOr(presentationTimestamp);
+    for (uint32_t i = 0; i < data.Length();) {
+      MediaRawData* sample = data[i].get();
+      if (sample->mTime >= lowerBound.ToMicroseconds() &&
+          sample->mTime < frameEndTimestamp.ToMicroseconds()) {
+        if (firstRemovedIndex.isNothing()) {
+          removedInterval =
+            TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                         TimeUnit::FromMicroseconds(sample->GetEndTime()));
+          firstRemovedIndex = Some(i);
+        } else {
+          removedInterval = removedInterval.Span(
+            TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                         TimeUnit::FromMicroseconds(sample->GetEndTime())));
         }
-      }
-    } else if (trackBuffer.mHighestEndTimestamp.ref() <= presentationTimestamp) {
-      for (uint32_t i = 0; i < data.Length(); i++) {
-        MediaRawData* sample = data[i].get();
-        if (sample->mTime >= trackBuffer.mHighestEndTimestamp.ref().ToMicroseconds() &&
-            sample->mTime < frameEndTimestamp.ToMicroseconds()) {
-          if (firstRemovedIndex < 0) {
-            removedInterval =
-              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
-                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration));
-            firstRemovedIndex = i;
-          } else {
-            removedInterval = removedInterval.Span(
-              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
-                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
+        trackBuffer.mSizeBuffer -= sizeof(*sample) + sample->mSize;
+        MSE_DEBUGV("Overlapping frame:%u ([%f, %f))",
+                  i,
+                  TimeUnit::FromMicroseconds(sample->mTime).ToSeconds(),
+                  TimeUnit::FromMicroseconds(sample->GetEndTime()).ToSeconds());
+        data.RemoveElementAt(i);
+
+        if (trackBuffer.mNextGetSampleIndex.isSome()) {
+          if (trackBuffer.mNextGetSampleIndex.ref() == i) {
+            MSE_DEBUG("Next sample to be played got evicted");
+            trackBuffer.mNextGetSampleIndex.reset();
+          } else if (trackBuffer.mNextGetSampleIndex.ref() > i) {
+            trackBuffer.mNextGetSampleIndex.ref()--;
           }
-          trackBuffer.mSizeBuffer -= sizeof(*sample) + sample->mSize;
-          data.RemoveElementAt(i);
         }
+      } else {
+        i++;
       }
     }
   }
   // 15. Remove decoding dependencies of the coded frames removed in the previous step:
   // Remove all coded frames between the coded frames removed in the previous step and the next random access point after those removed frames.
-  if (firstRemovedIndex >= 0) {
-    for (uint32_t i = firstRemovedIndex; i < data.Length(); i++) {
-      MediaRawData* sample = data[i].get();
+  if (firstRemovedIndex.isSome()) {
+    uint32_t start = firstRemovedIndex.ref();
+    uint32_t end = start;
+    for (;end < data.Length(); end++) {
+      MediaRawData* sample = data[end].get();
       if (sample->mKeyframe) {
         break;
       }
       removedInterval = removedInterval.Span(
         TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
-                     TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
-      trackBuffer.mSizeBuffer -= sizeof(*aSample) + sample->mSize;
-      data.RemoveElementAt(i);
+                     TimeUnit::FromMicroseconds(sample->GetEndTime())));
+      trackBuffer.mSizeBuffer -= sizeof(*sample) + sample->mSize;
     }
+    data.RemoveElementsAt(start, end - start);
+
+    MSE_DEBUG("Removing undecodable frames from:%u (frames:%u) ([%f, %f))",
+              start, end - start,
+              removedInterval.mStart.ToSeconds(), removedInterval.mEnd.ToSeconds());
+
+    if (trackBuffer.mNextGetSampleIndex.isSome()) {
+      if (trackBuffer.mNextGetSampleIndex.ref() >= start &&
+          trackBuffer.mNextGetSampleIndex.ref() < end) {
+        MSE_DEBUG("Next sample to be played got evicted");
+        trackBuffer.mNextGetSampleIndex.reset();
+      } else if (trackBuffer.mNextGetSampleIndex.ref() >= end) {
+        trackBuffer.mNextGetSampleIndex.ref() -= end - start;
+      }
+    }
+
     // Update our buffered range to exclude the range just removed.
     trackBuffer.mBufferedRanges -= removedInterval;
+    MOZ_ASSERT(trackBuffer.mNextInsertionIndex.isNothing() ||
+               trackBuffer.mNextInsertionIndex.ref() <= start);
   }
 
   // 16. Add the coded frame with the presentation timestamp, decode timestamp, and frame duration to the track buffer.
   aSample->mTime = presentationTimestamp.ToMicroseconds();
   aSample->mTimecode = decodeTimestamp.ToMicroseconds();
-  if (firstRemovedIndex >= 0) {
-    data.InsertElementAt(firstRemovedIndex, aSample);
+  aSample->mTrackInfo = trackBuffer.mLastInfo;
+
+  if (data.IsEmpty()) {
+    data.AppendElement(aSample);
+    MOZ_ASSERT(aSample->mKeyframe);
+    trackBuffer.mNextInsertionIndex = Some(data.Length());
+  } else if (trackBuffer.mNextInsertionIndex.isSome()) {
+    data.InsertElementAt(trackBuffer.mNextInsertionIndex.ref(), aSample);
+    MOZ_ASSERT(trackBuffer.mNextInsertionIndex.ref() == 0 ||
+               data[trackBuffer.mNextInsertionIndex.ref()]->mTrackInfo->GetID() == data[trackBuffer.mNextInsertionIndex.ref()-1]->mTrackInfo->GetID() ||
+               data[trackBuffer.mNextInsertionIndex.ref()]->mKeyframe);
+    trackBuffer.mNextInsertionIndex.ref()++;
+  } else if (presentationTimestamp < trackBuffer.mBufferedRanges.GetStart()) {
+    data.InsertElementAt(0, aSample);
+    MOZ_ASSERT(aSample->mKeyframe);
+    trackBuffer.mNextInsertionIndex = Some(size_t(1));
   } else {
-    if (data.IsEmpty() || aSample->mTimecode > data.LastElement()->mTimecode) {
-      data.AppendElement(aSample);
-    } else {
-      // Find where to insert frame.
-      for (uint32_t i = 0; i < data.Length(); i++) {
-        const auto& sample = data[i];
-        if (sample->mTimecode > aSample->mTimecode) {
-          data.InsertElementAt(i, aSample);
-          break;
-        }
+    // Find which discontinuity we should insert the frame before.
+    TimeInterval target;
+    for (const auto& interval : trackBuffer.mBufferedRanges) {
+      if (presentationTimestamp < interval.mStart) {
+        target = interval;
+        break;
       }
     }
+    if (target.IsEmpty()) {
+      // No existing ranges found after our frame presentation time.
+      // Insert frame at the end of array.
+      data.AppendElement(aSample);
+      MOZ_ASSERT(data.Length() <= 2 ||
+                 data[data.Length()-1]->mTrackInfo->GetID() == data[data.Length()-2]->mTrackInfo->GetID() ||
+                 data[data.Length()-1]->mKeyframe);
+      trackBuffer.mNextInsertionIndex = Some(data.Length());
+    }
+    for (uint32_t i = 0; i < data.Length(); i++) {
+      const nsRefPtr<MediaRawData>& sample = data[i];
+      TimeInterval sampleInterval{
+        TimeUnit::FromMicroseconds(sample->mTime),
+        TimeUnit::FromMicroseconds(sample->GetEndTime())};
+      if (target.Intersects(sampleInterval)) {
+        data.InsertElementAt(i, aSample);
+        MOZ_ASSERT(i != 0 &&
+                   (data[i]->mTrackInfo->GetID() == data[i-1]->mTrackInfo->GetID() ||
+                    data[i]->mKeyframe));
+        trackBuffer.mNextInsertionIndex = Some(size_t(i) + 1);
+        break;
+      }
+    }
+    MOZ_ASSERT(aSample->mKeyframe);
   }
   trackBuffer.mSizeBuffer += sizeof(*aSample) + aSample->mSize;
 
   // 17. Set last decode timestamp for track buffer to decode timestamp.
   trackBuffer.mLastDecodeTimestamp = Some(decodeTimestamp);
   // 18. Set last frame duration for track buffer to frame duration.
   trackBuffer.mLastFrameDuration =
     Some(TimeUnit::FromMicroseconds(aSample->mDuration));
@@ -1375,22 +1530,21 @@ TrackBuffersManager::ProcessFrame(MediaR
 void
 TrackBuffersManager::RecreateParser()
 {
   MOZ_ASSERT(OnTaskQueue());
   // Recreate our parser for only the data remaining. This is required
   // as it has parsed the entire InputBuffer provided.
   // Once the old TrackBuffer/MediaSource implementation is removed
   // we can optimize this part. TODO
-  nsRefPtr<MediaByteBuffer> initData = mParser->InitData();
   mParser = ContainerParser::CreateForMIMEType(mType);
-  if (initData) {
+  if (mInitData) {
     int64_t start, end;
-    mParser->ParseStartAndEndTimestamps(initData, start, end);
-    mProcessedInput = initData->Length();
+    mParser->ParseStartAndEndTimestamps(mInitData, start, end);
+    mProcessedInput = mInitData->Length();
   } else {
     mProcessedInput = 0;
   }
 }
 
 nsTArray<TrackBuffersManager::TrackData*>
 TrackBuffersManager::GetTracksList()
 {
@@ -1451,20 +1605,16 @@ TrackBuffersManager::RestartGroupStartTi
       NS_NewRunnableMethod(this, &TrackBuffersManager::RestartGroupStartTimestamp);
     GetTaskQueue()->Dispatch(task.forget());
     return;
   }
   MOZ_ASSERT(OnTaskQueue());
   mGroupStartTimestamp = Some(mGroupEndTimestamp);
 }
 
-TrackBuffersManager::~TrackBuffersManager()
-{
-}
-
 MediaInfo
 TrackBuffersManager::GetMetadata()
 {
   MonitorAutoLock mon(mMonitor);
   return mInfo;
 }
 
 const TimeIntervals&
@@ -1476,10 +1626,187 @@ TrackBuffersManager::Buffered(TrackInfo:
 
 const TrackBuffersManager::TrackBuffer&
 TrackBuffersManager::GetTrackBuffer(TrackInfo::TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   return GetTracksData(aTrack).mBuffers.LastElement();
 }
 
+TimeUnit
+TrackBuffersManager::Seek(TrackInfo::TrackType aTrack,
+                          const TimeUnit& aTime)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& trackBuffer = GetTracksData(aTrack);
+  const TrackBuffersManager::TrackBuffer& track = GetTrackBuffer(aTrack);
+  TimeUnit lastKeyFrameTime;
+  TimeUnit lastKeyFrameTimecode;
+  uint32_t lastKeyFrameIndex = 0;
+  for (uint32_t i = 0; i < track.Length(); i++) {
+    const nsRefPtr<MediaRawData>& sample = track[i];
+    TimeUnit sampleTime = TimeUnit::FromMicroseconds(sample->mTime);
+    if (sampleTime > aTime) {
+      break;
+    }
+    if (sample->mKeyframe) {
+      lastKeyFrameTimecode = TimeUnit::FromMicroseconds(sample->mTimecode);
+      lastKeyFrameTime = sampleTime;
+      lastKeyFrameIndex = i;
+    }
+    if (sampleTime == aTime) {
+      break;
+    }
+  }
+  trackBuffer.mNextGetSampleIndex = Some(lastKeyFrameIndex);
+  trackBuffer.mNextSampleTimecode = lastKeyFrameTimecode;
+  trackBuffer.mNextSampleTime = lastKeyFrameTime;
+
+  return lastKeyFrameTime;
+}
+
+uint32_t
+TrackBuffersManager::SkipToNextRandomAccessPoint(TrackInfo::TrackType aTrack,
+                                                 const TimeUnit& aTimeThreadshold,
+                                                 bool& aFound)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  uint32_t parsed = 0;
+  auto& trackData = GetTracksData(aTrack);
+  const TrackBuffer& track = GetTrackBuffer(aTrack);
+
+  uint32_t nextSampleIndex = trackData.mNextGetSampleIndex.valueOr(0);
+  for (uint32_t i = nextSampleIndex; i < track.Length(); i++) {
+    const nsRefPtr<MediaRawData>& sample = track[i];
+    if (sample->mKeyframe &&
+        sample->mTime >= aTimeThreadshold.ToMicroseconds()) {
+      trackData.mNextSampleTimecode =
+        TimeUnit::FromMicroseconds(sample->mTimecode);
+      trackData.mNextSampleTime =
+        TimeUnit::FromMicroseconds(sample->mTime);
+      trackData.mNextGetSampleIndex = Some(i);
+      aFound = true;
+      break;
+    }
+    parsed++;
+  }
+
+  return parsed;
+}
+
+already_AddRefed<MediaRawData>
+TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
+                               const TimeUnit& aFuzz,
+                               bool& aError)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& trackData = GetTracksData(aTrack);
+  const TrackBuffer& track = GetTrackBuffer(aTrack);
+
+  aError = false;
+
+  if (!track.Length() ||
+      (trackData.mNextGetSampleIndex.isSome() &&
+       trackData.mNextGetSampleIndex.ref() >= track.Length())) {
+    return nullptr;
+  }
+  if (trackData.mNextGetSampleIndex.isNothing() &&
+      trackData.mNextSampleTimecode == TimeUnit()) {
+    // First demux, get first sample.
+    trackData.mNextGetSampleIndex = Some(0u);
+  }
+
+  if (trackData.mNextGetSampleIndex.isSome()) {
+    const nsRefPtr<MediaRawData>& sample =
+      track[trackData.mNextGetSampleIndex.ref()];
+    if (trackData.mNextGetSampleIndex.ref() &&
+        sample->mTimecode > (trackData.mNextSampleTimecode + aFuzz).ToMicroseconds()) {
+      // Gap is too big. End of Stream or Waiting for Data.
+      return nullptr;
+    }
+
+    nsRefPtr<MediaRawData> p = sample->Clone();
+    if (!p) {
+      aError = true;
+      return nullptr;
+    }
+    trackData.mNextGetSampleIndex.ref()++;
+    // Estimate decode timestamp of the next sample.
+    trackData.mNextSampleTimecode =
+      TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration);
+    trackData.mNextSampleTime =
+      TimeUnit::FromMicroseconds(sample->GetEndTime());
+    return p.forget();
+  }
+
+  // Our previous index has been overwritten, attempt to find the new one.
+  for (uint32_t i = 0; i < track.Length(); i++) {
+    const nsRefPtr<MediaRawData>& sample = track[i];
+    TimeInterval sampleInterval{
+      TimeUnit::FromMicroseconds(sample->mTimecode),
+      TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration),
+      aFuzz};
+
+    if (sampleInterval.ContainsWithStrictEnd(trackData.mNextSampleTimecode)) {
+      nsRefPtr<MediaRawData> p = sample->Clone();
+      if (!p) {
+        // OOM
+        aError = true;
+        return nullptr;
+      }
+      trackData.mNextGetSampleIndex = Some(i+1);
+      trackData.mNextSampleTimecode = sampleInterval.mEnd;
+      trackData.mNextSampleTime =
+        TimeUnit::FromMicroseconds(sample->GetEndTime());
+      return p.forget();
+    }
+  }
+
+  // We couldn't find our sample by decode timestamp. Attempt to find it using
+  // presentation timestamp. There will likely be small jerkiness.
+    for (uint32_t i = 0; i < track.Length(); i++) {
+    const nsRefPtr<MediaRawData>& sample = track[i];
+    TimeInterval sampleInterval{
+      TimeUnit::FromMicroseconds(sample->mTime),
+      TimeUnit::FromMicroseconds(sample->GetEndTime()),
+      aFuzz};
+
+    if (sampleInterval.ContainsWithStrictEnd(trackData.mNextSampleTimecode)) {
+      nsRefPtr<MediaRawData> p = sample->Clone();
+      if (!p) {
+        // OOM
+        aError = true;
+        return nullptr;
+      }
+      trackData.mNextGetSampleIndex = Some(i+1);
+      // Estimate decode timestamp of the next sample.
+      trackData.mNextSampleTimecode = sampleInterval.mEnd;
+      trackData.mNextSampleTime =
+        TimeUnit::FromMicroseconds(sample->GetEndTime());
+      return p.forget();
+    }
+  }
+
+  MSE_DEBUG("Couldn't find sample (pts:%lld dts:%lld)",
+             trackData.mNextSampleTime.ToMicroseconds(),
+            trackData.mNextSampleTimecode.ToMicroseconds());
+  return nullptr;
+}
+
+TimeUnit
+TrackBuffersManager::GetNextRandomAccessPoint(TrackInfo::TrackType aTrack)
+{
+  auto& trackData = GetTracksData(aTrack);
+  MOZ_ASSERT(trackData.mNextGetSampleIndex.isSome());
+  const TrackBuffersManager::TrackBuffer& track = GetTrackBuffer(aTrack);
+
+  uint32_t i = trackData.mNextGetSampleIndex.ref();
+  for (; i < track.Length(); i++) {
+    const nsRefPtr<MediaRawData>& sample = track[i];
+    if (sample->mKeyframe) {
+      return TimeUnit::FromMicroseconds(sample->mTime);
+    }
+  }
+  return media::TimeUnit::FromInfinity();
+}
+
 }
 #undef MSE_DEBUG
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_TRACKBUFFERSMANAGER_H_
 #define MOZILLA_TRACKBUFFERSMANAGER_H_
 
 #include "SourceBufferContentManager.h"
+#include "MediaDataDemuxer.h"
 #include "MediaSourceDecoder.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Pair.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 #include "StateMirroring.h"
@@ -74,16 +75,24 @@ public:
   // Interface for MediaSourceDemuxer
   MediaInfo GetMetadata();
   const TrackBuffer& GetTrackBuffer(TrackInfo::TrackType aTrack);
   const TimeIntervals& Buffered(TrackInfo::TrackType);
   bool IsEnded() const
   {
     return mEnded;
   }
+  TimeUnit Seek(TrackInfo::TrackType aTrack, const TimeUnit& aTime);
+  uint32_t SkipToNextRandomAccessPoint(TrackInfo::TrackType aTrack,
+                                       const TimeUnit& aTimeThreadshold,
+                                       bool& aFound);
+  already_AddRefed<MediaRawData> GetSample(TrackInfo::TrackType aTrack,
+                                           const TimeUnit& aFuzz,
+                                           bool& aError);
+  TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack);
 
 #if defined(DEBUG)
   void Dump(const char* aPath) override;
 #endif
 
 private:
   virtual ~TrackBuffersManager();
   // All following functions run on the taskqueue.
@@ -139,16 +148,17 @@ private:
   // Those are used to parse the incoming input buffer.
 
   // Recreate the ContainerParser and only feed it with the previous init
   // segment found.
   void RecreateParser();
   nsAutoPtr<ContainerParser> mParser;
 
   // Demuxer objects and methods.
+  nsRefPtr<MediaByteBuffer> mInitData;
   nsRefPtr<SourceBufferResource> mCurrentInputBuffer;
   nsRefPtr<MediaDataDemuxer> mInputDemuxer;
   // Length already processed in current media segment.
   uint32_t mProcessedInput;
 
   void OnDemuxerInitDone(nsresult);
   void OnDemuxerInitFailed(DemuxerFailureReason aFailure);
   MediaPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest;
@@ -202,27 +212,51 @@ private:
     // Need random access point flag variable that keeps track of whether the
     // track buffer is waiting for a random access point coded frame.
     // The variable is initially set to true to indicate that random access
     // point coded frame is needed before anything can be added to the track
     // buffer.
     bool mNeedRandomAccessPoint;
     nsRefPtr<MediaTrackDemuxer> mDemuxer;
     MediaPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
+    // If set, position where the next contiguous frame will be inserted.
+    // If a discontinuity is detected, it will be unset and recalculated upon
+    // the next insertion.
+    Maybe<size_t> mNextInsertionIndex;
     // Samples just demuxed, but not yet parsed.
     TrackBuffer mQueuedSamples;
     // We only manage a single track of each type at this time.
     nsTArray<TrackBuffer> mBuffers;
     // Track buffer ranges variable that represents the presentation time ranges
     // occupied by the coded frames currently stored in the track buffer.
     TimeIntervals mBufferedRanges;
     // Byte size of all samples contained in this track buffer.
     uint32_t mSizeBuffer;
     // TrackInfo of the first metadata received.
-    UniquePtr<TrackInfo> mInfo;
+    nsRefPtr<SharedTrackInfo> mInfo;
+    // TrackInfo of the last metadata parsed (updated with each init segment.
+    nsRefPtr<SharedTrackInfo> mLastInfo;
+
+    // If set, position of the next sample to be retrieved by GetSample().
+    Maybe<uint32_t> mNextGetSampleIndex;
+    // Approximation of the next sample's decode timestamp.
+    TimeUnit mNextSampleTimecode;
+    // Approximation of the next sample's presentation timestamp.
+    TimeUnit mNextSampleTime;
+
+    void ResetAppendState()
+    {
+      mLastDecodeTimestamp.reset();
+      mLastFrameDuration.reset();
+      mHighestEndTimestamp.reset();
+      mNeedRandomAccessPoint = true;
+
+      mLongestFrameDuration.reset();
+      mNextInsertionIndex.reset();
+    }
   };
 
   bool ProcessFrame(MediaRawData* aSample, TrackData& aTrackData);
   void RejectProcessing(nsresult aRejectValue, const char* aName);
   void ResolveProcessing(bool aResolveValue, const char* aName);
   MediaPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest;
   MediaPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise;
 
--- a/dom/media/nsIDOMNavigatorUserMedia.idl
+++ b/dom/media/nsIDOMNavigatorUserMedia.idl
@@ -1,22 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIVariant.idl"
 
-[scriptable, builtinclass, uuid(bbfebbc6-76eb-407c-b77d-363a69bc5c30)]
+[scriptable, builtinclass, uuid(cebcefca-2de1-460d-b253-d0582c50b40f)]
 interface nsIMediaDevice : nsISupports
 {
   readonly attribute DOMString type;
   readonly attribute DOMString name;
   readonly attribute DOMString id;
-  readonly attribute DOMString facingMode;
   readonly attribute DOMString mediaSource;
 };
 
 [scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
 interface nsIGetUserMediaDevicesSuccessCallback : nsISupports
 {
   void onSuccess(in nsIVariant devices);
 };
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp
+++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp
@@ -4,17 +4,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "MediaCodecProxy.h"
 #include <OMX_IVCommon.h>
 #include <gui/Surface.h>
 #include <ICrypto.h>
 #include "GonkAudioDecoderManager.h"
 #include "MediaDecoderReader.h"
-#include "mp4_demuxer/Adts.h"
 #include "VideoUtils.h"
 #include "nsTArray.h"
 #include "mozilla/Logging.h"
 #include "stagefright/MediaBuffer.h"
 #include "stagefright/MetaData.h"
 #include "stagefright/MediaErrors.h"
 #include <stagefright/foundation/AMessage.h>
 #include <stagefright/foundation/ALooper.h>
@@ -33,29 +32,24 @@ using namespace android;
 typedef android::MediaCodecProxy MediaCodecProxy;
 
 namespace mozilla {
 
 GonkAudioDecoderManager::GonkAudioDecoderManager(const AudioInfo& aConfig)
   : mAudioChannels(aConfig.mChannels)
   , mAudioRate(aConfig.mRate)
   , mAudioProfile(aConfig.mProfile)
-  , mUseAdts(true)
   , mAudioBuffer(nullptr)
   , mMonitor("GonkAudioDecoderManager")
 {
   MOZ_COUNT_CTOR(GonkAudioDecoderManager);
   MOZ_ASSERT(mAudioChannels);
   mCodecSpecificData = aConfig.mCodecSpecificConfig;
   mMimeType = aConfig.mMimeType;
 
-  // Pass through mp3 without applying an ADTS header.
-  if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
-      mUseAdts = false;
-  }
 }
 
 GonkAudioDecoderManager::~GonkAudioDecoderManager()
 {
   MOZ_COUNT_DTOR(GonkAudioDecoderManager);
 }
 
 android::sp<MediaCodecProxy>
@@ -76,22 +70,22 @@ GonkAudioDecoderManager::Init(MediaDataD
   }
   if (!mDecoder->AskMediaCodecAndWait())
   {
     mDecoder = nullptr;
     return nullptr;
   }
   sp<AMessage> format = new AMessage;
   // Fixed values
-  GADM_LOG("Configure audio mime type:%s, chan no:%d, sample-rate:%d", mMimeType.get(), mAudioChannels, mAudioRate);
+  GADM_LOG("Configure audio mime type:%s, chan no:%d, sample-rate:%d, profile:%d",
+           mMimeType.get(), mAudioChannels, mAudioRate, mAudioProfile);
   format->setString("mime", mMimeType.get());
   format->setInt32("channel-count", mAudioChannels);
   format->setInt32("sample-rate", mAudioRate);
   format->setInt32("aac-profile", mAudioProfile);
-  format->setInt32("is-adts", true);
   status_t err = mDecoder->configure(format, nullptr, nullptr, 0);
   if (err != OK || !mDecoder->Prepare()) {
     return nullptr;
   }
 
   if (mMimeType.EqualsLiteral("audio/mp4a-latm")) {
     rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0,
                          android::MediaCodec::BUFFER_FLAG_CODECCONFIG);
@@ -115,19 +109,16 @@ GonkAudioDecoderManager::HasQueuedSample
 nsresult
 GonkAudioDecoderManager::Input(MediaRawData* aSample)
 {
   MonitorAutoLock mon(mMonitor);
   nsRefPtr<MediaRawData> sample;
 
   if (aSample) {
     sample = aSample;
-    if (!PerformFormatSpecificProcess(sample)) {
-      return NS_ERROR_FAILURE;
-    }
   } else {
     // It means EOS with empty sample.
     sample = new MediaRawData();
   }
 
   mQueueSample.AppendElement(sample);
 
   status_t rv;
@@ -149,35 +140,16 @@ GonkAudioDecoderManager::Input(MediaRawD
     } else {
       return NS_ERROR_UNEXPECTED;
     }
   }
 
   return NS_OK;
 }
 
-bool
-GonkAudioDecoderManager::PerformFormatSpecificProcess(MediaRawData* aSample)
-{
-  if (aSample && mUseAdts) {
-    int8_t frequency_index =
-        mp4_demuxer::Adts::GetFrequencyIndex(mAudioRate);
-    bool rv = mp4_demuxer::Adts::ConvertSample(mAudioChannels,
-                                               frequency_index,
-                                               mAudioProfile,
-                                               aSample);
-    if (!rv) {
-      GADM_LOG("Failed to apply ADTS header");
-      return false;
-    }
-  }
-
-  return true;
-}
-
 nsresult
 GonkAudioDecoderManager::CreateAudioData(int64_t aStreamOffset, AudioData **v) {
   if (!(mAudioBuffer != nullptr && mAudioBuffer->data() != nullptr)) {
     GADM_LOG("Audio Buffer is not valid!");
     return NS_ERROR_UNEXPECTED;
   }
 
   int64_t timeUs;
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.h
+++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.h
@@ -33,27 +33,24 @@ public:
   virtual nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) override;
 
   virtual nsresult Flush() override;
 
   virtual bool HasQueuedSample() override;
 
 private:
-  bool PerformFormatSpecificProcess(MediaRawData* aSample);
-
   nsresult CreateAudioData(int64_t aStreamOffset,
                               AudioData** aOutData);
 
   void ReleaseAudioBuffer();
 
   uint32_t mAudioChannels;
   uint32_t mAudioRate;
   const uint32_t mAudioProfile;
-  bool mUseAdts;
 
   MediaDataDecoderCallback*  mReaderCallback;
   android::MediaBuffer* mAudioBuffer;
   android::sp<ALooper> mLooper;
 
   // MediaCodedc's wrapper that performs the decoding.
   android::sp<android::MediaCodecProxy> mDecoder;
 
--- a/dom/media/systemservices/MediaChild.cpp
+++ b/dom/media/systemservices/MediaChild.cpp
@@ -1,154 +1,116 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaChild.h"
+#include "MediaParent.h"
 
-#include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/PBackgroundChild.h"
 #include "nsGlobalWindow.h"
 #include "mozilla/MediaManager.h"
 #include "mozilla/Logging.h"
 #include "nsQueryObject.h"
 
 #undef LOG
 PRLogModuleInfo *gMediaChildLog;
 #define LOG(args) MOZ_LOG(gMediaChildLog, mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 namespace media {
 
-static Child* sChild;
-
-template<typename ValueType> void
-ChildPledge<ValueType>::ActorCreated(PBackgroundChild* aActor)
-{
-  if (!sChild) {
-    // Create PMedia by sending a message to the parent
-    sChild = static_cast<Child*>(aActor->SendPMediaConstructor());
-  }
-  Run(sChild);
-}
-
-template<typename ValueType> void
-ChildPledge<ValueType>::ActorFailed()
-{
-  Pledge<ValueType>::Reject(NS_ERROR_UNEXPECTED);
-}
-
-template<typename ValueType> NS_IMPL_ADDREF(ChildPledge<ValueType>)
-template<typename ValueType> NS_IMPL_RELEASE(ChildPledge<ValueType>)
-template<typename ValueType> NS_INTERFACE_MAP_BEGIN(ChildPledge<ValueType>)
-NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
-NS_INTERFACE_MAP_END
-
-already_AddRefed<ChildPledge<nsCString>>
+already_AddRefed<Pledge<nsCString>>
 GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing)
 {
-  class Pledge : public ChildPledge<nsCString>
-  {
-  public:
-    explicit Pledge(const nsCString& aOrigin, bool aPrivateBrowsing)
-    : mOrigin(aOrigin), mPrivateBrowsing(aPrivateBrowsing) {}
-  private:
-    ~Pledge() {}
-    void Run(PMediaChild* aChild)
-    {
-      Child* child = static_cast<Child*>(aChild);
+  nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+  MOZ_ASSERT(mgr);
+
+  nsRefPtr<Pledge<nsCString>> p = new Pledge<nsCString>();
+  uint32_t id = mgr->mGetOriginKeyPledges.Append(*p);
 
-      uint32_t id = child->AddRequestPledge(*this);
-      child->SendGetOriginKey(id, mOrigin, mPrivateBrowsing);
-    }
-    const nsCString mOrigin;
-    const bool mPrivateBrowsing;
-  };
-
-  nsRefPtr<ChildPledge<nsCString>> p = new Pledge(aOrigin, aPrivateBrowsing);
-  nsCOMPtr<nsIIPCBackgroundChildCreateCallback> cb = do_QueryObject(p);
-  bool ok = ipc::BackgroundChild::GetOrCreateForCurrentThread(cb);
-  MOZ_RELEASE_ASSERT(ok);
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    mgr->GetNonE10sParent()->RecvGetOriginKey(id, aOrigin, aPrivateBrowsing);
+  } else {
+    Child::Get()->SendGetOriginKey(id, aOrigin, aPrivateBrowsing);
+  }
   return p.forget();
 }
 
-already_AddRefed<ChildPledge<bool>>
+void
 SanitizeOriginKeys(const uint64_t& aSinceWhen)
 {
-  class Pledge : public ChildPledge<bool>
-  {
-  public:
-    explicit Pledge(const uint64_t& aSinceWhen) : mSinceWhen(aSinceWhen) {}
-  private:
-    void Run(PMediaChild* aMedia)
-    {
-      aMedia->SendSanitizeOriginKeys(mSinceWhen);
-      mValue = true;
-      LOG(("SanitizeOriginKeys since %llu", mSinceWhen));
-      Resolve();
-    }
-    const uint64_t mSinceWhen;
-  };
+  LOG(("SanitizeOriginKeys since %llu", aSinceWhen));
 
-  nsRefPtr<ChildPledge<bool>> p = new Pledge(aSinceWhen);
-  nsCOMPtr<nsIIPCBackgroundChildCreateCallback> cb = do_QueryObject(p);
-  bool ok = ipc::BackgroundChild::GetOrCreateForCurrentThread(cb);
-  MOZ_RELEASE_ASSERT(ok);
-  return p.forget();
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    // Avoid opening MediaManager in this case, since this is called by
+    // sanitize.js when cookies are cleared, which can happen on startup.
+    ScopedDeletePtr<Parent<NonE10s>> tmpParent(new Parent<NonE10s>(true));
+    tmpParent->RecvSanitizeOriginKeys(aSinceWhen);
+  } else {
+    Child::Get()->SendSanitizeOriginKeys(aSinceWhen);
+  }
+}
+
+static Child* sChild;
+
+Child* Child::Get()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!sChild) {
+    sChild = static_cast<Child*>(dom::ContentChild::GetSingleton()->SendPMediaConstructor());
+  }
+  return sChild;
 }
 
 Child::Child()
+  : mActorDestroyed(false)
 {
   if (!gMediaChildLog) {
     gMediaChildLog = PR_NewLogModule("MediaChild");
   }
   LOG(("media::Child: %p", this));
   MOZ_COUNT_CTOR(Child);
 }
 
 Child::~Child()
 {
   LOG(("~media::Child: %p", this));
   sChild = nullptr;
   MOZ_COUNT_DTOR(Child);
 }
 
-uint32_t
-Child::AddRequestPledge(ChildPledge<nsCString>& aPledge)
+void Child::ActorDestroy(ActorDestroyReason aWhy)
 {
-  return mRequestPledges.Append(aPledge);
-}
-
-already_AddRefed<ChildPledge<nsCString>>
-Child::RemoveRequestPledge(uint32_t aRequestId)
-{
-  return mRequestPledges.Remove(aRequestId);
+  mActorDestroyed = true;
 }
 
 bool
 Child::RecvGetOriginKeyResponse(const uint32_t& aRequestId, const nsCString& aKey)
 {
-  nsRefPtr<ChildPledge<nsCString>> pledge = RemoveRequestPledge(aRequestId);
+  nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+  if (!mgr) {
+    return false;
+  }
+  nsRefPtr<Pledge<nsCString>> pledge = mgr->mGetOriginKeyPledges.Remove(aRequestId);
   if (pledge) {
     pledge->Resolve(aKey);
   }
   return true;
 }
 
 PMediaChild*
 AllocPMediaChild()
 {
-  Child* obj = new Child();
-  obj->AddRef();
-  return obj;
+  return new Child();
 }
 
 bool
 DeallocPMediaChild(media::PMediaChild *aActor)
 {
-  static_cast<Child*>(aActor)->Release();
+  delete static_cast<Child*>(aActor);
   return true;
 }
 
 }
 }
--- a/dom/media/systemservices/MediaChild.h
+++ b/dom/media/systemservices/MediaChild.h
@@ -5,67 +5,50 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_MediaChild_h
 #define mozilla_MediaChild_h
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/media/PMediaChild.h"
 #include "mozilla/media/PMediaParent.h"
-#include "nsIIPCBackgroundChildCreateCallback.h"
 #include "MediaUtils.h"
 
 namespace mozilla {
 namespace media {
 
 // media::Child implements proxying to the chrome process for some media-related
 // functions, for the moment just:
 //
 // GetOriginKey() - get a cookie-like persisted unique key for a given origin.
 // SanitizeOriginKeys() - reset persisted unique keys.
 
 // GetOriginKey and SanitizeOriginKeys are asynchronous APIs that return pledges
 // (promise-like objects) with the future value. Use pledge.Then(func) to access.
 
-class Child;
-
-template<typename ValueType>
-class ChildPledge : public Pledge<ValueType>,
-                    public nsIIPCBackgroundChildCreateCallback
-{
-  friend Child;
-  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
-  NS_DECL_ISUPPORTS
-public:
-  explicit ChildPledge() {};
-protected:
-  virtual ~ChildPledge() {}
-  virtual void Run(PMediaChild* aMedia) = 0;
-};
-
-already_AddRefed<ChildPledge<nsCString>>
+already_AddRefed<Pledge<nsCString>>
 GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing);
 
-already_AddRefed<ChildPledge<bool>>
+void
 SanitizeOriginKeys(const uint64_t& aSinceWhen);
 
 class Child : public PMediaChild
 {
-  NS_INLINE_DECL_REFCOUNTING(Child)
 public:
+  static Child* Get();
+
   Child();
 
-  bool RecvGetOriginKeyResponse(const uint32_t& aRequestId, const nsCString& aKey);
+  bool RecvGetOriginKeyResponse(const uint32_t& aRequestId, const nsCString& aKey) override;
 
-  uint32_t AddRequestPledge(ChildPledge<nsCString>& aPledge);
-  already_AddRefed<ChildPledge<nsCString>> RemoveRequestPledge(uint32_t aRequestId);
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual ~Child();
 private:
-  virtual ~Child();
 
-  CoatCheck<ChildPledge<nsCString>> mRequestPledges;
+  bool mActorDestroyed;
 };
 
 PMediaChild* AllocPMediaChild();
 bool DeallocPMediaChild(PMediaChild *aActor);
 
 } // namespace media
 } // namespace mozilla
 
--- a/dom/media/systemservices/MediaParent.cpp
+++ b/dom/media/systemservices/MediaParent.cpp
@@ -27,23 +27,23 @@ PRLogModuleInfo *gMediaParentLog;
 // deviceIds to be unique per origin, to avoid them being supercookies.
 
 #define ORIGINKEYS_FILE "enumerate_devices.txt"
 #define ORIGINKEYS_VERSION "1"
 
 namespace mozilla {
 namespace media {
 
-static StaticMutex gMutex;
-static ParentSingleton* sParentSingleton = nullptr;
+static Parent<PMediaParent>* sIPCServingParent;
 
-class ParentSingleton : public nsISupports
+static OriginKeyStore* sOriginKeyStore = nullptr;
+
+class OriginKeyStore : public nsISupports
 {
   NS_DECL_THREADSAFE_ISUPPORTS
-
   class OriginKey
   {
   public:
     static const size_t DecodedLength = 18;
     static const size_t EncodedLength = DecodedLength * 4 / 3;
 
     OriginKey(const nsACString& aKey, int64_t aSecondsStamp)
     : mKey(aKey)
@@ -319,200 +319,220 @@ class ParentSingleton : public nsISuppor
         Load();
       }
     }
   private:
     nsCOMPtr<nsIFile> mProfileDir;
   };
 
 private:
-  virtual ~ParentSingleton()
+  virtual ~OriginKeyStore()
   {
-    sParentSingleton = nullptr;
+    sOriginKeyStore = nullptr;
     LOG((__FUNCTION__));
   }
 
 public:
-  static ParentSingleton* Get()
+  static OriginKeyStore* Get()
   {
-    // Protect creation of singleton and access from multiple Background threads.
-    //
-    // Multiple Background threads happen because sanitize.js calls us from the
-    // chrome process and gets a thread separate from the one servicing ipc from
-    // the content process.
-
-    StaticMutexAutoLock lock(gMutex);
-    if (!sParentSingleton) {
-      sParentSingleton = new ParentSingleton();
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!sOriginKeyStore) {
+      sOriginKeyStore = new OriginKeyStore();
     }
-    return sParentSingleton;
+    return sOriginKeyStore;
   }
 
   // Only accessed on StreamTS thread
   OriginKeysLoader mOriginKeys;
   OriginKeysTable mPrivateBrowsingOriginKeys;
-
-  // Only accessed on return thread
-  CoatCheck<Pledge<nsCString>> mOutstandingPledges;
 };
 
-NS_IMPL_ISUPPORTS0(ParentSingleton)
+NS_IMPL_ISUPPORTS0(OriginKeyStore)
+
+template<> /* static */
+Parent<PMediaParent>* Parent<PMediaParent>::GetSingleton()
+{
+  return sIPCServingParent;
+}
 
-bool
-Parent::RecvGetOriginKey(const uint32_t& aRequestId,
+template<> /* static */
+Parent<NonE10s>* Parent<NonE10s>::GetSingleton()
+{
+  nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+  if (!mgr) {
+    return nullptr;
+  }
+  return mgr->GetNonE10sParent();
+}
+
+// TODO: Remove once upgraded to GCC 4.8+ on linux. Bogus error on static func:
+// error: 'this' was not captured for this lambda function
+
+template<class Super> static
+Parent<Super>* GccGetSingleton() { return Parent<Super>::GetSingleton(); };
+
+
+template<class Super> bool
+Parent<Super>::RecvGetOriginKey(const uint32_t& aRequestId,
                          const nsCString& aOrigin,
                          const bool& aPrivateBrowsing)
 {
-  // TODO: Replace all this when moving MediaParent to PContent soon (1037389)
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // First, get profile dir.
 
-  nsRefPtr<ParentSingleton> singleton(mSingleton);
-  nsCOMPtr<nsIThread> returnThread = NS_GetCurrentThread();
-  nsRefPtr<Pledge<nsCString>> p = new Pledge<nsCString>();
-  nsresult rv;
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIFile> profileDir;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                                       getter_AddRefs(profileDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
 
-  // First, over to main thread to get profile dir.
+  // Then over to stream-transport thread to do the actual file io.
+  // Stash a pledge to hold the answer and get an id for this request.
 
-  // Pledges are non-threadsafe by design, so check them and pass an id instead.
-  uint32_t id = singleton->mOutstandingPledges.Append(*p);
+  nsRefPtr<Pledge<nsCString>> p = new Pledge<nsCString>();
+  uint32_t id = mOutstandingPledges.Append(*p);
+
+  nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(sts);
+  nsRefPtr<OriginKeyStore> store(mOriginKeyStore);
+  bool sameProcess = mSameProcess;
 
-  rv = NS_DispatchToMainThread(NewRunnableFrom([id, returnThread,
-                                                singleton, aOrigin,
-                                                aPrivateBrowsing]() -> nsresult {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsIFile> profileDir;
-    nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                         getter_AddRefs(profileDir));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+  rv = sts->Dispatch(NewRunnableFrom([id, profileDir, store, sameProcess,
+                                      aOrigin, aPrivateBrowsing]() -> nsresult {
+    MOZ_ASSERT(!NS_IsMainThread());
+    store->mOriginKeys.SetProfileDir(profileDir);
+    nsCString result;
+    if (aPrivateBrowsing) {
+      store->mPrivateBrowsingOriginKeys.GetOriginKey(aOrigin, result);
+    } else {
+      store->mOriginKeys.GetOriginKey(aOrigin, result);
     }
 
-    // Then from there over to stream-transport thread to do the actual file io.
-
-    nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-    MOZ_ASSERT(sts);
-    rv = sts->Dispatch(NewRunnableFrom([profileDir, id, returnThread, singleton,
-                                        aOrigin, aPrivateBrowsing]() -> nsresult {
-      MOZ_ASSERT(!NS_IsMainThread());
-      singleton->mOriginKeys.SetProfileDir(profileDir);
-      nsCString result;
-      if (aPrivateBrowsing) {
-        singleton->mPrivateBrowsingOriginKeys.GetOriginKey(aOrigin, result);
-      } else {
-        singleton->mOriginKeys.GetOriginKey(aOrigin, result);
+    // Pass result back to main thread.
+    nsresult rv;
+    rv = NS_DispatchToMainThread(NewRunnableFrom([id, store, sameProcess,
+                                                  result]() -> nsresult {
+      Parent* parent = GccGetSingleton<Super>(); // GetSingleton();
+      if (!parent) {
+        return NS_OK;
       }
-
-      // Pass result back to original thread.
-      nsresult rv;
-      rv = returnThread->Dispatch(NewRunnableFrom([id, singleton,
-                                                   result]() -> nsresult {
-        nsRefPtr<Pledge<nsCString>> p = singleton->mOutstandingPledges.Remove(id);
-        if (!p) {
-          return NS_ERROR_UNEXPECTED;
-        }
-        p->Resolve(result);
-        return NS_OK;
-      }), NS_DISPATCH_NORMAL);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
+      nsRefPtr<Pledge<nsCString>> p = parent->mOutstandingPledges.Remove(id);
+      if (!p) {
+        return NS_ERROR_UNEXPECTED;
       }
+      p->Resolve(result);
       return NS_OK;
     }), NS_DISPATCH_NORMAL);
+
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
-  }));
+  }), NS_DISPATCH_NORMAL);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
-  nsRefPtr<media::Parent> keepAlive(this);
-  p->Then([this, keepAlive, aRequestId](const nsCString& aKey) mutable {
-    if (!mDestroyed) {
-      unused << SendGetOriginKeyResponse(aRequestId, aKey);
+  p->Then([aRequestId, sameProcess](const nsCString& aKey) mutable {
+    if (!sameProcess) {
+      if (!sIPCServingParent) {
+        return NS_OK;
+      }
+      unused << sIPCServingParent->SendGetOriginKeyResponse(aRequestId, aKey);
+    } else {
+      nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+      if (!mgr) {
+        return NS_OK;
+      }
+      nsRefPtr<Pledge<nsCString>> pledge =
+          mgr->mGetOriginKeyPledges.Remove(aRequestId);
+      if (pledge) {
+        pledge->Resolve(aKey);
+      }
     }
     return NS_OK;
   });
   return true;
 }
 
-bool
-Parent::RecvSanitizeOriginKeys(const uint64_t& aSinceWhen)
+template<class Super> bool
+Parent<Super>::RecvSanitizeOriginKeys(const uint64_t& aSinceWhen)
 {
-  nsRefPtr<ParentSingleton> singleton(mSingleton);
-
-  // First, over to main to get profile dir.
-  nsresult rv;
-
-  rv = NS_DispatchToMainThread(NewRunnableFrom([singleton,
-                                                aSinceWhen]() -> nsresult {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsIFile> profileDir;
-    nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIFile> profileDir;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                          getter_AddRefs(profileDir));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    // Then from there over to stream-transport thread to do the file io.
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  // Over to stream-transport thread to do the file io.
 
-    nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-    MOZ_ASSERT(sts);
-    rv = sts->Dispatch(NewRunnableFrom([profileDir, singleton, aSinceWhen]() -> nsresult {
-      MOZ_ASSERT(!NS_IsMainThread());
-      singleton->mOriginKeys.SetProfileDir(profileDir);
-      singleton->mPrivateBrowsingOriginKeys.Clear(aSinceWhen);
-      singleton->mOriginKeys.Clear(aSinceWhen);
-      return NS_OK;
-    }), NS_DISPATCH_NORMAL);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+  nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(sts);
+  nsRefPtr<OriginKeyStore> store(mOriginKeyStore);
+
+  rv = sts->Dispatch(NewRunnableFrom([profileDir, store, aSinceWhen]() -> nsresult {
+    MOZ_ASSERT(!NS_IsMainThread());
+    store->mOriginKeys.SetProfileDir(profileDir);
+    store->mPrivateBrowsingOriginKeys.Clear(aSinceWhen);
+    store->mOriginKeys.Clear(aSinceWhen);
     return NS_OK;
-  }));
+  }), NS_DISPATCH_NORMAL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   return true;
 }
 
-void
-Parent::ActorDestroy(ActorDestroyReason aWhy)
+template<class Super> void
+Parent<Super>::ActorDestroy(ActorDestroyReason aWhy)
 {
   // No more IPC from here
   mDestroyed = true;
   LOG((__FUNCTION__));
 }
 
-Parent::Parent()
-  : mSingleton(ParentSingleton::Get())
+template<class Super>
+Parent<Super>::Parent(bool aSameProcess)
+  : mOriginKeyStore(OriginKeyStore::Get())
   , mDestroyed(false)
+  , mSameProcess(aSameProcess)
 {
   if (!gMediaParentLog)
     gMediaParentLog = PR_NewLogModule("MediaParent");
   LOG(("media::Parent: %p", this));
 
   MOZ_COUNT_CTOR(Parent);
 }
 
-Parent::~Parent()
+template<class Super>
+Parent<Super>::~Parent()
 {
   LOG(("~media::Parent: %p", this));
 
   MOZ_COUNT_DTOR(Parent);
 }
 
 PMediaParent*
 AllocPMediaParent()
 {
-  Parent* obj = new Parent();
-  obj->AddRef();
-  return obj;
+  MOZ_ASSERT(!sIPCServingParent);
+  sIPCServingParent = new Parent<PMediaParent>();
+  return sIPCServingParent;
 }
 
 bool
 DeallocPMediaParent(media::PMediaParent *aActor)
 {
-  static_cast<Parent*>(aActor)->Release();
+  MOZ_ASSERT(sIPCServingParent == static_cast<Parent<PMediaParent>*>(aActor));
+  delete sIPCServingParent;
   return true;
 }
 
 }
 }
+
+// Instantiate templates to satisfy linker
+template class mozilla::media::Parent<mozilla::media::NonE10s>;
--- a/dom/media/systemservices/MediaParent.h
+++ b/dom/media/systemservices/MediaParent.h
@@ -11,35 +11,58 @@
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/media/PMediaParent.h"
 
 namespace mozilla {
 namespace media {
 
 // media::Parent implements the chrome-process side of ipc for media::Child APIs
+// A "SameProcess" version may also be created to service non-e10s calls.
 
-class ParentSingleton;
+class OriginKeyStore;
 
-class Parent : public PMediaParent
+class NonE10s
 {
-  NS_INLINE_DECL_REFCOUNTING(Parent)
+  typedef mozilla::ipc::IProtocolManager<mozilla::ipc::IProtocol>::ActorDestroyReason
+      ActorDestroyReason;
+protected:
+  virtual bool RecvGetOriginKey(const uint32_t& aRequestId,
+                                const nsCString& aOrigin,
+                                const bool& aPrivateBrowsing) = 0;
+  virtual bool RecvSanitizeOriginKeys(const uint64_t& aSinceWhen) = 0;
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) = 0;
+};
+
+// Super = PMediaParent or NonE10s
+
+template<class Super>
+class Parent : public Super
+{
+  typedef mozilla::ipc::IProtocolManager<mozilla::ipc::IProtocol>::ActorDestroyReason
+      ActorDestroyReason;
 public:
+  static Parent* GetSingleton();
+
   virtual bool RecvGetOriginKey(const uint32_t& aRequestId,
                                 const nsCString& aOrigin,
                                 const bool& aPrivateBrowsing) override;
   virtual bool RecvSanitizeOriginKeys(const uint64_t& aSinceWhen) override;
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
-  Parent();
-private:
+  explicit Parent(bool aSameProcess = false);
   virtual ~Parent();
+private:
 
-  nsRefPtr<ParentSingleton> mSingleton;
+  nsRefPtr<OriginKeyStore> mOriginKeyStore;
   bool mDestroyed;
+  bool mSameProcess;
+
+  CoatCheck<Pledge<nsCString>> mOutstandingPledges;
 };
 
 PMediaParent* AllocPMediaParent();
 bool DeallocPMediaParent(PMediaParent *aActor);
 
 } // namespace media
 } // namespace mozilla
 
--- a/dom/media/systemservices/MediaUtils.h
+++ b/dom/media/systemservices/MediaUtils.h
@@ -35,110 +35,212 @@ namespace media {
  *   nsRefPtr<media::Pledge<Foo>> p = GetFooAsynchronously(); // returns a pledge
  *   p->Then([](const Foo& foo) {
  *     // use foo here (same thread. Need not be thread-safe!)
  *   });
  *
  * See media::CoatCheck below for an example of GetFooAsynchronously().
  */
 
-template<typename ValueType>
-class Pledge
+class PledgeBase
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(PledgeBase);
+protected:
+  virtual ~PledgeBase() {};
+};
+
+template<typename ValueType, typename ErrorType = nsresult>
+class Pledge : public PledgeBase
 {
   // TODO: Remove workaround once mozilla allows std::function from <functional>
   // wo/std::function support, do template + virtual trick to accept lambdas
   class FunctorsBase
   {
   public:
     FunctorsBase() {}
-    virtual void Succeed(const ValueType& result) = 0;
-    virtual void Fail(nsresult rv) = 0;
+    virtual void Succeed(ValueType& result) = 0;
+    virtual void Fail(ErrorType& error) = 0;
     virtual ~FunctorsBase() {};
   };
 
 public:
-  NS_INLINE_DECL_REFCOUNTING(Pledge);
-  explicit Pledge() : mDone(false), mResult(NS_OK) {}
+  explicit Pledge() : mDone(false), mError(nullptr) {}
+  Pledge(const Pledge& aOther) = delete;
+  Pledge& operator = (const Pledge&) = delete;
 
   template<typename OnSuccessType>
   void Then(OnSuccessType aOnSuccess)
   {
-    Then(aOnSuccess, [](nsresult){});
+    Then(aOnSuccess, [](ErrorType&){});
   }
 
   template<typename OnSuccessType, typename OnFailureType>
   void Then(OnSuccessType aOnSuccess, OnFailureType aOnFailure)
   {
-    class F : public FunctorsBase
+    class Functors : public FunctorsBase
     {
     public:
-      F(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
+      Functors(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
         : mOnSuccess(aOnSuccess), mOnFailure(aOnFailure) {}
 
-      void Succeed(const ValueType& result)
+      void Succeed(ValueType& result)
       {
         mOnSuccess(result);
       }
-      void Fail(nsresult rv)
+      void Fail(ErrorType& error)
       {
-        mOnFailure(rv);
+        mOnFailure(error);
       };
 
       OnSuccessType mOnSuccess;
       OnFailureType mOnFailure;
     };
-    mFunctors = new F(aOnSuccess, aOnFailure);
+    mFunctors = new Functors(aOnSuccess, aOnFailure);
 
     if (mDone) {
-      if (mResult == NS_OK) {
+      if (!mError) {
         mFunctors->Succeed(mValue);
       } else {
-        mFunctors->Fail(mResult);
+        mFunctors->Fail(*mError);
       }
     }
   }
 
   void Resolve(const ValueType& aValue)
   {
     mValue = aValue;
     Resolve();
   }
 protected:
   void Resolve()
   {
     if (!mDone) {
       mDone = true;
-      MOZ_ASSERT(mResult == NS_OK);
+      MOZ_ASSERT(!mError);
       if (mFunctors) {
         mFunctors->Succeed(mValue);
       }
     }
   }
 
-  void Reject(nsresult rv)
+  void Reject(ErrorType rv)
   {
     if (!mDone) {
       mDone = true;
-      mResult = rv;
+      mError = rv;
       if (mFunctors) {
-        mFunctors->Fail(mResult);
+        mFunctors->Fail(mError);
       }
     }
   }
 
   ValueType mValue;
-protected:
+private:
   ~Pledge() {};
   bool mDone;
-  nsresult mResult;
+  nsRefPtr<ErrorType> mError;
+  ScopedDeletePtr<FunctorsBase> mFunctors;
+};
+
+template<typename ValueType>
+class Pledge<ValueType, nsresult>  : public PledgeBase
+{
+  // TODO: Remove workaround once mozilla allows std::function from <functional>
+  // wo/std::function support, do template + virtual trick to accept lambdas
+  class FunctorsBase
+  {
+  public:
+    FunctorsBase() {}
+    virtual void Succeed(ValueType& result) = 0;
+    virtual void Fail(nsresult error) = 0;
+    virtual ~FunctorsBase() {};
+  };
+
+public:
+  explicit Pledge() : mDone(false), mError(NS_OK) {}
+  Pledge(const Pledge& aOther) = delete;
+  Pledge& operator = (const Pledge&) = delete;
+
+  template<typename OnSuccessType>
+  void Then(OnSuccessType aOnSuccess)
+  {
+    Then(aOnSuccess, [](nsresult){});
+  }
+
+  template<typename OnSuccessType, typename OnFailureType>
+  void Then(OnSuccessType aOnSuccess, OnFailureType aOnFailure)
+  {
+    class Functors : public FunctorsBase
+    {
+    public:
+      Functors(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
+        : mOnSuccess(aOnSuccess), mOnFailure(aOnFailure) {}
+
+      void Succeed(ValueType& result)
+      {
+        mOnSuccess(result);
+      }
+      void Fail(nsresult rv)
+      {
+        mOnFailure(rv);
+      };
+
+      OnSuccessType mOnSuccess;
+      OnFailureType mOnFailure;
+    };
+    mFunctors = new Functors(aOnSuccess, aOnFailure);
+
+    if (mDone) {
+      if (mError == NS_OK) {
+        mFunctors->Succeed(mValue);
+      } else {
+        mFunctors->Fail(mError);
+      }
+    }
+  }
+
+  void Resolve(const ValueType& aValue)
+  {
+    mValue = aValue;
+    Resolve();
+  }
+protected:
+  void Resolve()
+  {
+    if (!mDone) {
+      mDone = true;
+      MOZ_ASSERT(mError == NS_OK);
+      if (mFunctors) {
+        mFunctors->Succeed(mValue);
+      }
+    }
+  }
+
+  void Reject(nsresult error)
+  {
+    if (!mDone) {
+      mDone = true;
+      mError = error;
+      if (mFunctors) {
+        mFunctors->Fail(mError);
+      }
+    }
+  }
+
+  ValueType mValue;
 private:
-  nsAutoPtr<FunctorsBase> mFunctors;
+  ~Pledge() {};
+  bool mDone;
+  nsresult mError;
+  ScopedDeletePtr<FunctorsBase> mFunctors;
 };
 
 /* media::NewRunnableFrom() - Create an nsRunnable from a lambda.
+ * media::NewTaskFrom()     - Create a Task from a lambda.
  *
  * Passing variables (closures) to an async function is clunky with nsRunnable:
  *
  *   void Foo()
  *   {
  *     class FooRunnable : public nsRunnable
  *     {
  *     public:
@@ -187,16 +289,37 @@ private:
 
 template<typename OnRunType>
 LambdaRunnable<OnRunType>*
 NewRunnableFrom(OnRunType aOnRun)
 {
   return new LambdaRunnable<OnRunType>(aOnRun);
 }
 
+template<typename OnRunType>
+class LambdaTask : public Task
+{
+public:
+  explicit LambdaTask(OnRunType& aOnRun) : mOnRun(aOnRun) {}
+private:
+  void
+  Run()
+  {
+    return mOnRun();
+  }
+  OnRunType mOnRun;
+};
+
+template<typename OnRunType>
+LambdaTask<OnRunType>*
+NewTaskFrom(OnRunType aOnRun)
+{
+  return new LambdaTask<OnRunType>(aOnRun);
+}
+
 /* media::CoatCheck - There and back again. Park an object in exchange for an id.
  *
  * A common problem with calling asynchronous functions that do work on other
  * threads or processes is how to pass in a heap object for use once the
  * function completes, without requiring that object to have threadsafe
  * refcounting, contain mutexes, be marshaled, or leak if things fail
  * (or worse, intermittent use-after-free because of lifetime issues).
  *
@@ -208,40 +331,40 @@ NewRunnableFrom(OnRunType aOnRun)
  *   class FooDoer
  *   {
  *     CoatCheck<Foo> mOutstandingFoos;
  *
  *   public:
  *     void DoFoo()
  *     {
  *       nsRefPtr<Foo> foo = new Foo();
- *       uint32_t requestId = mOutstandingFoos.Append(foo);
+ *       uint32_t requestId = mOutstandingFoos.Append(*foo);
  *       sChild->SendFoo(requestId);
  *     }
  *
  *     void RecvFooResponse(uint32_t requestId)
  *     {
  *       nsRefPtr<Foo> foo = mOutstandingFoos.Remove(requestId);
  *       if (foo) {
  *         // use foo
  *       }
  *     }
  *   };
  *
  * If you read media::Pledge earlier, here's how this is useful for pledges:
  *
  *   class FooGetter
  *   {
- *     CoatCheck<Foo> mOutstandingPledges;
+ *     CoatCheck<Pledge<Foo>> mOutstandingPledges;
  *
  *   public:
  *     already_addRefed<Pledge<Foo>> GetFooAsynchronously()
  *     {
  *       nsRefPtr<Pledge<Foo>> p = new Pledge<Foo>();
- *       uint32_t requestId = mOutstandingPledges.Append(p);
+ *       uint32_t requestId = mOutstandingPledges.Append(*p);
  *       sChild->SendFoo(requestId);
  *       return p.forget();
  *     }
  *
  *     void RecvFooResponse(uint32_t requestId, const Foo& fooResult)
  *     {
  *       nsRefPtr<Foo> p = mOutstandingPledges.Remove(requestId);
  *       if (p) {
--- a/dom/media/systemservices/PMedia.ipdl
+++ b/dom/media/systemservices/PMedia.ipdl
@@ -1,21 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PContent;
-include protocol PBackground;
 
 namespace mozilla {
 namespace media {
 
 protocol PMedia
 {
-  manager PBackground;
+  manager PContent;
 
 parent: