Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 29 Oct 2014 16:55:11 -0400
changeset 229295 0746593b7cde2f7e5dc9c82193946f0fab7939fa
parent 229294 8c589a6d637e7cad54652f03cbf87f978927fa39 (current diff)
parent 229167 80e18ff7c7b2da4caa773eb9931c0bbbcc2d321d (diff)
child 229296 b4729ee1bb8170c408aa1c94241f2d66d6cb84a9
push id7326
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:58:42 +0000
treeherdermozilla-aurora@d3a3b2a0f2f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
Merge m-c to fx-team. a=merge
dom/media/test/eme.js
dom/media/test/test_eme_canvas_blocked.html
dom/media/test/test_eme_playback.html
dom/media/test/test_eme_stream_capture_blocked.html
dom/plugins/test/mochitest/test_npruntime_npnsetexception.html
testing/marionette/client/marionette/tests/unit/test_switch_anonymous_content.py
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1037,8 +1037,11 @@ pref("services.mobileid.server.uri", "ht
 
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
 
 // UDPSocket API
 pref("dom.udpsocket.enabled", true);
+
+// Enable TV Manager API
+pref("dom.tv.enabled", true);
--- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- 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="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- 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="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/releng-flame-kk.tt
+++ b/b2g/config/flame-kk/releng-flame-kk.tt
@@ -1,9 +1,8 @@
 [
 {
-"size": 120750384,
-"digest": "0e0a0b0dcca020e3283ce8deb33d0eed48fab16ef2fd919120bd7b5abba00713210be17f466d11bf77cca3c9e3b663805be61774476cc669f0a75736d901edfd",
+"size": 91247216,
+"digest": "2b4be549f98695488ea7288d9e7f8ac0fa45112bedefa485a6e016c4af73fa21bb6b3992beda516f268417207c5deb57afad3959d3b1fbd07d5269b3a6be6a27",
 "algorithm": "sha512",
-"filename": "backup-flame.tar.xz",
-"comment": "v188-1"
+"filename": "backup-flame.tar.xz"
 }
 ]
--- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "ab5fb977047100ac985402e022d24da1dea68f24", 
+    "revision": "4830af61b34d751c57da15abfc2efcda571b12c0", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -192,16 +192,17 @@
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_cellbroadcast.xpt
 @BINPATH@/components/dom_mobilemessage.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_threads.xpt
 @BINPATH@/components/dom_traversal.xpt
+@BINPATH@/components/dom_tv.xpt
 @BINPATH@/components/dom_views.xpt
 @BINPATH@/components/dom_voicemail.xpt
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -1,26 +1,26 @@
 [DEFAULT]
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   addon1.xpi
   addon2.xpi
   addon3.xpi
   addon4.xpi
   addon5.xpi
   code_binary_search.coffee
   code_binary_search.js
   code_binary_search.map
   code_blackboxing_blackboxme.js
   code_blackboxing_one.js
   code_blackboxing_three.js
   code_blackboxing_two.js
   code_breakpoints-break-on-last-line-of-script-on-reload.js
   code_breakpoints-other-tabs.js
+  code_frame-script.js
   code_function-search-01.js
   code_function-search-02.js
   code_function-search-03.js
   code_location-changes.js
   code_math.js
   code_math.map
   code_math.min.js
   code_math_bogus_map.js
@@ -93,238 +93,457 @@ support-files =
   doc_watch-expressions.html
   doc_watch-expression-button.html
   doc_with-frame.html
   head.js
   sjs_random-javascript.sjs
   testactors.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
+skip-if = e10s
 [browser_dbg_addonactor.js]
+skip-if = e10s
 [browser_dbg_addon-sources.js]
+skip-if = e10s
 [browser_dbg_addon-modules.js]
+skip-if = e10s
 [browser_dbg_addon-modules-unpacked.js]
+skip-if = e10s
 [browser_dbg_addon-panels.js]
+skip-if = e10s
 [browser_dbg_addon-console.js]
-skip-if = os == 'win' # bug 1005274
+skip-if = e10s || os == 'win' # bug 1005274
 [browser_dbg_auto-pretty-print-01.js]
+skip-if = e10s
 [browser_dbg_auto-pretty-print-02.js]
+skip-if = e10s
 [browser_dbg_bfcache.js]
+skip-if = e10s
 [browser_dbg_blackboxing-01.js]
+skip-if = e10s && debug
 [browser_dbg_blackboxing-02.js]
+skip-if = e10s && debug
 [browser_dbg_blackboxing-03.js]
+skip-if = e10s && debug
 [browser_dbg_blackboxing-04.js]
+skip-if = e10s && debug
 [browser_dbg_blackboxing-05.js]
+skip-if = e10s && debug
 [browser_dbg_blackboxing-06.js]
+skip-if = e10s && debug
 [browser_dbg_breadcrumbs-access.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-01.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-02.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-03.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-04.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-05.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-06.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-07.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-08.js]
+skip-if = e10s
 [browser_dbg_break-on-dom-event-01.js]
-skip-if = os == "mac" || e10s # Bug 895426
+skip-if = e10s || os == "mac" || e10s # Bug 895426
 [browser_dbg_break-on-dom-event-02.js]
+skip-if = e10s
 [browser_dbg_breakpoints-actual-location.js]
+skip-if = e10s
 [browser_dbg_breakpoints-actual-location2.js]
+skip-if = e10s
 [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
+skip-if = e10s
 [browser_dbg_breakpoints-button-01.js]
+skip-if = e10s
 [browser_dbg_breakpoints-button-02.js]
+skip-if = e10s
 [browser_dbg_breakpoints-contextmenu-add.js]
+skip-if = e10s
 [browser_dbg_breakpoints-contextmenu.js]
+skip-if = e10s
 [browser_dbg_breakpoints-disabled-reload.js]
+skip-if = e10s
 [browser_dbg_breakpoints-editor.js]
+skip-if = e10s
 [browser_dbg_breakpoints-highlight.js]
+skip-if = e10s
 [browser_dbg_breakpoints-new-script.js]
+skip-if = e10s
 [browser_dbg_breakpoints-other-tabs.js]
+skip-if = e10s
 [browser_dbg_breakpoints-pane.js]
+skip-if = e10s
 [browser_dbg_breakpoints-reload.js]
+skip-if = e10s
 [browser_dbg_chrome-create.js]
+skip-if = e10s
 [browser_dbg_chrome-debugging.js]
+skip-if = e10s
 [browser_dbg_clean-exit-window.js]
 skip-if = true # Bug 933950 (leaky test)
 [browser_dbg_clean-exit.js]
+skip-if = e10s
 [browser_dbg_closure-inspection.js]
+skip-if = e10s
 [browser_dbg_cmd-blackbox.js]
+skip-if = e10s
 [browser_dbg_cmd-break.js]
+skip-if = e10s
 [browser_dbg_cmd-dbg.js]
+skip-if = e10s
 [browser_dbg_conditional-breakpoints-01.js]
+skip-if = e10s
 [browser_dbg_conditional-breakpoints-02.js]
+skip-if = e10s
 [browser_dbg_conditional-breakpoints-03.js]
+skip-if = e10s
 [browser_dbg_conditional-breakpoints-04.js]
+skip-if = e10s
 [browser_dbg_server-conditional-bp-01.js]
+skip-if = e10s
 [browser_dbg_server-conditional-bp-02.js]
+skip-if = e10s
 [browser_dbg_server-conditional-bp-03.js]
+skip-if = e10s
 [browser_dbg_server-conditional-bp-04.js]
+skip-if = e10s
 [browser_dbg_controller-evaluate-01.js]
+skip-if = e10s
 [browser_dbg_controller-evaluate-02.js]
+skip-if = e10s
 [browser_dbg_debugger-statement.js]
+skip-if = e10s
 [browser_dbg_editor-contextmenu.js]
+skip-if = e10s
 [browser_dbg_editor-mode.js]
+skip-if = e10s
 [browser_dbg_event-listeners-01.js]
+skip-if = e10s
 [browser_dbg_event-listeners-02.js]
+skip-if = e10s
 [browser_dbg_event-listeners-03.js]
+skip-if = e10s
 [browser_dbg_file-reload.js]
+skip-if = e10s
 [browser_dbg_function-display-name.js]
+skip-if = e10s
 [browser_dbg_global-method-override.js]
+skip-if = e10s
 [browser_dbg_globalactor.js]
+skip-if = e10s
 [browser_dbg_hit-counts-01.js]
+skip-if = e10s
 [browser_dbg_hit-counts-02.js]
+skip-if = e10s
 [browser_dbg_host-layout.js]
+skip-if = e10s
 [browser_dbg_iframes.js]
+skip-if = e10s
 [browser_dbg_instruments-pane-collapse.js]
+skip-if = e10s
 [browser_dbg_interrupts.js]
+skip-if = e10s
 [browser_dbg_listaddons.js]
+skip-if = e10s
 [browser_dbg_listtabs-01.js]
+skip-if = e10s
 [browser_dbg_listtabs-02.js]
+skip-if = e10s
 [browser_dbg_listtabs-03.js]
+skip-if = e10s
 [browser_dbg_location-changes-01-simple.js]
+skip-if = e10s
 [browser_dbg_location-changes-02-blank.js]
+skip-if = e10s
 [browser_dbg_location-changes-03-new.js]
+skip-if = e10s
 [browser_dbg_location-changes-04-breakpoint.js]
+skip-if = e10s
 [browser_dbg_multiple-windows.js]
+skip-if = e10s
 [browser_dbg_navigation.js]
+skip-if = e10s
 [browser_dbg_no-page-sources.js]
+skip-if = e10s
 [browser_dbg_on-pause-highlight.js]
+skip-if = e10s
 [browser_dbg_on-pause-raise.js]
-skip-if = os == "linux" || e10s # Bug 888811 & bug 891176
+skip-if = e10s || os == "linux" || e10s # Bug 888811 & bug 891176
 [browser_dbg_optimized-out-vars.js]
+skip-if = e10s
 [browser_dbg_panel-size.js]
+skip-if = e10s
 [browser_dbg_parser-01.js]
+skip-if = e10s
 [browser_dbg_parser-02.js]
+skip-if = e10s
 [browser_dbg_parser-03.js]
+skip-if = e10s
 [browser_dbg_parser-04.js]
+skip-if = e10s
 [browser_dbg_parser-05.js]
+skip-if = e10s
 [browser_dbg_parser-06.js]
+skip-if = e10s
 [browser_dbg_parser-07.js]
+skip-if = e10s
 [browser_dbg_parser-08.js]
+skip-if = e10s
 [browser_dbg_parser-09.js]
+skip-if = e10s
 [browser_dbg_parser-10.js]
+skip-if = e10s
 [browser_dbg_pause-exceptions-01.js]
+skip-if = e10s
 [browser_dbg_pause-exceptions-02.js]
+skip-if = e10s
 [browser_dbg_pause-resume.js]
+skip-if = e10s
 [browser_dbg_pause-warning.js]
+skip-if = e10s
 [browser_dbg_paused-keybindings.js]
+skip-if = e10s
 [browser_dbg_pretty-print-01.js]
+skip-if = e10s
 [browser_dbg_pretty-print-02.js]
+skip-if = e10s
 [browser_dbg_pretty-print-03.js]
+skip-if = e10s
 [browser_dbg_pretty-print-04.js]
+skip-if = e10s
 [browser_dbg_pretty-print-05.js]
+skip-if = e10s
 [browser_dbg_pretty-print-06.js]
+skip-if = e10s
 [browser_dbg_pretty-print-07.js]
+skip-if = e10s
 [browser_dbg_pretty-print-08.js]
+skip-if = e10s
 [browser_dbg_pretty-print-09.js]
+skip-if = e10s
 [browser_dbg_pretty-print-10.js]
+skip-if = e10s
 [browser_dbg_pretty-print-11.js]
+skip-if = e10s
 [browser_dbg_pretty-print-12.js]
+skip-if = e10s
 [browser_dbg_pretty-print-13.js]
+skip-if = e10s
 [browser_dbg_pretty-print-on-paused.js]
+skip-if = e10s
 [browser_dbg_progress-listener-bug.js]
+skip-if = e10s
 [browser_dbg_reload-preferred-script-01.js]
+skip-if = e10s
 [browser_dbg_reload-preferred-script-02.js]
+skip-if = e10s
 [browser_dbg_reload-preferred-script-03.js]
+skip-if = e10s
 [browser_dbg_reload-same-script.js]
+skip-if = e10s
 [browser_dbg_scripts-switching-01.js]
+skip-if = e10s
 [browser_dbg_scripts-switching-02.js]
+skip-if = e10s
 [browser_dbg_scripts-switching-03.js]
+skip-if = e10s
 [browser_dbg_search-autofill-identifier.js]
+skip-if = e10s
 [browser_dbg_search-basic-01.js]
+skip-if = e10s
 [browser_dbg_search-basic-02.js]
+skip-if = e10s
 [browser_dbg_search-basic-03.js]
+skip-if = e10s
 [browser_dbg_search-basic-04.js]
+skip-if = e10s
 [browser_dbg_search-global-01.js]
+skip-if = e10s
 [browser_dbg_search-global-02.js]
+skip-if = e10s
 [browser_dbg_search-global-03.js]
+skip-if = e10s
 [browser_dbg_search-global-04.js]
+skip-if = e10s
 [browser_dbg_search-global-05.js]
+skip-if = e10s
 [browser_dbg_search-global-06.js]
+skip-if = e10s
 [browser_dbg_search-popup-jank.js]
+skip-if = e10s
 [browser_dbg_search-sources-01.js]
+skip-if = e10s
 [browser_dbg_search-sources-02.js]
+skip-if = e10s
 [browser_dbg_search-sources-03.js]
+skip-if = e10s
 [browser_dbg_search-symbols.js]
+skip-if = e10s
 [browser_dbg_searchbox-help-popup-01.js]
+skip-if = e10s
 [browser_dbg_searchbox-help-popup-02.js]
+skip-if = e10s
 [browser_dbg_searchbox-parse.js]
+skip-if = e10s
 [browser_dbg_source-maps-01.js]
+skip-if = e10s
 [browser_dbg_source-maps-02.js]
+skip-if = e10s
 [browser_dbg_source-maps-03.js]
+skip-if = e10s
 [browser_dbg_source-maps-04.js]
+skip-if = e10s
 [browser_dbg_sources-cache.js]
+skip-if = e10s
 [browser_dbg_sources-labels.js]
+skip-if = e10s
 [browser_dbg_sources-sorting.js]
+skip-if = e10s
 [browser_dbg_split-console-paused-reload.js]
+skip-if = e10s
 [browser_dbg_stack-01.js]
+skip-if = e10s
 [browser_dbg_stack-02.js]
+skip-if = e10s
 [browser_dbg_stack-03.js]
+skip-if = e10s
 [browser_dbg_stack-04.js]
+skip-if = e10s
 [browser_dbg_stack-05.js]
+skip-if = e10s
 [browser_dbg_stack-06.js]
+skip-if = e10s
 [browser_dbg_stack-07.js]
+skip-if = e10s
 [browser_dbg_step-out.js]
+skip-if = e10s
 [browser_dbg_tabactor-01.js]
+skip-if = e10s
 [browser_dbg_tabactor-02.js]
+skip-if = e10s
 [browser_dbg_terminate-on-tab-close.js]
+skip-if = e10s
 [browser_dbg_tracing-01.js]
+skip-if = e10s
 [browser_dbg_tracing-02.js]
+skip-if = e10s
 [browser_dbg_tracing-03.js]
+skip-if = e10s
 [browser_dbg_tracing-04.js]
+skip-if = e10s
 [browser_dbg_tracing-05.js]
+skip-if = e10s
 [browser_dbg_tracing-06.js]
+skip-if = e10s
 [browser_dbg_tracing-07.js]
+skip-if = e10s
 [browser_dbg_tracing-08.js]
+skip-if = e10s
 [browser_dbg_variables-view-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-03.js]
+skip-if = e10s
 [browser_dbg_variables-view-04.js]
+skip-if = e10s
 [browser_dbg_variables-view-05.js]
+skip-if = e10s
 [browser_dbg_variables-view-06.js]
+skip-if = e10s
 [browser_dbg_variables-view-accessibility.js]
+skip-if = e10s
 [browser_dbg_variables-view-data.js]
+skip-if = e10s
 [browser_dbg_variables-view-edit-cancel.js]
+skip-if = e10s
 [browser_dbg_variables-view-edit-click.js]
-skip-if = (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
+skip-if = e10s || (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
 [browser_dbg_variables-view-edit-getset-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-edit-getset-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-edit-value.js]
+skip-if = e10s
 [browser_dbg_variables-view-edit-watch.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-03.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-04.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-05.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-pref.js]
+skip-if = e10s
 [browser_dbg_variables-view-filter-searchbox.js]
+skip-if = e10s
 [browser_dbg_variables-view-frame-parameters-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-frame-parameters-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-frame-parameters-03.js]
+skip-if = e10s
 [browser_dbg_variables-view-frame-with.js]
+skip-if = e10s
 [browser_dbg_variables-view-frozen-sealed-nonext.js]
-skip-if = buildapp == 'mulet'
+skip-if = e10s || buildapp == 'mulet'
 [browser_dbg_variables-view-hide-non-enums.js]
+skip-if = e10s
 [browser_dbg_variables-view-large-array-buffer.js]
+skip-if = e10s
 [browser_dbg_variables-view-override-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-override-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-03.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-04.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-05.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-06.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-07.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-08.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-09.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-10.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-11.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-12.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-13.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-14.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-15.js]
+skip-if = e10s
 [browser_dbg_variables-view-popup-16.js]
+skip-if = e10s
 [browser_dbg_variables-view-reexpand-01.js]
+skip-if = e10s
 [browser_dbg_variables-view-reexpand-02.js]
+skip-if = e10s
 [browser_dbg_variables-view-reexpand-03.js]
+skip-if = e10s
 [browser_dbg_variables-view-webidl.js]
+skip-if = e10s
 [browser_dbg_watch-expressions-01.js]
+skip-if = e10s
 [browser_dbg_watch-expressions-02.js]
+skip-if = e10s
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
@@ -2,22 +2,21 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that if we black box a source and then refresh, it is still black boxed.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
 
     waitForSourceShown(gPanel, ".coffee")
       .then(testBlackBoxSource)
       .then(testBlackBoxReload)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
@@ -43,12 +42,11 @@ function testBlackBoxReload() {
     ok(bbButton.checked, "Should still be black boxed.");
     ok(selectedSource.classList.contains("black-boxed"),
       "'black-boxed' class should still be applied");
   });
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
@@ -4,23 +4,22 @@
 /**
  * Test that black boxed frames are compressed into a single frame on the stack
  * view.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gFrames;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForSourceShown(gPanel, BLACKBOXME_URL)
       .then(testBlackBoxSource)
       .then(testBlackBoxStack)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
@@ -39,21 +38,18 @@ function testBlackBoxSource() {
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 3,
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
       "And one of them should be the combined black boxed frames.");
   });
 
-  // Spin the event loop before causing the debuggee to pause, to allow
-  // this function to return first.
-  executeSoon(() => gDebuggee.runTest());
+  callInTab(gTab, "runTest");
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -4,36 +4,35 @@
 /**
  * Test that black boxed frames are compressed into a single frame on the stack
  * view when we are already paused.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gFrames;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
       .then(testBlackBoxStack)
       .then(testBlackBoxSource)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.runTest();
+    callInTab(gTab, "runTest");
   });
 }
 
 function testBlackBoxStack() {
   is(gFrames.itemCount, 6,
     "Should get 6 frames.");
   is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
     "And none of them are black boxed.");
@@ -47,13 +46,12 @@ function testBlackBoxSource() {
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
       "And one of them should be the combined black boxed frames.");
   });
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -4,23 +4,22 @@
 /**
  * Test that we get a stack frame for each black boxed source, not a single one
  * for all of them.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gFrames;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForSourceShown(gPanel, BLACKBOXME_URL)
       .then(blackBoxSources)
       .then(testBlackBoxStack)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
@@ -41,21 +40,18 @@ function blackBoxSources() {
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 4,
       "Should get 4 frames (one -> two -> three -> doDebuggerStatement).");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
       "And 'one', 'two', and 'three' should each have their own black boxed frame.");
   });
 
-  // Spin the event loop before causing the debuggee to pause, to allow
-  // this function to return first.
-  executeSoon(() => gDebuggee.one());
+  callInTab(gTab, "one");
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
@@ -3,23 +3,22 @@
 
 /**
  * Test that a "this source is blackboxed" message is shown when necessary
  * and can be properly dismissed.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gDeck;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gDeck = gDebugger.document.getElementById("editor-deck");
 
     waitForSourceShown(gPanel, ".coffee")
       .then(testSourceEditorShown)
       .then(toggleBlackBoxing.bind(null, gPanel))
       .then(testBlackBoxMessageShown)
@@ -59,13 +58,12 @@ function testSourceEditorShownAgain() {
 }
 
 function getEditorBlackboxMessageButton() {
   return gDebugger.document.getElementById("black-boxed-message-button");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gDeck = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
@@ -3,35 +3,34 @@
 
 /**
  * Test that clicking the black box checkbox when paused doesn't re-select the
  * currently paused frame's source.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
       .then(testBlackBox)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.runTest();
+    callInTab(gTab, "runTest");
   });
 }
 
 function testBlackBox() {
   const selectedUrl = gSources.selectedValue;
 
   let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
     const newSelectedUrl = gSources.selectedValue;
@@ -45,13 +44,12 @@ function testBlackBox() {
   });
 
   gSources.selectedIndex = 0;
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/code_frame-script.js
@@ -0,0 +1,9 @@
+"use strict";
+
+dump("Frame script loaded.\n");
+
+addMessageListener("test:call", function (message) {
+  dump("Calling function with name " + message.data + ".\n");
+
+  XPCNativeWrapper.unwrap(content)[message.data]();
+});
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -23,16 +23,17 @@ let { DebuggerServer } = Cu.import("reso
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 let EventEmitter = require("devtools/toolkit/event-emitter");
 const { promiseInvoke } = require("devtools/async-utils");
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
+const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
 // All tests are asynchronous.
 waitForExplicitFinish();
@@ -81,16 +82,19 @@ function addTab(aUrl, aWindow) {
   let deferred = promise.defer();
   let targetWindow = aWindow || window;
   let targetBrowser = targetWindow.gBrowser;
 
   targetWindow.focus();
   let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
   let linkedBrowser = tab.linkedBrowser;
 
+  info("Loading frame script with url " + FRAME_SCRIPT_URL + ".");
+  linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+
   linkedBrowser.addEventListener("load", function onLoad() {
     linkedBrowser.removeEventListener("load", onLoad, true);
     info("Tab added and finished loading: " + aUrl);
     deferred.resolve(tab);
   }, true);
 
   return deferred.promise;
 }
@@ -931,9 +935,33 @@ function pushPrefs(...aPrefs) {
   SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
   return deferred.promise;
 }
 
 function popPrefs() {
   let deferred = promise.defer();
   SpecialPowers.popPrefEnv(deferred.resolve);
   return deferred.promise;
-}
\ No newline at end of file
+}
+
+function sendMessageToTab(tab, name, data, objects) {
+  info("Sending message with name " + name + " to tab.");
+
+  tab.linkedBrowser.messageManager.sendAsyncMessage(name, data, objects);
+}
+
+function waitForMessageFromTab(tab, name) {
+  info("Waiting for message with name " + name + " from tab.");
+
+  return new Promise(function (resolve) {
+    let messageManager = tab.linkedBrowser.messageManager;
+    messageManager.addMessageListener(name, function listener(message) {
+      messageManager.removeMessageListener(name, listener);
+      resolve(message);
+    });
+  });
+}
+
+function callInTab(tab, name) {
+  info("Calling function with name " + name + " in tab.");
+
+  sendMessageToTab(tab, "test:call", name);
+}
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -226,16 +226,17 @@
 @BINPATH@/components/dom_permissionsettings.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_cellbroadcast.xpt
 @BINPATH@/components/dom_mobilemessage.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_traversal.xpt
+@BINPATH@/components/dom_tv.xpt
 @BINPATH@/components/dom_voicemail.xpt
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @BINPATH@/components/dom_workers.xpt
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -120,16 +120,24 @@ PluginContent.prototype = {
     if (tagMimetype == "") {
       tagMimetype = pluginElement.type;
     }
 
     if (this.isKnownPlugin(pluginElement)) {
       pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
       pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
 
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
       permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
       fallbackType = pluginElement.defaultFallbackType;
       blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
       // Make state-softblocked == state-notblocked for our purposes,
       // they have the same UI. STATE_OUTDATED should not exist for plugin
       // items, but let's alias it anyway, just in case.
       if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
           blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3122,31 +3122,49 @@ nsDocShell::NotifyAsyncPanZoomStarted(co
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
             obs->AsyncPanZoomStarted(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
+
+    // Also notify child docshell
+    for (uint32_t i = 0; i < mChildList.Length(); ++i) {
+        nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
+        if (kid) {
+            nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
+            docShell->NotifyAsyncPanZoomStarted(aScrollPos);
+        }
+    }
 }
 
 void
 nsDocShell::NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
             obs->AsyncPanZoomStopped(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
+
+    // Also notify child docshell
+    for (uint32_t i = 0; i < mChildList.Length(); ++i) {
+        nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
+        if (kid) {
+            nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
+            docShell->NotifyAsyncPanZoomStopped(aScrollPos);
+        }
+    }
 }
 
 NS_IMETHODIMP
 nsDocShell::NotifyScrollObservers()
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
@@ -10386,18 +10404,18 @@ nsDocShell::DoURILoad(nsIURI * aURI,
       }
     }
 
     //hack
     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
     nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(do_QueryInterface(channel));
     if (httpChannelInternal) {
       if (aForceAllowCookies) {
-        httpChannelInternal->SetForceAllowThirdPartyCookie(true);
-      } 
+        httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+      }
       if (aFirstParty) {
         httpChannelInternal->SetDocumentURI(aURI);
       } else {
         httpChannelInternal->SetDocumentURI(aReferrerURI);
       }
     }
 
     nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -488,16 +488,21 @@ this.PermissionsTable =  { geolocation: 
                              access: ["read", "write"],
                              additional: ["settings-api"]
                            },
                            "engineering-mode": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
+                           },
+                           "tv": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
                            }
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
+#include "mozilla/dom/TVManager.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
 #include "nsGlobalWindow.h"
 #ifdef MOZ_B2G
@@ -168,16 +169,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellBroadcast)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVoicemail)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTVManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
 #ifdef MOZ_B2G_RIL
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileConnections)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIccManager)
 #endif
 #ifdef MOZ_B2G_BT
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBluetooth)
 #endif
@@ -251,16 +253,20 @@ Navigator::Invalidate()
     mTelephony = nullptr;
   }
 
   if (mVoicemail) {
     mVoicemail->Shutdown();
     mVoicemail = nullptr;
   }
 
+  if (mTVManager) {
+    mTVManager = nullptr;
+  }
+
   if (mConnection) {
     mConnection->Shutdown();
     mConnection = nullptr;
   }
 
 #ifdef MOZ_B2G_RIL
   if (mMobileConnections) {
     mMobileConnections = nullptr;
@@ -1088,17 +1094,20 @@ Navigator::SendBeacon(const nsAString& a
   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(do_QueryInterface(channel));
   nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   if (!httpChannelInternal) {
     aRv.Throw(NS_ERROR_DOM_BAD_URI);
     return false;
   }
   bool isForeign = true;
   thirdPartyUtil->IsThirdPartyWindow(mWindow, uri, &isForeign);
-  httpChannelInternal->SetForceAllowThirdPartyCookie(!isForeign);
+  uint32_t thirdPartyFlags = isForeign ?
+    0 :
+    nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
+  httpChannelInternal->SetThirdPartyFlags(thirdPartyFlags);
 
   nsCString mimeType;
   if (!aData.IsNull()) {
     nsCOMPtr<nsIInputStream> in;
 
     if (aData.Value().IsString()) {
       nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString());
       nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
@@ -1574,16 +1583,29 @@ Navigator::GetMozTelephony(ErrorResult& 
       return nullptr;
     }
     mTelephony = Telephony::Create(mWindow, aRv);
   }
 
   return mTelephony;
 }
 
+TVManager*
+Navigator::GetTv()
+{
+  if (!mTVManager) {
+    if (!mWindow) {
+      return nullptr;
+    }
+    mTVManager = TVManager::Create(mWindow);
+  }
+
+  return mTVManager;
+}
+
 #ifdef MOZ_B2G
 already_AddRefed<Promise>
 Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
                                 ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -2319,16 +2341,47 @@ Navigator::HasMobileIdSupport(JSContext*
   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
   permMgr->TestPermissionFromPrincipal(principal, "mobileid", &permission);
   return permission == nsIPermissionManager::PROMPT_ACTION ||
          permission == nsIPermissionManager::ALLOW_ACTION;
 }
 #endif
 
 /* static */
+bool
+Navigator::HasTVSupport(JSContext* aCx, JSObject* aGlobal)
+{
+  JS::Rooted<JSObject*> global(aCx, aGlobal);
+
+  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
+  if (!win) {
+    return false;
+  }
+
+  // Just for testing, we can enable TV for any kind of app.
+  if (Preferences::GetBool("dom.testing.tv_enabled_for_hosted_apps", false)) {
+    return true;
+  }
+
+  nsIDocument* doc = win->GetExtantDoc();
+  if (!doc || !doc->NodePrincipal()) {
+    return false;
+  }
+
+  nsIPrincipal* principal = doc->NodePrincipal();
+  uint16_t status;
+  if (NS_FAILED(principal->GetAppStatus(&status))) {
+    return false;
+  }
+
+  // Only support TV Manager API for certified apps for now.
+  return status == nsIPrincipal::APP_STATUS_CERTIFIED;
+}
+
+/* static */
 already_AddRefed<nsPIDOMWindow>
 Navigator::GetWindowFromGlobal(JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win =
     do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
   MOZ_ASSERT(!win || win->IsInnerWindow());
   return win.forget();
 }
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -88,16 +88,17 @@ class BluetoothManager;
 class IccManager;
 class MobileConnectionArray;
 #endif
 
 class PowerManager;
 class CellBroadcast;
 class Telephony;
 class Voicemail;
+class TVManager;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -218,16 +219,17 @@ public:
   void GetDeviceStorages(const nsAString& aType,
                          nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores,
                          ErrorResult& aRv);
   DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
   CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
   MobileMessageManager* GetMozMobileMessage();
   Telephony* GetMozTelephony(ErrorResult& aRv);
   Voicemail* GetMozVoicemail(ErrorResult& aRv);
+  TVManager* GetTv();
   network::Connection* GetConnection(ErrorResult& aRv);
   nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
   void MozSetMessageHandler(const nsAString& aType,
                             systemMessageCallback* aCallback,
                             ErrorResult& aRv);
   bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
 #ifdef MOZ_B2G
@@ -300,16 +302,18 @@ public:
   static bool HasDataStoreSupport(nsIPrincipal* aPrincipal);
 
   static bool HasDataStoreSupport(JSContext* cx, JSObject* aGlobal);
 
 #ifdef MOZ_B2G
   static bool HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal);
 #endif
 
+  static bool HasTVSupport(JSContext* aCx, JSObject* aGlobal);
+
   nsPIDOMWindow* GetParentObject() const
   {
     return GetWindow();
   }
 
   virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
 private:
@@ -329,16 +333,17 @@ private:
 #ifdef MOZ_B2G_FM
   nsRefPtr<FMRadio> mFMRadio;
 #endif
   nsRefPtr<PowerManager> mPowerManager;
   nsRefPtr<CellBroadcast> mCellBroadcast;
   nsRefPtr<MobileMessageManager> mMobileMessageManager;
   nsRefPtr<Telephony> mTelephony;
   nsRefPtr<Voicemail> mVoicemail;
+  nsRefPtr<TVManager> mTVManager;
   nsRefPtr<network::Connection> mConnection;
 #ifdef MOZ_B2G_RIL
   nsRefPtr<MobileConnectionArray> mMobileConnections;
   nsRefPtr<IccManager> mIccManager;
 #endif
 #ifdef MOZ_B2G_BT
   nsRefPtr<bluetooth::BluetoothManager> mBluetooth;
 #endif
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -1,8 +1,10 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ThirdPartyUtil.h"
 #include "nsNetUtil.h"
 #include "nsIServiceManager.h"
 #include "nsIHttpChannelInternal.h"
@@ -161,30 +163,57 @@ ThirdPartyUtil::IsThirdPartyChannel(nsIC
                                     nsIURI* aURI,
                                     bool* aResult)
 {
   NS_ENSURE_ARG(aChannel);
   NS_ASSERTION(aResult, "null outparam pointer");
 
   nsresult rv;
   bool doForce = false;
+  bool checkWindowChain = true;
+  bool parentIsThird = false;
   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
     do_QueryInterface(aChannel);
   if (httpChannelInternal) {
-    rv = httpChannelInternal->GetForceAllowThirdPartyCookie(&doForce);
+    uint32_t flags;
+    rv = httpChannelInternal->GetThirdPartyFlags(&flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+
     // If aURI was not supplied, and we're forcing, then we're by definition
     // not foreign. If aURI was supplied, we still want to check whether it's
     // foreign with respect to the channel URI. (The forcing only applies to
     // whatever window hierarchy exists above the channel.)
     if (doForce && !aURI) {
       *aResult = false;
       return NS_OK;
     }
+
+    if (flags & nsIHttpChannelInternal::THIRD_PARTY_PARENT_IS_THIRD_PARTY) {
+      // Check that the two PARENT_IS_{THIRD,SAME}_PARTY are mutually exclusive.
+      MOZ_ASSERT(!(flags & nsIHttpChannelInternal::THIRD_PARTY_PARENT_IS_SAME_PARTY));
+
+      // If we're not forcing and we know that the window chain of the channel
+      // is third party, then we know now that we're third party.
+      if (!doForce) {
+        *aResult = true;
+        return NS_OK;
+      }
+
+      checkWindowChain = false;
+      parentIsThird = true;
+    } else {
+      // In e10s, we can't check the parent chain in the parent, so we do so
+      // in the child and send the result to the parent.
+      // Note that we only check the window chain if neither
+      // THIRD_PARTY_PARENT_IS_* flag is set.
+      checkWindowChain = !(flags & nsIHttpChannelInternal::THIRD_PARTY_PARENT_IS_SAME_PARTY);
+      parentIsThird = false;
+    }
   }
 
   // Obtain the URI from the channel, and its base domain.
   nsCOMPtr<nsIURI> channelURI;
   aChannel->GetURI(getter_AddRefs(channelURI));
   NS_ENSURE_TRUE(channelURI, NS_ERROR_INVALID_ARG);
 
   nsCString channelDomain;
@@ -201,16 +230,22 @@ ThirdPartyUtil::IsThirdPartyChannel(nsIC
 
     // If it's foreign, or we're forcing, we're done.
     if (result || doForce) {
       *aResult = result;
       return NS_OK;
     }
   }
 
+  // If we've already computed this in the child process, we're done.
+  if (!checkWindowChain) {
+    *aResult = parentIsThird;
+    return NS_OK;
+  }
+
   // Find the associated window and its parent window.
   nsCOMPtr<nsILoadContext> ctx;
   NS_QueryNotificationCallbacks(aChannel, ctx);
   if (!ctx) return NS_ERROR_INVALID_ARG;
 
   // If there is no window, the consumer kicking off the load didn't provide one
   // to the channel. This is limited to loads of certain types of resources. If
   // those loads require cookies, the forceAllowThirdPartyCookie property should
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -4486,23 +4486,29 @@ nsContentUtils::AppendNodeTextContent(ns
       }
     }
   }
 
   return true;
 }
 
 bool
-nsContentUtils::HasNonEmptyTextContent(nsINode* aNode)
+nsContentUtils::HasNonEmptyTextContent(nsINode* aNode,
+                                       TextContentDiscoverMode aDiscoverMode)
 {
   for (nsIContent* child = aNode->GetFirstChild();
        child;
        child = child->GetNextSibling()) {
     if (child->IsNodeOfType(nsINode::eTEXT) &&
         child->TextLength() > 0) {
+        return true;
+    }
+
+    if (aDiscoverMode == eRecurseIntoChildren &&
+        HasNonEmptyTextContent(child, aDiscoverMode)) {
       return true;
     }
   }
 
   return false;
 }
 
 /* static */
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1235,22 +1235,29 @@ public:
 
   /**
    * Same as GetNodeTextContents but appends the result rather than sets it.
    */
   static bool AppendNodeTextContent(nsINode* aNode, bool aDeep,
                                     nsAString& aResult, const mozilla::fallible_t&);
 
   /**
-   * Utility method that checks if a given node has any non-empty
-   * children.
-   * NOTE! This method does not descend recursivly into elements.
-   * Though it would be easy to make it so if needed
+   * Utility method that checks if a given node has any non-empty children. This
+   * method does not descend recursively into children by default.
+   *
+   * @param aDiscoverMode Set to eRecurseIntoChildren to descend recursively
+   * into children.
    */
-  static bool HasNonEmptyTextContent(nsINode* aNode);
+  enum TextContentDiscoverMode MOZ_ENUM_TYPE(uint8_t) {
+    eRecurseIntoChildren, eDontRecurseIntoChildren
+  };
+
+  static bool HasNonEmptyTextContent(
+    nsINode* aNode,
+    TextContentDiscoverMode aDiscoverMode = eDontRecurseIntoChildren);
 
   /**
    * Delete strings allocated for nsContentList matches
    */
   static void DestroyMatchString(void* aData);
 
   /**
    * Unbinds the content from the tree and nulls it out if it's not null.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -702,16 +702,18 @@ GK_ATOM(oncompositionend, "oncomposition
 GK_ATOM(oncompositionstart, "oncompositionstart")
 GK_ATOM(oncompositionupdate, "oncompositionupdate")
 GK_ATOM(onconfigurationchange, "onconfigurationchange")
 GK_ATOM(onconnect, "onconnect")
 GK_ATOM(onconnected, "onconnected")
 GK_ATOM(onconnecting, "onconnecting")
 GK_ATOM(oncontextmenu, "oncontextmenu")
 GK_ATOM(oncopy, "oncopy")
+GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
+GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
 GK_ATOM(oncut, "oncut")
 GK_ATOM(ondatachange, "ondatachange")
 GK_ATOM(ondataerror, "ondataerror")
 GK_ATOM(ondblclick, "ondblclick")
 GK_ATOM(ondeleted, "ondeleted")
 GK_ATOM(ondeliverysuccess, "ondeliverysuccess")
 GK_ATOM(ondeliveryerror, "ondeliveryerror")
 GK_ATOM(ondevicefound, "ondevicefound")
@@ -741,16 +743,17 @@ GK_ATOM(ondragdrop, "ondragdrop")
 GK_ATOM(ondragend, "ondragend")
 GK_ATOM(ondragenter, "ondragenter")
 GK_ATOM(ondragexit, "ondragexit")
 GK_ATOM(ondraggesture, "ondraggesture")
 GK_ATOM(ondragleave, "ondragleave")
 GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
 GK_ATOM(ondrop, "ondrop")
+GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfacesdetected, "onfacesdetected")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfetch, "onfetch")
@@ -843,16 +846,17 @@ GK_ATOM(onremoteresumed, "onremoteresume
 GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onresuming, "onresuming")
 GK_ATOM(onresize, "onresize")
 GK_ATOM(onrtchange, "onrtchange")
+GK_ATOM(onscanningstatechanged, "onscanningstatechanged")
 GK_ATOM(onscostatuschanged, "onscostatuschanged")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
 GK_ATOM(onshutter, "onshutter")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -197,16 +197,17 @@
 #include "mozilla/Telemetry.h"
 #include "nsLocation.h"
 #include "nsHTMLDocument.h"
 #include "nsWrapperCacheInlines.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "prrng.h"
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
+#include "TouchCaret.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/HashChangeEvent.h"
 #include "mozilla/dom/MozSelfSupportBinding.h"
@@ -9351,16 +9352,34 @@ nsGlobalWindow::UpdateCommands(const nsA
                                                             anAction));
     }
   }
 
   if (gSelectionCaretPrefEnabled && mDoc && anAction.EqualsLiteral("selectionchange")) {
     SelectionChangeEventInit init;
     init.mBubbles = true;
     if (aSel) {
+      bool isTouchCaretVisible = false;
+      bool isCollapsed = aSel->Collapsed();
+
+      nsIPresShell *shell = mDoc->GetShell();
+      if (shell) {
+        nsRefPtr<TouchCaret> touchCaret = shell->GetTouchCaret();
+        if (touchCaret) {
+          isTouchCaretVisible = touchCaret->GetVisibility();
+        }
+      }
+
+      // Dispatch selection change events when touch caret is visible even if selection
+      // is collapsed because it could be the shortcut mode, otherwise ignore this
+      // UpdateCommands
+      if (isCollapsed && !isTouchCaretVisible) {
+        return NS_OK;
+      }
+
       Selection* selection = static_cast<Selection*>(aSel);
       int32_t rangeCount = selection->GetRangeCount();
       nsLayoutUtils::RectAccumulator accumulator;
       for (int32_t idx = 0; idx < rangeCount; ++idx) {
         nsRange* range = selection->GetRangeAt(idx);
         nsRange::CollectClientRects(&accumulator, range,
                                     range->GetStartParent(), range->StartOffset(),
                                     range->GetEndParent(), range->EndOffset(),
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -292,16 +292,20 @@ const kEventConstructors = {
   MozMmsEvent:                               { create: function (aName, aProps) {
                                                          return new MozMmsEvent(aName, aProps);
                                                        },
                                              },
   MozNFCPeerEvent:                           { create: function (aName, aProps) {
                                                          return new MozNFCPeerEvent(aName, aProps);
                                                        },
                                              },
+  MozNFCTagEvent:                            { create: function (aName, aProps) {
+                                                         return new MozNFCTagEvent(aName, aProps);
+                                                       },
+                                             },
   MozOtaStatusEvent:                         { create: function (aName, aProps) {
                                                           return new MozOtaStatusEvent(aName, aProps);
                                                        },
                                              },
   MozSettingsEvent:                          { create: function (aName, aProps) {
                                                          return new MozSettingsEvent(aName, aProps);
                                                        },
                                              },
@@ -463,16 +467,48 @@ const kEventConstructors = {
   TrackEvent:                                { create: function (aName, aProps) {
                                                          return new TrackEvent(aName, aProps);
                                                        },
                                              },
   TransitionEvent:                           { create: function (aName, aProps) {
                                                          return new TransitionEvent(aName, aProps);
                                                        },
                                              },
+  TVCurrentChannelChangedEvent:              { create: function (aName, aProps) {
+                                                         return new TVCurrentChannelChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVCurrentProgramChangedEvent:              { create: function (aName, aProps) {
+                                                         return new TVCurrentProgramChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVCurrentSourceChangedEvent:               { create: function (aName, aProps) {
+                                                         return new TVCurrentSourceChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVEITBroadcastedEvent:                     { create: function (aName, aProps) {
+                                                         return new TVEITBroadcastedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVParentalControlChangedEvent:             { create: function (aName, aProps) {
+                                                         return new TVParentalControlChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVProtectionStateChangedEvent:             { create: function (aName, aProps) {
+                                                         return new TVProtectionStateChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVScanningStateChangedEvent:               { create: function (aName, aProps) {
+                                                         return new TVScanningStateChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVTunerChangedEvent:                       { create: function (aName, aProps) {
+                                                         return new TVTunerChangedEvent(aName, aProps);
+                                                       },
+                                             },
   UIEvent:                                   { create: function (aName, aProps) {
                                                          return new UIEvent(aName, aProps);
                                                        },
                                              },
   UserProximityEvent:                        { create: function (aName, aProps) {
                                                          return new UserProximityEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -601,21 +601,16 @@ HTMLMediaElement::OnChannelRedirect(nsIC
 
   return NS_OK;
 }
 
 void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
-  // TODO: This should be handled by ChangeNetworkState() so we have only one
-  // place to call StopProgress().
-  if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
-    mDecoder->StopProgress();
-  }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
 void HTMLMediaElement::AbortExistingLoads()
 {
   // Abort any already-running instance of the resource selection algorithm.
   mLoadWaitStatus = NOT_WAITING;
@@ -1787,21 +1782,17 @@ NS_IMETHODIMP HTMLMediaElement::SetMuted
 
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded)
 {
   nsIDOMWindow* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return nullptr;
   }
-#ifdef MOZ_EME
-  if (ContainsRestrictedContent()) {
-    return nullptr;
-  }
-#endif
+
   OutputMediaStream* out = mOutputStreams.AppendElement();
 #ifdef DEBUG
   // Estimate hints based on the type of the media element
   // under the preference media.capturestream_hints for the
   // debug builds only. This allows WebRTC Peer Connection
   // to behave appropriately when media streams generated
   // via mozCaptureStream*() are added to the Peer Connection.
   // This functionality is planned to be used as part of Audio
@@ -3996,31 +3987,20 @@ NS_IMETHODIMP HTMLMediaElement::CanPlayC
 
 #ifdef MOZ_EME
 MediaKeys*
 HTMLMediaElement::GetMediaKeys() const
 {
   return mMediaKeys;
 }
 
-bool
-HTMLMediaElement::ContainsRestrictedContent()
-{
-  return GetMediaKeys() != nullptr;
-}
-
 already_AddRefed<Promise>
 HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
                                ErrorResult& aRv)
 {
-  if (MozAudioCaptured()) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-    return nullptr;
-  }
-
   nsCOMPtr<nsIGlobalObject> global =
     do_QueryInterface(OwnerDoc()->GetInnerWindow());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   if (aRv.Failed()) {
@@ -4034,29 +4014,27 @@ HTMLMediaElement::SetMediaKeys(mozilla::
     promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return promise.forget();
   }
   if (mMediaKeys) {
     // Existing MediaKeys object. Shut it down.
     mMediaKeys->Shutdown();
     mMediaKeys = nullptr;
   }
-  
+
   mMediaKeys = aMediaKeys;
   if (mMediaKeys) {
     if (NS_FAILED(mMediaKeys->Bind(this))) {
       promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
       mMediaKeys = nullptr;
       return promise.forget();
     }
     if (mDecoder) {
       mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
     }
-    // Update the same-origin status.
-    NotifyDecoderPrincipalChanged();
   }
   promise->MaybeResolve(JS::UndefinedHandleValue);
   return promise.forget();
 }
 
 MediaWaitingFor
 HTMLMediaElement::WaitingFor() const
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -545,17 +545,16 @@ public:
 
 
   bool IsEventAttributeName(nsIAtom* aName) MOZ_OVERRIDE;
 
   // Returns the principal of the "top level" document; the origin displayed
   // in the URL bar of the browser window.
   already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
 
-  bool ContainsRestrictedContent();
 #endif // MOZ_EME
 
   bool MozAutoplayEnabled() const
   {
     return mAutoplayEnabled;
   }
 
   already_AddRefed<DOMMediaStream> MozCaptureStream(ErrorResult& aRv);
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -32,16 +32,17 @@
 #include "nsITextControlFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsLinebreakConverter.h"
 #include "nsMappedAttributes.h"
 #include "nsPIDOMWindow.h"
 #include "nsPresContext.h"
 #include "nsPresState.h"
 #include "nsReadableUtils.h"
+#include "nsRuleData.h"
 #include "nsStyleConsts.h"
 #include "nsTextEditorState.h"
 #include "nsIController.h"
 
 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
 
 #define NS_NO_CONTENT_DISPATCH (1 << 0)
 
@@ -402,16 +403,28 @@ HTMLTextAreaElement::ParseAttribute(int3
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 HTMLTextAreaElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                            nsRuleData* aData)
 {
+  if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+    // wrap=off
+    nsCSSValue* whiteSpace = aData->ValueForWhiteSpace();
+    if (whiteSpace->GetUnit() == eCSSUnit_Null) {
+      const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::wrap);
+      if (value && value->Type() == nsAttrValue::eString &&
+          value->Equals(nsGkAtoms::OFF, eIgnoreCase)) {
+        whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_PRE, eCSSUnit_Enumerated);
+      }
+    }
+  }
+
   nsGenericHTMLFormElementWithState::MapDivAlignAttributeInto(aAttributes, aData);
   nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
 }
 
 nsChangeHint
 HTMLTextAreaElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
                                             int32_t aModType) const
 {
@@ -426,17 +439,23 @@ HTMLTextAreaElement::GetAttributeChangeH
     NS_UpdateHint(retval, NS_STYLE_HINT_FRAMECHANGE);
   }
   return retval;
 }
 
 NS_IMETHODIMP_(bool)
 HTMLTextAreaElement::IsAttributeMapped(const nsIAtom* aAttribute) const
 {
+  static const MappedAttributeEntry attributes[] {
+    { &nsGkAtoms::wrap },
+    { nullptr }
+  };
+
   static const MappedAttributeEntry* const map[] = {
+    attributes,
     sDivAlignAttributeMap,
     sCommonAttributeMap,
   };
 
   return FindAttributeDependence(aAttribute, map);
 }
 
 nsMapRuleToAttributesFunc
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/82711-1-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" wrap="off">
+  0  1  2  3
+  4  5
+
+  6  7  8
+  9
+
+
+  This is a long line that could wrap.
+</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/82711-1.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" style="white-space: pre">
+  0  1  2  3
+  4  5
+
+  6  7  8
+  9
+
+
+  This is a long line that could wrap.
+</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/82711-2-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" wrap="off" style="white-space: normal">
+  0  1  2  3
+  4  5
+
+  6  7  8
+  9
+
+
+  This is a long line that could wrap.
+</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/82711-2.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" style="white-space: normal">
+0 1 2 3 4 5 6 7 8 9 This is a long line that could wrap.
+</textarea>
+</body>
+</html>
--- a/dom/html/reftests/reftest.list
+++ b/dom/html/reftests/reftest.list
@@ -1,15 +1,18 @@
 # autofocus attribute (we can't test with mochitests)
 include autofocus/reftest.list
 include toblob-todataurl/reftest.list
 
 skip-if(B2G) == 41464-1a.html 41464-1-ref.html
 skip-if(B2G) == 41464-1b.html 41464-1-ref.html
 == 52019-1.html 52019-1-ref.html
+== 82711-1.html 82711-1-ref.html
+== 82711-2.html 82711-2-ref.html
+!= 82711-1-ref.html 82711-2-ref.html
 != 468263-1a.html about:blank
 != 468263-1b.html about:blank
 != 468263-1c.html about:blank
 != 468263-1d.html about:blank
 == 468263-2.html 468263-2-ref.html
 == 468263-2.html 468263-2-alternate-ref.html
 == 484200-1.html 484200-1-ref.html
 == 485377.html 485377-ref.html
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -244,24 +244,44 @@ this.Keyboard = {
       case 'Keyboard:Unregister':
         this._keyboardMM = null;
         this._keyboardID = -1;
         break;
     }
   },
 
   forwardEvent: function keyboardForwardEvent(newEventName, msg) {
-    this.formMM = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
-                            .frameLoader.messageManager;
+    let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
+                .frameLoader.messageManager;
+    if (newEventName === 'Keyboard:FocusChange' &&
+        msg.data.type === 'blur') {
+      // A blur message can't be sent to the keyboard if the focus has
+      // already taken away at first place.
+      // This check is here to prevent problem caused by out-of-order
+      // ipc messages from two processes.
+      if (mm !== this.formMM) {
+        return false;
+      }
+
+      this.sendToKeyboard(newEventName, msg.data);
+      return true;
+    }
+
+    this.formMM = mm;
 
     this.sendToKeyboard(newEventName, msg.data);
+    return true;
   },
 
   handleFocusChange: function keyboardHandleFocusChange(msg) {
-    this.forwardEvent('Keyboard:FocusChange', msg);
+    let isSent = this.forwardEvent('Keyboard:FocusChange', msg);
+
+    if (!isSent) {
+      return;
+    }
 
     // Chrome event, used also to render value selectors; that's why we need
     // the info about choices / min / max here as well...
     SystemAppProxy.dispatchEvent({
       type: 'inputmethod-contextchange',
       inputType: msg.data.type,
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -38,16 +38,17 @@
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/TestShellChild.h"
 #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/PluginModuleParent.h"
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #if defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
 #include "nsDirectoryServiceDefs.h"
 #elif defined(XP_LINUX)
 #include "mozilla/Sandbox.h"
@@ -927,16 +928,23 @@ ContentChild::DeallocPCycleCollectWithLo
 {
     // ...so when we get here, there's nothing for us to do.
     //
     // Also, we're already in ~CycleCollectWithLogsChild (q.v.) at
     // this point, so we shouldn't touch the actor in any case.
     return true;
 }
 
+mozilla::plugins::PPluginModuleParent*
+ContentChild::AllocPPluginModuleParent(mozilla::ipc::Transport* aTransport,
+                                       base::ProcessId aOtherProcess)
+{
+    return plugins::PluginModuleContentParent::Create(aTransport, aOtherProcess);
+}
+
 PContentBridgeChild*
 ContentChild::AllocPContentBridgeChild(mozilla::ipc::Transport* aTransport,
                                        base::ProcessId aOtherProcess)
 {
     return ContentBridgeChild::Create(aTransport, aOtherProcess);
 }
 
 PContentBridgeParent*
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -96,16 +96,20 @@ public:
     ContentBridgeParent* GetLastBridge() {
         MOZ_ASSERT(mLastBridge);
         ContentBridgeParent* parent = mLastBridge;
         mLastBridge = nullptr;
         return parent;
     }
     nsRefPtr<ContentBridgeParent> mLastBridge;
 
+    PPluginModuleParent *
+    AllocPPluginModuleParent(mozilla::ipc::Transport* transport,
+                             base::ProcessId otherProcess) MOZ_OVERRIDE;
+
     PContentBridgeParent*
     AllocPContentBridgeParent(mozilla::ipc::Transport* transport,
                               base::ProcessId otherProcess) MOZ_OVERRIDE;
     PContentBridgeChild*
     AllocPContentBridgeChild(mozilla::ipc::Transport* transport,
                              base::ProcessId otherProcess) MOZ_OVERRIDE;
 
     PCompositorChild*
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -65,16 +65,17 @@
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/PFileDescriptorSetParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/SharedBufferManagerParent.h"
 #include "mozilla/net/NeckoParent.h"
+#include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "nsAnonymousTemporaryFile.h"
 #include "nsAppRunner.h"
 #include "nsAutoPtr.h"
 #include "nsCDefaultURIFixup.h"
@@ -917,16 +918,30 @@ ContentParent::AnswerBridgeToChildProces
         return PContentBridge::Bridge(this, cp);
     } else {
         // You can't bridge to a process you didn't open!
         KillHard();
         return false;
     }
 }
 
+bool
+ContentParent::AnswerLoadPlugin(const uint32_t& aPluginId)
+{
+    return mozilla::plugins::SetupBridge(aPluginId, this);
+}
+
+bool
+ContentParent::RecvFindPlugins(const uint32_t& aPluginEpoch,
+                               nsTArray<PluginTag>* aPlugins,
+                               uint32_t* aNewPluginEpoch)
+{
+    return mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
+}
+
 /*static*/ TabParent*
 ContentParent::CreateBrowserOrApp(const TabContext& aContext,
                                   Element* aFrameElement,
                                   ContentParent* aOpenerContentParent)
 {
     if (!sCanLaunchSubprocesses) {
         return nullptr;
     }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -143,16 +143,21 @@ public:
 
     virtual bool RecvCreateChildProcess(const IPCTabContext& aContext,
                                         const hal::ProcessPriority& aPriority,
                                         uint64_t* aId,
                                         bool* aIsForApp,
                                         bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool AnswerBridgeToChildProcess(const uint64_t& id) MOZ_OVERRIDE;
 
+    virtual bool AnswerLoadPlugin(const uint32_t& aPluginId) MOZ_OVERRIDE;
+    virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
+                                 nsTArray<PluginTag>* aPlugins,
+                                 uint32_t* aNewPluginEpoch) MOZ_OVERRIDE;
+
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSIDOMGEOPOSITIONCALLBACK
 
     /**
      * MessageManagerCallback methods that we override.
--- a/dom/ipc/CrashReporterChild.cpp
+++ b/dom/ipc/CrashReporterChild.cpp
@@ -20,17 +20,17 @@ CrashReporterChild::GetCrashReporter()
   const InfallibleTArray<PCrashReporterChild*>* reporters = nullptr;
   switch (XRE_GetProcessType()) {
     case GeckoProcessType_Content: {
       ContentChild* child = ContentChild::GetSingleton();
       reporters = &child->ManagedPCrashReporterChild();
       break;
     }
     case GeckoProcessType_Plugin: {
-      PluginModuleChild* child = PluginModuleChild::current();
+      PluginModuleChild* child = PluginModuleChild::GetChrome();
       reporters = &child->ManagedPCrashReporterChild();
       break;
     }
     default:
       break;
   }
   if (reporters && reporters->Length() > 0) {
     return reporters->ElementAt(0);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -20,32 +20,34 @@ include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PImageBridge;
 include protocol PMemoryReportRequest;
 include protocol PMobileConnection;
 include protocol PNecko;
+include protocol PPluginModule;
 include protocol PPrinting;
 include protocol PScreenManager;
 include protocol PSharedBufferManager;
 include protocol PSms;
 include protocol PSpeechSynthesis;
 include protocol PStorage;
 include protocol PTelephony;
 include protocol PTestShell;
 include protocol PVoicemail;
 include protocol PJavaScript;
 include protocol PRemoteSpellcheckEngine;
 include DOMTypes;
 include JavaScriptTypes;
 include InputStreamParams;
 include PTabContext;
 include URIParams;
+include PluginTypes;
 include ProtocolTypes;
 
 // Workaround to prevent error if PContentChild.cpp & PContentBridgeParent.cpp
 // are put into different UnifiedProtocolsXX.cpp files.
 // XXX Remove this once bug 1069073 is fixed
 include "mozilla/dom/PContentBridgeParent.h";
 
 include "mozilla/dom/indexedDB/SerializationHelpers.h";
@@ -323,16 +325,18 @@ struct ClipboardCapabilities {
 
 union MaybeFileDesc {
     FileDescriptor;
     void_t;
 };
 
 prio(normal upto high) intr protocol PContent
 {
+    parent spawns PPluginModule;
+
     parent opens PCompositor;
     parent opens PSharedBufferManager;
     parent opens PImageBridge;
     child opens PBackground;
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
@@ -527,16 +531,39 @@ parent:
         returns (bool isOffline, nsString[] dictionaries,
                  ClipboardCapabilities clipboardCaps);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority)
         returns (uint64_t id, bool isForApp, bool isForBrowser);
     intr BridgeToChildProcess(uint64_t id);
 
+    /**
+     * This call connects the content process to a plugin process. While this
+     * call runs, a new PluginModuleParent will be created in the ContentChild
+     * via bridging. The corresponding PluginModuleChild will live in the plugin
+     * process. We use intr semantics here to ensure that the PluginModuleParent
+     * allocation message is dispatched before LoadPlugin returns.
+     */
+    intr LoadPlugin(uint32_t pluginId);
+
+    /**
+     * This call returns the set of plugins loaded in the chrome
+     * process. However, in many cases this set will not have changed since the
+     * last FindPlugins message. Consequently, the chrome process increments an
+     * epoch number every time the set of plugins changes. The content process
+     * sends up the last epoch it observed. If the epochs are the same, the
+     * chrome process returns no plugins. Otherwise it returns a complete list.
+     *
+     * |pluginEpoch| is the epoch last observed by the content
+     * process. |newPluginEpoch| is the current epoch in the chrome process. If
+     * |pluginEpoch == newPluginEpoch|, then |plugins| will be left empty.
+     */
+    sync FindPlugins(uint32_t pluginEpoch) returns (PluginTag[] plugins, uint32_t newPluginEpoch);
+
     async PJavaScript();
 
     sync PRemoteSpellcheckEngine();
     PDeviceStorageRequest(DeviceStorageParams params);
 
     PFileSystemRequest(FileSystemParams params);
 
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -494,18 +494,19 @@ void MediaDecoder::Shutdown()
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
   if (mResource) {
     mResource->Close();
   }
 
   ChangeState(PLAY_STATE_SHUTDOWN);
 
-  // If we hit this assertion, there might be a bug in network state transition.
-  NS_ASSERTION(!mProgressTimer, "Progress timer should've been stopped.");
+  if (mProgressTimer) {
+    StopProgress();
+  }
   mOwner = nullptr;
 
   MediaShutdownManager::Instance().Unregister(this);
 }
 
 MediaDecoder::~MediaDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/gmp-plugin/gmp-test-decryptor.cpp
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -7,18 +7,18 @@
 #include "gmp-test-storage.h"
 
 #include <string>
 #include <vector>
 #include <iostream>
 #include <istream>
 #include <iterator>
 #include <sstream>
-#include <assert.h>
 
+#include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/NullPtr.h"
 
 using namespace std;
 
 FakeDecryptor* FakeDecryptor::sInstance = nullptr;
 
 static bool sFinishedTruncateTest = false;
@@ -31,30 +31,30 @@ MaybeFinish()
   if (sFinishedTruncateTest && sFinishedReplaceTest && sMultiClientTest) {
     FakeDecryptor::Message("test-storage complete");
   }
 }
 
 FakeDecryptor::FakeDecryptor()
   : mCallback(nullptr)
 {
-  assert(!sInstance);
+  MOZ_ASSERT(!sInstance);
   sInstance = this;
 }
 
 void FakeDecryptor::DecryptingComplete()
 {
   sInstance = nullptr;
   delete this;
 }
 
 void
 FakeDecryptor::Message(const std::string& aMessage)
 {
-  assert(sInstance);
+  MOZ_ASSERT(sInstance);
   const static std::string sid("fake-session-id");
   sInstance->mCallback->SessionMessage(sid.c_str(), sid.size(),
                                        (const uint8_t*)aMessage.c_str(), aMessage.size(),
                                        nullptr, 0);
 }
 
 std::vector<std::string>
 Tokenize(const std::string& aString)
--- a/dom/media/gmp-plugin/gmp-test-storage.cpp
+++ b/dom/media/gmp-plugin/gmp-test-storage.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gmp-test-storage.h"
 #include <vector>
-#include <assert.h>
+
+#include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 
 class WriteRecordClient : public GMPRecordClient {
 public:
   GMPErr Init(GMPRecord* aRecord,
               GMPTask* aContinuation,
               const uint8_t* aData,
               uint32_t aDataSize) {
@@ -114,17 +115,17 @@ private:
   GMPRecord* mRecord;
   ReadContinuation* mContinuation;
 };
 
 GMPErr
 ReadRecord(const std::string& aRecordName,
            ReadContinuation* aContinuation)
 {
-  assert(aContinuation);
+  MOZ_ASSERT(aContinuation);
   GMPRecord* record;
   ReadRecordClient* client = new ReadRecordClient();
   auto err = GMPOpenRecord(aRecordName.c_str(),
                            aRecordName.size(),
                            &record,
                            client);
   if (GMP_FAILED(err)) {
     return err;
@@ -135,24 +136,24 @@ ReadRecord(const std::string& aRecordNam
 extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
 
 GMPErr
 GMPOpenRecord(const char* aName,
               uint32_t aNameLength,
               GMPRecord** aOutRecord,
               GMPRecordClient* aClient)
 {
-  assert(g_platform_api);
+  MOZ_ASSERT(g_platform_api);
   return g_platform_api->createrecord(aName, aNameLength, aOutRecord, aClient);
 }
 
 GMPErr
 GMPRunOnMainThread(GMPTask* aTask)
 {
-  assert(g_platform_api);
+  MOZ_ASSERT(g_platform_api);
   return g_platform_api->runonmainthread(aTask);
 }
 
 class OpenRecordClient : public GMPRecordClient {
 public:
   GMPErr Init(GMPRecord* aRecord,
               OpenContinuation* aContinuation) {
     mRecord = aRecord;
@@ -175,17 +176,17 @@ private:
   GMPRecord* mRecord;
   OpenContinuation* mContinuation;
 };
 
 GMPErr
 GMPOpenRecord(const std::string& aRecordName,
               OpenContinuation* aContinuation)
 {
-  assert(aContinuation);
+  MOZ_ASSERT(aContinuation);
   GMPRecord* record;
   OpenRecordClient* client = new OpenRecordClient();
   auto err = GMPOpenRecord(aRecordName.c_str(),
                            aRecordName.size(),
                            &record,
                            client);
   if (GMP_FAILED(err)) {
     return err;
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -79,28 +79,16 @@ public:
   }
 
   void SetTaskQueue(MediaTaskQueue* aTaskQueue)
   {
     MOZ_ASSERT((!mTaskQueue && aTaskQueue) || (mTaskQueue && !aTaskQueue));
     mTaskQueue = aTaskQueue;
   }
 
-  void BreakCycles()
-  {
-    if (mReader) {
-      mReader->BreakCycles();
-      mReader = nullptr;
-    }
-    mTaskQueue = nullptr;
-#ifdef MOZ_EME
-    mCDMProxy = nullptr;
-#endif
-  }
-
 #ifdef MOZ_EME
   virtual nsresult SetCDMProxy(CDMProxy* aProxy)
   {
     MOZ_ASSERT(NS_IsMainThread());
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mCDMProxy = aProxy;
     return NS_OK;
   }
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -386,17 +386,17 @@ TrackBuffer::ContainsTime(int64_t aTime)
 }
 
 void
 TrackBuffer::BreakCycles()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
-    mDecoders[i]->BreakCycles();
+    mDecoders[i]->GetReader()->BreakCycles();
   }
   mDecoders.Clear();
 
   // These are cleared in Shutdown()
   MOZ_ASSERT(mInitializedDecoders.IsEmpty());
   MOZ_ASSERT(!mParentDecoder);
 }
 
deleted file mode 100644
--- a/dom/media/test/eme.js
+++ /dev/null
@@ -1,178 +0,0 @@
-const KEYSYSTEM_TYPE = "org.w3.clearkey";
-
-function bail(message)
-{
-  return function(err) {
-    ok(false, message);
-    if (err) {
-      info(err);
-    }
-    SimpleTest.finish();
-  }
-}
-
-function ArrayBufferToString(arr)
-{
-  var str = '';
-  var view = new Uint8Array(arr);
-  for (var i = 0; i < view.length; i++) {
-    str += String.fromCharCode(view[i]);
-  }
-  return str;
-}
-
-function StringToArrayBuffer(str)
-{
-  var arr = new ArrayBuffer(str.length);
-  var view = new Uint8Array(arr);
-  for (var i = 0; i < str.length; i++) {
-    view[i] = str.charCodeAt(i);
-  }
-  return arr;
-}
-
-function Base64ToHex(str)
-{
-  var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
-  var res = "";
-  for (var i = 0; i < bin.length; i++) {
-    res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
-  }
-  return res;
-}
-
-function HexToBase64(hex)
-{
-  var bin = "";
-  for (var i = 0; i < hex.length; i += 2) {
-    bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
-  }
-  return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
-}
-
-function UpdateSessionFunc(test) {
-  return function(ev) {
-    var msgStr = ArrayBufferToString(ev.message);
-    var msg = JSON.parse(msgStr);
-
-    info("got message from CDM: " + msgStr);
-    is(msg.type, test.sessionType, "Key session type should match");
-    ok(msg.kids, "message event should contain key ID array");
-
-    var outKeys = [];
-
-    for (var i = 0; i < msg.kids.length; i++) {
-      var id64 = msg.kids[i];
-      var idHex = Base64ToHex(msg.kids[i]).toLowerCase();
-      var key = test.keys[idHex];
-
-      if (key) {
-        info("found key " + key + " for key id " + idHex);
-        outKeys.push({
-          "kty":"oct",
-          "alg":"A128KW",
-          "kid":id64,
-          "k":HexToBase64(key)
-        });
-      } else {
-        bail("Couldn't find key for key id " + idHex);
-      }
-    }
-
-    var update = JSON.stringify({
-      "keys" : outKeys,
-      "type" : msg.type
-    });
-    info("sending update message to CDM: " + update);
-
-    ev.target.update(StringToArrayBuffer(update)).then(function() {
-      info("MediaKeySession update ok!");
-    }, bail("MediaKeySession update failed"));
-  }
-}
-
-function PlayFragmented(test, elem)
-{
-  return new Promise(function(resolve, reject) {
-    var ms = new MediaSource();
-    elem.src = URL.createObjectURL(ms);
-
-    var sb;
-    var curFragment = 0;
-
-    function addNextFragment() {
-      if (curFragment >= test.fragments.length) {
-        ms.endOfStream();
-        resolve();
-        return;
-      }
-
-      var fragmentFile = test.fragments[curFragment++];
-
-      var req = new XMLHttpRequest();
-      req.open("GET", fragmentFile);
-      req.responseType = "arraybuffer";
-
-      req.addEventListener("load", function() {
-        sb.appendBuffer(new Uint8Array(req.response));
-      });
-
-      info("fetching resource " + fragmentFile);
-      req.send(null);
-    }
-
-    ms.addEventListener("sourceopen", function () {
-      sb = ms.addSourceBuffer(test.type);
-      sb.addEventListener("updateend", addNextFragment);
-
-      addNextFragment();
-    })
-  });
-}
-
-// Returns a promise that is resovled when the media element is ready to have
-// its play() function called; when it's loaded MSE fragments, or once the load
-// has started for non-MSE video.
-function LoadTest(test, elem)
-{
-  if (test.fragments) {
-    return PlayFragmented(test, elem);
-  }
-
-  // This file isn't fragmented; set the media source normally.
-  return new Promise(function(resolve, reject) {
-    elem.src = test.name;
-    resolve();
-  });
-}
-
-function SetupEME(test, token, params)
-{
-  var v = document.createElement("video");
-
-  var onSetKeysFail = (params && params.onSetKeysFail)
-    ? params.onSetKeysFail
-    : bail(token + " Failed to set MediaKeys on <video> element");
-  
-  v.addEventListener("encrypted", function(ev) {
-    info(token + " got encrypted event");
-    MediaKeys.create(KEYSYSTEM_TYPE).then(function(mediaKeys) {
-      info(token + " created MediaKeys object ok");
-      mediaKeys.sessions = [];
-      return v.setMediaKeys(mediaKeys);
-    }, bail("failed to create MediaKeys object")).then(function() {
-      info(token + " set MediaKeys on <video> element ok");
-
-      var session = v.mediaKeys.createSession(test.sessionType);
-      if (params && params.onsessioncreated) {
-        params.onsessioncreated(session);
-      }
-      session.addEventListener("message", UpdateSessionFunc(test));
-      session.generateRequest(ev.initDataType, ev.initData).then(function() {
-      }, bail(token + " Failed to initialise MediaKeySession"));
-
-    }, onSetKeysFail);
-  });
-
-  return v;
-}
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -119,17 +119,17 @@ var gPlayTests = [
 
   // oggz-chop stream
   { name:"bug482461.ogv", type:"video/ogg", duration:4.34 },
   // Theora only oggz-chop stream
   { name:"bug482461-theora.ogv", type:"video/ogg", duration:4.138 },
   // With first frame a "duplicate" (empty) frame.
   { name:"bug500311.ogv", type:"video/ogg", duration:1.96 },
   // Small audio file
-  { name:"small-shot.ogg", type:"video/ogg", duration:0.276 },
+  { name:"small-shot.ogg", type:"audio/ogg", duration:0.276 },
   // More audio in file than video.
   { name:"short-video.ogv", type:"video/ogg", duration:1.081 },
   // First Theora data packet is zero bytes.
   { name:"bug504613.ogv", type:"video/ogg", duration:Number.NaN },
   // Multiple audio streams.
   { name:"bug516323.ogv", type:"video/ogg", duration:4.208 },
   // oggz-chop with non-keyframe as first frame
   { name:"bug556821.ogv", type:"video/ogg", duration:2.551 },
@@ -674,23 +674,27 @@ function checkMetadata(msg, e, test) {
     ok(Math.abs(e.duration - test.duration) < 0.1,
        msg + " duration (" + e.duration + ") should be around " + test.duration);
   }
 }
 
 // Returns the first test from candidates array which we can play with the
 // installed video backends.
 function getPlayableVideo(candidates) {
-  var v = document.createElement("video");
-  var resources = candidates.filter(function(x){return /^video/.test(x.type) && v.canPlayType(x.type);});
+  var resources = getPlayableVideos(candidates);
   if (resources.length > 0)
     return resources[0];
   return null;
 }
 
+function getPlayableVideos(candidates) {
+  var v = document.createElement("video");
+  return candidates.filter(function(x){return /^video/.test(x.type) && v.canPlayType(x.type);});
+}
+
 function getPlayableAudio(candidates) {
   var v = document.createElement("audio");
   var resources = candidates.filter(function(x){return /^audio/.test(x.type) && v.canPlayType(x.type);});
   if (resources.length > 0)
     return resources[0];
   return null;
 }
 
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -135,17 +135,16 @@ support-files =
   detodos.opus
   detodos.opus^headers^
   detodos.webm
   detodos.webm^headers^
   dirac.ogg
   dirac.ogg^headers^
   dynamic_redirect.sjs
   dynamic_resource.sjs
-  eme.js
   gizmo-frag-cenc1.m4s
   gizmo-frag-cenc2.m4s
   gizmo-frag-cencinit.mp4
   file_access_controls.html
   fragment_noplay.js
   fragment_play.js
   gizmo.mp4
   gizmo.mp4^headers^
@@ -356,21 +355,17 @@ skip-if = (toolkit == 'android' && proce
 [test_contentDuration7.html]
 [test_controls.html]
 [test_currentTime.html]
 [test_decode_error.html]
 [test_decoder_disable.html]
 [test_defaultMuted.html]
 [test_delay_load.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
-[test_eme_canvas_blocked.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
-[test_eme_playback.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
-[test_eme_stream_capture_blocked.html]
+[test_encryptedMediaExtensions.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
 [test_error_in_video_document.html]
 skip-if = toolkit == 'android' || (os == 'win' && !debug) || (os == 'mac' && !debug) # bug 608634
 [test_error_on_404.html]
 [test_fastSeek.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_fastSeek-forwards.html]
 [test_imagecapture.html]
@@ -397,17 +392,16 @@ skip-if = (toolkit == 'android' && proce
 [test_mediarecorder_getencodeddata.html]
 [test_mediarecorder_record_4ch_audiocontext.html]
 [test_mediarecorder_record_audiocontext.html]
 [test_mediarecorder_record_audiocontext_mlk.html]
 [test_mediarecorder_record_audionode.html]
 [test_mediarecorder_record_gum_video_timeslice.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # mimetype check, bug 969289
 [test_mediarecorder_record_immediate_stop.html]
-skip-if = toolkit == 'gonk' && debug
 [test_mediarecorder_record_no_timeslice.html]
 skip-if = toolkit == 'gonk' && debug
 [test_mediarecorder_record_nosrc.html]
 [test_mediarecorder_record_session.html]
 [test_mediarecorder_record_startstopstart.html]
 skip-if = toolkit == 'gonk' && debug
 [test_mediarecorder_record_stopms.html]
 [test_mediarecorder_record_timeslice.html]
deleted file mode 100644
--- a/dom/media/test/test_eme_canvas_blocked.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test Encrypted Media Extensions</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="eme.js"></script>
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-var manager = new MediaTestManager;
-
-function startTest(test, token)
-{
-  manager.started(token);
-
-  var sessions = [];
-
-  var v = SetupEME(test, token);
-  v.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this.
-
-  v.addEventListener("canplay", function(ev) {
-    var video = ev.target;
-    var canvas = document.createElement("canvas");
-    canvas.width = video.videoWidth;
-    canvas.height = video.videoHeight;
-    document.body.appendChild(canvas);
-    var ctx = canvas.getContext("2d");
-    var threwError = false;
-    try {
-      ctx.drawImage(video, 0, 0);
-    } catch (ex) {
-      threwError = true;
-    }
-    ok(threwError, token + " - Should throw an error when trying to draw EME video to canvas.");
-    manager.finished(token);
-  });
-
-  v.addEventListener("error", bail(token + " got error event"));
-
-  LoadTest(test, v);
-}
-
-function beginTest() {
-  manager.runTests(gEMETests, startTest);
-}
-
-var prefs = [
-  [ "media.mediasource.enabled", true ],
-  [ "media.mediasource.ignore_codecs", true ],
-];
-
-if (/Linux/.test(navigator.userAgent) ||
-    !document.createElement('video').canPlayType("video/mp4")) {
-  // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
-  prefs.push([ "media.fragmented-mp4.exposed", true ]);
-  prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
-}
-
-SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_eme_stream_capture_blocked.html
+++ /dev/null
@@ -1,102 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test Encrypted Media Extensions</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="eme.js"></script>
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-var manager = new MediaTestManager;
-
-function startTest(test, token)
-{
-  // Three cases:
-  // 1. setting MediaKeys on an element captured by MediaElementSource should fail, and
-  // 2. creating a MediaElementSource on a media element with a MediaKeys should fail, and
-  // 3. capturing a media element with mozCaptureStream that has a MediaKeys should fail.
-
-  // Case 1. setting MediaKeys on an element captured by MediaElementSource should fail.
-  var case1token = token + "_case1";
-  var setKeysFailed = function() {
-    ok(true, case1token + " setMediaKeys failed as expected.");
-    manager.finished(case1token);
-  };
-  var v1 = SetupEME(test, case1token,  { onSetKeysFail: setKeysFailed });
-  var context = new AudioContext();
-  var node = context.createMediaElementSource(v1);
-  v1.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this.
-  v1.addEventListener("error", bail(case1token + " got error event"));
-  v1.addEventListener("canplay", function(ev) {
-    ok(false, case1token + " should never reach canplay, as setMediaKeys should fail");
-  });
-  manager.started(case1token);
-  LoadTest(test, v1);
-
-
-  // Case 2. creating a MediaElementSource on a media element with a MediaKeys should fail.
-  var case2token = token + "_case2";
-  var v2 = SetupEME(test, case2token);
-  v2.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this.
-  v2.addEventListener("error", bail(case2token + " got error event"));
-  v2.addEventListener("canplay", function(ev) {
-    ok(true, case2token + " should reach canplay");
-    var threw = false;
-    try {
-      var context = new AudioContext();
-      var node = context.createMediaElementSource(v2);
-    } catch (e) {
-      threw = true;
-    }
-    ok(threw, "Should throw an error creating a MediaElementSource on an EME video.");
-    manager.finished(case2token);
-  });
-  manager.started(case2token);
-  LoadTest(test, v2);
-
-
-  // Case 3. capturing a media element with mozCaptureStream that has a MediaKeys should fail.
-  var case3token = token + "_case3";
-  var v3 = SetupEME(test, case3token);
-  v3.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this.
-  v3.addEventListener("error", bail(case3token + " got error event"));
-  v3.addEventListener("canplay", function(ev) {
-    ok(true, case3token + " should reach canplay");
-    var threw = false;
-    try {
-      var stream = v3.mozCaptureStreamUntilEnded();
-    } catch (e) {
-      threw = true;
-    }
-    ok(threw, "Should throw an error calling mozCaptureStreamUntilEnded an EME video.");
-    manager.finished(case3token);
-  });
-  manager.started(case3token);
-  LoadTest(test, v3);
-}
-
-function beginTest() {
-  manager.runTests(gEMETests, startTest);
-}
-
-var prefs = [
-  [ "media.mediasource.enabled", true ],
-  [ "media.mediasource.ignore_codecs", true ],
-];
-
-if (/Linux/.test(navigator.userAgent) ||
-    !document.createElement('video').canPlayType("video/mp4")) {
-  // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
-  prefs.push([ "media.fragmented-mp4.exposed", true ]);
-  prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
-}
-
-SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
-</script>
-</pre>
-</body>
-</html>
rename from dom/media/test/test_eme_playback.html
rename to dom/media/test/test_encryptedMediaExtensions.html
--- a/dom/media/test/test_eme_playback.html
+++ b/dom/media/test/test_encryptedMediaExtensions.html
@@ -1,96 +1,246 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test Encrypted Media Extensions</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="eme.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var manager = new MediaTestManager;
 
+const KEYSYSTEM_TYPE = "org.w3.clearkey";
 
-function KeysChangeFunc(session, keys, token) {
+function bail(message)
+{
+  return function(err) {
+    ok(false, message);
+    if (err) {
+      info(err);
+    }
+    SimpleTest.finish();
+  }
+}
+
+function ArrayBufferToString(arr)
+{
+  var str = '';
+  var view = new Uint8Array(arr);
+  for (var i = 0; i < view.length; i++) {
+    str += String.fromCharCode(view[i]);
+  }
+  return str;
+}
+
+function StringToArrayBuffer(str)
+{
+  var arr = new ArrayBuffer(str.length);
+  var view = new Uint8Array(arr);
+  for (var i = 0; i < str.length; i++) {
+    view[i] = str.charCodeAt(i);
+  }
+  return arr;
+}
+
+function Base64ToHex(str)
+{
+  var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
+  var res = "";
+  for (var i = 0; i < bin.length; i++) {
+    res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+  }
+  return res;
+}
+
+function HexToBase64(hex)
+{
+  var bin = "";
+  for (var i = 0; i < hex.length; i += 2) {
+    bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+  }
+  return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+}
+
+function UpdateSessionFunc(test) {
+  return function(ev) {
+    var msgStr = ArrayBufferToString(ev.message);
+    var msg = JSON.parse(msgStr);
+
+    info("got message from CDM: " + msgStr);
+    is(msg.type, test.sessionType, "Key session type should match");
+    ok(msg.kids, "message event should contain key ID array");
+
+    var outKeys = [];
+
+    for (var i = 0; i < msg.kids.length; i++) {
+      var id64 = msg.kids[i];
+      var idHex = Base64ToHex(msg.kids[i]).toLowerCase();
+      var key = test.keys[idHex];
+
+      if (key) {
+        info("found key " + key + " for key id " + idHex);
+        outKeys.push({
+          "kty":"oct",
+          "alg":"A128KW",
+          "kid":id64,
+          "k":HexToBase64(key)
+        });
+      } else {
+        bail("Couldn't find key for key id " + idHex);
+      }
+    }
+
+    var update = JSON.stringify({
+      "keys" : outKeys,
+      "type" : msg.type
+    });
+    info("sending update message to CDM: " + update);
+
+    ev.target.update(StringToArrayBuffer(update)).then(function() {
+      info("MediaKeySession update ok!");
+    }, bail("MediaKeySession update failed"));
+  }
+}
+
+function PlayFragmented(test, elem)
+{
+  var ms = new MediaSource();
+  elem.src = URL.createObjectURL(ms);
+
+  var sb;
+  var curFragment = 0;
+
+  function addNextFragment() {
+    if (curFragment >= test.fragments.length) {
+      ms.endOfStream();
+      elem.play();
+      return;
+    }
+
+    var fragmentFile = test.fragments[curFragment++];
+
+    var req = new XMLHttpRequest();
+    req.open("GET", fragmentFile);
+    req.responseType = "arraybuffer";
+
+    req.addEventListener("load", function() {
+      sb.appendBuffer(new Uint8Array(req.response));
+    });
+
+    info("fetching resource " + fragmentFile);
+    req.send(null);
+  }
+
+  ms.addEventListener("sourceopen", function () {
+    sb = ms.addSourceBuffer(test.type);
+    sb.addEventListener("updateend", addNextFragment);
+
+    addNextFragment();
+  });
+}
+
+function PlayTest(test, elem)
+{
+  if (test.fragments) {
+    PlayFragmented(test, elem);
+    return;
+  }
+
+  // This file isn't fragmented; set the media source normally.
+  elem.src = test.name;
+  elem.play();
+}
+
+function KeysChangeFunc(session, keys) {
   session.keyIdsReceived = [];
   for (var keyid in keys) {
     info("Set " + keyid + " to false in session.keyIdsReceived");
     session.keyIdsReceived[keyid] = false;
   }
   return function(ev) {
     var session = ev.target;
     session.gotKeysChanged = true;
     session.getUsableKeyIds().then(function(keyIds) {
       for (var k = 0; k < keyIds.length; k++) {
         var kid = Base64ToHex(window.btoa(ArrayBufferToString(keyIds[k])));
-        ok(kid in session.keyIdsReceived, token + " session.keyIdsReceived contained " + kid + " as expected.");
+        ok(kid in session.keyIdsReceived, "session.keyIdsReceived contained " + kid + " as expected.");
         session.keyIdsReceived[kid] = true;
       }
     }, bail("Failed to get keyIds"));
   }
 }
 
 function startTest(test, token)
 {
-  manager.started(token);
-
-  var sessions = [];
+  manager.started(test._token);
 
-  var v = SetupEME(test, token,
-    {
-      onsessioncreated: function(session) {
-        sessions.push(session);
-        session.addEventListener("keyschange", KeysChangeFunc(session, test.keys, token), false);
-      }
-    }
-  );
-
+  var v = document.createElement("video");
   var gotEncrypted = false;
   var gotPlaying = false;
 
   v.addEventListener("encrypted", function(ev) {
+    gotEncrypted = true;
+
+    info(token + " got encrypted event");
     ok(MediaKeys.isTypeSupported(KEYSYSTEM_TYPE, ev.initDataType, test.type),
        token + " MediaKeys should support this keysystem");
-    gotEncrypted = true;
+
+    MediaKeys.create(KEYSYSTEM_TYPE).then(function(mediaKeys) {
+      info(token + " created MediaKeys object ok");
+      mediaKeys.sessions = [];
+      return v.setMediaKeys(mediaKeys);
+    }, bail("failed to create MediaKeys object")).then(function() {
+      info(token + " set MediaKeys on <video> element ok");
+
+      ok(MediaKeys.isTypeSupported(KEYSYSTEM_TYPE, ev.initDataType, test.type),
+         "MediaKeys should still support keysystem after CDM created...");
+
+      var session = v.mediaKeys.createSession(test.sessionType);
+      v.mediaKeys.sessions.push(session);
+      session.addEventListener("keyschange", KeysChangeFunc(session, test.keys), false);
+      session.addEventListener("message", UpdateSessionFunc(test));
+      session.generateRequest(ev.initDataType, ev.initData).then(function() {
+      }, bail(token + " Failed to initialise MediaKeySession"));
+
+    }, bail(token + " Failed to set MediaKeys on <video> element"));
   });
 
   v.addEventListener("playing", function () { gotPlaying = true; });
 
   v.addEventListener("ended", function(ev) {
     ok(true, token + " got ended event");
-    manager.finished(token);
+    manager.finished(test._token);
 
     ok(gotEncrypted, token + " encrypted event should have fired");
     ok(gotPlaying, token + " playing event should have fired");
 
     ok(Math.abs(test.duration - v.duration) < 0.1,
        token + " Duration of video should be corrrect");
     ok(Math.abs(test.duration - v.currentTime) < 0.1,
        token + " Current time should be same as duration");
-
     // Verify all sessions had all keys went sent the to the CDM usable, and thus
     // that we received keyschange event(s).
+    var sessions = v.mediaKeys.sessions;
     is(sessions.length, 1, "should have 1 session");
     for (var i = 0; i < sessions.length; i++) {
       var session = sessions[i];
-      ok(session.gotKeysChanged, token + " should have received at least one keychange event");
+      ok(session.gotKeysChanged, "Should have received at least one keychange event");
       for (var kid in session.keyIdsReceived) {
-        ok(session.keyIdsReceived[kid], token + " key with id " + kid + " was usable as expected");
+        ok(session.keyIdsReceived[kid], "key with id " + kid + " was usable as expected");
       }
     }
-
-   });
+  });
 
   v.addEventListener("error", bail(token + " got error event"));
 
-  LoadTest(test, v).then(function(){v.play();}, bail(token + " failed to load"));
+  PlayTest(test, v);
 }
 
 function testIsTypeSupported()
 {
   var t = MediaKeys.isTypeSupported;
   const clearkey = "org.w3.clearkey";
   ok(!t("bogus", "bogon", "video/bogus"), "Invalid type.");
   ok(t(clearkey), "ClearKey supported.");
--- a/dom/media/test/test_reset_src.html
+++ b/dom/media/test/test_reset_src.html
@@ -9,78 +9,87 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="manifest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank"
 href="https://bugzilla.mozilla.org/show_bug.cgi?id=804875">Mozilla Bug 804875</a>
 
-<video style="border: 4px solid red" controls></video>
 <canvas></canvas>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
+var manager = new MediaTestManager;
+
+function finish(v) {
+  removeNodeAndSource(v);
+  manager.finished(v.token);
+}
+
 function onLoadedMetadata_Audio(e) {
   var t = e.target;
-  is(t.videoHeight, 0, "videoHeight should be zero when there is no video.");
-  is(t.videoWidth, 0, "videoWidth should be zero when there is no video.");
-  is(t.mozPaintedFrames, 0, "mozPaintedFrames should be zero when there is no video.");
-  is(t.mozFrameDelay, 0, "mozFrameDelay should be zero when there is no video.");
+  is(t.videoHeight, 0, t.name + ": videoHeight should be zero when there is no video.");
+  is(t.videoWidth, 0, t.name + ": videoWidth should be zero when there is no video.");
+  is(t.mozPaintedFrames, 0, t.name + ": mozPaintedFrames should be zero when there is no video.");
+  is(t.mozFrameDelay, 0, t.name + ": mozFrameDelay should be zero when there is no video.");
   var c = document.getElementsByTagName("canvas")[0].getContext("2d");
   try {
     c.drawImage(t, 0, 0, t.videoHeight, t.videoWidth);
   } catch (e) {
-    ok(true, "Trying to draw to a canvas should throw, since we don't have a frame anymore");
-    SimpleTest.finish();
+    ok(true, t.name + ": Trying to draw to a canvas should throw, since we don't have a frame anymore");
+    finish(t);
     return;
   }
-  ok(false, "We should not succeed to draw a video frame on the canvas.");
+  ok(false, t.name + ": We should not succeed to draw a video frame on the canvas.");
 }
 
 function onTimeUpdate_Video(e) {
   var t = e.target;
   if (t.currentTime < t.duration / 4) {
     return;
   }
   t.removeEventListener("timeupdate", onTimeUpdate_Video);
-  ok(t.mozPaintedFrames > 0, "mozPaintedFrames should be positive, is " + t.mozPaintedFrames + ".");
-  ok(t.mozFrameDelay >= 0, "mozFrameDelay should be positive or zero, is " + t.mozFrameDelay + ".");
+  ok(t.mozPaintedFrames > 0, t.name + ": mozPaintedFrames should be positive, is " + t.mozPaintedFrames + ".");
+  ok(t.mozFrameDelay >= 0, t.name + ": mozFrameDelay should be positive or zero, is " + t.mozFrameDelay + ".");
 
-  if (v._firstTime) {
+  if (t._firstTime) {
     t.src = t.src;
-    v._firstTime = false;
+    t._firstTime = false;
   } else {
     var source = getPlayableAudio(gPlayTests);
     if (!source) {
       todo("No audio file available.")
-      SimpleTest.finish();
+      finish(t);
     } else {
       t.removeEventListener("loadedmetadata", onLoadedMetadata_Video);
       t.addEventListener("loadedmetadata", onLoadedMetadata_Audio);
       t.src = source.name;
     }
   }
 }
 
 function onLoadedMetadata_Video(e) {
   var t = e.target;
-  ok(t.videoHeight != 0, "We should have a videoHeight.");
-  ok(t.videoWidth != 0, "We should have a videoWidth.");
+  isnot(t.videoHeight, 0, t.name + ": We should have a videoHeight.");
+  isnot(t.videoWidth, 0, t.name + ": We should have a videoWidth.");
   t.addEventListener("timeupdate", onTimeUpdate_Video);
   t.play();
 }
 
-var v = document.getElementsByTagName("video")[0];
-v._firstTime = true;
-var source = getPlayableVideo(gPlayTests);
-if (!source) {
-  todo("No video file available.");
-} else {
+function startTest(test, token) {
+  var v = document.createElement('video');
+  document.body.appendChild(v);
+  v.preload = "metadata";
+  v._firstTime = true;
   v.addEventListener("loadedmetadata", onLoadedMetadata_Video);
-  v.src = source.name;
-  SimpleTest.waitForExplicitFinish();
+  v.src = test.name;
+  v.token = token;
+  v.name = test.name;
+  manager.started(token);
 }
+
+manager.runTests(getPlayableVideos(gSmallTests.concat(gSeekTests)), startTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -280,22 +280,16 @@ AudioContext::CreateAnalyser()
 already_AddRefed<MediaElementAudioSourceNode>
 AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement,
                                        ErrorResult& aRv)
 {
   if (mIsOffline) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
-#ifdef MOZ_EME
-  if (aMediaElement.ContainsRestrictedContent()) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-    return nullptr;
-  }
-#endif
   nsRefPtr<DOMMediaStream> stream = aMediaElement.MozCaptureStream(aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   nsRefPtr<MediaElementAudioSourceNode> mediaElementAudioSourceNode =
     new MediaElementAudioSourceNode(this, stream);
   return mediaElementAudioSourceNode.forget();
 }
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -88,16 +88,17 @@ DIRS += [
     'ipc',
     'identity',
     'workers',
     'camera',
     'audiochannel',
     'promise',
     'smil',
     'telephony',
+    'tv',
     'voicemail',
     'inputmethod',
     'webidl',
     'xbl',
     'xml',
     'xslt',
     'xul',
     'resourcestats',
--- a/dom/network/NetUtils.cpp
+++ b/dom/network/NetUtils.cpp
@@ -23,16 +23,18 @@ InitNetUtilsLib()
 
 static void*
 GetNetUtilsLibHandle()
 {
   PR_CallOnce(&sInitNetUtilsLib, InitNetUtilsLib);
   return sNetUtilsLib;
 }
 
+mozilla::Mutex NetUtils::sIfcMutex("NetUtils::sIfcMutex");
+
 // static
 void*
 NetUtils::GetSharedLibrary()
 {
   void* netLib = GetNetUtilsLibHandle();
   if (!netLib) {
     NS_WARNING("No /system/lib/libnetutils.so");
   }
@@ -57,94 +59,93 @@ DEFINE_DLFUNC(ifc_reset_connections, int
 DEFINE_DLFUNC(ifc_set_default_route, int32_t, const char*, in_addr_t)
 DEFINE_DLFUNC(ifc_add_route, int32_t, const char*, const char*, uint32_t, const char*)
 DEFINE_DLFUNC(ifc_remove_route, int32_t, const char*, const char*, uint32_t, const char*)
 DEFINE_DLFUNC(ifc_remove_host_routes, int32_t, const char*)
 DEFINE_DLFUNC(ifc_remove_default_route, int32_t, const char*)
 DEFINE_DLFUNC(dhcp_stop, int32_t, const char*)
 
 NetUtils::NetUtils()
-  : mIfcMutex("NetUtils::mIfcMutex")
 {
 }
 
 int32_t NetUtils::do_ifc_enable(const char *ifname)
 {
   USE_DLFUNC(ifc_enable)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_enable(ifname);
 }
 
 int32_t NetUtils::do_ifc_disable(const char *ifname)
 {
   USE_DLFUNC(ifc_disable)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_disable(ifname);
 }
 
 int32_t NetUtils::do_ifc_configure(const char *ifname,
                                        in_addr_t address,
                                        uint32_t prefixLength,
                                        in_addr_t gateway,
                                        in_addr_t dns1,
                                        in_addr_t dns2)
 {
   USE_DLFUNC(ifc_configure)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   int32_t ret = ifc_configure(ifname, address, prefixLength, gateway, dns1, dns2);
   return ret;
 }
 
 int32_t NetUtils::do_ifc_reset_connections(const char *ifname,
                                                const int32_t resetMask)
 {
   USE_DLFUNC(ifc_reset_connections)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_reset_connections(ifname, resetMask);
 }
 
 int32_t NetUtils::do_ifc_set_default_route(const char *ifname,
                                            in_addr_t gateway)
 {
   USE_DLFUNC(ifc_set_default_route)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_set_default_route(ifname, gateway);
 }
 
 int32_t NetUtils::do_ifc_add_route(const char *ifname,
                                    const char *dst,
                                    uint32_t prefixLength,
                                    const char *gateway)
 {
   USE_DLFUNC(ifc_add_route)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_add_route(ifname, dst, prefixLength, gateway);
 }
 
 int32_t NetUtils::do_ifc_remove_route(const char *ifname,
                                       const char *dst,
                                       uint32_t prefixLength,
                                       const char *gateway)
 {
   USE_DLFUNC(ifc_remove_route)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_remove_route(ifname, dst, prefixLength, gateway);
 }
 
 int32_t NetUtils::do_ifc_remove_host_routes(const char *ifname)
 {
   USE_DLFUNC(ifc_remove_host_routes)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_remove_host_routes(ifname);
 }
 
 int32_t NetUtils::do_ifc_remove_default_route(const char *ifname)
 {
   USE_DLFUNC(ifc_remove_default_route)
-  mozilla::MutexAutoLock lock(mIfcMutex);
+  mozilla::MutexAutoLock lock(sIfcMutex);
   return ifc_remove_default_route(ifname);
 }
 
 int32_t NetUtils::do_dhcp_stop(const char *ifname)
 {
   USE_DLFUNC(dhcp_stop)
   return dhcp_stop(ifname);
 }
--- a/dom/network/NetUtils.h
+++ b/dom/network/NetUtils.h
@@ -56,17 +56,17 @@ public:
                              char *dns2,
                              char *server,
                              uint32_t  *lease,
                              char* vendorinfo);
 
   static int32_t SdkVersion();
 
 private:
-  mozilla::Mutex mIfcMutex;
+  static mozilla::Mutex sIfcMutex;
 };
 
 // Defines a function type with the right arguments and return type.
 #define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args);
 
 // Set up a dlsymed function ready to use.
 #define USE_DLFUNC(name)                                                      \
   FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name);            \
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -403,22 +403,29 @@ NfcContentHelper.prototype = {
         if (result.errorMsg) {
           this.fireRequestError(atob(result.requestId), result.errorMsg);
         } else {
           this.fireRequestSuccess(atob(result.requestId), result);
         }
         break;
       case "NFC:DOMEvent":
         switch (result.event) {
-          case NFC.NFC_PEER_EVENT_READY:
+          case NFC.PEER_EVENT_READY:
             this.eventTarget.notifyPeerReady(result.sessionToken);
             break;
-          case NFC.NFC_PEER_EVENT_LOST:
+          case NFC.PEER_EVENT_LOST:
             this.eventTarget.notifyPeerLost(result.sessionToken);
             break;
+          case NFC.TAG_EVENT_FOUND:
+            let event = new NfcTagEvent(result.techList);
+            this.eventTarget.notifyTagFound(result.sessionToken, event, result.records);
+            break;
+          case NFC.TAG_EVENT_LOST:
+            this.eventTarget.notifyTagLost(result.sessionToken);
+            break;
         }
         break;
     }
   },
 
   handle: function handle(name, result) {
     switch (name) {
       case NFC.SETTING_NFC_DEBUG:
@@ -457,11 +464,20 @@ NfcContentHelper.prototype = {
   handleCheckP2PRegistrationResponse: function handleCheckP2PRegistrationResponse(result) {
     // Privilaged status API. Always fire success to avoid using exposed props.
     // The receiver must check the boolean mapped status code to handle.
     let requestId = atob(result.requestId);
     this.fireRequestSuccess(requestId, !result.errorMsg);
   },
 };
 
+function NfcTagEvent(techList) {
+  this.techList = techList;
+}
+NfcTagEvent.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcTagEvent]),
+
+  techList: null
+};
+
 if (NFC_ENABLED) {
   this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NfcContentHelper]);
 }
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -242,29 +242,44 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       let isValid = (sessionToken != null) && (target != null);
       if (!isValid) {
         debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
         return;
       }
 
       // Remember the target that receives onpeerready.
       this.currentPeer = target;
-      this.notifyDOMEvent(target, {event: NFC.NFC_PEER_EVENT_READY,
+      this.notifyDOMEvent(target, {event: NFC.PEER_EVENT_READY,
                                    sessionToken: sessionToken});
     },
 
+    onTagFound: function onTagFound(message) {
+      message.event = NFC.TAG_EVENT_FOUND;
+      for (let target of this.eventTargets) {
+        this.notifyDOMEvent(target, message);
+      }
+      delete message.event;
+    },
+
+    onTagLost: function onTagLost(sessionToken) {
+      for (let target of this.eventTargets) {
+        this.notifyDOMEvent(target, {event: NFC.TAG_EVENT_LOST,
+                                     sessionToken: sessionToken});
+      }
+    },
+
     onPeerLost: function onPeerLost(sessionToken) {
       if (!this.currentPeer) {
         // The target is already killed.
         return;
       }
 
       // For peerlost, the message is delievered to the target which
       // onpeerready has been called before.
-      this.notifyDOMEvent(this.currentPeer, {event: NFC.NFC_PEER_EVENT_LOST,
+      this.notifyDOMEvent(this.currentPeer, {event: NFC.PEER_EVENT_LOST,
                                              sessionToken: sessionToken});
       this.currentPeer = null;
     },
 
     /**
      * nsIMessageListener interface methods.
      */
 
@@ -521,27 +536,34 @@ Nfc.prototype = {
         break;
       case "TechDiscoveredNotification":
         message.type = "techDiscovered";
         // Update the upper layers with a session token (alias)
         message.sessionToken =
           SessionHelper.registerSession(message.sessionId, message.techList);
 
         // Do not expose the actual session to the content
+        let sessionId = message.sessionId;
         delete message.sessionId;
 
+        if (!SessionHelper.isP2PSession(sessionId)) {
+          gMessageManager.onTagFound(message);
+        }
+
         gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
         break;
       case "TechLostNotification":
         message.type = "techLost";
 
         // Update the upper layers with a session token (alias)
         message.sessionToken = SessionHelper.getToken(message.sessionId);
         if (SessionHelper.isP2PSession(message.sessionId)) {
           gMessageManager.onPeerLost(message.sessionToken);
+        } else {
+          gMessageManager.onTagLost(message.sessionToken);
         }
 
         SessionHelper.unregisterSession(message.sessionId);
         // Do not expose the actual session to the content
         delete message.sessionId;
 
         gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
         break;
--- a/dom/nfc/gonk/nfc_consts.js
+++ b/dom/nfc/gonk/nfc_consts.js
@@ -81,13 +81,15 @@ this.NFC_POWER_LEVEL_DISABLED       = 0;
 this.NFC_POWER_LEVEL_LOW            = 1;
 this.NFC_POWER_LEVEL_ENABLED        = 2;
 
 this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
 this.TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
 
 this.SETTING_NFC_DEBUG = "nfc.debugging.enabled";
 
-this.NFC_PEER_EVENT_READY = 0x01;
-this.NFC_PEER_EVENT_LOST  = 0x02;
+this.PEER_EVENT_READY = 0x01;
+this.PEER_EVENT_LOST  = 0x02;
+this.TAG_EVENT_FOUND = 0x03;
+this.TAG_EVENT_LOST  = 0x04;
 
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -2,34 +2,62 @@
  * 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 "nsIDOMDOMRequest.idl"
 
 interface nsIVariant;
 
-[scriptable, uuid(e81cc1ac-6f0b-4581-a9fb-7ee47ed0158e)]
+[scriptable, uuid(44eb02f4-6eba-4c12-8ed1-c142513776c9)]
+interface nsINfcTagEvent : nsISupports
+{
+  readonly attribute nsIVariant techList;
+};
+
+[scriptable, uuid(8d77d653-f76a-4a21-ae3f-c4cc6074d8ec)]
 interface nsINfcDOMEventTarget : nsISupports
 {
   /**
+   * Callback function used to notify tagfound.
+   *
+   * @param sessionToken
+   *        SessionToken received from parent process
+   * @param event
+   *        nsINfcTagFoundEvent received from parent process.
+   * @param ndefRecords
+   *        NDEF records pre-read during tag-discovered.
+   */
+  void notifyTagFound(in DOMString sessionToken,
+                      in nsINfcTagEvent event,
+                      in nsIVariant ndefRecords);
+
+  /**
+   * Callback function used to notify taglost.
+   *
+   * @param sessionToken
+   *        SessionToken received from parent process
+   */
+  void notifyTagLost(in DOMString sessionToken);
+
+  /**
    * Callback function used to notify peerready.
    *
    * @param sessionToken
-   *        SessionToken received from Chrome process
+   *        SessionToken received from parent process
    */
-   void notifyPeerReady(in DOMString sessionToken);
+  void notifyPeerReady(in DOMString sessionToken);
 
   /**
    * Callback function used to notify peerlost.
    *
    * @param sessionToken
-   *        SessionToken received from Chrome process
+   *        SessionToken received from parent process
    */
-   void notifyPeerLost(in DOMString sessionToken);
+  void notifyPeerLost(in DOMString sessionToken);
 };
 
 [scriptable, uuid(cb9c934d-a7fa-422b-bcc1-4ac39741e6ec)]
 interface nsINfcContentHelper : nsISupports
 {
   const long NFC_EVENT_PEER_READY = 0x01;
   const long NFC_EVENT_PEER_LOST  = 0x02;
 
@@ -39,104 +67,104 @@ interface nsINfcContentHelper : nsISuppo
 
   nsIDOMDOMRequest readNDEF(in nsIDOMWindow window, in DOMString sessionToken);
   nsIDOMDOMRequest writeNDEF(in nsIDOMWindow window, in nsIVariant records, in DOMString sessionToken);
   nsIDOMDOMRequest makeReadOnlyNDEF(in nsIDOMWindow window, in DOMString sessionToken);
 
   nsIDOMDOMRequest connect(in nsIDOMWindow window, in unsigned long techType, in DOMString sessionToken);
   nsIDOMDOMRequest close(in nsIDOMWindow window, in DOMString sessionToken);
 
- /**
-  * Initiate Send file operation
-  *
-  * @param window
-  *        Current window
-  *
-  * @param blob
-  *        Raw data of the file to be sent. This object represents a file-like
-  *        (nsIDOMFile) object of immutable, raw data. The blob data needs
-  *        to be 'object wrapped' before calling this interface.
-  *
-  * @param sessionToken
-  *        Current token
-  *
-  * Returns DOMRequest, if initiation of send file operation is successful
-  * then 'onsuccess' is called else 'onerror'
-  */
+  /**
+   * Initiate send file operation.
+   *
+   * @param window
+   *        Current window
+   *
+   * @param blob
+   *        Raw data of the file to be sent. This object represents a file-like
+   *        (nsIDOMFile) object of immutable, raw data. The blob data needs
+   *        to be 'object wrapped' before calling this interface.
+   *
+   * @param sessionToken
+   *        Current token
+   *
+   * Returns DOMRequest, if initiation of send file operation is successful
+   * then 'onsuccess' is called else 'onerror'
+   */
   nsIDOMDOMRequest sendFile(in nsIDOMWindow window,
                             in jsval blob,
                             in DOMString sessionToken);
 
   /**
    * Register the event target.
    *
    * @param target  An instance of the nsINfcDOMEventTarget.
    */
   void registerEventTarget(in nsINfcDOMEventTarget target);
 
- /**
-  * Register the given application id with Chrome process
-  *
-  * @param window
-  *        Current window
-  *
-  * @param appId
-  *        Application ID to be registered
-  */
+  /**
+   * Register the given application id with parent process
+   *
+   * @param window
+   *        Current window
+   *
+   * @param appId
+   *        Application ID to be registered
+   */
   void registerTargetForPeerReady(in nsIDOMWindow window,
                                   in unsigned long appId);
 
- /**
-  * Unregister the given application id with Chrome process
-  *
-  * @param window
-  *        Current window
-  *
-  * @param appId
-  *        Application ID to be registered
-  */
+  /**
+   * Unregister the given application id with parent process
+   *
+   * @param window
+   *        Current window
+   *
+   * @param appId
+   *        Application ID to be registered
+   */
   void unregisterTargetForPeerReady(in nsIDOMWindow window,
                                     in unsigned long appId);
 
- /**
-  * Checks if the given application's id is a registered peer target (with the Chrome process)
-  *
-  * @param window
-  *        Current window
-  *
-  * @param appId
-  *        Application ID to be updated with Chrome process
-  *
-  * Returns DOMRequest, if appId is registered then 'onsuccess' is called else 'onerror'
-  */
+  /**
+   * Checks if the given application's id is a registered peer target (with the parent process)
+   *
+   * @param window
+   *        Current window
+   *
+   * @param appId
+   *        Application ID to be updated with parent process
+   *
+   * Returns DOMRequest, if appId is registered then 'onsuccess' is called else 'onerror'
+   */
   nsIDOMDOMRequest checkP2PRegistration(in nsIDOMWindow window, in unsigned long appId);
 
- /**
-  * Notify the Chrome process that user has accepted to share nfc message on P2P UI
-  *
-  * @param window
-  *        Current window
-  *
-  * @param appId
-  *        Application ID that is capable of handling NFC_EVENT_PEER_READY event
-  */
+  /**
+   * Notify the parent process that user has accepted to share nfc message on P2P UI
+   *
+   * @param window
+   *        Current window
+   *
+   * @param appId
+   *        Application ID that is capable of handling NFC_EVENT_PEER_READY event
+   */
   void notifyUserAcceptedP2P(in nsIDOMWindow window, in unsigned long appId);
 
- /**
-  * Notify the status of sendFile operation to Chrome process
-  *
-  * @param window
-  *        Current window
-  *
-  * @param status
-  *        Status of sendFile operation
-  *
-  * @param requestId
-  *        Request ID of SendFile DOM Request
-  */
+  /**
+   * Notify the status of sendFile operation to parent process
+   *
+   * @param window
+   *        Current window
+   *
+   * @param status
+   *        Status of sendFile operation
+   *
+   * @param requestId
+   *        Request ID of SendFile DOM Request
+   */
   void notifySendFileStatus(in nsIDOMWindow window,
                             in octet status,
                             in DOMString requestId);
 
   /**
    * Power on the NFC hardware and start polling for NFC tags or devices.
    */
   nsIDOMDOMRequest startPoll(in nsIDOMWindow window);
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -120,16 +120,21 @@ function mozNfc() {
 mozNfc.prototype = {
   _nfcContentHelper: null,
   _window: null,
   nfcObject: null,
 
   init: function init(aWindow) {
     debug("mozNfc init called");
     this._window = aWindow;
+    this.defineEventHandlerGetterSetter("ontagfound");
+    this.defineEventHandlerGetterSetter("ontaglost");
+    this.defineEventHandlerGetterSetter("onpeerready");
+    this.defineEventHandlerGetterSetter("onpeerlost");
+
     if (this._nfcContentHelper) {
       this._nfcContentHelper.init(aWindow);
     }
   },
 
   // Only apps which have nfc-manager permission can call the following interfaces
   // 'checkP2PRegistration' , 'notifyUserAcceptedP2P' , 'notifySendFileStatus',
   // 'startPoll', 'stopPoll', and 'powerOff'.
@@ -157,54 +162,39 @@ mozNfc.prototype = {
   stopPoll: function stopPoll() {
     return this._nfcContentHelper.stopPoll(this._window);
   },
 
   powerOff: function powerOff() {
     return this._nfcContentHelper.powerOff(this._window);
   },
 
-  getNFCTag: function getNFCTag(sessionToken) {
-    let obj = new MozNFCTag(this._window, sessionToken);
-    if (this._nfcContentHelper.checkSessionToken(sessionToken)) {
-      return this._window.MozNFCTag._create(this._window, obj);
-    }
-    return null;
-  },
-
   getNFCPeer: function getNFCPeer(sessionToken) {
     if (!sessionToken || !this._nfcContentHelper.checkSessionToken(sessionToken)) {
       return null;
     }
 
     if (!this.nfcObject || this.nfcObject.session != sessionToken) {
       let obj = new MozNFCPeer(this._window, sessionToken);
       this.nfcObject = obj;
       this.nfcObject.contentObject = this._window.MozNFCPeer._create(this._window, obj);
     }
 
     return this.nfcObject.contentObject;
   },
 
-  // get/set onpeerready
-  get onpeerready() {
-    return this.__DOM_IMPL__.getEventHandler("onpeerready");
-  },
-
-  set onpeerready(handler) {
-    this.__DOM_IMPL__.setEventHandler("onpeerready", handler);
-  },
-
-  // get/set onpeerlost
-  get onpeerlost() {
-    return this.__DOM_IMPL__.getEventHandler("onpeerlost");
-  },
-
-  set onpeerlost(handler) {
-    this.__DOM_IMPL__.setEventHandler("onpeerlost", handler);
+  defineEventHandlerGetterSetter: function defineEventHandlerGetterSetter(name) {
+    Object.defineProperty(this, name, {
+      get: function get() {
+        return this.__DOM_IMPL__.getEventHandler(name);
+      },
+      set: function set(handler) {
+        this.__DOM_IMPL__.setEventHandler(name, handler);
+      }
+    });
   },
 
   eventListenerWasAdded: function(eventType) {
     if (eventType !== "peerready") {
       return;
     }
 
     let appId = this._window.document.nodePrincipal.appId;
@@ -215,16 +205,64 @@ mozNfc.prototype = {
     if (eventType !== "peerready") {
       return;
     }
 
     let appId = this._window.document.nodePrincipal.appId;
     this._nfcContentHelper.unregisterTargetForPeerReady(this._window, appId);
   },
 
+  notifyTagFound: function notifyTagFound(sessionToken, event, records) {
+    if (this.hasDeadWrapper()) {
+      dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
+      return;
+    }
+
+    if (!this.checkPermissions(["nfc-read", "nfc-write"])) {
+      return;
+    }
+
+    let tag = new MozNFCTag(this._window, sessionToken);
+    let tagContentObj = this._window.MozNFCTag._create(this._window, tag);
+
+    let length = records ? records.length : 0;
+    let ndefRecords = records ? [] : null;
+    for (let i = 0; i < length; i++) {
+      let record = records[i];
+      ndefRecords.push(new this._window.MozNDEFRecord({tnf: record.tnf,
+                                                       type: record.type,
+                                                       id: record.id,
+                                                       payload: record.payload}));
+    }
+
+    let eventData = {
+      "tag": tagContentObj,
+      "ndefRecords": ndefRecords
+    };
+
+    debug("fire ontagfound " + sessionToken);
+    let tagEvent = new this._window.MozNFCTagEvent("tagfound", eventData);
+    this.__DOM_IMPL__.dispatchEvent(tagEvent);
+  },
+
+  notifyTagLost: function notifyTagLost(sessionToken) {
+    if (this.hasDeadWrapper()) {
+      dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
+      return;
+    }
+
+    if (!this.checkPermissions(["nfc-read", "nfc-write"])) {
+      return;
+    }
+
+    debug("fire ontaglost " + sessionToken);
+    let event = new this._window.Event("taglost");
+    this.__DOM_IMPL__.dispatchEvent(event);
+  },
+
   notifyPeerReady: function notifyPeerReady(sessionToken) {
     if (this.hasDeadWrapper()) {
       dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
       return;
     }
 
     this.session = sessionToken;
 
@@ -254,16 +292,31 @@ mozNfc.prototype = {
 
     this.session = null;
 
     debug("fire onpeerlost");
     let event = new this._window.Event("peerlost");
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
+  checkPermissions: function checkPermissions(perms) {
+    let principal = this._window.document.nodePrincipal;
+    for (let perm of perms) {
+      let permValue =
+        Services.perms.testExactPermissionFromPrincipal(principal, perm);
+      if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
+        return true;
+      } else {
+        debug("doesn't have " + perm + " permission.");
+      }
+    }
+
+    return false;
+  },
+
   hasDeadWrapper: function hasDeadWrapper() {
     return Cu.isDeadWrapper(this._window) || Cu.isDeadWrapper(this.__DOM_IMPL__);
   },
 
   classID: Components.ID("{6ff2b290-2573-11e3-8224-0800200c9a66}"),
   contractID: "@mozilla.org/navigatorNfc;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer,
--- a/dom/nfc/tests/marionette/test_nfc_peer.js
+++ b/dom/nfc/tests/marionette/test_nfc_peer.js
@@ -186,34 +186,21 @@ function testPeerShouldThrow() {
 function testPeerInvalidToken() {
   log("testPeerInvalidToken");
   let peer = nfc.getNFCPeer("fakeSessionToken");
   is(peer, null, "NFCPeer should be null on wrong session token");
 
   runNextTest();
 }
 
-/**
- * Added for completeness in Bug 1042651,
- * TODO: remove once Bug 963531 lands
- */
-function testTagInvalidToken() {
-  log("testTagInvalidToken");
-  let tag = nfc.getNFCTag("fakeSessionToken");
-  is(tag, null, "NFCTag should be null on wrong session token");
-
-  runNextTest();
-}
-
 let tests = [
   testPeerReady,
   testGetNFCPeer,
   testCheckP2PRegFailure,
   testPeerLostShouldBeCalled,
   testPeerLostShouldNotBeCalled,
   testPeerShouldThrow,
-  testPeerInvalidToken,
-  testTagInvalidToken
+  testPeerInvalidToken
 ];
 
 SpecialPowers.pushPermissions(
   [{"type": "nfc-manager", "allow": true, context: document},
    {"type": "nfc-write", "allow": true, context: document}], runTests);
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -75,17 +75,18 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/PluginLibrary.h"
 using mozilla::PluginLibrary;
 
 #include "mozilla/PluginPRLibrary.h"
 using mozilla::PluginPRLibrary;
 
 #include "mozilla/plugins/PluginModuleParent.h"
-using mozilla::plugins::PluginModuleParent;
+using mozilla::plugins::PluginModuleChromeParent;
+using mozilla::plugins::PluginModuleContentParent;
 
 #ifdef MOZ_X11
 #include "mozilla/X11Util.h"
 #endif
 
 #ifdef XP_WIN
 #include <windows.h>
 #include "mozilla/WindowsVersion.h"
@@ -242,16 +243,20 @@ nsNPAPIPlugin::PluginCrashed(const nsASt
 {
   nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
   host->PluginCrashed(this, pluginDumpID, browserDumpID);
 }
 
 bool
 nsNPAPIPlugin::RunPluginOOP(const nsPluginTag *aPluginTag)
 {
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    return true;
+  }
+
 #if (MOZ_WIDGET_GTK == 3)
   // We force OOP on Linux/GTK3 because some plugins use GTK2 and both GTK
   // libraries can't be loaded in the same process.
   return true;
 #else
   if (PR_GetEnv("MOZ_DISABLE_OOP_PLUGINS")) {
     return false;
   }
@@ -383,18 +388,22 @@ nsNPAPIPlugin::RunPluginOOP(const nsPlug
 
 inline PluginLibrary*
 GetNewPluginLibrary(nsPluginTag *aPluginTag)
 {
   if (!aPluginTag) {
     return nullptr;
   }
 
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    return PluginModuleContentParent::LoadModule(aPluginTag->mId);
+  }
+
   if (nsNPAPIPlugin::RunPluginOOP(aPluginTag)) {
-    return PluginModuleParent::LoadModule(aPluginTag->mFullPath.get());
+    return PluginModuleChromeParent::LoadModule(aPluginTag->mFullPath.get(), aPluginTag->mId);
   }
   return new PluginPRLibrary(aPluginTag->mFullPath.get(), aPluginTag->mLibrary);
 }
 
 // Creates an nsNPAPIPlugin object. One nsNPAPIPlugin object exists per plugin (not instance).
 nsresult
 nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
 {
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -43,17 +43,20 @@
 #include "nsPluginLogging.h"
 #include "nsIScriptChannel.h"
 #include "nsIBlocklistService.h"
 #include "nsVersionComparator.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsICategoryManager.h"
 #include "nsPluginStreamListenerPeer.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/LoadInfo.h"
+#include "mozilla/plugins/PluginBridge.h"
+#include "mozilla/plugins/PluginTypes.h"
 #include "mozilla/Preferences.h"
 
 #include "nsEnumeratorUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMCID.h"
 #include "nsISupportsPrimitives.h"
 
 #include "nsXULAppAPI.h"
@@ -101,16 +104,17 @@
 #endif
 
 #if MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 using namespace mozilla;
 using mozilla::TimeStamp;
+using mozilla::plugins::PluginTag;
 
 // Null out a strong ref to a linked list iteratively to avoid
 // exhausting the stack (bug 486349).
 #define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_)                \
   {                                                                  \
     while (list_) {                                                  \
       type_ temp = list_->mNext_;                                    \
       list_->mNext_ = nullptr;                                        \
@@ -304,16 +308,20 @@ nsPluginHost::GetInst()
 }
 
 bool nsPluginHost::IsRunningPlugin(nsPluginTag * aPluginTag)
 {
   if (!aPluginTag || !aPluginTag->mPlugin) {
     return false;
   }
 
+  if (aPluginTag->mContentProcessRunningCount) {
+    return true;
+  }
+
   for (uint32_t i = 0; i < mInstances.Length(); i++) {
     nsNPAPIPluginInstance *instance = mInstances[i].get();
     if (instance &&
         instance->GetPlugin() == aPluginTag->mPlugin &&
         instance->IsRunning()) {
       return true;
     }
   }
@@ -1267,16 +1275,86 @@ nsresult nsPluginHost::EnsurePluginLoade
     if (NS_FAILED(rv)) {
       return rv;
     }
     aPluginTag->mPlugin = plugin;
   }
   return NS_OK;
 }
 
+nsresult
+nsPluginHost::GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+  // If plugins haven't been scanned yet, do so now
+  LoadPlugins();
+
+  nsPluginTag* pluginTag = PluginWithId(aPluginId);
+  if (pluginTag) {
+    nsresult rv = EnsurePluginLoaded(pluginTag);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    // We only get here if a content process doesn't have a PluginModuleParent
+    // for the given plugin already. Therefore, this counter is counting the
+    // number of outstanding PluginModuleParents for the plugin, excluding the
+    // one from the chrome process.
+    pluginTag->mContentProcessRunningCount++;
+    NS_ADDREF(*aPlugin = pluginTag->mPlugin);
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+class nsPluginUnloadRunnable : public nsRunnable
+{
+public:
+  explicit nsPluginUnloadRunnable(uint32_t aPluginId) : mPluginId(aPluginId) {}
+
+  NS_IMETHOD Run()
+  {
+    nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+    if (!host) {
+      return NS_OK;
+    }
+    nsPluginTag* pluginTag = host->PluginWithId(mPluginId);
+    if (!pluginTag) {
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(pluginTag->mContentProcessRunningCount > 0);
+    pluginTag->mContentProcessRunningCount--;
+
+    if (!pluginTag->mContentProcessRunningCount) {
+      if (!host->IsRunningPlugin(pluginTag)) {
+        pluginTag->TryUnloadPlugin(false);
+      }
+    }
+    return NS_OK;
+  }
+
+protected:
+  uint32_t mPluginId;
+};
+
+void
+nsPluginHost::NotifyContentModuleDestroyed(uint32_t aPluginId)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+  // This is called in response to a message from the plugin. Don't unload the
+  // plugin until the message handler is off the stack.
+  nsRefPtr<nsPluginUnloadRunnable> runnable =
+    new nsPluginUnloadRunnable(aPluginId);
+  NS_DispatchToMainThread(runnable);
+}
+
 nsresult nsPluginHost::GetPlugin(const char *aMimeType, nsNPAPIPlugin** aPlugin)
 {
   nsresult rv = NS_ERROR_FAILURE;
   *aPlugin = nullptr;
 
   if (!aMimeType)
     return NS_ERROR_ILLEGAL_VALUE;
 
@@ -1609,16 +1687,27 @@ nsPluginHost::FirstPluginWithPath(const 
   for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
     if (tag->mFullPath.Equals(path)) {
       return tag;
     }
   }
   return nullptr;
 }
 
+nsPluginTag*
+nsPluginHost::PluginWithId(uint32_t aId)
+{
+  for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
+    if (tag->mId == aId) {
+      return tag;
+    }
+  }
+  return nullptr;
+}
+
 namespace {
 
 int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& localfile)
 {
   PRTime fileModTime = 0;
 
 #if defined(XP_MACOSX)
   // On OS X the date of a bundle's "contents" (i.e. of its Info.plist file)
@@ -1695,22 +1784,42 @@ struct CompareFilesByTime
   Equals(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
   {
     return GetPluginLastModifiedTime(a) == GetPluginLastModifiedTime(b);
   }
 };
 
 } // anonymous namespace
 
+void
+nsPluginHost::AddPluginTag(nsPluginTag* aPluginTag)
+{
+  aPluginTag->mNext = mPlugins;
+  mPlugins = aPluginTag;
+
+  if (aPluginTag->IsActive()) {
+    nsAdoptingCString disableFullPage =
+      Preferences::GetCString(kPrefDisableFullPage);
+    for (uint32_t i = 0; i < aPluginTag->mMimeTypes.Length(); i++) {
+      if (!IsTypeInList(aPluginTag->mMimeTypes[i], disableFullPage)) {
+        RegisterWithCategoryManager(aPluginTag->mMimeTypes[i],
+                                    ePluginRegister);
+      }
+    }
+  }
+}
+
 typedef NS_NPAPIPLUGIN_CALLBACK(char *, NP_GETMIMEDESCRIPTION)(void);
 
 nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
                                             bool aCreatePluginList,
                                             bool *aPluginsChanged)
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
   NS_ENSURE_ARG_POINTER(aPluginsChanged);
   nsresult rv;
 
   *aPluginsChanged = false;
 
 #ifdef PLUGIN_LOGGING
   nsAutoCString dirPath;
   pluginsDir->GetNativePath(dirPath);
@@ -1890,65 +1999,32 @@ nsresult nsPluginHost::ScanPluginsDirect
     }
 
     // If we're not creating a plugin list, simply looking for changes,
     // then we're done.
     if (!aCreatePluginList) {
       return NS_OK;
     }
 
-    // Add plugin tags such that the list is ordered by modification date,
-    // newest to oldest. This is ugly, it'd be easier with just about anything
-    // other than a single-directional linked list.
-    if (mPlugins) {
-      nsPluginTag *prev = nullptr;
-      nsPluginTag *next = mPlugins;
-      while (next) {
-        if (pluginTag->mLastModifiedTime >= next->mLastModifiedTime) {
-          pluginTag->mNext = next;
-          if (prev) {
-            prev->mNext = pluginTag;
-          } else {
-            mPlugins = pluginTag;
-          }
-          break;
-        }
-        prev = next;
-        next = prev->mNext;
-        if (!next) {
-          prev->mNext = pluginTag;
-        }
-      }
-    } else {
-      mPlugins = pluginTag;
-    }
-
-    if (pluginTag->IsActive()) {
-      nsAdoptingCString disableFullPage =
-        Preferences::GetCString(kPrefDisableFullPage);
-      for (uint32_t i = 0; i < pluginTag->mMimeTypes.Length(); i++) {
-        if (!IsTypeInList(pluginTag->mMimeTypes[i], disableFullPage)) {
-          RegisterWithCategoryManager(pluginTag->mMimeTypes[i],
-                                      ePluginRegister);
-        }
-      }
-    }
+    AddPluginTag(pluginTag);
   }
 
   if (warnOutdated) {
     Preferences::SetBool("plugins.update.notifyUser", true);
   }
 
   return NS_OK;
 }
 
 nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum,
                                                 bool aCreatePluginList,
                                                 bool *aPluginsChanged)
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
     bool hasMore;
     while (NS_SUCCEEDED(dirEnum->HasMoreElements(&hasMore)) && hasMore) {
       nsCOMPtr<nsISupports> supports;
       nsresult rv = dirEnum->GetNext(getter_AddRefs(supports));
       if (NS_FAILED(rv))
         continue;
       nsCOMPtr<nsIFile> nextDir(do_QueryInterface(supports, &rv));
       if (NS_FAILED(rv))
@@ -1963,16 +2039,44 @@ nsresult nsPluginHost::ScanPluginsDirect
 
       // if changes are detected and we are not creating the list, do not proceed
       if (!aCreatePluginList && *aPluginsChanged)
         break;
     }
     return NS_OK;
 }
 
+void
+nsPluginHost::IncrementChromeEpoch()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  mPluginEpoch++;
+}
+
+uint32_t
+nsPluginHost::ChromeEpoch()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  return mPluginEpoch;
+}
+
+uint32_t
+nsPluginHost::ChromeEpochForContent()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+  return mPluginEpoch;
+}
+
+void
+nsPluginHost::SetChromeEpochForContent(uint32_t aEpoch)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+  mPluginEpoch = aEpoch;
+}
+
 nsresult nsPluginHost::LoadPlugins()
 {
 #ifdef ANDROID
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     return NS_OK;
   }
 #endif
   // do not do anything if it is already done
@@ -1985,35 +2089,92 @@ nsresult nsPluginHost::LoadPlugins()
 
   bool pluginschanged;
   nsresult rv = FindPlugins(true, &pluginschanged);
   if (NS_FAILED(rv))
     return rv;
 
   // only if plugins have changed will we notify plugin-change observers
   if (pluginschanged) {
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+      IncrementChromeEpoch();
+    }
+
     nsCOMPtr<nsIObserverService> obsService =
       mozilla::services::GetObserverService();
     if (obsService)
       obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr);
   }
 
   return NS_OK;
 }
 
+nsresult
+nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+
+  dom::ContentChild* cp = dom::ContentChild::GetSingleton();
+  nsTArray<PluginTag> plugins;
+  uint32_t parentEpoch;
+  if (!cp->SendFindPlugins(ChromeEpochForContent(), &plugins, &parentEpoch)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (parentEpoch != ChromeEpochForContent()) {
+    SetChromeEpochForContent(parentEpoch);
+    *aPluginsChanged = true;
+    if (!aCreatePluginList) {
+      return NS_OK;
+    }
+
+    for (size_t i = 0; i < plugins.Length(); i++) {
+      PluginTag& tag = plugins[i];
+
+      // Don't add the same plugin again.
+      if (PluginWithId(tag.id())) {
+        continue;
+      }
+
+      nsPluginTag *pluginTag = new nsPluginTag(tag.id(),
+                                               tag.name().get(),
+                                               tag.description().get(),
+                                               tag.filename().get(),
+                                               "", // aFullPath
+                                               tag.version().get(),
+                                               nsTArray<nsCString>(tag.mimeTypes()),
+                                               nsTArray<nsCString>(tag.mimeDescriptions()),
+                                               nsTArray<nsCString>(tag.extensions()),
+                                               tag.isJavaPlugin(),
+                                               tag.isFlashPlugin(),
+                                               tag.lastModifiedTime(),
+                                               tag.isFromExtension());
+      AddPluginTag(pluginTag);
+    }
+  }
+
+  mPluginsLoaded = true;
+  return NS_OK;
+}
+
 // if aCreatePluginList is false we will just scan for plugins
 // and see if any changes have been made to the plugins.
 // This is needed in ReloadPlugins to prevent possible recursive reloads
 nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChanged)
 {
   Telemetry::AutoTimer<Telemetry::FIND_PLUGINS> telemetry;
 
   NS_ENSURE_ARG_POINTER(aPluginsChanged);
 
   *aPluginsChanged = false;
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    return FindPluginsInContent(aCreatePluginList, aPluginsChanged);
+  }
+
   nsresult rv;
 
   // Read cached plugins info. If the profile isn't yet available then don't
   // scan for plugins
   if (ReadPluginInfo() == NS_ERROR_NOT_AVAILABLE)
     return NS_OK;
 
 #ifdef XP_WIN
@@ -2162,19 +2323,68 @@ nsresult nsPluginHost::FindPlugins(bool 
 
   // No more need for cached plugins. Clear it up.
   NS_ITERATIVE_UNREF_LIST(nsRefPtr<nsPluginTag>, mCachedPlugins, mNext);
   NS_ITERATIVE_UNREF_LIST(nsRefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
 
   return NS_OK;
 }
 
+bool
+mozilla::plugins::FindPluginsForContent(uint32_t aPluginEpoch,
+                                        nsTArray<PluginTag>* aPlugins,
+                                        uint32_t* aNewPluginEpoch)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+  nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+  host->FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
+  return true;
+}
+
+void
+nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch,
+                                    nsTArray<PluginTag>* aPlugins,
+                                    uint32_t* aNewPluginEpoch)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+  // Load plugins so that the epoch is correct.
+  LoadPlugins();
+
+  *aNewPluginEpoch = ChromeEpoch();
+  if (aPluginEpoch == ChromeEpoch()) {
+    return;
+  }
+
+  nsTArray<nsRefPtr<nsPluginTag>> plugins;
+  GetPlugins(plugins);
+
+  for (size_t i = 0; i < plugins.Length(); i++) {
+    nsRefPtr<nsPluginTag> tag = plugins[i];
+    aPlugins->AppendElement(PluginTag(tag->mId,
+                                      tag->mName,
+                                      tag->mDescription,
+                                      tag->mMimeTypes,
+                                      tag->mMimeDescriptions,
+                                      tag->mExtensions,
+                                      tag->mIsJavaPlugin,
+                                      tag->mIsFlashPlugin,
+                                      tag->mFileName,
+                                      tag->mVersion,
+                                      tag->mLastModifiedTime,
+                                      tag->IsFromExtension()));
+  }
+}
+
 nsresult
 nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag)
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
   ReadPluginInfo();
   WritePluginInfo();
   NS_ITERATIVE_UNREF_LIST(nsRefPtr<nsPluginTag>, mCachedPlugins, mNext);
   NS_ITERATIVE_UNREF_LIST(nsRefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
 
   if (!aPluginTag) {
     return NS_OK;
   }
@@ -2256,16 +2466,17 @@ nsPluginHost::RegisterWithCategoryManage
                                   true);
     }
   }
 }
 
 nsresult
 nsPluginHost::WritePluginInfo()
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   nsresult rv = NS_OK;
   nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv));
   if (NS_FAILED(rv))
     return rv;
 
   directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
                         getter_AddRefs(mPluginRegFile));
@@ -2400,16 +2611,18 @@ nsPluginHost::WritePluginInfo()
   NS_ENSURE_SUCCESS(rv, rv);
   rv = pluginReg->MoveToNative(parent, kPluginRegistryFilename);
   return rv;
 }
 
 nsresult
 nsPluginHost::ReadPluginInfo()
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
   const long PLUGIN_REG_MIMETYPES_ARRAY_SIZE = 12;
   const long PLUGIN_REG_MAX_MIMETYPES = 1000;
 
   // we need to import the legacy flags from the plugin registry once
   const bool pluginStateImported =
     Preferences::GetDefaultBool("plugin.importedState", false);
 
   nsresult rv;
@@ -3537,16 +3750,17 @@ nsPluginHost::PluginCrashed(nsNPAPIPlugi
     }
   }
 
   // Only after all instances have been invalidated is it safe to null
   // out nsPluginTag.mPlugin. The next time we try to create an
   // instance of this plugin we reload it (launch a new plugin process).
 
   crashedPluginTag->mPlugin = nullptr;
+  crashedPluginTag->mContentProcessRunningCount = 0;
 
 #ifdef XP_WIN
   CheckForDisabledWindows();
 #endif
 }
 
 nsNPAPIPluginInstance*
 nsPluginHost::FindInstance(const char *mimetype)
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -23,24 +23,26 @@
 #include "nsTArray.h"
 #include "nsTObserverArray.h"
 #include "nsITimer.h"
 #include "nsPluginTags.h"
 #include "nsPluginPlayPreviewInfo.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIIDNService.h"
 #include "nsCRT.h"
+#include "mozilla/plugins/PluginTypes.h"
 
 class nsNPAPIPlugin;
 class nsIComponentManager;
 class nsIFile;
 class nsIChannel;
 class nsPluginNativeWindow;
 class nsObjectLoadingContent;
 class nsPluginInstanceOwner;
+class nsPluginUnloadRunnable;
 class nsNPAPIPluginInstance;
 class nsNPAPIPluginStreamListener;
 class nsIPluginInstanceOwner;
 class nsIInputStream;
 class nsIStreamListener;
 
 class nsInvalidPluginTag : public nsISupports
 {
@@ -83,16 +85,19 @@ public:
   nsresult SetUpPluginInstance(const char *aMimeType,
                                nsIURI *aURL,
                                nsPluginInstanceOwner *aOwner);
   bool PluginExistsForType(const char* aMimeType);
 
   nsresult IsPluginEnabledForExtension(const char* aExtension, const char* &aMimeType);
 
   void GetPlugins(nsTArray<nsRefPtr<nsPluginTag> >& aPluginArray);
+  void FindPluginsForContent(uint32_t aPluginEpoch,
+                             nsTArray<mozilla::plugins::PluginTag>* aPlugins,
+                             uint32_t* aNewPluginEpoch);
 
   nsresult GetURL(nsISupports* pluginInst,
                   const char* url,
                   const char* target,
                   nsNPAPIPluginStreamListener* streamListener,
                   const char* altHost,
                   const char* referrer,
                   bool forceJSEnabled);
@@ -186,22 +191,26 @@ public:
   nsresult InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL,
                                      nsObjectLoadingContent *aContent,
                                      nsPluginInstanceOwner** aOwner);
 
   // Does not accept nullptr and should never fail.
   nsPluginTag* TagForPlugin(nsNPAPIPlugin* aPlugin);
 
   nsresult GetPlugin(const char *aMimeType, nsNPAPIPlugin** aPlugin);
+  nsresult GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin);
+  void NotifyContentModuleDestroyed(uint32_t aPluginId);
 
   nsresult NewPluginStreamListener(nsIURI* aURL,
                                    nsNPAPIPluginInstance* aInstance,
                                    nsIStreamListener **aStreamListener);
 
 private:
+  friend class nsPluginUnloadRunnable;
+
   nsresult
   TrySetUpPluginInstance(const char *aMimeType, nsIURI *aURL, nsPluginInstanceOwner *aOwner);
 
   nsPluginTag*
   FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& matches);
 
   // Return an nsPluginTag for this type, if any.  If aCheckEnabled is
   // true, only enabled plugins will be returned.
@@ -209,24 +218,28 @@ private:
   FindPluginForType(const char* aMimeType, bool aCheckEnabled);
 
   nsPluginTag*
   FindPluginEnabledForExtension(const char* aExtension, const char* &aMimeType);
 
   nsresult
   FindStoppedPluginForURL(nsIURI* aURL, nsIPluginInstanceOwner *aOwner);
 
+  nsresult FindPluginsInContent(bool aCreatePluginList, bool * aPluginsChanged);
+
   nsresult
   FindPlugins(bool aCreatePluginList, bool * aPluginsChanged);
 
   // Registers or unregisters the given mime type with the category manager
   // (performs no checks - see UpdateCategoryManager)
   enum nsRegisterType { ePluginRegister, ePluginUnregister };
   void RegisterWithCategoryManager(nsCString &aMimeType, nsRegisterType aType);
 
+  void AddPluginTag(nsPluginTag* aPluginTag);
+
   nsresult
   ScanPluginsDirectory(nsIFile *pluginsDir,
                        bool aCreatePluginList,
                        bool *aPluginsChanged);
 
   nsresult
   ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum,
                            bool aCreatePluginList,
@@ -250,21 +263,33 @@ private:
   // Checks to see if a tag object is in our list of live tags.
   bool IsLiveTag(nsIPluginTag* tag);
   
   // Checks our list of live tags for an equivalent tag.
   nsPluginTag* HaveSamePlugin(const nsPluginTag * aPluginTag);
     
   // Returns the first plugin at |path|
   nsPluginTag* FirstPluginWithPath(const nsCString& path);
+  nsPluginTag* PluginWithId(uint32_t aId);
 
   nsresult EnsurePrivateDirServiceProvider();
 
   void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag);
 
+  // To be used by the chrome process whenever the set of plugins changes.
+  void IncrementChromeEpoch();
+
+  // To be used by the chrome process; returns the current epoch.
+  uint32_t ChromeEpoch();
+
+  // To be used by the content process to get/set the last observed epoch value
+  // from the chrome process.
+  uint32_t ChromeEpochForContent();
+  void SetChromeEpochForContent(uint32_t aEpoch);
+
   nsRefPtr<nsPluginTag> mPlugins;
   nsRefPtr<nsPluginTag> mCachedPlugins;
   nsRefPtr<nsInvalidPluginTag> mInvalidPlugins;
   nsTArray< nsRefPtr<nsPluginPlayPreviewInfo> > mPlayPreviewMimeTypes;
   bool mPluginsLoaded;
 
   // set by pref plugin.override_internal_types
   bool mOverrideInternalTypes;
@@ -290,16 +315,22 @@ private:
   nsresult NormalizeHostname(nsCString& host);
   nsresult EnumerateSiteData(const nsACString& domain,
                              const InfallibleTArray<nsCString>& sites,
                              InfallibleTArray<nsCString>& result,
                              bool firstMatchOnly);
 
   nsWeakPtr mCurrentDocument; // weak reference, we use it to id document only
 
+  // This epoch increases each time we load the list of plugins from disk.
+  // In the chrome process, this stores the actual epoch.
+  // In the content process, this stores the last epoch value observed
+  // when reading plugins from chrome.
+  uint32_t mPluginEpoch;
+
   static nsIFile *sPluginTempDir;
 
   // We need to hold a global ptr to ourselves because we register for
   // two different CIDs for some reason...
   static nsPluginHost* sInst;
 };
 
 class MOZ_STACK_CLASS PluginDestructionGuard : protected PRCList
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -61,20 +61,24 @@ MakePrefNameForPlugin(const char* const 
 static nsCString
 GetStatePrefNameForPlugin(nsPluginTag* aTag)
 {
   return MakePrefNameForPlugin("state", aTag);
 }
 
 /* nsPluginTag */
 
+uint32_t nsPluginTag::sNextId;
+
 nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
                          int64_t aLastModifiedTime,
                          bool fromExtension)
-  : mName(aPluginInfo->fName),
+  : mId(sNextId++),
+    mContentProcessRunningCount(0),
+    mName(aPluginInfo->fName),
     mDescription(aPluginInfo->fDescription),
     mLibrary(nullptr),
     mIsJavaPlugin(false),
     mIsFlashPlugin(false),
     mFileName(aPluginInfo->fFileName),
     mFullPath(aPluginInfo->fFullPath),
     mVersion(aPluginInfo->fVersion),
     mLastModifiedTime(aLastModifiedTime),
@@ -98,17 +102,19 @@ nsPluginTag::nsPluginTag(const char* aNa
                          const char* aVersion,
                          const char* const* aMimeTypes,
                          const char* const* aMimeDescriptions,
                          const char* const* aExtensions,
                          int32_t aVariants,
                          int64_t aLastModifiedTime,
                          bool fromExtension,
                          bool aArgsAreUTF8)
-  : mName(aName),
+  : mId(sNextId++),
+    mContentProcessRunningCount(0),
+    mName(aName),
     mDescription(aDescription),
     mLibrary(nullptr),
     mIsJavaPlugin(false),
     mIsFlashPlugin(false),
     mFileName(aFileName),
     mFullPath(aFullPath),
     mVersion(aVersion),
     mLastModifiedTime(aLastModifiedTime),
@@ -119,16 +125,49 @@ nsPluginTag::nsPluginTag(const char* aNa
 {
   InitMime(aMimeTypes, aMimeDescriptions, aExtensions,
            static_cast<uint32_t>(aVariants));
   if (!aArgsAreUTF8)
     EnsureMembersAreUTF8();
   FixupVersion();
 }
 
+nsPluginTag::nsPluginTag(uint32_t aId,
+                         const char* aName,
+                         const char* aDescription,
+                         const char* aFileName,
+                         const char* aFullPath,
+                         const char* aVersion,
+                         nsTArray<nsCString> aMimeTypes,
+                         nsTArray<nsCString> aMimeDescriptions,
+                         nsTArray<nsCString> aExtensions,
+                         bool aIsJavaPlugin,
+                         bool aIsFlashPlugin,
+                         int64_t aLastModifiedTime,
+                         bool aFromExtension)
+  : mId(aId),
+    mContentProcessRunningCount(0),
+    mName(aName),
+    mDescription(aDescription),
+    mMimeTypes(aMimeTypes),
+    mMimeDescriptions(aMimeDescriptions),
+    mExtensions(aExtensions),
+    mLibrary(nullptr),
+    mIsJavaPlugin(aIsJavaPlugin),
+    mIsFlashPlugin(aIsFlashPlugin),
+    mFileName(aFileName),
+    mVersion(aVersion),
+    mLastModifiedTime(aLastModifiedTime),
+    mNiceFileName(),
+    mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
+    mCachedBlocklistStateValid(false),
+    mIsFromExtension(aFromExtension)
+{
+}
+
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
 NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -46,16 +46,29 @@ public:
               const char* aVersion,
               const char* const* aMimeTypes,
               const char* const* aMimeDescriptions,
               const char* const* aExtensions,
               int32_t aVariants,
               int64_t aLastModifiedTime,
               bool fromExtension,
               bool aArgsAreUTF8 = false);
+  nsPluginTag(uint32_t aId,
+              const char* aName,
+              const char* aDescription,
+              const char* aFileName,
+              const char* aFullPath,
+              const char* aVersion,
+              nsTArray<nsCString> aMimeTypes,
+              nsTArray<nsCString> aMimeDescriptions,
+              nsTArray<nsCString> aExtensions,
+              bool aIsJavaPlugin,
+              bool aIsFlashPlugin,
+              int64_t aLastModifiedTime,
+              bool aFromExtension);
 
   void TryUnloadPlugin(bool inShutdown);
 
   // plugin is enabled and not blocklisted
   bool IsActive();
 
   bool IsEnabled();
   void SetEnabled(bool enabled);
@@ -69,16 +82,20 @@ public:
   void ImportFlagsToPrefs(uint32_t flag);
 
   bool HasSameNameAndMimes(const nsPluginTag *aPluginTag) const;
   nsCString GetNiceFileName();
 
   bool IsFromExtension() const;
 
   nsRefPtr<nsPluginTag> mNext;
+  uint32_t      mId;
+
+  // Number of PluginModuleParents living in all content processes.
+  size_t        mContentProcessRunningCount;
   nsCString     mName; // UTF-8
   nsCString     mDescription; // UTF-8
   nsTArray<nsCString> mMimeTypes; // UTF-8
   nsTArray<nsCString> mMimeDescriptions; // UTF-8
   nsTArray<nsCString> mExtensions; // UTF-8
   PRLibrary     *mLibrary;
   nsRefPtr<nsNPAPIPlugin> mPlugin;
   bool          mIsJavaPlugin;
@@ -101,11 +118,13 @@ private:
   bool          mIsFromExtension;
 
   void InitMime(const char* const* aMimeTypes,
                 const char* const* aMimeDescriptions,
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
+
+  static uint32_t sNextId;
 };
 
 #endif // nsPluginTags_h_
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -1,28 +1,31 @@
 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* 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 PPluginInstance;
 include protocol PPluginScriptableObject;
 include protocol PCrashReporter;
+include protocol PContent;
 
 using NPError from "npapi.h";
 using NPNVariable from "npapi.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h";
 using struct nsID from "nsID.h";
 
 namespace mozilla {
 namespace plugins {
 
 intr protocol PPluginModule
 {
+  bridges PContent, PPluginModule;
+
   manages PPluginInstance;
   manages PCrashReporter;
 
 both:
   // Window-specific message which instructs the interrupt mechanism to enter
   // a nested event loop for the current interrupt call.
   async ProcessNativeEventsInInterruptCall();
 
@@ -102,16 +105,20 @@ parent:
 
   // OS X Specific calls to allow the plugin to manage the cursor.
   async SetCursor(NSCursorInfo cursorInfo);
   async ShowCursor(bool show);
   async PushCursor(NSCursorInfo cursorInfo);
   async PopCursor();
   sync GetNativeCursorsSupported() returns (bool supported);
 
-  sync NPN_SetException(nullable PPluginScriptableObject actor,
-                        nsCString message);
+  sync NPN_SetException(nsCString message);
 
   async NPN_ReloadPlugins(bool aReloadPages);
+
+  // Notifies the chrome process that a PluginModuleChild linked to a content
+  // process was destroyed. The chrome process may choose to asynchronously shut
+  // down the plugin process in response.
+  async NotifyContentModuleDestroyed();
 };
 
 } // namespace plugins
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et :
+ * 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_plugins_PluginBridge_h
+#define mozilla_plugins_PluginBridge_h
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+}
+
+namespace plugins {
+
+bool
+SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent);
+
+bool
+FindPluginsForContent(uint32_t aPluginEpoch,
+                      nsTArray<PluginTag>* aPlugins,
+                      uint32_t* aNewPluginEpoch);
+
+} // namespace plugins
+} // namespace mozilla
+
+#endif // mozilla_plugins_PluginBridge_h
--- a/dom/plugins/ipc/PluginHangUIParent.cpp
+++ b/dom/plugins/ipc/PluginHangUIParent.cpp
@@ -64,17 +64,17 @@ private:
   uint32_t mResponseTimeMs;
   uint32_t mTimeoutMs;
 };
 } // anonymous namespace
 
 namespace mozilla {
 namespace plugins {
 
-PluginHangUIParent::PluginHangUIParent(PluginModuleParent* aModule,
+PluginHangUIParent::PluginHangUIParent(PluginModuleChromeParent* aModule,
                                        const int32_t aHangUITimeoutPref,
                                        const int32_t aChildTimeoutPref)
   : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"),
     mModule(aModule),
     mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U),
     mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U),
     mMainThreadMessageLoop(MessageLoop::current()),
     mIsShowing(false),
--- a/dom/plugins/ipc/PluginHangUIParent.h
+++ b/dom/plugins/ipc/PluginHangUIParent.h
@@ -15,32 +15,32 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/plugins/PluginMessageUtils.h"
 
 #include "MiniShmParent.h"
 
 namespace mozilla {
 namespace plugins {
 
-class PluginModuleParent;
+class PluginModuleChromeParent;
 
 /**
  * This class is responsible for launching and communicating with the 
  * plugin-hang-ui process.
  *
  * NOTE: PluginHangUIParent is *not* an IPDL actor! In this case, "Parent" 
  *       is describing the fact that firefox is the parent process to the 
  *       plugin-hang-ui process, which is the PluginHangUIChild.
  *       PluginHangUIParent and PluginHangUIChild are a matched pair.
  * @see PluginHangUIChild
  */
 class PluginHangUIParent : public MiniShmObserver
 {
 public:
-  PluginHangUIParent(PluginModuleParent* aModule,
+  PluginHangUIParent(PluginModuleChromeParent* aModule,
                      const int32_t aHangUITimeoutPref,
                      const int32_t aChildTimeoutPref);
   virtual ~PluginHangUIParent();
 
   /**
    * Spawn the plugin-hang-ui.exe child process and terminate the given 
    * plugin container process if the user elects to stop the hung plugin.
    *
@@ -130,17 +130,17 @@ private:
   bool
   UnwatchHangUIChildProcess(bool aWait);
 
   static
   VOID CALLBACK SOnHangUIProcessExit(PVOID aContext, BOOLEAN aIsTimer);
 
 private:
   Mutex mMutex;
-  PluginModuleParent* mModule;
+  PluginModuleChromeParent* mModule;
   const uint32_t mTimeoutPrefMs;
   const uint32_t mIPCTimeoutMs;
   MessageLoop* mMainThreadMessageLoop;
   bool mIsShowing;
   unsigned int mLastUserResponse;
   base::ProcessHandle mHangUIProcessHandle;
   NativeWindowHandle mMainWindowHandle;
   HANDLE mRegWait;
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -160,16 +160,17 @@ PluginInstanceChild::PluginInstanceChild
     , mIsTransparent(false)
     , mSurfaceType(gfxSurfaceType::Max)
     , mCurrentInvalidateTask(nullptr)
     , mCurrentAsyncSetWindowTask(nullptr)
     , mPendingPluginCall(false)
     , mDoAlphaExtraction(false)
     , mHasPainted(false)
     , mSurfaceDifferenceRect(0,0,0,0)
+    , mDestroyed(false)
 {
     memset(&mWindow, 0, sizeof(mWindow));
     mWindow.type = NPWindowTypeWindow;
     mData.ndata = (void*) this;
     mData.pdata = nullptr;
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     mWindow.ws_info = &mWsInfo;
     memset(&mWsInfo, 0, sizeof(mWsInfo));
@@ -207,17 +208,17 @@ PluginInstanceChild::~PluginInstanceChil
         UnscheduleTimer(mCARefreshTimer);
     }
 #endif
 }
 
 int
 PluginInstanceChild::GetQuirks()
 {
-    return PluginModuleChild::current()->GetQuirks();
+    return PluginModuleChild::GetChrome()->GetQuirks();
 }
 
 NPError
 PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue,
                                                  NPObject** aObject)
 {
     PluginScriptableObjectChild* actor = nullptr;
     NPError result = NPERR_NO_ERROR;
@@ -2388,17 +2389,17 @@ PluginInstanceChild::GetActorForNPObject
     if (aObject->_class == PluginScriptableObjectChild::GetClass()) {
         // One of ours! It's a browser-provided object.
         ChildNPObject* object = static_cast<ChildNPObject*>(aObject);
         NS_ASSERTION(object->parent, "Null actor!");
         return object->parent;
     }
 
     PluginScriptableObjectChild* actor =
-        PluginModuleChild::current()->GetActorForNPObject(aObject);
+        PluginScriptableObjectChild::GetActorForNPObject(aObject);
     if (actor) {
         // Plugin-provided object that we've previously wrapped.
         return actor;
     }
 
     actor = new PluginScriptableObjectChild(LocalObject);
     if (!SendPPluginScriptableObjectConstructor(actor)) {
         NS_ERROR("Failed to send constructor message!");
@@ -3271,17 +3272,17 @@ PluginInstanceChild::ShowPluginFrame()
         XSync(mWsInfo.display, False);
     } else
 #endif
 #ifdef XP_WIN
     if (SharedDIBSurface::IsSharedDIBSurface(mCurrentSurface)) {
         SharedDIBSurface* s = static_cast<SharedDIBSurface*>(mCurrentSurface.get());
         if (!mCurrentSurfaceActor) {
             base::SharedMemoryHandle handle = nullptr;
-            s->ShareToProcess(PluginModuleChild::current()->OtherProcess(), &handle);
+            s->ShareToProcess(OtherProcess(), &handle);
 
             mCurrentSurfaceActor =
                 SendPPluginSurfaceConstructor(handle,
                                               mCurrentSurface->GetSize(),
                                               haveTransparentPixels);
         }
         currSurf = mCurrentSurfaceActor;
         s->Flush();
@@ -3673,22 +3674,23 @@ PluginInstanceChild::ClearAllSurfaces()
         mCGLayer = nullptr;
     }
 
     mDoubleBufferCARenderer.ClearFrontSurface();
     mDoubleBufferCARenderer.ClearBackSurface();
 #endif
 }
 
-bool
-PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
+void
+PluginInstanceChild::Destroy()
 {
-    PLUGIN_LOG_DEBUG_METHOD;
-    AssertPluginThread();
-    *aResult = NPERR_NO_ERROR;
+    if (mDestroyed) {
+        return;
+    }
+    mDestroyed = true;
 
 #if defined(OS_WIN)
     SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1);
 #endif
 
     InfallibleTArray<PBrowserStreamChild*> streams;
     ManagedPBrowserStreamChild(streams);
 
@@ -3702,17 +3704,17 @@ PluginInstanceChild::AnswerNPP_Destroy(N
     for (uint32_t i = 0; i < streams.Length(); ++i)
         static_cast<BrowserStreamChild*>(streams[i])->FinishDelivery();
 
     mTimers.Clear();
 
     // NPP_Destroy() should be a synchronization point for plugin threads
     // calling NPN_AsyncCall: after this function returns, they are no longer
     // allowed to make async calls on this instance.
-    PluginModuleChild::current()->NPP_Destroy(this);
+    static_cast<PluginModuleChild *>(Manager())->NPP_Destroy(this);
     mData.ndata = 0;
 
     if (mCurrentInvalidateTask) {
         mCurrentInvalidateTask->Cancel();
         mCurrentInvalidateTask = nullptr;
     }
     if (mCurrentAsyncSetWindowTask) {
         mCurrentAsyncSetWindowTask->Cancel();
@@ -3724,17 +3726,17 @@ PluginInstanceChild::AnswerNPP_Destroy(N
             mAsyncInvalidateTask->Cancel();
             mAsyncInvalidateTask = nullptr;
         }
     }
 
     ClearAllSurfaces();
 
     mDeletingHash = new nsTHashtable<DeletingObjectEntry>;
-    PluginModuleChild::current()->FindNPObjectsForInstance(this);
+    PluginScriptableObjectChild::NotifyOfInstanceShutdown(this);
 
     mDeletingHash->EnumerateEntries(InvalidateObject, nullptr);
     mDeletingHash->EnumerateEntries(DeleteObject, nullptr);
 
     // Null out our cached actors as they should have been killed in the
     // PluginInstanceDestroyed call above.
     mCachedWindowActor = nullptr;
     mCachedElementActor = nullptr;
@@ -3756,11 +3758,27 @@ PluginInstanceChild::AnswerNPP_Destroy(N
 #ifdef MOZ_WIDGET_GTK
     if (mWindow.type == NPWindowTypeWindow && !mXEmbed) {
       xt_client_xloop_destroy();
     }
 #endif
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     DeleteWindow();
 #endif
+}
+
+bool
+PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
+{
+    PLUGIN_LOG_DEBUG_METHOD;
+    AssertPluginThread();
+    *aResult = NPERR_NO_ERROR;
+
+    Destroy();
 
     return true;
 }
+
+void
+PluginInstanceChild::ActorDestroy(ActorDestroyReason why)
+{
+    Destroy();
+}
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -50,17 +50,18 @@ namespace plugins {
 class PBrowserStreamChild;
 class BrowserStreamChild;
 class StreamNotifyChild;
 
 class PluginInstanceChild : public PPluginInstanceChild
 {
     friend class BrowserStreamChild;
     friend class PluginStreamChild;
-    friend class StreamNotifyChild; 
+    friend class StreamNotifyChild;
+    friend class PluginScriptableObjectChild;
 
 #ifdef OS_WIN
     friend LRESULT CALLBACK PluginWindowProc(HWND hWnd,
                                              UINT message,
                                              WPARAM wParam,
                                              LPARAM lParam);
     static LRESULT CALLBACK PluginWindowProcInternal(HWND hWnd,
                                                      UINT message,
@@ -519,16 +520,20 @@ private:
     void ClearCurrentSurface();
 
     // Swap mCurrentSurface/mBackSurface and their associated actors
     void SwapSurfaces();
 
     // Clear all surfaces in response to NPP_Destroy
     void ClearAllSurfaces();
 
+    void Destroy();
+
+    void ActorDestroy(ActorDestroyReason why);
+
     // Set as true when SetupLayer called
     // and go with different path in InvalidateRect function
     bool mLayersRendering;
 
     // Current surface available for rendering
     nsRefPtr<gfxASurface> mCurrentSurface;
 
     // Back surface, just keeping reference to
@@ -592,14 +597,17 @@ private:
     // that we ask a plugin to paint at least once even if it's invisible;
     // some plugin (instances) rely on this in order to work properly.
     bool mHasPainted;
 
     // Cached rectangle rendered to previous surface(mBackSurface)
     // Used for reading back to current surface and syncing data,
     // in plugin coordinates.
     nsIntRect mSurfaceDifferenceRect;
+
+    // Has this instance been destroyed, either by ActorDestroy or NPP_Destroy?
+    bool mDestroyed;
 };
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // ifndef dom_plugins_PluginInstanceChild_h
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -1773,17 +1773,17 @@ PluginInstanceParent::SharedSurfaceSetWi
     if (NS_FAILED(mSharedSurfaceDib.Create(reinterpret_cast<HDC>(aWindow->window),
                                            newPort.width, newPort.height, false)))
       return false;
 
     // save the new shared surface size we just allocated
     mSharedSize = newPort;
 
     base::SharedMemoryHandle handle;
-    if (NS_FAILED(mSharedSurfaceDib.ShareToProcess(mParent->ChildProcessHandle(), &handle)))
+    if (NS_FAILED(mSharedSurfaceDib.ShareToProcess(OtherProcess(), &handle)))
       return false;
 
     aRemoteWindow.surfaceHandle = handle;
 
     return true;
 }
 
 void
--- a/dom/plugins/ipc/PluginInterposeOSX.mm
+++ b/dom/plugins/ipc/PluginInterposeOSX.mm
@@ -540,17 +540,17 @@ void NSCursorInfo::SetCustomImageData(ui
 }
 
 // This should never be called from the browser process -- only from the
 // plugin process.
 bool NSCursorInfo::GetNativeCursorsSupported()
 {
   if (mNativeCursorsSupported == -1) {
     AssertPluginThread();
-    PluginModuleChild *pmc = PluginModuleChild::current();
+    PluginModuleChild *pmc = PluginModuleChild::GetChrome();
     if (pmc) {
       bool result = pmc->GetNativeCursorsSupported();
       if (result) {
         mNativeCursorsSupported = 1;
       } else {
         mNativeCursorsSupported = 0;
       }
     }
@@ -686,60 +686,60 @@ void FocusPluginProcess() {
     SetFrontProcess(&this_process);
   }
 }
 
 void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds,
                                      bool modal) {
   AssertPluginThread();
 
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc)
     pmc->PluginShowWindow(window_id, modal, bounds);
 }
 
 void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) {
   AssertPluginThread();
 
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc)
     pmc->PluginHideWindow(window_id);
 }
 
 void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo)
 {
   AssertPluginThread();
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc) {
     pmc->SetCursor(aCursorInfo);
   }
 }
 
 void NotifyBrowserOfShowCursor(bool show)
 {
   AssertPluginThread();
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc) {
     pmc->ShowCursor(show);
   }
 }
 
 void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo)
 {
   AssertPluginThread();
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc) {
     pmc->PushCursor(aCursorInfo);
   }
 }
 
 void NotifyBrowserOfPopCursor()
 {
   AssertPluginThread();
-  PluginModuleChild *pmc = PluginModuleChild::current();
+  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
   if (pmc) {
     pmc->PopCursor();
   }
 }
 
 struct WindowInfo {
   uint32_t window_id;
   CGRect bounds;
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -60,17 +60,18 @@ using mozilla::dom::CrashReporterChild;
 using mozilla::dom::PCrashReporterChild;
 
 #if defined(XP_WIN)
 const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
 const wchar_t * kMozillaWindowClass = L"MozillaWindowClass";
 #endif
 
 namespace {
-PluginModuleChild* gInstance = nullptr;
+PluginModuleChild* gChromeInstance = nullptr;
+nsTArray<PluginModuleChild*>* gAllInstances;
 }
 
 #ifdef MOZ_WIDGET_QT
 typedef void (*_gtk_init_fn)(int argc, char **argv);
 static _gtk_init_fn s_gtk_init = nullptr;
 static PRLibrary *sGtkLib = nullptr;
 #endif
 
@@ -79,80 +80,156 @@ static PRLibrary *sGtkLib = nullptr;
 static bool gDelayFlashFocusReplyUntilEval = false;
 // Used to fix GetWindowInfo problems with internal flash settings dialogs
 static WindowsDllInterceptor sUser32Intercept;
 typedef BOOL (WINAPI *GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
 static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr;
 static HWND sBrowserHwnd = nullptr;
 #endif
 
-PluginModuleChild::PluginModuleChild()
+/* static */
+PluginModuleChild*
+PluginModuleChild::CreateForContentProcess(mozilla::ipc::Transport* aTransport,
+                                           base::ProcessId aOtherProcess)
+{
+    PluginModuleChild* child = new PluginModuleChild(false);
+    ProcessHandle handle;
+    if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
+        // XXX need to kill |aOtherProcess|, it's boned
+        return nullptr;
+    }
+
+    if (!child->InitForContent(handle, XRE_GetIOMessageLoop(), aTransport)) {
+        return nullptr;
+    }
+
+    return child;
+}
+
+PluginModuleChild::PluginModuleChild(bool aIsChrome)
   : mLibrary(0)
   , mPluginFilename("")
   , mQuirks(QUIRKS_NOT_INITIALIZED)
+  , mIsChrome(aIsChrome)
+  , mTransport(nullptr)
   , mShutdownFunc(0)
   , mInitializeFunc(0)
 #if defined(OS_WIN) || defined(OS_MACOSX)
   , mGetEntryPointsFunc(0)
 #elif defined(MOZ_WIDGET_GTK)
   , mNestedLoopTimerId(0)
 #elif defined(MOZ_WIDGET_QT)
   , mNestedLoopTimerObject(0)
 #endif
 #ifdef OS_WIN
   , mNestedEventHook(nullptr)
   , mGlobalCallWndProcHook(nullptr)
 #endif
 {
-    NS_ASSERTION(!gInstance, "Something terribly wrong here!");
+    if (!gAllInstances) {
+        gAllInstances = new nsTArray<PluginModuleChild*>(1);
+    }
+    gAllInstances->AppendElement(this);
+
     memset(&mFunctions, 0, sizeof(mFunctions));
-    memset(&mSavedData, 0, sizeof(mSavedData));
-    gInstance = this;
+    if (mIsChrome) {
+        MOZ_ASSERT(!gChromeInstance);
+        gChromeInstance = this;
+    }
     mUserAgent.SetIsVoid(true);
 #ifdef XP_MACOSX
     mac_plugin_interposing::child::SetUpCocoaInterposing();
 #endif
 }
 
 PluginModuleChild::~PluginModuleChild()
 {
-    NS_ASSERTION(gInstance == this, "Something terribly wrong here!");
-
-    // We don't unload the plugin library in case it uses atexit handlers or
-    // other similar hooks.
+    if (mTransport) {
+        // For some reason IPDL doesn't autmatically delete the channel for a
+        // bridged protocol (bug 1090570). So we have to do it ourselves. This
+        // code is only invoked for PluginModuleChild instances created via
+        // bridging; otherwise mTransport is null.
+        XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(mTransport));
+    }
 
-    DeinitGraphics();
-    PluginScriptableObjectChild::ClearIdentifiers();
+    gAllInstances->RemoveElement(this);
+    MOZ_ASSERT_IF(mIsChrome, gAllInstances->Length() == 0);
+    if (gAllInstances->IsEmpty()) {
+        delete gAllInstances;
+        gAllInstances = nullptr;
+    }
 
-    gInstance = nullptr;
+    if (mIsChrome) {
+        MOZ_ASSERT(gChromeInstance == this);
+
+        // We don't unload the plugin library in case it uses atexit handlers or
+        // other similar hooks.
+
+        DeinitGraphics();
+        PluginScriptableObjectChild::ClearIdentifiers();
+
+        gChromeInstance = nullptr;
+    }
 }
 
 // static
 PluginModuleChild*
-PluginModuleChild::current()
+PluginModuleChild::GetChrome()
 {
-    NS_ASSERTION(gInstance, "Null instance!");
-    return gInstance;
+    MOZ_ASSERT(gChromeInstance);
+    return gChromeInstance;
 }
 
 bool
-PluginModuleChild::Init(const std::string& aPluginFilename,
-                        base::ProcessHandle aParentProcessHandle,
-                        MessageLoop* aIOLoop,
-                        IPC::Channel* aChannel)
+PluginModuleChild::CommonInit(base::ProcessHandle aParentProcessHandle,
+                              MessageLoop* aIOLoop,
+                              IPC::Channel* aChannel)
 {
     PLUGIN_LOG_DEBUG_METHOD;
 
-    GetIPCChannel()->SetAbortOnError(true);
-
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
+    // Bug 1090573 - Don't do this for connections to content processes.
     GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 
+    if (!Open(aChannel, aParentProcessHandle, aIOLoop))
+        return false;
+
+    memset((void*) &mFunctions, 0, sizeof(mFunctions));
+    mFunctions.size = sizeof(mFunctions);
+    mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
+
+    return true;
+}
+
+bool
+PluginModuleChild::InitForContent(base::ProcessHandle aParentProcessHandle,
+                                  MessageLoop* aIOLoop,
+                                  IPC::Channel* aChannel)
+{
+    if (!CommonInit(aParentProcessHandle, aIOLoop, aChannel)) {
+        return false;
+    }
+
+    mTransport = aChannel;
+
+    mLibrary = GetChrome()->mLibrary;
+    mQuirks = GetChrome()->mQuirks;
+    mFunctions = GetChrome()->mFunctions;
+
+    return true;
+}
+
+bool
+PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
+                                 base::ProcessHandle aParentProcessHandle,
+                                 MessageLoop* aIOLoop,
+                                 IPC::Channel* aChannel)
+{
 #ifdef XP_WIN
     COMMessageFilter::Initialize(this);
 #endif
 
     NS_ASSERTION(aChannel, "need a channel");
 
     if (!InitGraphics())
         return false;
@@ -196,22 +273,21 @@ PluginModuleChild::Init(const std::strin
 #endif
     {
         nsresult rv = pluginFile.LoadPlugin(&mLibrary);
         if (NS_FAILED(rv))
             return false;
     }
     NS_ASSERTION(mLibrary, "couldn't open shared object");
 
-    if (!Open(aChannel, aParentProcessHandle, aIOLoop))
+    if (!CommonInit(aParentProcessHandle, aIOLoop, aChannel)) {
         return false;
+    }
 
-    memset((void*) &mFunctions, 0, sizeof(mFunctions));
-    mFunctions.size = sizeof(mFunctions);
-    mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
+    GetIPCChannel()->SetAbortOnError(true);
 
     // TODO: use PluginPRLibrary here
 
 #if defined(OS_LINUX) || defined(OS_BSD)
     mShutdownFunc =
         (NP_PLUGINSHUTDOWN) PR_FindFunctionSymbol(mLibrary, "NP_Shutdown");
 
     // create the new plugin handler
@@ -586,16 +662,17 @@ PluginModuleChild::DeinitGraphics()
     XCloseDisplay(DefaultXDisplay());
 #endif
 }
 
 bool
 PluginModuleChild::AnswerNP_Shutdown(NPError *rv)
 {
     AssertPluginThread();
+    MOZ_ASSERT(mIsChrome);
 
 #if defined XP_WIN
     mozilla::widget::StopAudioSession();
 #endif
 
     // the PluginModuleParent shuts down this process after this interrupt
     // call pops off its stack
 
@@ -673,16 +750,23 @@ PluginModuleChild::RecvSetAudioSessionDa
 
 void
 PluginModuleChild::QuickExit()
 {
     NS_WARNING("plugin process _exit()ing");
     _exit(0);
 }
 
+PPluginModuleChild*
+PluginModuleChild::AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport,
+                                           base::ProcessId aOtherProcess)
+{
+    return PluginModuleChild::CreateForContentProcess(aTransport, aOtherProcess);
+}
+
 PCrashReporterChild*
 PluginModuleChild::AllocPCrashReporterChild(mozilla::dom::NativeThreadId* id,
                                             uint32_t* processType)
 {
     return new CrashReporterChild();
 }
 
 bool
@@ -703,16 +787,27 @@ PluginModuleChild::AnswerPCrashReporterC
     *processType = XRE_GetProcessType();
 #endif
     return true;
 }
 
 void
 PluginModuleChild::ActorDestroy(ActorDestroyReason why)
 {
+    if (!mIsChrome) {
+        PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
+        if (chromeInstance) {
+            chromeInstance->SendNotifyContentModuleDestroyed();
+        }
+
+        // Destroy ourselves once we finish other teardown activities.
+        MessageLoop::current()->PostTask(FROM_HERE, new DeleteTask<PluginModuleChild>(this));
+        return;
+    }
+
     if (AbnormalShutdown == why) {
         NS_WARNING("shutting down early because of crash!");
         QuickExit();
     }
 
     // doesn't matter why we're being destroyed; it's up to us to
     // initiate (clean) shutdown
     XRE_ShutdownChildProcess();
@@ -727,69 +822,16 @@ const char*
 PluginModuleChild::GetUserAgent()
 {
     if (mUserAgent.IsVoid() && !CallNPN_UserAgent(&mUserAgent))
         return nullptr;
 
     return NullableStringGet(mUserAgent);
 }
 
-bool
-PluginModuleChild::RegisterActorForNPObject(NPObject* aObject,
-                                            PluginScriptableObjectChild* aActor)
-{
-    AssertPluginThread();
-    NS_ASSERTION(aObject && aActor, "Null pointer!");
-
-    NPObjectData* d = mObjectMap.GetEntry(aObject);
-    if (!d) {
-        NS_ERROR("NPObject not in object table");
-        return false;
-    }
-
-    d->actor = aActor;
-    return true;
-}
-
-void
-PluginModuleChild::UnregisterActorForNPObject(NPObject* aObject)
-{
-    AssertPluginThread();
-    NS_ASSERTION(aObject, "Null pointer!");
-
-    NPObjectData* d = mObjectMap.GetEntry(aObject);
-    NS_ASSERTION(d, "NPObject not in object table");
-    if (d) {
-        d->actor = nullptr;
-    }
-}
-
-PluginScriptableObjectChild*
-PluginModuleChild::GetActorForNPObject(NPObject* aObject)
-{
-    AssertPluginThread();
-    NS_ASSERTION(aObject, "Null pointer!");
-
-    NPObjectData* d = mObjectMap.GetEntry(aObject);
-    if (!d) {
-        NS_ERROR("Plugin using object not created with NPN_CreateObject?");
-        return nullptr;
-    }
-
-    return d->actor;
-}
-
-#ifdef DEBUG
-bool
-PluginModuleChild::NPObjectIsRegistered(NPObject* aObject)
-{
-    return !!mObjectMap.GetEntry(aObject);
-}
-#endif
-
 //-----------------------------------------------------------------------------
 // FIXME/cjones: just getting this out of the way for the moment ...
 
 namespace mozilla {
 namespace plugins {
 namespace child {
 
 static NPError
@@ -1080,17 +1122,17 @@ NPError
 
         case NPNVjavascriptEnabledBool: // Intentional fall-through
         case NPNVasdEnabledBool: // Intentional fall-through
         case NPNVisOfflineBool: // Intentional fall-through
         case NPNVSupportsXEmbedBool: // Intentional fall-through
         case NPNVSupportsWindowless: { // Intentional fall-through
             NPError result;
             bool value;
-            PluginModuleChild::current()->
+            PluginModuleChild::GetChrome()->
                 CallNPN_GetValue_WithBoolReturn(aVariable, &result, &value);
             *(NPBool*)aValue = value ? true : false;
             return result;
         }
 #if defined(MOZ_WIDGET_GTK)
         case NPNVxDisplay: {
             if (aNPP) {
                 return InstCast(aNPP)->NPN_GetValue(aVariable, aValue);
@@ -1271,17 +1313,20 @@ uint32_t
 }
 
 void
 _reloadplugins(NPBool aReloadPages)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     ENSURE_PLUGIN_THREAD_VOID();
 
-    PluginModuleChild::current()->SendNPN_ReloadPlugins(!!aReloadPages);
+    // Send the reload message to all modules. Chrome will need to reload from
+    // disk and content will need to request a new list of plugin tags from
+    // chrome.
+    PluginModuleChild::GetChrome()->SendNPN_ReloadPlugins(!!aReloadPages);
 }
 
 void
 _invalidaterect(NPP aNPP,
                 NPRect* aInvalidRect)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     ENSURE_PLUGIN_THREAD_VOID();
@@ -1310,17 +1355,17 @@ void
     // never be necessary.
 }
 
 const char*
 _useragent(NPP aNPP)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     ENSURE_PLUGIN_THREAD(nullptr);
-    return PluginModuleChild::current()->GetUserAgent();
+    return PluginModuleChild::GetChrome()->GetUserAgent();
 }
 
 void*
 _memalloc(uint32_t aSize)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     // Only assert plugin thread here for consistency with in-process plugins.
     AssertPluginThread();
@@ -1541,28 +1586,17 @@ void
 
 void
 _setexception(NPObject* aNPObj,
               const NPUTF8* aMessage)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     ENSURE_PLUGIN_THREAD_VOID();
 
-    PluginModuleChild* self = PluginModuleChild::current();
-    PluginScriptableObjectChild* actor = nullptr;
-    if (aNPObj) {
-        actor = self->GetActorForNPObject(aNPObj);
-        if (!actor) {
-            NS_ERROR("Failed to get actor!");
-            return;
-        }
-    }
-
-    self->SendNPN_SetException(static_cast<PPluginScriptableObjectChild*>(actor),
-                               NullableString(aMessage));
+    // Do nothing. We no longer support this API.
 }
 
 void
 _pushpopupsenabledstate(NPP aNPP,
                         NPBool aEnabled)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     ENSURE_PLUGIN_THREAD_VOID();
@@ -1742,17 +1776,17 @@ NPError
 
     NPBool success = _convertpoint(instance, 
                                   pluginX,  pluginY, NPCoordinateSpacePlugin, 
                                  &screenX, &screenY, NPCoordinateSpaceScreen);
 
     if (success) {
         return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(menu,
                                     screenX, screenY,
-                                    PluginModuleChild::current(),
+                                    PluginModuleChild::GetChrome(),
                                     ProcessBrowserEvents);
     } else {
         NS_WARNING("Convertpoint failed, could not created contextmenu.");
         return NPERR_GENERIC_ERROR;
     }
 
 #else
     NS_WARNING("Not supported on this platform!");
@@ -1800,32 +1834,34 @@ void
 
 //-----------------------------------------------------------------------------
 
 bool
 PluginModuleChild::AnswerNP_GetEntryPoints(NPError* _retval)
 {
     PLUGIN_LOG_DEBUG_METHOD;
     AssertPluginThread();
+    MOZ_ASSERT(mIsChrome);
 
 #if defined(OS_LINUX) || defined(OS_BSD)
     return true;
 #elif defined(OS_WIN) || defined(OS_MACOSX)
     *_retval = mGetEntryPointsFunc(&mFunctions);
     return true;
 #else
 #  error Please implement me for your platform
 #endif
 }
 
 bool
 PluginModuleChild::AnswerNP_Initialize(NPError* _retval)
 {
     PLUGIN_LOG_DEBUG_METHOD;
     AssertPluginThread();
+    MOZ_ASSERT(mIsChrome);
 
 #ifdef OS_WIN
     SetEventHooks();
 #endif
 
 #ifdef MOZ_X11
     // Send the parent our X socket to act as a proxy reference for our X
     // resources.
@@ -2045,20 +2081,17 @@ PluginModuleChild::NPN_CreateObject(NPP 
     }
 
     if (newObject) {
         newObject->_class = aClass;
         newObject->referenceCount = 1;
         NS_LOG_ADDREF(newObject, 1, "NPObject", sizeof(NPObject));
     }
 
-    NPObjectData* d = static_cast<PluginModuleChild*>(i->Manager())
-        ->mObjectMap.PutEntry(newObject);
-    NS_ASSERTION(!d->instance, "New NPObject already mapped?");
-    d->instance = i;
+    PluginScriptableObjectChild::RegisterObject(newObject, i);
 
     return newObject;
 }
 
 NPObject*
 PluginModuleChild::NPN_RetainObject(NPObject* aNPObj)
 {
     AssertPluginThread();
@@ -2072,25 +2105,25 @@ PluginModuleChild::NPN_RetainObject(NPOb
     return aNPObj;
 }
 
 void
 PluginModuleChild::NPN_ReleaseObject(NPObject* aNPObj)
 {
     AssertPluginThread();
 
-    NPObjectData* d = current()->mObjectMap.GetEntry(aNPObj);
-    if (!d) {
+    PluginInstanceChild* instance = PluginScriptableObjectChild::GetInstanceForNPObject(aNPObj);
+    if (!instance) {
         NS_ERROR("Releasing object not in mObjectMap?");
         return;
     }
 
     DeletingObjectEntry* doe = nullptr;
-    if (d->instance->mDeletingHash) {
-        doe = d->instance->mDeletingHash->GetEntry(aNPObj);
+    if (instance->mDeletingHash) {
+        doe = instance->mDeletingHash->GetEntry(aNPObj);
         if (!doe) {
             NS_ERROR("An object for a destroyed instance isn't in the instance deletion hash");
             return;
         }
         if (doe->mDeleted)
             return;
     }
 
@@ -2109,39 +2142,21 @@ void
 PluginModuleChild::DeallocNPObject(NPObject* aNPObj)
 {
     if (aNPObj->_class && aNPObj->_class->deallocate) {
         aNPObj->_class->deallocate(aNPObj);
     } else {
         child::_memfree(aNPObj);
     }
 
-    NPObjectData* d = current()->mObjectMap.GetEntry(aNPObj);
-    if (d->actor)
-        d->actor->NPObjectDestroyed();
-
-    current()->mObjectMap.RemoveEntry(aNPObj);
-}
+    PluginScriptableObjectChild* actor = PluginScriptableObjectChild::GetActorForNPObject(aNPObj);
+    if (actor)
+        actor->NPObjectDestroyed();
 
-void
-PluginModuleChild::FindNPObjectsForInstance(PluginInstanceChild* instance)
-{
-    NS_ASSERTION(instance->mDeletingHash, "filling null mDeletingHash?");
-    mObjectMap.EnumerateEntries(CollectForInstance, instance);
-}
-
-PLDHashOperator
-PluginModuleChild::CollectForInstance(NPObjectData* d, void* userArg)
-{
-    PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(userArg);
-    if (d->instance == instance) {
-        NPObject* o = d->GetKey();
-        instance->mDeletingHash->PutEntry(o);
-    }
-    return PL_DHASH_NEXT;
+    PluginScriptableObjectChild::UnregisterObject(aNPObj);
 }
 
 NPIdentifier
 PluginModuleChild::NPN_GetStringIdentifier(const NPUTF8* aName)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     AssertPluginThread();
 
@@ -2267,17 +2282,17 @@ PluginModuleChild::CallWindowProcHook(in
     }
 
     return CallNextHookEx(nullptr, nCode, wParam, lParam);
 }
 
 LRESULT CALLBACK
 PluginModuleChild::NestedInputEventHook(int nCode, WPARAM wParam, LPARAM lParam)
 {
-    PluginModuleChild* self = current();
+    PluginModuleChild* self = GetChrome();
     uint32_t len = self->mIncallPumpingStack.Length();
     if (nCode >= 0 && len && !self->mIncallPumpingStack[len - 1]._spinning) {
         MessageLoop* loop = MessageLoop::current();
         self->SendProcessNativeEventsInInterruptCall();
         IncallFrame& f = self->mIncallPumpingStack[len - 1];
         f._spinning = true;
         f._savedNestableTasksAllowed = loop->NestableTasksAllowed();
         loop->SetNestableTasksAllowed(true);
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -71,16 +71,20 @@ protected:
     }
 
     virtual bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
 
     // Implement the PPluginModuleChild interface
     virtual bool AnswerNP_GetEntryPoints(NPError* rv) MOZ_OVERRIDE;
     virtual bool AnswerNP_Initialize(NPError* rv) MOZ_OVERRIDE;
 
+    virtual PPluginModuleChild*
+    AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport,
+                            base::ProcessId aOtherProcess) MOZ_OVERRIDE;
+
     virtual PPluginInstanceChild*
     AllocPPluginInstanceChild(const nsCString& aMimeType,
                               const uint16_t& aMode,
                               const InfallibleTArray<nsCString>& aNames,
                               const InfallibleTArray<nsCString>& aValues,
                               NPError* rv) MOZ_OVERRIDE;
 
     virtual bool
@@ -135,43 +139,44 @@ protected:
 
     virtual bool
     RecvProcessNativeEventsInInterruptCall() MOZ_OVERRIDE;
 
     virtual bool
     AnswerGeckoGetProfile(nsCString* aProfile) MOZ_OVERRIDE;
 
 public:
-    PluginModuleChild();
+    PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
+    bool CommonInit(base::ProcessHandle aParentProcessHandle,
+                    MessageLoop* aIOLoop,
+                    IPC::Channel* aChannel);
+
     // aPluginFilename is UTF8, not native-charset!
-    bool Init(const std::string& aPluginFilename,
-              base::ProcessHandle aParentProcessHandle,
-              MessageLoop* aIOLoop,
-              IPC::Channel* aChannel);
+    bool InitForChrome(const std::string& aPluginFilename,
+                       base::ProcessHandle aParentProcessHandle,
+                       MessageLoop* aIOLoop,
+                       IPC::Channel* aChannel);
+
+    bool InitForContent(base::ProcessHandle aParentProcessHandle,
+                        MessageLoop* aIOLoop,
+                        IPC::Channel* aChannel);
+
+    static PluginModuleChild*
+    CreateForContentProcess(mozilla::ipc::Transport* aTransport,
+                            base::ProcessId aOtherProcess);
 
     void CleanUp();
 
     const char* GetUserAgent();
 
     static const NPNetscapeFuncs sBrowserFuncs;
 
-    static PluginModuleChild* current();
-
-    bool RegisterActorForNPObject(NPObject* aObject,
-                                  PluginScriptableObjectChild* aActor);
-
-    void UnregisterActorForNPObject(NPObject* aObject);
-
-    PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject);
-
-#ifdef DEBUG
-    bool NPObjectIsRegistered(NPObject* aObject);
-#endif
+    static PluginModuleChild* GetChrome();
 
     /**
      * The child implementation of NPN_CreateObject.
      */
     static NPObject* NPN_CreateObject(NPP aNPP, NPClass* aClass);
     /**
      * The child implementation of NPN_RetainObject.
      */
@@ -294,27 +299,29 @@ private:
     virtual void ExitedCxxStack() MOZ_OVERRIDE;
 #endif
 
     PRLibrary* mLibrary;
     nsCString mPluginFilename; // UTF8
     nsCString mUserAgent;
     int mQuirks;
 
+    bool mIsChrome;
+    Transport* mTransport;
+
     // we get this from the plugin
     NP_PLUGINSHUTDOWN mShutdownFunc;
 #if defined(OS_LINUX) || defined(OS_BSD)
     NP_PLUGINUNIXINIT mInitializeFunc;
 #elif defined(OS_WIN) || defined(OS_MACOSX)
     NP_PLUGININIT mInitializeFunc;
     NP_GETENTRYPOINTS mGetEntryPointsFunc;
 #endif
 
     NPPluginFuncs mFunctions;
-    NPSavedData mSavedData;
 
 #if defined(MOZ_WIDGET_GTK)
     // If a plugin spins a nested glib event loop in response to a
     // synchronous IPC message from the browser, the loop might break
     // only after the browser responds to a request sent by the
     // plugin.  This can happen if a plugin uses gtk's synchronous
     // copy/paste, for example.  But because the browser is blocked on
     // a condvar, it can't respond to the request.  This situation
@@ -348,56 +355,28 @@ private:
     // g_main_context_iteration, or 0 when dispatched directly from
     // MessagePumpForUI.
     int mTopLoopDepth;
 #  endif
 #elif defined (MOZ_WIDGET_QT)
     NestedLoopTimer *mNestedLoopTimerObject;
 #endif
 
-    struct NPObjectData : public nsPtrHashKey<NPObject>
-    {
-        explicit NPObjectData(const NPObject* key)
-            : nsPtrHashKey<NPObject>(key)
-            , instance(nullptr)
-            , actor(nullptr)
-        { }
-
-        // never nullptr
-        PluginInstanceChild* instance;
-
-        // sometimes nullptr (no actor associated with an NPObject)
-        PluginScriptableObjectChild* actor;
-    };
-    /**
-     * mObjectMap contains all the currently active NPObjects (from NPN_CreateObject until the
-     * final release/dealloc, whether or not an actor is currently associated with the object.
-     */
-    nsTHashtable<NPObjectData> mObjectMap;
-
 public: // called by PluginInstanceChild
     /**
      * Dealloc an NPObject after last-release or when the associated instance
      * is destroyed. This function will remove the object from mObjectMap.
      */
     static void DeallocNPObject(NPObject* o);
 
     NPError NPP_Destroy(PluginInstanceChild* instance) {
         return mFunctions.destroy(instance->GetNPP(), 0);
     }
 
-    /**
-     * Fill PluginInstanceChild.mDeletingHash with all the remaining NPObjects
-     * associated with that instance.
-     */
-    void FindNPObjectsForInstance(PluginInstanceChild* instance);
-
 private:
-    static PLDHashOperator CollectForInstance(NPObjectData* d, void* userArg);
-
 #if defined(OS_WIN)
     virtual void EnteredCall() MOZ_OVERRIDE;
     virtual void ExitedCall() MOZ_OVERRIDE;
 
     // Entered/ExitedCall notifications keep track of whether the plugin has
     // entered a nested event loop within this interrupt call.
     struct IncallFrame
     {
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -10,19 +10,22 @@
 #include <QtCore/QEventLoop>
 #include "NestedLoopTimer.h"
 #endif
 
 #include "mozilla/plugins/PluginModuleParent.h"
 
 #include "base/process_util.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PCrashReporterParent.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
+#include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsAutoPtr.h"
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
@@ -78,26 +81,92 @@ static const char kHangUIMinDisplayPref[
 template<>
 struct RunnableMethodTraits<mozilla::plugins::PluginModuleParent>
 {
     typedef mozilla::plugins::PluginModuleParent Class;
     static void RetainCallee(Class* obj) { }
     static void ReleaseCallee(Class* obj) { }
 };
 
+bool
+mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent)
+{
+    nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+    nsRefPtr<nsNPAPIPlugin> plugin;
+    nsresult rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin));
+    if (NS_FAILED(rv)) {
+        return false;
+    }
+    PluginModuleParent* chromeParent = static_cast<PluginModuleParent*>(plugin->GetLibrary());
+    return PPluginModule::Bridge(aContentParent, chromeParent);
+}
+
+PluginModuleContentParent* PluginModuleContentParent::sSavedModuleParent;
+
+/* static */ PluginLibrary*
+PluginModuleContentParent::LoadModule(uint32_t aPluginId)
+{
+    MOZ_ASSERT(!sSavedModuleParent);
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+
+    /*
+     * We send a LoadPlugin message to the chrome process using an intr
+     * message. Before it sends its response, it sends a message to create
+     * PluginModuleParent instance. That message is handled by
+     * PluginModuleContentParent::Create, which saves the instance in
+     * sSavedModuleParent. We fetch it from there after LoadPlugin finishes.
+     */
+    dom::ContentChild* cp = dom::ContentChild::GetSingleton();
+    if (!cp->CallLoadPlugin(aPluginId)) {
+        return nullptr;
+    }
+
+    PluginModuleContentParent* parent = sSavedModuleParent;
+    MOZ_ASSERT(parent);
+    sSavedModuleParent = nullptr;
+
+    return parent;
+}
+
+/* static */ PluginModuleContentParent*
+PluginModuleContentParent::Create(mozilla::ipc::Transport* aTransport,
+                                  base::ProcessId aOtherProcess)
+{
+    nsAutoPtr<PluginModuleContentParent> parent(new PluginModuleContentParent());
+    ProcessHandle handle;
+    if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
+        // Bug 1090578 - need to kill |aOtherProcess|, it's boned.
+        return nullptr;
+    }
+
+    MOZ_ASSERT(!sSavedModuleParent);
+    sSavedModuleParent = parent;
+
+    DebugOnly<bool> ok = parent->Open(aTransport, handle, XRE_GetIOMessageLoop(),
+                                      mozilla::ipc::ParentSide);
+    MOZ_ASSERT(ok);
+
+    // Request Windows message deferral behavior on our channel. This
+    // applies to the top level and all sub plugin protocols since they
+    // all share the same channel.
+    parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
+
+    return parent.forget();
+}
+
 // static
 PluginLibrary*
-PluginModuleParent::LoadModule(const char* aFilePath)
+PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
 
     int32_t prefSecs = Preferences::GetInt(kLaunchTimeoutPref, 0);
 
     // Block on the child process being launched and initialized.
-    nsAutoPtr<PluginModuleParent> parent(new PluginModuleParent(aFilePath));
+    nsAutoPtr<PluginModuleChromeParent> parent(new PluginModuleChromeParent(aFilePath, aPluginId));
     bool launched = parent->mSubprocess->Launch(prefSecs * 1000);
     if (!launched) {
         // We never reached open
         parent->mShutdown = true;
         return nullptr;
     }
     parent->Open(parent->mSubprocess->GetChannel(),
                  parent->mSubprocess->GetChildProcessHandle());
@@ -119,25 +188,50 @@ PluginModuleParent::LoadModule(const cha
     mozilla::MutexAutoLock lock(parent->mCrashReporterMutex);
     parent->mCrashReporter = parent->CrashReporter();
 #endif
 #endif
 
     return parent.forget();
 }
 
-
-PluginModuleParent::PluginModuleParent(const char* aFilePath)
-    : mSubprocess(new PluginProcessParent(aFilePath))
+PluginModuleParent::PluginModuleParent(bool aIsChrome)
+    : mIsChrome(aIsChrome)
     , mShutdown(false)
     , mClearSiteDataSupported(false)
     , mGetSitesWithDataSupported(false)
     , mNPNIface(nullptr)
     , mPlugin(nullptr)
     , mTaskFactory(MOZ_THIS_IN_INITIALIZER_LIST())
+{
+}
+
+PluginModuleParent::~PluginModuleParent()
+{
+    if (!OkToCleanup()) {
+        NS_RUNTIMEABORT("unsafe destruction");
+    }
+
+    if (!mShutdown) {
+        NS_WARNING("Plugin host deleted the module without shutting down.");
+        NPError err;
+        NP_Shutdown(&err);
+    }
+}
+
+PluginModuleContentParent::PluginModuleContentParent()
+    : PluginModuleParent(false)
+{
+}
+
+PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId)
+    : PluginModuleParent(true)
+    , mSubprocess(new PluginProcessParent(aFilePath))
+    , mPluginId(aPluginId)
+    , mChromeTaskFactory(MOZ_THIS_IN_INITIALIZER_LIST())
     , mHangAnnotationFlags(0)
 #ifdef XP_WIN
     , mPluginCpuUsageOnHang()
     , mHangUIParent(nullptr)
     , mHangUIEnabled(true)
     , mIsTimerReset(true)
 #ifdef MOZ_CRASHREPORTER
     , mCrashReporterMutex("PluginModuleParent::mCrashReporterMutex")
@@ -160,17 +254,17 @@ PluginModuleParent::PluginModuleParent(c
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     InitPluginProfiling();
 #endif
 
     mozilla::HangMonitor::RegisterAnnotator(*this);
 }
 
-PluginModuleParent::~PluginModuleParent()
+PluginModuleChromeParent::~PluginModuleChromeParent()
 {
     if (!OkToCleanup()) {
         NS_RUNTIMEABORT("unsafe destruction");
     }
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     ShutdownPluginProfiling();
 #endif
@@ -207,17 +301,17 @@ PluginModuleParent::~PluginModuleParent(
     }
 #endif
 
     mozilla::HangMonitor::UnregisterAnnotator(*this);
 }
 
 #ifdef MOZ_CRASHREPORTER
 void
-PluginModuleParent::WriteExtraDataForMinidump(AnnotationTable& notes)
+PluginModuleChromeParent::WriteExtraDataForMinidump(AnnotationTable& notes)
 {
 #ifdef XP_WIN
     // mCrashReporterMutex is already held by the caller
     mCrashReporterMutex.AssertCurrentThreadOwns();
 #endif
     typedef nsDependentCString CS;
 
     // Get the plugin filename, try to get just the file leafname
@@ -252,58 +346,58 @@ PluginModuleParent::WriteExtraDataForMin
 #endif
         }
 #endif
     }
 }
 #endif  // MOZ_CRASHREPORTER
 
 void
-PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout)
+PluginModuleChromeParent::SetChildTimeout(const int32_t aChildTimeout)
 {
     int32_t timeoutMs = (aChildTimeout > 0) ? (1000 * aChildTimeout) :
                       MessageChannel::kNoTimeout;
     SetReplyTimeoutMs(timeoutMs);
 }
 
 void
-PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule)
+PluginModuleChromeParent::TimeoutChanged(const char* aPref, void* aModule)
 {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 #ifndef XP_WIN
     if (!strcmp(aPref, kChildTimeoutPref)) {
       // The timeout value used by the parent for children
       int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0);
-      static_cast<PluginModuleParent*>(aModule)->SetChildTimeout(timeoutSecs);
+      static_cast<PluginModuleChromeParent*>(aModule)->SetChildTimeout(timeoutSecs);
 #else
     if (!strcmp(aPref, kChildTimeoutPref) ||
         !strcmp(aPref, kHangUIMinDisplayPref) ||
         !strcmp(aPref, kHangUITimeoutPref)) {
-      static_cast<PluginModuleParent*>(aModule)->EvaluateHangUIState(true);
+      static_cast<PluginModuleChromeParent*>(aModule)->EvaluateHangUIState(true);
 #endif // XP_WIN
     } else if (!strcmp(aPref, kParentTimeoutPref)) {
       // The timeout value used by the child for its parent
       int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0);
-      unused << static_cast<PluginModuleParent*>(aModule)->SendSetParentHangTimeout(timeoutSecs);
+      unused << static_cast<PluginModuleChromeParent*>(aModule)->SendSetParentHangTimeout(timeoutSecs);
     }
 }
 
 void
-PluginModuleParent::CleanupFromTimeout(const bool aFromHangUI)
+PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI)
 {
     if (mShutdown) {
       return;
     }
 
     if (!OkToCleanup()) {
         // there's still plugin code on the C++ stack, try again
         MessageLoop::current()->PostDelayedTask(
             FROM_HERE,
-            mTaskFactory.NewRunnableMethod(
-                &PluginModuleParent::CleanupFromTimeout, aFromHangUI), 10);
+            mChromeTaskFactory.NewRunnableMethod(
+                &PluginModuleChromeParent::CleanupFromTimeout, aFromHangUI), 10);
         return;
     }
 
     /* If the plugin container was terminated by the Plugin Hang UI, 
        then either the I/O thread detects a channel error, or the 
        main thread must set the error (whomever gets there first).
        OTOH, if we terminate and return false from 
        ShouldContinueFromReplyTimeout, then the channel state has 
@@ -380,35 +474,35 @@ GetProcessCpuUsage(const InfallibleTArra
   return true;
 }
 
 } // anonymous namespace
 
 #endif // #ifdef XP_WIN
 
 void
-PluginModuleParent::EnteredCxxStack()
+PluginModuleChromeParent::EnteredCxxStack()
 {
     mHangAnnotationFlags |= kInPluginCall;
 }
 
 void
-PluginModuleParent::ExitedCxxStack()
+PluginModuleChromeParent::ExitedCxxStack()
 {
     mHangAnnotationFlags = 0;
 #ifdef XP_WIN
     FinishHangUI();
 #endif
 }
 
 /**
  * This function is always called by the HangMonitor thread.
  */
 void
-PluginModuleParent::AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations)
+PluginModuleChromeParent::AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations)
 {
     uint32_t flags = mHangAnnotationFlags;
     if (flags) {
         /* We don't actually annotate anything specifically for kInPluginCall;
            we use it to determine whether to annotate other things. It will
            be pretty obvious from the ChromeHang stack that we're in a plugin
            call when the hang occurred. */
         if (flags & kHangUIShown) {
@@ -446,32 +540,32 @@ CreateFlashMinidump(DWORD processId, Thr
   bool res = CreateAdditionalChildMinidump(handle, 0, parentMinidump, name);
   base::CloseProcessHandle(handle);
 
   return res;
 }
 #endif
 
 bool
-PluginModuleParent::ShouldContinueFromReplyTimeout()
+PluginModuleChromeParent::ShouldContinueFromReplyTimeout()
 {
 #ifdef XP_WIN
     if (LaunchHangUI()) {
         return true;
     }
     // If LaunchHangUI returned false then we should proceed with the 
     // original plugin hang behaviour and kill the plugin container.
     FinishHangUI();
 #endif // XP_WIN
     TerminateChildProcess(MessageLoop::current());
     return false;
 }
 
 void
-PluginModuleParent::TerminateChildProcess(MessageLoop* aMsgLoop)
+PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop)
 {
 #ifdef MOZ_CRASHREPORTER
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
     CrashReporterParent* crashReporter = mCrashReporter;
     if (!crashReporter) {
         // If mCrashReporter is null then the hang has ended, the plugin module
         // is shutting down. There's nothing to do here.
@@ -551,18 +645,18 @@ PluginModuleParent::TerminateChildProces
     }
 #endif
 
     // this must run before the error notification from the channel,
     // or not at all
     bool isFromHangUI = aMsgLoop != MessageLoop::current();
     aMsgLoop->PostTask(
         FROM_HERE,
-        mTaskFactory.NewRunnableMethod(
-            &PluginModuleParent::CleanupFromTimeout, isFromHangUI));
+        mChromeTaskFactory.NewRunnableMethod(
+            &PluginModuleChromeParent::CleanupFromTimeout, isFromHangUI));
 
     if (!KillProcess(OtherProcess(), 1, false))
         NS_WARNING("failed to kill subprocess!");
 }
 
 bool
 PluginModuleParent::GetPluginDetails(nsACString& aPluginName,
                                      nsACString& aPluginVersion)
@@ -577,17 +671,17 @@ PluginModuleParent::GetPluginDetails(nsA
     }
     aPluginName = pluginTag->mName;
     aPluginVersion = pluginTag->mVersion;
     return true;
 }
 
 #ifdef XP_WIN
 void
-PluginModuleParent::EvaluateHangUIState(const bool aReset)
+PluginModuleChromeParent::EvaluateHangUIState(const bool aReset)
 {
     int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10);
     int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0);
     int32_t timeoutSecs = 0;
     if (autoStopSecs > 0 && autoStopSecs < minDispSecs) {
         /* If we're going to automatically terminate the plugin within a 
            time frame shorter than minDispSecs, there's no point in 
            showing the hang UI; it would just flash briefly on the screen. */
@@ -611,17 +705,17 @@ PluginModuleParent::EvaluateHangUIState(
             autoStopSecs *= 2;
         }
     }
     mIsTimerReset = false;
     SetChildTimeout(autoStopSecs);
 }
 
 bool
-PluginModuleParent::LaunchHangUI()
+PluginModuleChromeParent::LaunchHangUI()
 {
     if (!mHangUIEnabled) {
         return false;
     }
     if (mHangUIParent) {
         if (mHangUIParent->IsShowing()) {
             // We've already shown the UI but the timeout has expired again.
             return false;
@@ -648,17 +742,17 @@ PluginModuleParent::LaunchHangUI()
            after kChildTimeoutPref seconds if the user doesn't respond to 
            the hang UI. */
         EvaluateHangUIState(false);
     }
     return retval;
 }
 
 void
-PluginModuleParent::FinishHangUI()
+PluginModuleChromeParent::FinishHangUI()
 {
     if (mHangUIEnabled && mHangUIParent) {
         bool needsCancel = mHangUIParent->IsShowing();
         // If we're still showing, send a Cancel notification
         if (needsCancel) {
             mHangUIParent->Cancel();
         }
         /* If we cancelled the UI or if the user issued a response,
@@ -669,25 +763,25 @@ PluginModuleParent::FinishHangUI()
                UI was displayed. Now that we're finishing the UI, we need to 
                switch it back to kHangUITimeoutPref. */
             EvaluateHangUIState(true);
         }
     }
 }
 
 void
-PluginModuleParent::OnHangUIContinue()
+PluginModuleChromeParent::OnHangUIContinue()
 {
     mHangAnnotationFlags |= kHangUIContinued;
 }
 #endif // XP_WIN
 
 #ifdef MOZ_CRASHREPORTER
 CrashReporterParent*
-PluginModuleParent::CrashReporter()
+PluginModuleChromeParent::CrashReporter()
 {
     return static_cast<CrashReporterParent*>(ManagedPCrashReporterParent()[0]);
 }
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
 static void
 RemoveMinidump(nsIFile* minidump)
 {
@@ -699,17 +793,17 @@ RemoveMinidump(nsIFile* minidump)
     if (GetExtraFileForMinidump(minidump,
                                 getter_AddRefs(extraFile))) {
         extraFile->Remove(true);
     }
 }
 #endif // MOZ_CRASHREPORTER_INJECTOR
 
 void
-PluginModuleParent::ProcessFirstMinidump()
+PluginModuleChromeParent::ProcessFirstMinidump()
 {
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
 #endif
     CrashReporterParent* crashReporter = CrashReporter();
     if (!crashReporter)
         return;
 
@@ -776,20 +870,16 @@ PluginModuleParent::ProcessFirstMinidump
 }
 #endif
 
 void
 PluginModuleParent::ActorDestroy(ActorDestroyReason why)
 {
     switch (why) {
     case AbnormalShutdown: {
-#ifdef MOZ_CRASHREPORTER
-        ProcessFirstMinidump();
-#endif
-
         mShutdown = true;
         // Defer the PluginCrashed method so that we don't re-enter
         // and potentially modify the actor child list while enumerating it.
         if (mPlugin)
             MessageLoop::current()->PostTask(
                 FROM_HERE,
                 mTaskFactory.NewRunnableMethod(
                     &PluginModuleParent::NotifyPluginCrashed));
@@ -800,16 +890,28 @@ PluginModuleParent::ActorDestroy(ActorDe
         break;
 
     default:
         NS_RUNTIMEABORT("Unexpected shutdown reason for toplevel actor.");
     }
 }
 
 void
+PluginModuleChromeParent::ActorDestroy(ActorDestroyReason why)
+{
+    if (why == AbnormalShutdown) {
+#ifdef MOZ_CRASHREPORTER
+        ProcessFirstMinidump();
+#endif
+    }
+
+    PluginModuleParent::ActorDestroy(why);
+}
+
+void
 PluginModuleParent::NotifyPluginCrashed()
 {
     if (!OkToCleanup()) {
         // there's still plugin code on the C++ stack.  try again
         MessageLoop::current()->PostDelayedTask(
             FROM_HERE,
             mTaskFactory.NewRunnableMethod(
                 &PluginModuleParent::NotifyPluginCrashed), 10);
@@ -1168,23 +1270,26 @@ PluginModuleParent::NP_Initialize(NPNets
 
     mNPNIface = bFuncs;
 
     if (mShutdown) {
         *error = NPERR_GENERIC_ERROR;
         return NS_ERROR_FAILURE;
     }
 
-    if (!CallNP_Initialize(error)) {
-        Close();
-        return NS_ERROR_FAILURE;
-    }
-    else if (*error != NPERR_NO_ERROR) {
-        Close();
-        return NS_OK;
+    *error = NPERR_NO_ERROR;
+    if (IsChrome()) {
+        if (!CallNP_Initialize(error)) {
+            Close();
+            return NS_ERROR_FAILURE;
+        }
+        else if (*error != NPERR_NO_ERROR) {
+            Close();
+            return NS_OK;
+        }
     }
 
     SetPluginFuncs(pFuncs);
 
     return NS_OK;
 }
 #else
 nsresult
@@ -1194,16 +1299,27 @@ PluginModuleParent::NP_Initialize(NPNets
 
     mNPNIface = bFuncs;
 
     if (mShutdown) {
         *error = NPERR_GENERIC_ERROR;
         return NS_ERROR_FAILURE;
     }
 
+    *error = NPERR_NO_ERROR;
+    return NS_OK;
+}
+
+nsresult
+PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
+{
+    nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error);
+    if (NS_FAILED(rv))
+        return rv;
+
     if (!CallNP_Initialize(error)) {
         Close();
         return NS_ERROR_FAILURE;
     }
     if (*error != NPERR_NO_ERROR) {
         Close();
         return NS_OK;
     }
@@ -1233,17 +1349,20 @@ PluginModuleParent::NP_Shutdown(NPError*
 {
     PLUGIN_LOG_DEBUG_METHOD;
 
     if (mShutdown) {
         *error = NPERR_GENERIC_ERROR;
         return NS_ERROR_FAILURE;
     }
 
-    bool ok = CallNP_Shutdown(error);
+    bool ok = true;
+    if (IsChrome()) {
+        ok = CallNP_Shutdown(error);
+    }
 
     // if NP_Shutdown() is nested within another interrupt call, this will
     // break things.  but lord help us if we're doing that anyway; the
     // plugin dso will have been unloaded on the other side by the
     // CallNP_Shutdown() message
     Close();
 
     return ok ? NS_OK : NS_ERROR_FAILURE;
@@ -1271,26 +1390,31 @@ PluginModuleParent::NP_GetValue(void *fu
 }
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
 nsresult
 PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
 {
     NS_ASSERTION(pFuncs, "Null pointer!");
 
-    // We need to have the child process update its function table
-    // here by actually calling NP_GetEntryPoints since the parent's
-    // function table can reflect nullptr entries in the child's table.
-    if (!CallNP_GetEntryPoints(error)) {
-        return NS_ERROR_FAILURE;
-    }
-    else if (*error != NPERR_NO_ERROR) {
-        return NS_OK;
+    // We need to have the plugin process update its function table here by
+    // actually calling NP_GetEntryPoints. The parent's function table will
+    // reflect nullptr entries in the child's table once SetPluginFuncs is
+    // called.
+
+    if (IsChrome()) {
+        if (!CallNP_GetEntryPoints(error)) {
+            return NS_ERROR_FAILURE;
+        }
+        else if (*error != NPERR_NO_ERROR) {
+            return NS_OK;
+        }
     }
 
+    *error = NPERR_NO_ERROR;
     SetPluginFuncs(pFuncs);
 
     return NS_OK;
 }
 #endif
 
 nsresult
 PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance,
@@ -1342,19 +1466,25 @@ PluginModuleParent::NPP_New(NPMIMEType p
         return NS_ERROR_FAILURE;
     }
 
     if (*error != NPERR_NO_ERROR) {
         NPP_Destroy(instance, 0);
         return NS_ERROR_FAILURE;
     }
 
+    UpdatePluginTimeout();
+
+    return NS_OK;
+}
+
+void
+PluginModuleChromeParent::UpdatePluginTimeout()
+{
     TimeoutChanged(kParentTimeoutPref, this);
-    
-    return NS_OK;
 }
 
 nsresult
 PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags,
                                       uint64_t maxAge)
 {
     if (!mClearSiteDataSupported)
         return NS_ERROR_NOT_AVAILABLE;
@@ -1523,25 +1653,38 @@ PluginModuleParent::RecvPluginHideWindow
     return false;
 #endif
 }
 
 PCrashReporterParent*
 PluginModuleParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id,
                                               uint32_t* processType)
 {
+    MOZ_CRASH("unreachable");
+}
+
+bool
+PluginModuleParent::DeallocPCrashReporterParent(PCrashReporterParent* actor)
+{
+    MOZ_CRASH("unreachable");
+}
+
+PCrashReporterParent*
+PluginModuleChromeParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id,
+                                                    uint32_t* processType)
+{
 #ifdef MOZ_CRASHREPORTER
     return new CrashReporterParent();
 #else
     return nullptr;
 #endif
 }
 
 bool
-PluginModuleParent::DeallocPCrashReporterParent(PCrashReporterParent* actor)
+PluginModuleChromeParent::DeallocPCrashReporterParent(PCrashReporterParent* actor)
 {
 #ifdef MOZ_CRASHREPORTER
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
     if (actor == static_cast<PCrashReporterParent*>(mCrashReporter)) {
         mCrashReporter = nullptr;
     }
 #endif
@@ -1617,42 +1760,44 @@ PluginModuleParent::RecvGetNativeCursors
 #else
     NS_NOTREACHED(
         "PluginInstanceParent::RecvGetNativeCursorSupportLevel not implemented!");
     return false;
 #endif
 }
 
 bool
-PluginModuleParent::RecvNPN_SetException(PPluginScriptableObjectParent* aActor,
-                                         const nsCString& aMessage)
+PluginModuleParent::RecvNPN_SetException(const nsCString& aMessage)
 {
     PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
 
-    NPObject* aNPObj = nullptr;
-    if (aActor) {
-        aNPObj = static_cast<PluginScriptableObjectParent*>(aActor)->GetObject(true);
-        if (!aNPObj) {
-            NS_ERROR("Failed to get object!");
-            return false;
-        }
-    }
-    mozilla::plugins::parent::_setexception(aNPObj, NullableStringGet(aMessage));
+    // This function ignores its first argument.
+    mozilla::plugins::parent::_setexception(nullptr, NullableStringGet(aMessage));
     return true;
 }
 
 bool
 PluginModuleParent::RecvNPN_ReloadPlugins(const bool& aReloadPages)
 {
     PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
 
     mozilla::plugins::parent::_reloadplugins(aReloadPages);
     return true;
 }
 
+bool
+PluginModuleChromeParent::RecvNotifyContentModuleDestroyed()
+{
+    nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+    if (host) {
+        host->NotifyContentModuleDestroyed(mPluginId);
+    }
+    return true;
+}
+
 #ifdef MOZ_CRASHREPORTER_INJECTOR
 
 // We only add the crash reporter to subprocess which have the filename
 // FlashPlayerPlugin*
 #define FLASH_PROCESS_PREFIX "FLASHPLAYERPLUGIN"
 
 static DWORD
 GetFlashChildOfPID(DWORD pid, HANDLE snapshot)
@@ -1673,17 +1818,17 @@ GetFlashChildOfPID(DWORD pid, HANDLE sna
     }
     return 0;
 }
 
 // We only look for child processes of the Flash plugin, NPSWF*
 #define FLASH_PLUGIN_PREFIX "NPSWF"
 
 void
-PluginModuleParent::InitializeInjector()
+PluginModuleChromeParent::InitializeInjector()
 {
     if (!Preferences::GetBool("dom.ipc.plugins.flash.subprocess.crashreporter.enabled", false))
         return;
 
     nsCString path(Process()->GetPluginFilePath().c_str());
     ToUpperCase(path);
     int32_t lastSlash = path.RFindCharInSet("\\/");
     if (kNotFound == lastSlash)
@@ -1705,17 +1850,17 @@ PluginModuleParent::InitializeInjector()
         mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, snapshot);
         if (mFlashProcess2) {
             InjectCrashReporterIntoProcess(mFlashProcess2, this);
         }
     }
 }
 
 void
-PluginModuleParent::OnCrash(DWORD processID)
+PluginModuleChromeParent::OnCrash(DWORD processID)
 {
     if (!mShutdown) {
         GetIPCChannel()->CloseWithError();
         KillProcess(OtherProcess(), 1, false);
     }
 }
 
 #endif // MOZ_CRASHREPORTER_INJECTOR
@@ -1751,26 +1896,26 @@ PluginProfilerObserver::Observe(nsISuppo
       if (success && !result.IsEmpty()) {
           pse->AddSubProfile(result.get());
       }
     }
     return NS_OK;
 }
 
 void
-PluginModuleParent::InitPluginProfiling()
+PluginModuleChromeParent::InitPluginProfiling()
 {
     nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
     if (observerService) {
         mProfilerObserver = new PluginProfilerObserver(this);
         observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false);
     }
 }
 
 void
-PluginModuleParent::ShutdownPluginProfiling()
+PluginModuleChromeParent::ShutdownPluginProfiling()
 {
     nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
     if (observerService) {
         observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
     }
 }
 #endif
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -48,104 +48,75 @@ class PluginHangUIParent;
  *
  * This class implements the NPP API from the perspective of the rest
  * of Gecko, forwarding NPP calls along to the child process that is
  * actually running the plugin.
  *
  * This class /also/ implements a version of the NPN API, because the
  * child process needs to make these calls back into Gecko proper.
  * This class is responsible for "actually" making those function calls.
+ *
+ * If a plugin is running, there will always be one PluginModuleParent for it in
+ * the chrome process. In addition, any content process using the plugin will
+ * have its own PluginModuleParent. The subclasses PluginModuleChromeParent and
+ * PluginModuleContentParent implement functionality that is specific to one
+ * case or the other.
  */
 class PluginModuleParent
     : public PPluginModuleParent
     , public PluginLibrary
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     , public CrashReporter::InjectorCrashCallback
 #endif
-    , public mozilla::HangMonitor::Annotator
 {
-private:
+protected:
     typedef mozilla::PluginLibrary PluginLibrary;
     typedef mozilla::dom::PCrashReporterParent PCrashReporterParent;
     typedef mozilla::dom::CrashReporterParent CrashReporterParent;
 
-protected:
-
     PPluginInstanceParent*
     AllocPPluginInstanceParent(const nsCString& aMimeType,
                                const uint16_t& aMode,
                                const InfallibleTArray<nsCString>& aNames,
                                const InfallibleTArray<nsCString>& aValues,
                                NPError* rv) MOZ_OVERRIDE;
 
     virtual bool
     DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) MOZ_OVERRIDE;
 
 public:
-    // aFilePath is UTF8, not native!
-    explicit PluginModuleParent(const char* aFilePath);
+    explicit PluginModuleParent(bool aIsChrome);
     virtual ~PluginModuleParent();
 
+    bool IsChrome() const { return mIsChrome; }
+
     virtual void SetPlugin(nsNPAPIPlugin* plugin) MOZ_OVERRIDE
     {
         mPlugin = plugin;
     }
 
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
-    /**
-     * LoadModule
-     *
-     * This may or may not launch a plugin child process,
-     * and may or may not be very expensive.
-     */
-    static PluginLibrary* LoadModule(const char* aFilePath);
-
     const NPNetscapeFuncs* GetNetscapeFuncs() {
         return mNPNIface;
     }
 
-    PluginProcessParent* Process() const { return mSubprocess; }
-    base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); }
-
     bool OkToCleanup() const {
         return !IsOnCxxStack();
     }
 
     void ProcessRemoteNativeEventsInInterruptCall();
 
-    void TerminateChildProcess(MessageLoop* aMsgLoop);
-
-    virtual void
-    EnteredCxxStack() MOZ_OVERRIDE;
-
-    virtual void
-    ExitedCxxStack() MOZ_OVERRIDE;
-
-    virtual void
-    AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) MOZ_OVERRIDE;
-
-#ifdef XP_WIN
-    /**
-     * Called by Plugin Hang UI to notify that the user has clicked continue.
-     * Used for chrome hang annotations.
-     */
-    void
-    OnHangUIContinue();
-#endif // XP_WIN
-
 protected:
     virtual mozilla::ipc::RacyInterruptPolicy
     MediateInterruptRace(const Message& parent, const Message& child) MOZ_OVERRIDE
     {
         return MediateRace(parent, child);
     }
 
-    virtual bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
-
     virtual bool
     RecvBackUpXResources(const FileDescriptor& aXSocketFd) MOZ_OVERRIDE;
 
     virtual bool
     AnswerNPN_UserAgent(nsCString* userAgent) MOZ_OVERRIDE;
 
     virtual bool
     AnswerNPN_GetValue_WithBoolReturn(const NPNVariable& aVariable,
@@ -182,37 +153,30 @@ protected:
 
     virtual bool
     RecvPopCursor() MOZ_OVERRIDE;
 
     virtual bool
     RecvGetNativeCursorsSupported(bool* supported) MOZ_OVERRIDE;
 
     virtual bool
-    RecvNPN_SetException(PPluginScriptableObjectParent* aActor,
-                         const nsCString& aMessage) MOZ_OVERRIDE;
+    RecvNPN_SetException(const nsCString& aMessage) MOZ_OVERRIDE;
 
     virtual bool
     RecvNPN_ReloadPlugins(const bool& aReloadPages) MOZ_OVERRIDE;
 
     static PluginInstanceParent* InstCast(NPP instance);
     static BrowserStreamParent* StreamCast(NPP instance, NPStream* s);
 
-private:
-    void SetPluginFuncs(NPPluginFuncs* aFuncs);
+protected:
+    virtual void UpdatePluginTimeout() {}
 
-    // Implement the module-level functions from NPAPI; these are
-    // normally resolved directly from the DSO.
-#ifdef OS_LINUX
-    NPError NP_Initialize(const NPNetscapeFuncs* npnIface,
-                          NPPluginFuncs* nppIface);
-#else
-    NPError NP_Initialize(const NPNetscapeFuncs* npnIface);
-    NPError NP_GetEntryPoints(NPPluginFuncs* nppIface);
-#endif
+    virtual bool RecvNotifyContentModuleDestroyed() MOZ_OVERRIDE { return true; }
+
+    void SetPluginFuncs(NPPluginFuncs* aFuncs);
 
     // NPP-like API that Gecko calls are trampolined into.  These 
     // messages then get forwarded along to the plugin instance,
     // and then eventually the child process.
 
     static NPError NPP_Destroy(NPP instance, NPSavedData** save);
 
     static NPError NPP_SetWindow(NPP instance, NPWindow* window);
@@ -250,16 +214,17 @@ private:
                                          const nsIntRect& aRect) MOZ_OVERRIDE;
 
 #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK)
     virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error);
 #else
     virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error);
 #endif
     virtual nsresult NP_Shutdown(NPError* error);
+
     virtual nsresult NP_GetMIMEDescription(const char** mimeDesc);
     virtual nsresult NP_GetValue(void *future, NPPVariable aVariable,
                                  void *aValue, NPError* error);
 #if defined(XP_WIN) || defined(XP_MACOSX)
     virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error);
 #endif
     virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
                              uint16_t mode, int16_t argc, char* argn[],
@@ -269,54 +234,152 @@ private:
                                        uint64_t maxAge);
     virtual nsresult NPP_GetSitesWithData(InfallibleTArray<nsCString>& result);
 
 #if defined(XP_MACOSX)
     virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing);
     virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor);
 #endif
 
-private:
-    CrashReporterParent* CrashReporter();
-
-#ifdef MOZ_CRASHREPORTER
-    void ProcessFirstMinidump();
-    void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
-#endif
-    void CleanupFromTimeout(const bool aByHangUI);
-    void SetChildTimeout(const int32_t aChildTimeout);
-    static void TimeoutChanged(const char* aPref, void* aModule);
+protected:
     void NotifyPluginCrashed();
 
-#ifdef MOZ_ENABLE_PROFILER_SPS
-    void InitPluginProfiling();
-    void ShutdownPluginProfiling();
-#endif
-
-    PluginProcessParent* mSubprocess;
+    bool mIsChrome;
     bool mShutdown;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     const NPNetscapeFuncs* mNPNIface;
     nsNPAPIPlugin* mPlugin;
     ScopedMethodFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
     nsRefPtr<nsIObserver> mProfilerObserver;
+    nsCString mPluginName;
+    nsCString mPluginVersion;
+
+#ifdef MOZ_X11
+    // Dup of plugin's X socket, used to scope its resources to this
+    // object instead of the plugin process's lifetime
+    ScopedClose mPluginXSocketFdDup;
+#endif
+
+    bool
+    GetPluginDetails(nsACString& aPluginName, nsACString& aPluginVersion);
+
+    friend class mozilla::dom::CrashReporterParent;
+};
+
+class PluginModuleContentParent : public PluginModuleParent
+{
+  public:
+    static PluginLibrary* LoadModule(uint32_t aPluginId);
+
+    static PluginModuleContentParent* Create(mozilla::ipc::Transport* aTransport,
+                                             base::ProcessId aOtherProcess);
+
+  private:
+    explicit PluginModuleContentParent();
+
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+    void OnCrash(DWORD processID) MOZ_OVERRIDE {}
+#endif
+
+    static PluginModuleContentParent* sSavedModuleParent;
+};
+
+class PluginModuleChromeParent
+    : public PluginModuleParent
+    , public mozilla::HangMonitor::Annotator
+{
+  public:
+    /**
+     * LoadModule
+     *
+     * This may or may not launch a plugin child process,
+     * and may or may not be very expensive.
+     */
+    static PluginLibrary* LoadModule(const char* aFilePath, uint32_t aPluginId);
+
+    virtual ~PluginModuleChromeParent();
+
+    void TerminateChildProcess(MessageLoop* aMsgLoop);
+
+#ifdef XP_WIN
+    /**
+     * Called by Plugin Hang UI to notify that the user has clicked continue.
+     * Used for chrome hang annotations.
+     */
+    void
+    OnHangUIContinue();
+#endif // XP_WIN
+
+private:
+    virtual void
+    EnteredCxxStack() MOZ_OVERRIDE;
+
+    void
+    ExitedCxxStack() MOZ_OVERRIDE;
+
+    virtual void
+    AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) MOZ_OVERRIDE;
+
+    virtual bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
+
+#ifdef MOZ_CRASHREPORTER
+    void ProcessFirstMinidump();
+    void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
+#endif
+
+    virtual PCrashReporterParent*
+    AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id,
+                              uint32_t* processType) MOZ_OVERRIDE;
+    virtual bool
+    DeallocPCrashReporterParent(PCrashReporterParent* actor) MOZ_OVERRIDE;
+
+    PluginProcessParent* Process() const { return mSubprocess; }
+    base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); }
+
+#if !defined(XP_UNIX) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GONK)
+    virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error);
+#endif
+
+    virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
+
+    // aFilePath is UTF8, not native!
+    explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId);
+
+    CrashReporterParent* CrashReporter();
+
+    void CleanupFromTimeout(const bool aByHangUI);
+    void SetChildTimeout(const int32_t aChildTimeout);
+    static void TimeoutChanged(const char* aPref, void* aModule);
+
+    virtual void UpdatePluginTimeout() MOZ_OVERRIDE;
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    void InitPluginProfiling();
+    void ShutdownPluginProfiling();
+#endif
+
+    virtual bool RecvNotifyContentModuleDestroyed() MOZ_OVERRIDE;
+
+    PluginProcessParent* mSubprocess;
+    uint32_t mPluginId;
+
+    ScopedMethodFactory<PluginModuleChromeParent> mChromeTaskFactory;
+
     enum HangAnnotationFlags
     {
         kInPluginCall = (1u << 0),
         kHangUIShown = (1u << 1),
         kHangUIContinued = (1u << 2),
         kHangUIDontShow = (1u << 3)
     };
     Atomic<uint32_t> mHangAnnotationFlags;
-    nsCString mPluginName;
-    nsCString mPluginVersion;
 #ifdef XP_WIN
     InfallibleTArray<float> mPluginCpuUsageOnHang;
     PluginHangUIParent *mHangUIParent;
     bool mHangUIEnabled;
     bool mIsTimerReset;
 #ifdef MOZ_CRASHREPORTER
     /**
      * This mutex protects the crash reporter when the Plugin Hang UI event
@@ -344,25 +407,16 @@ private:
 
     /**
      * Finishes the Plugin Hang UI and cancels if it is being shown to the user.
      */
     void
     FinishHangUI();
 #endif
 
-    bool
-    GetPluginDetails(nsACString& aPluginName, nsACString& aPluginVersion);
-
-#ifdef MOZ_X11
-    // Dup of plugin's X socket, used to scope its resources to this
-    // object instead of the plugin process's lifetime
-    ScopedClose mPluginXSocketFdDup;
-#endif
-
     friend class mozilla::dom::CrashReporterParent;
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     void InitializeInjector();
     
     void OnCrash(DWORD processID) MOZ_OVERRIDE;
 
     DWORD mFlashProcess1;
--- a/dom/plugins/ipc/PluginProcessChild.cpp
+++ b/dom/plugins/ipc/PluginProcessChild.cpp
@@ -114,19 +114,19 @@ PluginProcessChild::Init()
 #  error Sorry
 #endif
 
     if (NS_FAILED(nsRegion::InitStatic())) {
       NS_ERROR("Could not initialize nsRegion");
       return false;
     }
 
-    return mPlugin.Init(pluginFilename, ParentHandle(),
-                        IOThreadChild::message_loop(),
-                        IOThreadChild::channel());
+    return mPlugin.InitForChrome(pluginFilename, ParentHandle(),
+                                 IOThreadChild::message_loop(),
+                                 IOThreadChild::channel());
 }
 
 void
 PluginProcessChild::CleanUp()
 {
 #ifdef XP_WIN
     ::OleUninitialize();
 #endif
--- a/dom/plugins/ipc/PluginProcessChild.h
+++ b/dom/plugins/ipc/PluginProcessChild.h
@@ -14,17 +14,18 @@ namespace mozilla {
 namespace plugins {
 //-----------------------------------------------------------------------------
 
 class PluginProcessChild : public mozilla::ipc::ProcessChild {
 protected:
     typedef mozilla::ipc::ProcessChild ProcessChild;
 
 public:
-    explicit PluginProcessChild(ProcessHandle aParentHandle) : ProcessChild(aParentHandle)
+    explicit PluginProcessChild(ProcessHandle aParentHandle)
+      : ProcessChild(aParentHandle), mPlugin(true)
     { }
 
     virtual ~PluginProcessChild()
     { }
 
     virtual bool Init() MOZ_OVERRIDE;
     virtual void CleanUp() MOZ_OVERRIDE;
 
--- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp
+++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp
@@ -534,17 +534,17 @@ PluginScriptableObjectChild::PluginScrip
   AssertPluginThread();
 }
 
 PluginScriptableObjectChild::~PluginScriptableObjectChild()
 {
   AssertPluginThread();
 
   if (mObject) {
-    PluginModuleChild::current()->UnregisterActorForNPObject(mObject);
+    UnregisterActor(mObject);
 
     if (mObject->_class == GetClass()) {
       NS_ASSERTION(mType == Proxy, "Wrong type!");
       static_cast<ChildNPObject*>(mObject)->parent = nullptr;
     }
     else {
       NS_ASSERTION(mType == LocalObject, "Wrong type!");
       PluginModuleChild::sBrowserFuncs.releaseobject(mObject);
@@ -564,18 +564,18 @@ PluginScriptableObjectChild::InitializeP
   NS_ASSERTION(mInstance, "Null manager?!");
 
   NPObject* object = CreateProxyObject();
   if (!object) {
     NS_ERROR("Failed to create object!");
     return false;
   }
 
-  if (!PluginModuleChild::current()->RegisterActorForNPObject(object, this)) {
-    NS_ERROR("RegisterActorForNPObject failed");
+  if (!RegisterActor(object)) {
+    NS_ERROR("RegisterActor failed");
     return false;
   }
 
   mObject = object;
   return true;
 }
 
 void
@@ -589,18 +589,18 @@ PluginScriptableObjectChild::InitializeL
   mInstance = static_cast<PluginInstanceChild*>(Manager());
   NS_ASSERTION(mInstance, "Null manager?!");
 
   PluginModuleChild::sBrowserFuncs.retainobject(aObject);
 
   NS_ASSERTION(!mProtectCount, "Should be zero!");
   mProtectCount++;
 
-  if (!PluginModuleChild::current()->RegisterActorForNPObject(aObject, this)) {
-    NS_ERROR("RegisterActorForNPObject failed");
+  if (!RegisterActor(aObject)) {
+    NS_ERROR("RegisterActor failed");
   }
 
   mObject = aObject;
 }
 
 NPObject*
 PluginScriptableObjectChild::CreateProxyObject()
 {
@@ -683,17 +683,17 @@ void
 PluginScriptableObjectChild::DropNPObject()
 {
   NS_ASSERTION(mObject, "Invalidated object!");
   NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!");
   NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!");
 
   // We think we're about to be deleted, but we could be racing with the other
   // process.
-  PluginModuleChild::current()->UnregisterActorForNPObject(mObject);
+  UnregisterActor(mObject);
   mObject = nullptr;
 
   SendUnprotect();
 }
 
 void
 PluginScriptableObjectChild::NPObjectDestroyed()
 {
@@ -1176,8 +1176,110 @@ PluginScriptableObjectChild::Evaluate(NP
 
   if (!success) {
     return false;
   }
 
   ConvertToVariant(result, *aResult);
   return true;
 }
+
+nsTHashtable<PluginScriptableObjectChild::NPObjectData>* PluginScriptableObjectChild::sObjectMap;
+
+bool
+PluginScriptableObjectChild::RegisterActor(NPObject* aObject)
+{
+  AssertPluginThread();
+  MOZ_ASSERT(aObject, "Null pointer!");
+
+  NPObjectData* d = sObjectMap->GetEntry(aObject);
+  if (!d) {
+    NS_ERROR("NPObject not in object table");
+    return false;
+  }
+
+  d->actor = this;
+  return true;
+}
+
+void
+PluginScriptableObjectChild::UnregisterActor(NPObject* aObject)
+{
+  AssertPluginThread();
+  MOZ_ASSERT(aObject, "Null pointer!");
+
+  NPObjectData* d = sObjectMap->GetEntry(aObject);
+  MOZ_ASSERT(d, "NPObject not in object table");
+  if (d) {
+    d->actor = nullptr;
+  }
+}
+
+/* static */ PluginScriptableObjectChild*
+PluginScriptableObjectChild::GetActorForNPObject(NPObject* aObject)
+{
+  AssertPluginThread();
+  MOZ_ASSERT(aObject, "Null pointer!");
+
+  NPObjectData* d = sObjectMap->GetEntry(aObject);
+  if (!d) {
+    NS_ERROR("Plugin using object not created with NPN_CreateObject?");
+    return nullptr;
+  }
+
+  return d->actor;
+}
+
+/* static */ void
+PluginScriptableObjectChild::RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance)
+{
+  AssertPluginThread();
+
+  if (!sObjectMap) {
+    sObjectMap = new nsTHashtable<PluginScriptableObjectChild::NPObjectData>();
+  }
+
+  NPObjectData* d = sObjectMap->PutEntry(aObject);
+  MOZ_ASSERT(!d->instance, "New NPObject already mapped?");
+  d->instance = aInstance;
+}
+
+/* static */ void
+PluginScriptableObjectChild::UnregisterObject(NPObject* aObject)
+{
+  AssertPluginThread();
+
+  sObjectMap->RemoveEntry(aObject);
+
+  if (!sObjectMap->Count()) {
+    delete sObjectMap;
+    sObjectMap = nullptr;
+  }
+}
+
+/* static */ PluginInstanceChild*
+PluginScriptableObjectChild::GetInstanceForNPObject(NPObject* aObject)
+{
+  AssertPluginThread();
+  NPObjectData* d = sObjectMap->GetEntry(aObject);
+  if (!d) {
+    return nullptr;
+  }
+  return d->instance;
+}
+
+/* static */ PLDHashOperator
+PluginScriptableObjectChild::CollectForInstance(NPObjectData* d, void* userArg)
+{
+    PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(userArg);
+    if (d->instance == instance) {
+        NPObject* o = d->GetKey();
+        instance->mDeletingHash->PutEntry(o);
+    }
+    return PL_DHASH_NEXT;
+}
+
+/* static */ void
+PluginScriptableObjectChild::NotifyOfInstanceShutdown(PluginInstanceChild* aInstance)
+{
+  AssertPluginThread();
+  sObjectMap->EnumerateEntries(CollectForInstance, aInstance);
+}
--- a/dom/plugins/ipc/PluginScriptableObjectChild.h
+++ b/dom/plugins/ipc/PluginScriptableObjectChild.h
@@ -212,16 +212,32 @@ public:
     DISALLOW_COPY_AND_ASSIGN(StackIdentifier);
 
     PluginIdentifier mIdentifier;
     nsRefPtr<StoredIdentifier> mStored;
   };
 
   static void ClearIdentifiers();
 
+  bool RegisterActor(NPObject* aObject);
+  void UnregisterActor(NPObject* aObject);
+
+  static PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject);
+
+  static void RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance);
+  static void UnregisterObject(NPObject* aObject);
+
+  static PluginInstanceChild* GetInstanceForNPObject(NPObject* aObject);
+
+  /**
+   * Fill PluginInstanceChild.mDeletingHash with all the remaining NPObjects
+   * associated with that instance.
+   */
+  static void NotifyOfInstanceShutdown(PluginInstanceChild* aInstance);
+
 private:
   static NPObject*
   ScriptableAllocate(NPP aInstance,
                      NPClass* aClass);
 
   static void
   ScriptableInvalidate(NPObject* aObject);
 
@@ -292,14 +308,37 @@ private:
 
   static const NPClass sNPClass;
 
   static StoredIdentifier* HashIdentifier(const nsCString& aIdentifier);
   static void UnhashIdentifier(StoredIdentifier* aIdentifier);
 
   typedef nsDataHashtable<nsCStringHashKey, nsRefPtr<StoredIdentifier>> IdentifierTable;
   static IdentifierTable sIdentifiers;
+
+  struct NPObjectData : public nsPtrHashKey<NPObject>
+  {
+    explicit NPObjectData(const NPObject* key)
+    : nsPtrHashKey<NPObject>(key),
+      instance(nullptr),
+      actor(nullptr)
+    { }
+
+    // never nullptr
+    PluginInstanceChild* instance;
+
+    // sometimes nullptr (no actor associated with an NPObject)
+    PluginScriptableObjectChild* actor;
+  };
+
+  static PLDHashOperator CollectForInstance(NPObjectData* d, void* userArg);
+
+  /**
+   * mObjectMap contains all the currently active NPObjects (from NPN_CreateObject until the
+   * final release/dealloc, whether or not an actor is currently associated with the object.
+   */
+  static nsTHashtable<NPObjectData>* sObjectMap;
 };
 
 } /* namespace plugins */
 } /* namespace mozilla */
 
 #endif /* dom_plugins_PluginScriptableObjectChild_h */
--- a/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h
+++ b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h
@@ -85,17 +85,17 @@ mozilla::plugins::ConvertToVariant(const
 
       npn->retainobject(object);
       OBJECT_TO_NPVARIANT(object, aVariant);
       break;
     }
 
     case Variant::TPPluginScriptableObjectChild: {
       NS_ASSERTION(!aInstance, "No instance should be given!");
-      NS_ASSERTION(PluginModuleChild::current(),
+      NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin,
                    "Should be running on child only!");
 
       NPObject* object = NPObjectFromVariant(aRemoteVariant);
       NS_ASSERTION(object, "Null object?!");
 
       PluginModuleChild::sBrowserFuncs.retainobject(object);
       OBJECT_TO_NPVARIANT(object, aVariant);
       break;
--- a/dom/plugins/ipc/PluginScriptableObjectUtils.h
+++ b/dom/plugins/ipc/PluginScriptableObjectUtils.h
@@ -107,17 +107,17 @@ ReleaseRemoteVariant(Variant& aVariant)
         const_cast<PluginScriptableObjectParent*>(
           reinterpret_cast<const PluginScriptableObjectParent*>(
             aVariant.get_PPluginScriptableObjectParent()));
       actor->Unprotect();
       break;
     }
 
     case Variant::TPPluginScriptableObjectChild: {
-      NS_ASSERTION(PluginModuleChild::current(),
+      NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin,
                    "Should only be running in the child!");
       PluginScriptableObjectChild* actor =
         const_cast<PluginScriptableObjectChild*>(
           reinterpret_cast<const PluginScriptableObjectChild*>(
             aVariant.get_PPluginScriptableObjectChild()));
       actor->Unprotect();
       break;
     }
--- a/dom/plugins/ipc/PluginTypes.ipdlh
+++ b/dom/plugins/ipc/PluginTypes.ipdlh
@@ -1,16 +1,32 @@
 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* 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/. */
 
 namespace mozilla {
 namespace plugins {
 
+struct PluginTag
+{
+  uint32_t id;
+  nsCString name;
+  nsCString description;
+  nsCString[] mimeTypes;
+  nsCString[] mimeDescriptions;
+  nsCString[] extensions;
+  bool isJavaPlugin;
+  bool isFlashPlugin;
+  nsCString filename;
+  nsCString version;
+  int64_t lastModifiedTime;
+  bool isFromExtension;
+};
+
 union PluginIdentifier
 {
   nsCString;
   int32_t;
 };
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -16,16 +16,17 @@ EXPORTS.mozilla.plugins += [
     'BrowserStreamChild.h',
     'BrowserStreamParent.h',
     'ChildAsyncCall.h',
     'ChildTimer.h',
     'NPEventAndroid.h',
     'NPEventOSX.h',
     'NPEventUnix.h',
     'NPEventWindows.h',
+    'PluginBridge.h',
     'PluginInstanceChild.h',
     'PluginInstanceParent.h',
     'PluginMessageUtils.h',
     'PluginModuleChild.h',
     'PluginModuleParent.h',
     'PluginProcessChild.h',
     'PluginProcessParent.h',
     'PluginScriptableObjectChild.h',
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') || e10s #b2g-desktop(tests that use plugins)
+skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') || (e10s && debug) #b2g-desktop(tests that use plugins)
 support-files =
   307-xo-redirect.sjs
   crashing_subpage.html
   file_bug738396.html
   file_bug771202.html
   file_bug863792.html
   large-pic.jpg
   loremipsum.txt
@@ -58,35 +58,35 @@ skip-if = (!crashreporter) || true # Bug
 [test_CrashService_crash.html]
 skip-if = !crashreporter || e10s
 [test_CrashService_hang.html]
 skip-if = !crashreporter || e10s
 [test_defaultValue.html]
 [test_enumerate.html]
 [test_fullpage.html]
 [test_getauthenticationinfo.html]
+skip-if = e10s
 [test_hanging.html]
-skip-if = !crashreporter
+skip-if = !crashreporter || e10s
 [test_instance_re-parent.html]
 [test_instance_unparent1.html]
 [test_instance_unparent2.html]
 [test_instance_unparent3.html]
 [test_instantiation.html]
 [test_mixed_case_mime.html]
 [test_multipleinstanceobjects.html]
 [test_newstreamondestroy.html]
 [test_npn_asynccall.html]
 [test_npn_timers.html]
 [test_npobject_getters.html]
 [test_npruntime_construct.html]
 [test_npruntime_identifiers.html]
 [test_npruntime_npnevaluate.html]
 [test_npruntime_npninvoke.html]
 [test_npruntime_npninvokedefault.html]
-[test_npruntime_npnsetexception.html]
 [test_painting.html]
 [test_plugin_scroll_painting.html]
 skip-if = true # Bug 596491
 [test_pluginstream_asfile.html]
 [test_pluginstream_asfileonly.html]
 [test_pluginstream_err.html]
 [test_pluginstream_geturl.html]
 [test_pluginstream_geturlnotify.html]
@@ -103,15 +103,16 @@ skip-if = true # Bug 596491
 skip-if = true # disabled due to oddness, perhaps scrolling of the mochitest window?
 [test_propertyAndMethod.html]
 [test_queryContentsScaleFactor.html]
 skip-if = toolkit != "cocoa"
 [test_redirect_handling.html]
 [test_secondPlugin.html]
 [test_src_url_change.html]
 [test_streamNotify.html]
+skip-if = e10s
 [test_streamatclose.html]
 [test_twostreams.html]
 [test_windowed_invalidate.html]
 skip-if = os != "win"
 [test_visibility.html]
 skip-if = toolkit == "cocoa"
 [test_zero_opacity.html]
--- a/dom/plugins/test/mochitest/test_instance_re-parent.html
+++ b/dom/plugins/test/mochitest/test_instance_re-parent.html
@@ -5,17 +5,17 @@
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="utils.js"></script>
 </head>
 <body onload="begin()">
   <script type="application/javascript;version=1.8">
   SimpleTest.waitForExplicitFinish();
-  getTestPlugin().enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED;
+  setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
   var exceptionThrown = false;
   var p = null;
   var d1 = null;
   var d2 = null;
 
   var destroyed = false;
 
deleted file mode 100644
--- a/dom/plugins/test/mochitest/test_npruntime_npnsetexception.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<html>
-<head>
-  <title>NPN_SetException Tests</title>
-  <script type="text/javascript" 
-          src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="utils.js"></script>
-  <link rel="stylesheet" type="text/css" 
-        href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="runTests()">
-  <script class="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-  setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
-
-  function runTests() {
-    // test a single exception thrown in scriptable invoke
-    var plugin = document.getElementById("plugin1");
-    plugin.throwExceptionNextInvoke();
-    try {
-      plugin.npnInvokeTest("badFunction");
-      ok(false, "exception not thrown");
-    }
-    catch (e) {
-      is(e, "badFunction", "wrong exception thrown");
-    }
-    
-    // test multiple exceptions thrown in scriptable invokedefault
-    plugin.throwExceptionNextInvoke();
-    try {
-      plugin("first exception", "second exception");
-      ok(false, "exception not thrown");
-    }
-    catch (e) {
-      is(e, "second exception", "wrong exception thrown");
-    }    
-
-    // test calling exception with NULL message
-    plugin.throwExceptionNextInvoke();
-    try {
-      plugin();
-      ok(false, "exception not thrown");
-    }
-    catch (e) {
-      is(e.message, "Error calling method on NPObject!", "wrong exception thrown");
-    }    
-
-    SimpleTest.finish();
-  }
-  </script>
-
-  <p id="display"></p>
-
-  <embed id="plugin1" type="application/x-test" width="400" height="100">
-  </embed>
-
-  <div id="verbose">
-  </div>
- </body>
- </html>
--- a/dom/plugins/test/mochitest/test_secondPlugin.html
+++ b/dom/plugins/test/mochitest/test_secondPlugin.html
@@ -38,35 +38,36 @@
       function run() {
         // Add "Test Plug-in" (but not "Second Test Plug-in") to the list of
         // unhidden plugins. This test must modify the "plugins.enumerable_names"
         // pref BEFORE accessing the navigator.plugins or navigator.mimeTypes
         // arrays because they only read the pref when they first initialize
         // their internal arrays!
         var prefs = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch);
         var defaultEnumerableNamesPref = prefs.getCharPref("plugins.enumerable_names");
-        prefs.setCharPref("plugins.enumerable_names", defaultEnumerableNamesPref + ",Test Plug-in");
-
+        SpecialPowers.pushPrefEnv(
+          {'set': [["plugins.enumerable_names", defaultEnumerableNamesPref + ",Test Plug-in"]]},
+          finishRun
+        );
+      }
+      function finishRun() {
         var pluginElement = document.getElementById("plugin");
         is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin");
 
         ok(navigator.plugins["Test Plug-in"], "Should have queried a non-hidden plugin named 'Test Plug-in'");
         ok(navigator.plugins["Second Test Plug-in"], "Should have queried a hidden plugin named 'Test Plug-in'");
 
         ok(findPlugin("Test Plug-in"), "Should have found a non-hidden plugin named 'Test Plug-in'");
         ok(!findPlugin("Second Test Plug-in"), "Should NOT found a hidden plugin named 'Test Plug-in'");
 
         ok(navigator.mimeTypes["application/x-test"], "Should have queried a non-hidden MIME type named 'application/x-test'");
         ok(navigator.mimeTypes["application/x-second-test"], "Should have queried a MIME type named 'application/x-second-test'");
 
         ok(findMimeType("application/x-test"), "Should have found a non-hidden MIME type named 'application/x-test'");
         ok(!findMimeType("application/x-second-test"), "Should NOT have found a MIME type named 'application/x-second-test'");
 
-        // Restore original pref to hide "Test Plug-in" and "Second Test Plug-in".
-        prefs.setCharPref("plugins.enumerable_names", defaultEnumerableNamesPref);
-
         SimpleTest.finish();
       }
     </script>
 
     <object id="plugin" type="application/x-second-test" width=200 height=200></object>
   </body>
 </html>
--- a/dom/plugins/test/mochitest/utils.js
+++ b/dom/plugins/test/mochitest/utils.js
@@ -30,21 +30,29 @@ function getTestPlugin(pluginName) {
   ok(false, "Could not find plugin tag with plugin name '" + name + "'");
   return null;
 }
 
 // call this to set the test plugin(s) initially expected enabled state.
 // it will automatically be reset to it's previous value after the test
 // ends
 function setTestPluginEnabledState(newEnabledState, pluginName) {
+  var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+  if (!oldEnabledState) {
+    ok(false, "Cannot find plugin '" + plugin + "'");
+    return;
+  }
   var plugin = getTestPlugin(pluginName);
-  var oldEnabledState = plugin.enabledState;
-  plugin.enabledState = newEnabledState;
+  while (plugin.enabledState != newEnabledState) {
+    // Run a nested event loop to wait for the preference change to
+    // propagate to the child. Yuck!
+    SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+  }
   SimpleTest.registerCleanupFunction(function() {
-    getTestPlugin(pluginName).enabledState = oldEnabledState;
+    SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
   });
 }
 
 function crashAndGetCrashServiceRecord(crashMethodName, callback) {
   var crashMan =
     SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").
     Services.crashmanager;
 
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -1980,16 +1980,20 @@ scriptableSetProperty(NPObject* npobj, N
 }
 
 bool
 scriptableRemoveProperty(NPObject* npobj, NPIdentifier name)
 {
   for (int i = 0; i < int(ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) {
     if (name == sPluginPropertyIdentifiers[i]) {
       NPN_ReleaseVariantValue(&sPluginPropertyValues[i]);
+
+      // Avoid double frees (see test_propertyAndMethod.html, which deletes a
+      // property that doesn't exist).
+      VOID_TO_NPVARIANT(sPluginPropertyValues[i]);
       return true;
     }
   }
   return false;
 }
 
 bool
 scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, uint32_t* count)
--- a/dom/settings/SettingsManager.js
+++ b/dom/settings/SettingsManager.js
@@ -50,17 +50,22 @@ function SettingsLock(aSettingsManager) 
   this._settingsManager = aSettingsManager;
   this._id = uuidgen.generateUUID().toString();
 
   // DOMRequestIpcHelper.initHelper sets this._window
   this.initDOMRequestHelper(this._settingsManager._window, ["Settings:Get:OK", "Settings:Get:KO",
                                                             "Settings:Clear:OK", "Settings:Clear:KO",
                                                             "Settings:Set:OK", "Settings:Set:KO",
                                                             "Settings:Finalize:OK", "Settings:Finalize:KO"]);
-  this.sendMessage("Settings:CreateLock", {lockID: this._id, isServiceLock: false});
+  let createLockPayload = {
+    lockID: this._id,
+    isServiceLock: false,
+    windowID: this._settingsManager.innerWindowID
+  };
+  this.sendMessage("Settings:CreateLock", createLockPayload);
   Services.tm.currentThread.dispatch(this._closeHelper.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
 
   // We only want to file closeHelper once per set of receiveMessage calls.
   this._closeCalled = true;
 }
 
 SettingsLock.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
@@ -250,17 +255,16 @@ SettingsLock.prototype = {
                                          Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
 function SettingsManager() {
   this._callbacks = null;
   this._isRegistered = false;
   this._locks = [];
-  this._principal = null;
 }
 
 SettingsManager.prototype = {
   _wrap: function _wrap(obj) {
     return Cu.cloneInto(obj, this._window);
   },
 
   set onsettingchange(aHandler) {
@@ -371,27 +375,28 @@ SettingsManager.prototype = {
     }
     this.checkMessageRegistration();
   },
 
   init: function(aWindow) {
     if (DEBUG) debug("SettingsManager init");
     mrm.registerStrongReporter(this);
     cpmm.addMessageListener("Settings:Change:Return:OK", this);
-    this._window = aWindow;
-    this._principal = this._window.document.nodePrincipal;
-    Services.obs.addObserver(this, "dom-window-destroyed", false);
+    Services.obs.addObserver(this, "inner-window-destroyed", false);
     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     this.innerWindowID = util.currentInnerWindowID;
+    this._window = aWindow;
   },
 
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "dom-window-destroyed") {
-      let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      if (window == this._window) {
+    if (DEBUG) debug("Topic: " + aTopic);
+    if (aTopic === "inner-window-destroyed") {
+      let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+      if (wId === this.innerWindowID) {
+        if (DEBUG) debug("Received: inner-window-destroyed for valid innerWindowID=" + wId + ", cleanup.");
         this.cleanup();
       }
     }
   },
 
   collectReports: function(aCallback, aData, aAnonymize) {
     for (let topic in this._callbacks) {
       let length = this._callbacks[topic].length;
@@ -412,31 +417,21 @@ SettingsManager.prototype = {
                          Ci.nsIMemoryReporter.UNITS_COUNT,
                          this._callbacks[topic].length,
                          "The number of settings observers for this topic.",
                          aData);
     }
   },
 
   cleanup: function() {
-    Services.obs.removeObserver(this, "dom-window-destroyed");
+    Services.obs.removeObserver(this, "inner-window-destroyed");
     cpmm.removeMessageListener("Settings:Change:Return:OK", this);
     mrm.unregisterStrongReporter(this);
-    // At this point, the window is dying, so there's nothing left
-    // that we could do with our lock. Go ahead and run finalize on
-    // it to make sure changes are commited.
-    for (let i = 0; i < this._locks.length; ++i) {
-      if (DEBUG) debug("Lock alive at destroy, finalizing: " + this._locks[i]);
-      cpmm.sendAsyncMessage("Settings:Finalize",
-                            {lockID: this._locks[i]},
-                            undefined,
-                            this._principal);
-    }
+    this.innerWindowID = null;
     this._window = null;
-    this._innerWindowID = null;
   },
 
   classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),
   contractID: "@mozilla.org/settingsManager;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer,
                                          Ci.nsIObserver,
                                          Ci.nsIMemoryReporter]),
--- a/dom/settings/SettingsRequestManager.jsm
+++ b/dom/settings/SettingsRequestManager.jsm
@@ -13,16 +13,17 @@ const Cu = Components.utils;
 this.EXPORTED_SYMBOLS = [];
 
 Cu.import("resource://gre/modules/SettingsDB.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PermissionsTable.jsm");
 
 const kXpcomShutdownObserverTopic      = "xpcom-shutdown";
+const kInnerWindowDestroyed            = "inner-window-destroyed";
 const kMozSettingsChangedObserverTopic = "mozsettings-changed";
 const kSettingsReadSuffix              = "-read";
 const kSettingsWriteSuffix             = "-write";
 const kSettingsClearPermission         = "settings-clear";
 const kAllSettingsReadPermission       = "settings" + kSettingsReadSuffix;
 const kAllSettingsWritePermission      = "settings" + kSettingsWriteSuffix;
 // Any application with settings permissions, be it for all settings
 // or a single one, will need to be able to access the settings API.
@@ -66,22 +67,24 @@ let SettingsPermissions = {
     return this.hasAllReadPermission(aPrincipal) || this.checkPermission(aPrincipal, "settings:" + aSettingsName + kSettingsReadSuffix);
   },
   hasWritePermission: function(aPrincipal, aSettingsName) {
     return this.hasAllWritePermission(aPrincipal) || this.checkPermission(aPrincipal, "settings:" + aSettingsName + kSettingsWriteSuffix);
   }
 };
 
 
-function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock) {
+function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock, aWindowID) {
   return {
     // ID Shared with the object on the child side
     lockID: aLockID,
     // Is this a content lock or a settings service lock?
     isServiceLock: aIsServiceLock,
+    // Which inner window ID
+    windowID: aWindowID,
     // Tasks to be run once the lock is at the head of the queue
     tasks: [],
     // This is set to true once a transaction is ready to run, but is not at the
     // head of the lock queue.
     consumable: false,
     // Holds values that are requested to be set until the lock lifetime ends,
     // then commits them to the DB.
     queuedSets: {},
@@ -166,16 +169,17 @@ let SettingsRequestManager = {
 
   init: function() {
     if (DEBUG) debug("init");
     this.settingsDB.init();
     this.messages.forEach((function(msgName) {
       ppmm.addMessageListener(msgName, this);
     }).bind(this));
     Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
+    Services.obs.addObserver(this, kInnerWindowDestroyed, false);
   },
 
   _serializePreservingBinaries: function _serializePreservingBinaries(aObject) {
     function needsUUID(aValue) {
       if (!aValue || !aValue.constructor) {
         return false;
       }
       return (aValue.constructor.name == "Date") || (aValue instanceof Ci.nsIDOMFile) ||
@@ -643,25 +647,31 @@ let SettingsRequestManager = {
       function(task) {
         this.runTasks(lockID);
       }.bind(this), function(ret) {
         dump("-*- SettingsRequestManager: SETTINGS DATABASE ERROR: Cannot make DB connection!\n");
     });
   },
 
   observe: function(aSubject, aTopic, aData) {
-    if (DEBUG) debug("observe");
+    if (DEBUG) debug("observe: " + aTopic);
     switch (aTopic) {
       case kXpcomShutdownObserverTopic:
         this.messages.forEach((function(msgName) {
           ppmm.removeMessageListener(msgName, this);
         }).bind(this));
         Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
         ppmm = null;
         break;
+
+      case kInnerWindowDestroyed:
+        let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+        this.forceFinalizeChildLocksNonOOP(wId);
+        break;
+
       default:
         if (DEBUG) debug("Wrong observer topic: " + aTopic);
         break;
     }
   },
 
   sendSettingsChange: function(aKey, aValue, aIsServiceLock) {
     this.broadcastMessage("Settings:Change:Return:OK",
@@ -740,52 +750,69 @@ let SettingsRequestManager = {
     // If index is 0, the lock we just removed was at the head of
     // the queue, so possibly queue the next lock if it's
     // consumable.
     if (index == 0) {
       this.queueConsume();
     }
   },
 
-  removeMessageManager: function(aMsgMgr, aPrincipal) {
-    if (DEBUG) debug("Removing message manager");
+  hasLockFinalizeTask: function(lock) {
+    // Go in reverse order because finalize should be the last one
+    for (let task_index = lock.tasks.length; task_index >= 0; task_index--) {
+      if (lock.tasks[task_index]
+          && lock.tasks[task_index].operation === "finalize") {
+        return true;
+      }
+    }
+    return false;
+  },
+
+  enqueueForceFinalize: function(lock, principal) {
+    if (!this.hasLockFinalizeTask(lock)) {
+      if (DEBUG) debug("Alive lock has pending tasks: " + lock.lockID);
+      this.queueTask("finalize", {lockID: lock.lockID}, principal).then(
+        function() {
+          if (DEBUG) debug("Alive lock " + lockId + " succeeded to force-finalize");
+        },
+        function(error) {
+          if (DEBUG) debug("Alive lock " + lockId + " failed to force-finalize due to error: " + error);
+        }
+      );
+    }
+  },
+
+  forceFinalizeChildLocksNonOOP: function(windowId) {
+    if (DEBUG) debug("Forcing finalize on child locks, non OOP");
+
+    for (let lockId of Object.keys(this.lockInfo)) {
+      let lock = this.lockInfo[lockId];
+      if (lock.windowID === windowId) {
+        let principal = this.mmPrincipals.get(lock._mm);
+        this.enqueueForceFinalize(lock, principal);
+      }
+    }
+  },
+
+  forceFinalizeChildLocksOOP: function(aMsgMgr, aPrincipal) {
+    if (DEBUG) debug("Forcing finalize on child locks, OOP");
+
     let msgMgrPrincipal = this.mmPrincipals.get(aMsgMgr);
     this.removeObserver(aMsgMgr);
 
-    let lockIDs = Object.keys(this.lockInfo);
-    for (let i in lockIDs) {
-      let lockId = lockIDs[i];
+    for (let lockId of Object.keys(this.lockInfo)) {
       let lock = this.lockInfo[lockId];
       if (lock._mm === aMsgMgr && msgMgrPrincipal === aPrincipal) {
-        let is_finalizing = false;
-        let task_index;
-        // Go in reverse order because finalize should be the last one
-        for (task_index = lock.tasks.length; task_index >= 0; task_index--) {
-          if (lock.tasks[task_index]
-              && lock.tasks[task_index].operation === "finalize") {
-            is_finalizing = true;
-            break;
-          }
-        }
-        if (!is_finalizing) {
-          this.queueTask("finalize", {lockID: lockId}, aPrincipal).then(
-            function() {
-              if (DEBUG) debug("Lock " + lockId + " with dead message manager finalized");
-            },
-            function(error) {
-              if (DEBUG) debug("Lock " + lockId + " with dead message manager NOT FINALIZED due to error: " + error);
-            }
-          );
-        }
+        this.enqueueForceFinalize(lock, aPrincipal);
       }
     }
   },
 
   receiveMessage: function(aMessage) {
-    if (DEBUG) debug("receiveMessage " + aMessage.name);
+    if (DEBUG) debug("receiveMessage " + aMessage.name + ": " + JSON.stringify(aMessage.data));
 
     let msg = aMessage.data;
     let mm = aMessage.target;
 
     function returnMessage(name, data) {
       try {
         mm.sendAsyncMessage(name, data);
       } catch (e) {
@@ -827,42 +854,46 @@ let SettingsRequestManager = {
         }
       default:
       break;
     }
 
     switch (aMessage.name) {
       case "child-process-shutdown":
         if (DEBUG) debug("Child process shutdown received.");
-        this.removeMessageManager(mm, aMessage.principal);
+        this.forceFinalizeChildLocksOOP(mm, aMessage.principal);
         break;
       case "Settings:RegisterForMessages":
         if (!SettingsPermissions.hasSomeReadPermission(aMessage.principal)) {
           Cu.reportError("Settings message " + aMessage.name +
                          " from a content process with no 'settings-api-read' privileges.");
           aMessage.target.assertPermission("message-manager-no-read-kill");
           return;
         }
         this.addObserver(mm, aMessage.principal);
         break;
       case "Settings:UnregisterForMessages":
         this.removeObserver(mm);
         break;
       case "Settings:CreateLock":
-        if (DEBUG) debug("Received CreateLock for " + msg.lockID + " from " + aMessage.principal.origin);
+        if (DEBUG) debug("Received CreateLock for " + msg.lockID + " from " + aMessage.principal.origin + " window: " + msg.windowID);
         // If we try to create a lock ID that collides with one
         // already in the system, consider it a security violation and
         // kill.
         if (msg.lockID in this.settingsLockQueue) {
           Cu.reportError("Trying to queue a lock with the same ID as an already queued lock. Killing app.");
           aMessage.target.assertPermission("lock-id-duplicate-kill");
           return;
         }
         this.settingsLockQueue.push(msg.lockID);
-        this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB, mm, msg.lockID, msg.isServiceLock);
+        this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB,
+                                                     mm,
+                                                     msg.lockID,
+                                                     msg.isServiceLock,
+                                                     msg.windowID);
         break;
       case "Settings:Get":
         if (DEBUG) debug("Received getRequest from " + msg.lockID);
         this.queueTask("get", msg, aMessage.principal).then(function(settings) {
             returnMessage("Settings:Get:OK", {
               lockID: msg.lockID,
               requestID: msg.requestID,
               settings: settings
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -104,16 +104,21 @@ typedef Tuple3<NetdCommand*, CommandChai
 // A macro for native function call return value check.
 // For native function call, non-zero return value means failure.
 #define RETURN_IF_FAILED(rv) do { \
   if (SUCCESS != rv) { \
     return rv; \
   } \
 } while (0);
 
+#define WARN_IF_FAILED(rv) do { \
+  if (SUCCESS != rv) { \
+    WARN("Error (%d) occurred in %s (%s:%d)", rv, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
+  } \
+} while (0);
 
 static NetworkUtils* gNetworkUtils;
 static nsTArray<QueueData> gCommandQueue;
 static CurrentCommand gCurrentCommand;
 static bool gPending = false;
 static nsTArray<nsCString> gReason;
 static NetworkParams *gWifiTetheringParms = 0;
 
@@ -1383,17 +1388,17 @@ CommandResult NetworkUtils::resetConnect
 CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
 {
   NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
 
   if (!aOptions.mOldIfname.IsEmpty()) {
     // Remove IPv4's default route.
     RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname)));
     // Remove IPv6's default route.
-    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
+    WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
   }
 
   uint32_t length = aOptions.mGateways.Length();
   if (length > 0) {
     for (uint32_t i = 0; i < length; i++) {
       NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
 
       int type = getIpType(autoGateway.get());
@@ -1439,19 +1444,19 @@ CommandResult NetworkUtils::removeDefaul
   for (uint32_t i = 0; i < length; i++) {
     NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
 
     int type = getIpType(autoGateway.get());
     if (type != AF_INET && type != AF_INET6) {
       return EAFNOSUPPORT;
     }
 
-    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
-                                                    type == AF_INET ? "0.0.0.0" : "::",
-                                                    0, autoGateway.get()));
+    WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
+                                                  type == AF_INET ? "0.0.0.0" : "::",
+                                                  0, autoGateway.get()));
   }
 
   return SUCCESS;
 }
 
 /**
  * Add host route for given network interface.
  */
@@ -1538,17 +1543,17 @@ CommandResult NetworkUtils::removeNetwor
     }
 
     char subnetStr[INET6_ADDRSTRLEN];
     if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
       return EINVAL;
     }
 
     // Remove default route.
-    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
+    WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
 
     // Remove subnet route.
     RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL));
     return SUCCESS;
   }
 
   /* type == AF_INET */
   uint32_t ip = inet_addr(autoIp.get());
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -528,16 +528,20 @@ this.ICC_EF_PNN    = 0x6fc5;
 this.ICC_EF_OPL    = 0x6fc6;
 this.ICC_EF_MBDN   = 0x6fc7;
 this.ICC_EF_EXT6   = 0x6fc8;   // Ext record for EF[MBDN]
 this.ICC_EF_MBI    = 0x6fc9;
 this.ICC_EF_MWIS   = 0x6fca;
 this.ICC_EF_CFIS   = 0x6fcb;
 this.ICC_EF_SPDI   = 0x6fcd;
 
+// CPHS files to be supported
+this.ICC_EF_CPHS_INFO = 0x6f16; // CPHS Information
+this.ICC_EF_CPHS_MBN  = 0x6f17; // Mailbox Numbers
+
 // CSIM files
 this.ICC_EF_CSIM_IMSI_M   = 0x6f22;
 this.ICC_EF_CSIM_CDMAHOME = 0x6f28;
 this.ICC_EF_CSIM_CST      = 0x6f32; // CDMA Service table
 this.ICC_EF_CSIM_SPN      = 0x6f41;
 
 this.ICC_PHASE_1 = 0x00;
 this.ICC_PHASE_2 = 0x02;
@@ -1304,16 +1308,28 @@ this.GECKO_ICC_SERVICES = {
     MWIS: 48,
     SPDI: 51
   },
   // @see 3GPP2 C.S0023-D 3.4.18 (RUIM).
   ruim: {
     ENHANCED_PHONEBOOK: 6,
     SPN: 17,
     SDN: 18
+  },
+  // @see B.3.1.1 CPHS Information in CPHS Phase 2:
+  // Indicates which of the CPHS 'optional' data-fields are present in the SIM card:
+  //   EF_CPHS_CSP, EF_CPHS_SST, EF_CPHS_MBN, EF_CPHS_ONSF, EF_CPHS_INFO_NUM
+  // Note: Mandatory EFs are: (B.3.1 Enhanced SIM Requirements)
+  //   EF_CPHS_CFF, EF_CPHS_VMI, EF_CPHS_ONS, EF_CPHS_INFO
+  cphs: {
+    CSP: 1,
+    SST: 2,
+    MBN: 3,
+    ONSF: 4,
+    INFO_NUM: 5
   }
 };
 
 /**
  * Cell Broadcast constants
  */
 
 this.CB_FORMAT_GSM  = 0;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -12145,16 +12145,18 @@ ICCFileHelperObject.prototype = {
       case ICC_EF_SST:
       case ICC_EF_PHASE:
       case ICC_EF_CBMI:
       case ICC_EF_CBMID:
       case ICC_EF_CBMIR:
       case ICC_EF_OPL:
       case ICC_EF_PNN:
       case ICC_EF_GID1:
+      case ICC_EF_CPHS_INFO:
+      case ICC_EF_CPHS_MBN:
         return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
       default:
         return null;
     }
   },
 
   /**
    * This function handles EFs for USIM.
@@ -12171,16 +12173,22 @@ ICCFileHelperObject.prototype = {
       case ICC_EF_SPDI:
       case ICC_EF_CBMI:
       case ICC_EF_CBMID:
       case ICC_EF_CBMIR:
       case ICC_EF_OPL:
       case ICC_EF_PNN:
       case ICC_EF_SMS:
       case ICC_EF_GID1:
+      // CPHS spec was provided in 1997 based on SIM requirement, there is no
+      // detailed info about how these ICC_EF_CPHS_XXX are allocated in USIM.
+      // What we can do now is to follow what has been done in AOSP to have file
+      // path equal to MF_SIM/DF_GSM.
+      case ICC_EF_CPHS_INFO:
+      case ICC_EF_CPHS_MBN:
         return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
       default:
         // The file ids in USIM phone book entries are decided by the
         // card manufacturer. So if we don't match any of the cases
         // above and if its a USIM return the phone book path.
         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
     }
   },
@@ -13096,17 +13104,28 @@ SimRecordHelperObject.prototype = {
   context: null,
 
   /**
    * Fetch (U)SIM records.
    */
   fetchSimRecords: function() {
     this.context.RIL.getIMSI();
     this.readAD();
-    this.readSST();
+    // CPHS was widely introduced in Europe during GSM(2G) era to provide easier
+    // access to carrier's core service like voicemail, call forwarding, manual
+    // PLMN selection, and etc.
+    // Addition EF like EF_CPHS_MBN, EF_CPHS_CPHS_CFF, EF_CPHS_VWI, etc are
+    // introduced to support these feature.
+    // In USIM, the replancement of these EFs are provided. (EF_MBDN, EF_MWIS, ...)
+    // However, some carriers in Europe still rely on these EFs.
+    this.readCphsInfo(() => this.readSST(),
+                      (aErrorMsg) => {
+                        this.context.debug("Failed to read CPHS_INFO: " + aErrorMsg);
+                        this.readSST();
+                      });
   },
 
   /**
    * Read EF_phase.
    * This EF is only available in SIM.
    */
   readSimPhase: function() {
     function callback() {
@@ -13427,16 +13446,23 @@ SimRecordHelperObject.prototype = {
         if (DEBUG) this.context.debug("SPN: SPN service is not available");
       }
 
       if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
         if (DEBUG) this.context.debug("MDN: MDN available.");
         this.readMBDN();
       } else {
         if (DEBUG) this.context.debug("MDN: MDN service is not available");
+
+        if (ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+          // read CPHS_MBN in advance if MBDN is not available.
+          this.readCphsMBN();
+        } else {
+          if (DEBUG) this.context.debug("CPHS_MBN: CPHS_MBN service is not available");
+        }
       }
 
       if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
         if (DEBUG) this.context.debug("MWIS: MWIS is available");
         this.readMWIS();
       } else {
         if (DEBUG) this.context.debug("MWIS: MWIS is not available");
       }
@@ -13499,16 +13525,25 @@ SimRecordHelperObject.prototype = {
    *
    * @see TS 131.102, clause 4.2.60
    */
   readMBDN: function() {
     function callback(options) {
       let RIL = this.context.RIL;
       let contact =
         this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+      if ((!contact ||
+           ((!contact.alphaId || contact.alphaId == "") &&
+            (!contact.number || contact.number == ""))) &&
+          this.context.ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+        // read CPHS_MBN in advance if MBDN is invalid or empty.
+        this.readCphsMBN();
+        return;
+      }
+
       if (!contact ||
           (RIL.iccInfoPrivate.mbdn !== undefined &&
            RIL.iccInfoPrivate.mbdn === contact.number)) {
         return;
       }
       RIL.iccInfoPrivate.mbdn = contact.number;
       if (DEBUG) {
         this.context.debug("MBDN, alphaId=" + contact.alphaId +
@@ -14076,16 +14111,127 @@ SimRecordHelperObject.prototype = {
       }
     }
 
     this.context.ICCIOHelper.loadTransparentEF({
       fileId: ICC_EF_GID1,
       callback: callback.bind(this)
     });
   },
+
+  /**
+   * Read CPHS Phase & Service Table from CPHS Info.
+   *
+   * @See  B.3.1.1 CPHS Information in CPHS Phase 2.
+   *
+   * @param onsuccess     Callback to be called when success.
+   * @param onerror       Callback to be called when error.
+   */
+  readCphsInfo: function(onsuccess, onerror) {
+    function callback() {
+      try {
+        let Buf = this.context.Buf;
+        let RIL = this.context.RIL;
+
+        let strLen = Buf.readInt32();
+        // Each octet is encoded into two chars.
+        let octetLen = strLen / 2;
+        let cphsInfo = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+        Buf.readStringDelimiter(strLen);
+        if (DEBUG) {
+          let str = "";
+          for (let i = 0; i < cphsInfo.length; i++) {
+            str += cphsInfo[i] + ", ";
+          }
+          this.context.debug("CPHS INFO: " + str);
+        }
+
+        /**
+         * CPHS INFORMATION
+         *
+         * Byte 1: CPHS Phase
+         * 01 phase 1
+         * 02 phase 2
+         * etc.
+         *
+         * Byte 2: CPHS Service Table
+         * +----+----+----+----+----+----+----+----+
+         * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+         * +----+----+----+----+----+----+----+----+
+         * |   ONSF  |   MBN   |   SST   |   CSP   |
+         * | Phase 2 |   ALL   | Phase 1 |   All   |
+         * +----+----+----+----+----+----+----+----+
+         *
+         * Byte 3: CPHS Service Table continued
+         * +----+----+----+----+----+----+----+----+
+         * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+         * +----+----+----+----+----+----+----+----+
+         * |   RFU   |   RFU   |   RFU   | INFO_NUM|
+         * |         |         |         | Phase 2 |
+         * +----+----+----+----+----+----+----+----+
+         */
+        let cphsPhase = cphsInfo[0];
+        if (cphsPhase == 1) {
+          // Clear 'Phase 2 only' services.
+          cphsInfo[1] &= 0x3F;
+          // We don't know whether Byte 3 is available in CPHS phase 1 or not.
+          // Add boundary check before accessing it.
+          if (cphsInfo.length > 2) {
+            cphsInfo[2] = 0x00;
+          }
+        } else if (cphsPhase == 2) {
+          // Clear 'Phase 1 only' services.
+          cphsInfo[1] &= 0xF3;
+        } else {
+          throw new Error("Unknown CPHS phase: " + cphsPhase);
+        }
+
+        RIL.iccInfoPrivate.cphsSt = cphsInfo.subarray(1);
+        onsuccess();
+      } catch(e) {
+        onerror(e.toString());
+      }
+    }
+
+    this.context.ICCIOHelper.loadTransparentEF({
+      fileId: ICC_EF_CPHS_INFO,
+      callback: callback.bind(this),
+      onerror: onerror
+    });
+  },
+
+  /**
+   * Read CPHS MBN. (Mailbox Numbers)
+   *
+   * @See B.4.2.2 Voice Message Retrieval and Indicator Clearing
+   */
+  readCphsMBN: function() {
+    function callback(options) {
+      let RIL = this.context.RIL;
+      let contact =
+        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+      if (!contact ||
+          (RIL.iccInfoPrivate.mbdn !== undefined &&
+           RIL.iccInfoPrivate.mbdn === contact.number)) {
+        return;
+      }
+      RIL.iccInfoPrivate.mbdn = contact.number;
+      if (DEBUG) {
+        this.context.debug("CPHS_MDN, alphaId=" + contact.alphaId +
+                           " number=" + contact.number);
+      }
+      contact.rilMessageType = "iccmbdn";
+      RIL.sendChromeMessage(contact);
+    }
+
+    this.context.ICCIOHelper.loadLinearFixedEF({
+      fileId: ICC_EF_CPHS_MBN,
+      callback: callback.bind(this)
+    });
+  }
 };
 
 function RuimRecordHelperObject(aContext) {
   this.context = aContext;
 }
 RuimRecordHelperObject.prototype = {
   context: null,
 
@@ -14657,18 +14803,18 @@ ICCUtilsHelperObject.prototype = {
        * Service id is valid in 1..N, and 2 bits are used to code each service.
        *
        * +----+--  --+----+----+
        * | b8 | ...  | b2 | b1 |
        * +----+--  --+----+----+
        *
        * b1 = 0, service not allocated.
        *      1, service allocated.
-       * b2 = 0, service not activatd.
-       *      1, service allocated.
+       * b2 = 0, service not activated.
+       *      1, service activated.
        *
        * @see 3GPP TS 51.011 10.3.7.
        */
       let simService;
       if (RIL.appType == CARD_APPTYPE_SIM) {
         simService = GECKO_ICC_SERVICES.sim[geckoService];
       } else {
         simService = GECKO_ICC_SERVICES.ruim[geckoService];
@@ -14684,18 +14830,16 @@ ICCUtilsHelperObject.prototype = {
        * Service id is valid in 1..N, and 1 bit is used to code each service.
        *
        * +----+--  --+----+----+
        * | b8 | ...  | b2 | b1 |
        * +----+--  --+----+----+
        *
        * b1 = 0, service not avaiable.
        *      1, service available.
-       * b2 = 0, service not avaiable.
-       *      1, service available.
        *
        * @see 3GPP TS 31.102 4.2.8.
        */
       let usimService = GECKO_ICC_SERVICES.usim[geckoService];
       if (!usimService) {
         return false;
       }
       usimService -= 1;
@@ -14704,16 +14848,59 @@ ICCUtilsHelperObject.prototype = {
     }
 
     return (serviceTable !== null) &&
            (index < serviceTable.length) &&
            ((serviceTable[index] & bitmask) !== 0);
   },
 
   /**
+   * Get whether specificed CPHS service is available.
+   *
+   * @param geckoService
+   *        Service name like "MDN", etc.
+   *
+   * @return true if the service is enabled, false otherwise.
+   */
+  isCphsServiceAvailable: function(geckoService) {
+    let RIL = this.context.RIL;
+    let serviceTable = RIL.iccInfoPrivate.cphsSt;
+
+    if (!(serviceTable instanceof Uint8Array)) {
+      return false;
+    }
+
+    /**
+     * Service id is valid in 1..N, and 2 bits are used to code each service.
+     *
+     * +----+--  --+----+----+
+     * | b8 | ...  | b2 | b1 |
+     * +----+--  --+----+----+
+     *
+     * b1 = 0, service not allocated.
+     *      1, service allocated.
+     * b2 = 0, service not activated.
+     *      1, service activated.
+     *
+     * @See  B.3.1.1 CPHS Information in CPHS Phase 2.
+     */
+    let cphsService  = GECKO_ICC_SERVICES.cphs[geckoService];
+
+    if (!cphsService) {
+      return false;
+    }
+    cphsService -= 1;
+    let index = Math.floor(cphsService / 4);
+    let bitmask = 2 << ((cphsService % 4) << 1);
+
+    return (index < serviceTable.length) &&
+           ((serviceTable[index] & bitmask) !== 0);
+  },
+
+  /**
    * Check if the string is of GSM default 7-bit coded alphabets with bit 8
    * set to 0.
    *
    * @param str  String to be checked.
    */
   isGsm8BitAlphabet: function(str) {
     if (!str) {
       return false;
--- a/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js
@@ -291,8 +291,36 @@ add_test(function test_get_network_name_
 
   // Current PLMN is not HPLMN, and according to LAC, we should get
   // the 8th PNN record after wild char (ie: ';') handling.
   testGetNetworkNameFromICC({mcc: "002", mnc: "03", lac: 0x0001},
                             {longName: "PNN8Long", shortName: "PNN8Short"});
 
   run_next_test();
 });
+
+/**
+ * Verify ICCUtilsHelper.isCphsServiceAvailable.
+ */
+add_test(function test_is_cphs_service_available() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let ICCUtilsHelper = context.ICCUtilsHelper;
+  let RIL = context.RIL;
+  RIL.iccInfoPrivate.cphsSt = Uint8Array(2);
+
+  function test_table(cphsSt, geckoService) {
+    RIL.iccInfoPrivate.cphsSt.set(cphsSt);
+
+    for (let service in GECKO_ICC_SERVICES.cphs) {
+      do_check_eq(ICCUtilsHelper.isCphsServiceAvailable(service),
+                  geckoService == service);
+    }
+  }
+
+  test_table([0x03, 0x00], "CSP");
+  test_table([0x0C, 0x00], "SST");
+  test_table([0x30, 0x00], "MBN");
+  test_table([0xC0, 0x00], "ONSF");
+  test_table([0x00, 0x03], "INFO_NUM");
+
+  run_next_test();
+});
--- a/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js
@@ -155,50 +155,60 @@ add_test(function test_reading_optional_
   do_test(buildSST(supportedEf), supportedEf);
 
   run_next_test();
 });
 
 /**
  * Verify fetchSimRecords.
  */
-add_test(function test_fetch_sim_recodes() {
+add_test(function test_fetch_sim_records() {
   let worker = newWorker();
   let context = worker.ContextPool._contexts[0];
   let RIL = context.RIL;
   let iccRecord = context.ICCRecordHelper;
   let simRecord = context.SimRecordHelper;
 
-  function testFetchSimRecordes(expectCalled) {
+  function testFetchSimRecordes(expectCalled, expectCphsSuccess) {
     let ifCalled = [];
 
     RIL.getIMSI = function() {
       ifCalled.push("getIMSI");
     };
 
     simRecord.readAD = function() {
       ifCalled.push("readAD");
     };
 
+    simRecord.readCphsInfo = function(onsuccess, onerror) {
+      ifCalled.push("readCphsInfo");
+      if (expectCphsSuccess) {
+        onsuccess();
+      } else {
+        onerror();
+      }
+    };
+
     simRecord.readSST = function() {
       ifCalled.push("readSST");
     };
 
     simRecord.fetchSimRecords();
 
     for (let i = 0; i < expectCalled.length; i++ ) {
       if (ifCalled[i] != expectCalled[i]) {
         do_print(expectCalled[i] + " is not called.");
         do_check_true(false);
       }
     }
   }
 
-  let expectCalled = ["getIMSI", "readAD", "readSST"];
-  testFetchSimRecordes(expectCalled);
+  let expectCalled = ["getIMSI", "readAD", "readCphsInfo", "readSST"];
+  testFetchSimRecordes(expectCalled, true);
+  testFetchSimRecordes(expectCalled, false);
 
   run_next_test();
 });
 
 /**
  * Verify SimRecordHelper.readMWIS
  */
 add_test(function test_read_mwis() {
@@ -777,17 +787,17 @@ add_test(function test_reading_img_basic
 add_test(function test_reading_img_length_error() {
   let worker = newUint8Worker();
   let context = worker.ContextPool._contexts[0];
   let record = context.SimRecordHelper;
   let helper = context.GsmPDUHelper;
   let ril    = context.RIL;
   let buf    = context.Buf;
   let io     = context.ICCIOHelper;
- 
+
   let test_data = [
     {/* Offset length not enough, should be 4. */
      img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x04, 0x00, 0x06],
      iidf: [0xff, 0xff, 0xff, // Offset.
             0x05, 0x05, 0x11, 0x22, 0x33, 0xfe]},
     {/* iidf data length not enough, should be 6. */
      img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06],
      iidf: [0x05, 0x05, 0x11, 0x22, 0x33]}];
@@ -1287,8 +1297,209 @@ add_test(function test_reading_img_color
     record.readIMG(0, onsuccess);
   }
 
   for (let i = 0; i< test_data.length; i++) {
     do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected);
   }
   run_next_test();
 });
+
+/**
+ * Verify SimRecordHelper.readCphsInfo
+ */
+add_test(function test_read_cphs_info() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let RIL = context.RIL;
+  let pduHelper = context.GsmPDUHelper;
+  let recordHelper = context.SimRecordHelper;
+  let buf  = context.Buf;
+  let io  = context.ICCIOHelper;
+  let cphsPDU = Uint8Array(3);
+
+  io.loadTransparentEF = function(options) {
+    if (cphsPDU) {
+      // Write data size
+      buf.writeInt32(cphsPDU.length * 2);
+
+      // Write CPHS INFO
+      for (let i = 0; i < cphsPDU.length; i++) {
+        pduHelper.writeHexOctet(cphsPDU[i]);
+      }
+
+      // Write string delimiter
+      buf.writeStringDelimiter(cphsPDU.length * 2);
+
+      if (options.callback) {
+        options.callback(options);
+      }
+    } else {
+      do_print("cphsPDU[] is not set.");
+    }
+  };
+
+  function do_test(cphsInfo, cphsSt) {
+    let onsuccess = false;
+    let onerror = false;
+
+    delete RIL.iccInfoPrivate.cphsSt;
+    cphsPDU.set(cphsInfo);
+    recordHelper.readCphsInfo(() => { onsuccess = true; },
+                              () => { onerror = true; });
+
+    do_check_true((cphsSt) ? onsuccess : onerror);
+    do_check_false((cphsSt) ? onerror : onsuccess);
+    if (cphsSt) {
+      do_check_eq(RIL.iccInfoPrivate.cphsSt.length, cphsSt.length);
+      for (let i = 0; i < cphsSt.length; i++) {
+        do_check_eq(RIL.iccInfoPrivate.cphsSt[i], cphsSt[i]);
+      }
+    } else {
+      do_check_eq(RIL.iccInfoPrivate.cphsSt, cphsSt);
+    }
+  }
+
+  do_test([
+    0x01, // Phase 1
+    0xFF, // All available & activated
+    0x03  // All available & activated
+  ],
+  [
+    0x3F, // All services except ONSF(bit 8-7) are available and activated.
+    0x00  // INFO_NUM shall not be available & activated.
+  ]);
+
+  do_test([
+    0x02, // Phase 2
+    0xFF, // All available & activated
+    0x03  // All available & activated
+  ],
+  [
+    0xF3, // All services except ONSF are available and activated.
+    0x03  // INFO_NUM shall not be available & activated.
+  ]);
+
+  do_test([
+    0x03, // Phase 3
+    0xFF, // All available & activated
+    0x03  // All available & activated
+  ],
+  undefined); // RIL.iccInfoPrivate.cphsSt shall be remained as 'undefined'.
+
+  run_next_test();
+});
+
+/**
+ * Verify SimRecordHelper.readMBDN/SimRecordHelper.readCphsMBN
+ */
+add_test(function test_read_voicemail_number() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let RIL = context.RIL;
+  let pduHelper = context.GsmPDUHelper;
+  let recordHelper = context.SimRecordHelper;
+  let buf    = context.Buf;
+  let io     = context.ICCIOHelper;
+  let postedMessage;
+
+  worker.postMessage = function(message) {
+    postedMessage = message;
+  };
+
+  io.loadLinearFixedEF = function(options) {
+    let mbnData = [
+      0x56, 0x6F, 0x69, 0x63, 0x65, 0x6D, 0x61, 0x69,
+      0x6C, 0xFF, // Alpha Identifier: Voicemail
+      0x03,       // Length of BCD number: 3
+      0x80,       // TOA: Unknown
+      0x11, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, // Dialing Number: 111
+      0xFF,       // Capability/Configuration Record Identifier
+      0xFF        // Extension Record Identifier
+    ];
+
+    // Write data size
+    buf.writeInt32(mbnData.length * 2);
+
+    // Write MBN
+    for (let i = 0; i < mbnData.length; i++) {
+      pduHelper.writeHexOctet(mbnData[i]);
+    }
+
+    // Write string delimiter
+    buf.writeStringDelimiter(mbnData.length * 2);
+
+    options.recordSize = mbnData.length;
+    if (options.callback) {
+      options.callback(options);
+    }
+  };
+
+  function do_test(funcName, msgCount) {
+    postedMessage = null;
+    delete RIL.iccInfoPrivate.mbdn;
+    recordHelper[funcName]();
+
+    do_check_eq("iccmbdn", postedMessage.rilMessageType);
+    do_check_eq("Voicemail", postedMessage.alphaId);
+    do_check_eq("111", postedMessage.number);
+  }
+
+  do_test("readMBDN");
+  do_test("readCphsMBN");
+
+  run_next_test();
+});
+
+/**
+ * Verify the recovery from SimRecordHelper.readCphsMBN() if MBDN is not valid
+ * or is empty after SimRecordHelper.readMBDN().
+ */
+add_test(function test_read_mbdn_recovered_from_cphs_mbn() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let RIL = context.RIL;
+  let pduHelper = context.GsmPDUHelper;
+  let recordHelper = context.SimRecordHelper;
+  let iccUtilsHelper = context.ICCUtilsHelper;
+  let buf    = context.Buf;
+  let io     = context.ICCIOHelper;
+
+  io.loadLinearFixedEF = function(options) {
+    let mbnData = [
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+    ];
+
+    // Write data size
+    buf.writeInt32(mbnData.length * 2);
+
+    // Write MBN
+    for (let i = 0; i < mbnData.length; i++) {
+      pduHelper.writeHexOctet(mbnData[i]);
+    }
+
+    // Write string delimiter
+    buf.writeStringDelimiter(mbnData.length * 2);
+
+    options.recordSize = mbnData.length;
+    if (options.callback) {
+      options.callback(options);
+    }
+  };
+
+  iccUtilsHelper.isCphsServiceAvailable = function(geckoService) {
+    return geckoService == "MBN";
+  };
+
+  let isRecovered = false;
+  recordHelper.readCphsMBN = function(onComplete) {
+    isRecovered = true;
+  };
+
+  recordHelper.readMBDN();
+
+  do_check_eq(RIL.iccInfoPrivate.mbdn, undefined);
+  do_check_true(isRecovered);
+
+  run_next_test();
+});
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1229,16 +1229,34 @@ var interfaceNamesInGlobalScope =
     {name: "TreeColumns", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeContentView", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeSelection", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TreeWalker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVChannel", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVCurrentChannelChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVCurrentSourceChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVEITBroadcastedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVManager", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVProgram", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVScanningStateChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVSource", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVTuner", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "UDPMessageEvent", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "UDPSocket", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UIEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UndoManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/tv/FakeTVService.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TVServiceRunnables.h"
+#include "nsCOMPtr.h"
+#include "nsIMutableArray.h"
+#include "nsServiceManagerUtils.h"
+#include "FakeTVService.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(FakeTVService, nsITVService)
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::GetSourceListener(nsITVSourceListener** aSourceListener)
+{
+  *aSourceListener = mSourceListener;
+  NS_ADDREF(*aSourceListener);
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::SetSourceListener(nsITVSourceListener* aSourceListener)
+{
+  mSourceListener = aSourceListener;
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::GetTuners(nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> tunerDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!tunerDataList) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, tunerDataList);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::SetSource(const nsAString& aTunerId,
+                         const nsAString& aSourceType,
+                         nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, nullptr);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::StartScanningChannels(const nsAString& aTunerId,
+                                     const nsAString& aSourceType,
+                                     nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, nullptr);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::StopScanningChannels(const nsAString& aTunerId,
+                                    const nsAString& aSourceType,
+                                    nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, nullptr);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::ClearScannedChannelsCache()
+{
+  // TODO Implement in follow-up patches.
+
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::SetChannel(const nsAString& aTunerId,
+                          const nsAString& aSourceType,
+                          const nsAString& aChannelNumber,
+                          nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!channelDataList) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, channelDataList);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::GetChannels(const nsAString& aTunerId,
+                           const nsAString& aSourceType,
+                           nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!channelDataList) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, channelDataList);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::GetPrograms(const nsAString& aTunerId,
+                           const nsAString& aSourceType,
+                           const nsAString& aChannelNumber,
+                           uint64_t startTime,
+                           uint64_t endTime,
+                           nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> programDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!programDataList) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, programDataList);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+/* virtual */ NS_IMETHODIMP
+FakeTVService::GetOverlayId(const nsAString& aTunerId,
+                            nsITVServiceCallback* aCallback)
+{
+  if (!aCallback) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> overlayIds = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!overlayIds) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO Implement in follow-up patches.
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new TVServiceNotifyRunnable(aCallback, overlayIds);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/FakeTVService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FakeTVService_h
+#define mozilla_dom_FakeTVService_h
+
+#include "nsCOMPtr.h"
+#include "nsITVService.h"
+
+#define FAKE_TV_SERVICE_CONTRACTID \
+  "@mozilla.org/tv/faketvservice;1"
+#define FAKE_TV_SERVICE_CID \
+  { 0x60fb3c53, 0x017f, 0x4340, { 0x91, 0x1b, 0xd5, 0x5c, 0x31, 0x28, 0x88, 0xb6 } }
+
+namespace mozilla {
+namespace dom {
+
+class FakeTVService MOZ_FINAL : public nsITVService
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITVSERVICE
+
+  FakeTVService() {}
+
+  // TODO More members might be added in follow-up patches to help testing.
+
+private:
+  ~FakeTVService() {}
+
+  nsCOMPtr<nsITVSourceListener> mSourceListener;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FakeTVService_h
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVChannel.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TVServiceCallbacks.h"
+#include "mozilla/dom/TVServiceFactory.h"
+#include "mozilla/dom/TVSource.h"
+#include "mozilla/dom/TVTuner.h"
+#include "mozilla/dom/TVUtils.h"
+#include "nsITVService.h"
+#include "nsServiceManagerUtils.h"
+#include "TVChannel.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TVChannel, DOMEventTargetHelper,
+                                   mTVService, mSource)
+
+NS_IMPL_ADDREF_INHERITED(TVChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVChannel, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVChannel)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVChannel::TVChannel(nsPIDOMWindow* aWindow,
+                     TVSource* aSource)
+  : DOMEventTargetHelper(aWindow)
+  , mSource(aSource)
+{
+  MOZ_ASSERT(mSource);
+}
+
+TVChannel::~TVChannel()
+{
+}
+
+/* static */ already_AddRefed<TVChannel>
+TVChannel::Create(nsPIDOMWindow* aWindow,
+                  TVSource* aSource,
+                  nsITVChannelData* aData)
+{
+  nsRefPtr<TVChannel> channel = new TVChannel(aWindow, aSource);
+  return (channel->Init(aData)) ? channel.forget() : nullptr;
+}
+
+bool
+TVChannel::Init(nsITVChannelData* aData)
+{
+  NS_ENSURE_TRUE(aData, false);
+
+  nsString channelType;
+  aData->GetType(channelType);
+  mType = ToTVChannelType(channelType);
+  if (NS_WARN_IF(mType == TVChannelType::EndGuard_)) {
+    return false;
+  }
+
+  aData->GetNetworkId(mNetworkId);
+  aData->GetTransportStreamId(mTransportStreamId);
+  aData->GetServiceId(mServiceId);
+  aData->GetName(mName);
+  aData->GetNumber(mNumber);
+  aData->GetIsEmergency(&mIsEmergency);
+  aData->GetIsFree(&mIsFree);
+
+  mTVService = TVServiceFactory::AutoCreateTVService();
+  NS_ENSURE_TRUE(mTVService, false);
+
+  return true;
+}
+
+/* virtual */ JSObject*
+TVChannel::WrapObject(JSContext* aCx)
+{
+  return TVChannelBinding::Wrap(aCx, this);
+}
+
+nsresult
+TVChannel::DispatchTVEvent(nsIDOMEvent* aEvent)
+{
+  return DispatchTrustedEvent(aEvent);
+}
+
+already_AddRefed<Promise>
+TVChannel::GetPrograms(const TVGetProgramsOptions& aOptions, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<TVTuner> tuner = mSource->Tuner();
+  nsString tunerId;
+  tuner->GetId(tunerId);
+
+  uint64_t startTime = aOptions.mStartTime.WasPassed() ?
+                       aOptions.mStartTime.Value() :
+                       PR_Now();
+  uint64_t endTime = aOptions.mDuration.WasPassed() ?
+                     (startTime + aOptions.mDuration.Value()) :
+                     std::numeric_limits<uint64_t>::max();
+  nsCOMPtr<nsITVServiceCallback> callback =
+    new TVServiceProgramGetterCallback(this, promise, false);
+  nsresult rv =
+    mTVService->GetPrograms(tunerId,
+                            ToTVSourceTypeStr(mSource->Type()),
+                            mNumber,
+                            startTime,
+                            endTime,
+                            callback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
+
+  return promise.forget();
+}
+
+void
+TVChannel::GetNetworkId(nsAString& aNetworkId) const
+{
+  aNetworkId = mNetworkId;
+}
+
+void
+TVChannel::GetTransportStreamId(nsAString& aTransportStreamId) const
+{
+  aTransportStreamId = mTransportStreamId;
+}
+
+void
+TVChannel::GetServiceId(nsAString& aServiceId) const
+{
+  aServiceId = mServiceId;
+}
+
+already_AddRefed<TVSource>
+TVChannel::Source() const
+{
+  nsRefPtr<TVSource> source = mSource;
+  return source.forget();
+}
+
+TVChannelType
+TVChannel::Type() const
+{
+  return mType;
+}
+
+void
+TVChannel::GetName(nsAString& aName) const
+{
+  aName = mName;
+}
+
+void
+TVChannel::GetNumber(nsAString& aNumber) const
+{
+  aNumber = mNumber;
+}
+
+bool
+TVChannel::IsEmergency() const
+{
+  return mIsEmergency;
+}
+
+bool
+TVChannel::IsFree() const
+{
+  return mIsFree;
+}
+
+already_AddRefed<Promise>
+TVChannel::GetCurrentProgram(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<TVTuner> tuner = mSource->Tuner();
+  nsString tunerId;
+  tuner->GetId(tunerId);
+
+  // Get only one program from now on.
+  nsCOMPtr<nsITVServiceCallback> callback =
+    new TVServiceProgramGetterCallback(this, promise, true);
+  nsresult rv =
+    mTVService->GetPrograms(tunerId,
+                            ToTVSourceTypeStr(mSource->Type()),
+                            mNumber,
+                            PR_Now(),
+                            std::numeric_limits<uint64_t>::max(),
+                            callback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
+
+  return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVChannel.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVChannel_h
+#define mozilla_dom_TVChannel_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+// Include TVChannelBinding.h since enum TVChannelType can't be forward declared.
+#include "mozilla/dom/TVChannelBinding.h"
+
+class nsITVChannelData;
+class nsITVService;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class TVProgram;
+class TVSource;
+
+class TVChannel MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVChannel, DOMEventTargetHelper)
+
+  static already_AddRefed<TVChannel> Create(nsPIDOMWindow* aWindow,
+                                            TVSource* aSource,
+                                            nsITVChannelData* aData);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  nsresult DispatchTVEvent(nsIDOMEvent* aEvent);
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<Promise> GetPrograms(const TVGetProgramsOptions& aOptions,
+                                        ErrorResult& aRv);
+
+  already_AddRefed<Promise> GetCurrentProgram(ErrorResult& aRv);
+
+  void GetNetworkId(nsAString& aNetworkId) const;
+
+  void GetTransportStreamId(nsAString& aTransportStreamId) const;
+
+  void GetServiceId(nsAString& aServiceId) const;
+
+  already_AddRefed<TVSource> Source() const;
+
+  TVChannelType Type() const;
+
+  void GetName(nsAString& aName) const;
+
+  void GetNumber(nsAString& aNumber) const;
+
+  bool IsEmergency() const;
+
+  bool IsFree() const;
+
+private:
+  TVChannel(nsPIDOMWindow* aWindow,
+            TVSource* aSource);
+
+  ~TVChannel();
+
+  bool Init(nsITVChannelData* aData);
+
+  nsCOMPtr<nsITVService> mTVService;
+  nsRefPtr<TVSource> mSource;
+  nsString mNetworkId;
+  nsString mTransportStreamId;
+  nsString mServiceId;
+  TVChannelType mType;
+  nsString mNumber;
+  nsString mName;
+  bool mIsEmergency;
+  bool mIsFree;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVChannel_h
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVListeners.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TVSource.h"
+#include "mozilla/dom/TVTuner.h"
+#include "mozilla/dom/TVUtils.h"
+#include "TVListeners.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(TVSourceListener, nsITVSourceListener)
+
+void
+TVSourceListener::RegisterSource(TVSource* aSource)
+{
+  nsString tunerId;
+  nsRefPtr<TVTuner> tuner = aSource->Tuner();
+  tuner->GetId(tunerId);
+
+  nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
+  if (!mSources.Get(tunerId, &tunerSources)) {
+    tunerSources = new nsRefPtrHashtable<nsStringHashKey, TVSource>();
+    mSources.Put(tunerId, tunerSources);
+  }
+
+  nsString sourceType = ToTVSourceTypeStr(aSource->Type());
+  tunerSources->Put(sourceType, aSource);
+}
+
+void
+TVSourceListener::UnregisterSource(TVSource* aSource)
+{
+  nsString tunerId;
+  nsRefPtr<TVTuner> tuner = aSource->Tuner();
+  tuner->GetId(tunerId);
+
+  nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
+  if (!mSources.Get(tunerId, &tunerSources)) {
+    return;
+  }
+
+  nsString sourceType = ToTVSourceTypeStr(aSource->Type());
+  tunerSources->Remove(sourceType);
+}
+
+/* virtual */ NS_IMETHODIMP
+TVSourceListener::NotifyChannelScanned(const nsAString& aTunerId,
+                                       const nsAString& aSourceType,
+                                       nsITVChannelData* aChannelData)
+{
+  nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
+  source->NotifyChannelScanned(aChannelData);
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+TVSourceListener::NotifyChannelScanComplete(const nsAString& aTunerId,
+                                            const nsAString& aSourceType)
+{
+  nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
+  source->NotifyChannelScanComplete();
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+TVSourceListener::NotifyChannelScanStopped(const nsAString& aTunerId,
+                                           const nsAString& aSourceType)
+{
+  nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
+  source->NotifyChannelScanStopped();
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+TVSourceListener::NotifyEITBroadcasted(const nsAString& aTunerId,
+                                       const nsAString& aSourceType,
+                                       nsITVChannelData* aChannelData,
+                                       nsITVProgramData** aProgramDataList,
+                                       const uint32_t aCount)
+{
+  nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
+  source->NotifyEITBroadcasted(aChannelData, aProgramDataList, aCount);
+  return NS_OK;
+}
+
+already_AddRefed<TVSource>
+TVSourceListener::GetSource(const nsAString& aTunerId,
+                            const nsAString& aSourceType)
+{
+  nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
+  if (!mSources.Get(aTunerId, &tunerSources)) {
+    return nullptr;
+  }
+
+  nsRefPtr<TVSource> source;
+  tunerSources->Get(aSourceType, getter_AddRefs(source));
+  return source.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVListeners.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVListeners_h
+#define mozilla_dom_TVListeners_h
+
+#include "nsClassHashtable.h"
+#include "nsITVService.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+class TVSource;
+
+class TVSourceListener : public nsITVSourceListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITVSOURCELISTENER
+
+  void RegisterSource(TVSource* aSource);
+
+  void UnregisterSource(TVSource* aSource);
+
+private:
+  ~TVSourceListener() {}
+
+  already_AddRefed<TVSource> GetSource(const nsAString& aTunerId,
+                                       const nsAString& aSourceType);
+
+  // The tuner ID acts as the key of the outer table; whereas the source type is
+  // the key for the inner one.
+  nsClassHashtable<nsStringHashKey, nsRefPtrHashtable<nsStringHashKey, TVSource>> mSources;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVListeners_h
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVManager.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TVManagerBinding.h"
+#include "mozilla/dom/TVServiceCallbacks.h"
+#include "mozilla/dom/TVServiceFactory.h"
+#include "mozilla/dom/TVTuner.h"
+#include "nsITVService.h"
+#include "nsServiceManagerUtils.h"
+#include "TVManager.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TVManager, DOMEventTargetHelper, mTVService,
+                                   mTuners, mPendingGetTunersPromises)
+
+NS_IMPL_ADDREF_INHERITED(TVManager, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVManager, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVManager)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVManager::TVManager(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+  , mIsReady(false)
+{
+}
+
+TVManager::~TVManager()
+{
+}
+
+/* static */ already_AddRefed<TVManager>
+TVManager::Create(nsPIDOMWindow* aWindow)
+{
+  nsRefPtr<TVManager> manager = new TVManager(aWindow);
+  return (manager->Init()) ? manager.forget() : nullptr;
+}
+
+bool
+TVManager::Init()
+{
+  mTVService = TVServiceFactory::AutoCreateTVService();
+  NS_ENSURE_TRUE(mTVService, false);
+
+  nsCOMPtr<nsITVServiceCallback> callback = new TVServiceTunerGetterCallback(this);
+  nsresult rv = mTVService->GetTuners(callback);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+/* virtual */ JSObject*
+TVManager::WrapObject(JSContext* aCx)
+{
+  return TVManagerBinding::Wrap(aCx, this);
+}
+
+nsresult
+TVManager::SetTuners(const nsTArray<nsRefPtr<TVTuner>>& aTuners)
+{
+  // Should be called only when TV Manager hasn't been ready yet.
+  if (mIsReady) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  mTuners = aTuners;
+  mIsReady = true;
+
+  // Resolve pending promises.
+  uint32_t length = mPendingGetTunersPromises.Length();
+  for(uint32_t i = 0; i < length; i++) {
+    mPendingGetTunersPromises[i]->MaybeResolve(mTuners);
+  }
+  mPendingGetTunersPromises.Clear();
+  return NS_OK;
+}
+
+void
+TVManager::RejectPendingGetTunersPromises(nsresult aRv)
+{
+  // Reject pending promises.
+  uint32_t length = mPendingGetTunersPromises.Length();
+  for(uint32_t i = 0; i < length; i++) {
+    mPendingGetTunersPromises[i]->MaybeReject(aRv);
+  }
+  mPendingGetTunersPromises.Clear();
+}
+
+nsresult
+TVManager::DispatchTVEvent(nsIDOMEvent* aEvent)
+{
+  return DispatchTrustedEvent(aEvent);
+}
+
+already_AddRefed<Promise>
+TVManager::GetTuners(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Keep track of the promise when the manager hasn't been ready yet.
+  if (mIsReady) {
+    promise->MaybeResolve(mTuners);
+  } else {
+    mPendingGetTunersPromises.AppendElement(promise);
+  }
+
+  return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVManager.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVManager_h
+#define mozilla_dom_TVManager_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+
+class nsITVService;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class TVTuner;
+
+class TVManager MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVManager, DOMEventTargetHelper)
+
+  static already_AddRefed<TVManager> Create(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  nsresult SetTuners(const nsTArray<nsRefPtr<TVTuner>>& aTuners);
+
+  void RejectPendingGetTunersPromises(nsresult aRv);
+
+  nsresult DispatchTVEvent(nsIDOMEvent* aEvent);
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<Promise> GetTuners(ErrorResult& aRv);
+
+private:
+  explicit TVManager(nsPIDOMWindow* aWindow);
+
+  ~TVManager();
+
+  bool Init();
+
+  nsCOMPtr<nsITVService> mTVService;
+  nsTArray<nsRefPtr<TVTuner>> mTuners;
+  bool mIsReady;
+  nsTArray<nsRefPtr<Promise>> mPendingGetTunersPromises;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVManager_h
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVProgram.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TVChannel.h"
+#include "mozilla/dom/TVProgramBinding.h"
+#include "nsITVService.h"
+#include "TVProgram.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TVProgram, mOwner, mChannel)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TVProgram)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TVProgram)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TVProgram)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TVProgram::TVProgram(nsISupports* aOwner,
+                     TVChannel* aChannel,
+                     nsITVProgramData* aData)
+  : mOwner(aOwner)
+  , mChannel(aChannel)
+{
+  MOZ_ASSERT(mChannel);
+  MOZ_ASSERT(aData);
+
+  aData->GetEventId(mEventId);
+  aData->GetTitle(mTitle);
+  aData->GetStartTime(&mStartTime);
+  aData->GetDuration(&mDuration);
+  aData->GetDescription(mDescription);
+  aData->GetRating(mRating);
+
+  uint32_t count;
+  char** languages;
+  aData->GetAudioLanguages(&count, &languages);
+  SetLanguages(count, languages, mAudioLanguages);
+  aData->GetSubtitleLanguages(&count, &languages);
+  SetLanguages(count, languages, mSubtitleLanguages);
+}
+
+TVProgram::~TVProgram()
+{
+}
+
+/* virtual */ JSObject*
+TVProgram::WrapObject(JSContext* aCx)
+{
+  return TVProgramBinding::Wrap(aCx, this);
+}
+
+void
+TVProgram::GetAudioLanguages(nsTArray<nsString>& aLanguages) const
+{
+  aLanguages = mAudioLanguages;
+}
+
+void
+TVProgram::GetSubtitleLanguages(nsTArray<nsString>& aLanguages) const
+{
+  aLanguages = mSubtitleLanguages;
+}
+
+void
+TVProgram::GetEventId(nsAString& aEventId) const
+{
+  aEventId = mEventId;
+}
+
+already_AddRefed<TVChannel>
+TVProgram::Channel() const
+{
+  nsRefPtr<TVChannel> channel = mChannel;
+  return channel.forget();
+}
+
+void
+TVProgram::GetTitle(nsAString& aTitle) const
+{
+  aTitle = mTitle;
+}
+
+uint64_t
+TVProgram::StartTime() const
+{
+  return mStartTime;
+}
+
+uint64_t
+TVProgram::Duration() const
+{
+  return mDuration;
+}
+
+void
+TVProgram::GetDescription(nsAString& aDescription) const
+{
+  aDescription = mDescription;
+}
+
+void
+TVProgram::GetRating(nsAString& aRating) const
+{
+  aRating = mRating;
+}
+
+void
+TVProgram::SetLanguages(uint32_t aCount,
+                        char** aLanguages,
+                        nsTArray<nsString>& aLanguageList)
+{
+  for (uint32_t i = 0; i < aCount; i++) {
+    nsString language;
+    language.AssignASCII(aLanguages[i]);
+    aLanguageList.AppendElement(language);
+  }
+  NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(aCount, aLanguages);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVProgram.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVProgram_h
+#define mozilla_dom_TVProgram_h
+
+#include "nsWrapperCache.h"
+
+class nsITVProgramData;
+
+namespace mozilla {
+namespace dom {
+
+class TVChannel;
+
+class TVProgram MOZ_FINAL : public nsISupports
+                          , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TVProgram)
+
+  TVProgram(nsISupports* aOwner,
+            TVChannel* aChannel,
+            nsITVProgramData* aData);
+
+  // WebIDL (internal functions)
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  void GetAudioLanguages(nsTArray<nsString>& aLanguages) const;
+
+  void GetSubtitleLanguages(nsTArray<nsString>& aLanguages) const;
+
+  void GetEventId(nsAString& aEventId) const;
+
+  already_AddRefed<TVChannel> Channel() const;
+
+  void GetTitle(nsAString& aTitle) const;
+
+  uint64_t StartTime() const;
+
+  uint64_t Duration() const;
+
+  void GetDescription(nsAString& aDescription) const;
+
+  void GetRating(nsAString& aRating) const;
+
+private:
+  ~TVProgram();
+
+  void SetLanguages(uint32_t aCount,
+                    char** aLanguages,
+                    nsTArray<nsString>& aLanguageList);
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsRefPtr<TVChannel> mChannel;
+  nsString mEventId;
+  nsString mTitle;
+  uint64_t mStartTime;
+  uint64_t mDuration;
+  nsString mDescription;
+  nsString mRating;
+  bool mIsInterrupting;
+  nsTArray<nsString> mAudioLanguages;
+  nsTArray<nsString> mSubtitleLanguages;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVProgram_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVServiceCallbacks.cpp
@@ -0,0 +1,492 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TVManager.h"
+#include "mozilla/dom/TVProgram.h"
+#include "mozilla/dom/TVSource.h"
+#include "mozilla/dom/TVTuner.h"
+#include "nsArrayUtils.h"
+#include "TVServiceCallbacks.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Implementation of TVServiceSourceSetterCallback
+ */
+
+NS_IMPL_CYCLE_COLLECTION(TVServiceSourceSetterCallback, mTuner, mPromise)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TVServiceSourceSetterCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TVServiceSourceSetterCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TVServiceSourceSetterCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsITVServiceCallback)
+NS_INTERFACE_MAP_END
+
+TVServiceSourceSetterCallback::TVServiceSourceSetterCallback(TVTuner* aTuner,
+                                                             Promise* aPromise,
+                                                             TVSourceType aSourceType)
+  : mTuner(aTuner)
+  , mPromise(aPromise)
+  , mSourceType(aSourceType)
+{
+  MOZ_ASSERT(mTuner);
+  MOZ_ASSERT(mPromise);
+}
+
+TVServiceSourceSetterCallback::~TVServiceSourceSetterCallback()
+{
+}
+
+/* virtual */ NS_IMETHODIMP
+TVServiceSourceSetterCallback::NotifySuccess(nsIArray* aDataList)
+{
+  // |aDataList| is expected to be null for setter callbacks.
+  if (aDataList) {
+    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv = mTuner->SetCurrentSource(mSourceType);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mPromise->MaybeReject(rv);
+    return rv;
+  }
+
+  mPromise->MaybeResolve(JS::UndefinedHandleValue);
+  return NS_OK;
+}
+
+/* virtual */ NS_IMETHODIMP
+TVServiceSourceSetterCallback::NotifyError(uint16_t aErrorCode)
+{
+  switch (aErrorCode) {
+  case nsITVServiceCallback::TV_ERROR_FAILURE:
+  case nsITVServiceCallback::TV_ERROR_INVALID_ARG:
+    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return NS_OK;
+  case nsITVServiceCallback::TV_ERROR_NO_SIGNAL:
+    mPromise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+    return NS_OK;
+  case nsITVServiceCallback::TV_ERROR_NOT_SUPPORTED:
+    mPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return NS_OK;
+  }
+
+  mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  return NS_ERROR_ILLEGAL_VALUE;
+}
+
+
+/*
+ * Implementation of TVServiceChannelScanCallback
+ */
+
+NS_IMPL_CYCLE_COLLECTION(TVServiceChannelScanCallback, mSource, mPromise)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TVServiceChannelScanCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TVServiceChannelScanCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TVServiceChannelScanCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
</