Merge b2g-inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Oct 2014 15:58:07 -0400
changeset 213158 9e2062306f6279283b5260ee7c71fe7e62d4c340
parent 213106 2df5473a8fbb6d0b1ae41cddd580fa7e89ee8844 (current diff)
parent 213157 57030be7a2b5b51ae3f57c5733ae2994ba73628c (diff)
child 213208 e0b505a37b1c0bdeb1fd9523a49eb36b58d2133d
push id27741
push userryanvm@gmail.com
push dateThu, 30 Oct 2014 20:02:43 +0000
treeherdermozilla-central@9e2062306f62 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c. a=merge
dom/tv/test/file_app.sjs
dom/tv/test/file_app.template.webapp
dom/tv/test/file_tv_non_permitted_app.html
dom/tv/test/mochitest.ini
dom/tv/test/moz.build
dom/tv/test/test_tv_non_permitted_app.html
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -977,18 +977,23 @@ pref("osfile.reset_worker_delay", 5000);
 // APZC preferences.
 //
 // Gaia relies heavily on scroll events for now, so lets fire them
 // more often than the default value (100).
 pref("apz.asyncscroll.throttle", 40);
 pref("apz.pan_repaint_interval", 16);
 
 // APZ physics settings, tuned by UX designers
+pref("apz.fling_curve_function_x1", "0.0");
+pref("apz.fling_curve_function_y1", "0.0");
+pref("apz.fling_curve_function_x2", "0.58");
+pref("apz.fling_curve_function_y2", "1.0");
+pref("apz.fling_curve_threshold_inches_per_ms", "0.03");
+pref("apz.fling_friction", "0.003");
 pref("apz.max_velocity_inches_per_ms", "0.07");
-pref("apz.fling_friction", "0.003");
 
 // Tweak default displayport values to reduce the risk of running out of
 // memory when zooming in
 pref("apz.x_skate_size_multiplier", "1.25");
 pref("apz.y_skate_size_multiplier", "1.5");
 pref("apz.x_stationary_size_multiplier", "1.5");
 pref("apz.y_stationary_size_multiplier", "1.8");
 pref("apz.enlarge_displayport_when_clipped", 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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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/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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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"/>
@@ -121,16 +121,18 @@
   <project name="platform/system/netd" path="system/netd" revision="a6531f7befb49b1c81bc0de7e51c5482b308e1c5"/>
   <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/>
   <project name="platform/system/vold" path="system/vold" revision="42fa2a0f14f965970a4b629a176bbd2666edf017"/>
   <project name="platform/external/icu4c" path="external/icu4c" revision="d3ec7428eb276db43b7ed0544e09344a6014806c"/>
   <remove-project name="platform/frameworks/base"/>
   <remove-project name="platform/frameworks/native"/>
   <remove-project name="platform/hardware/libhardware"/>
   <remove-project name="platform/external/bluetooth/bluedroid"/>
+  <remove-project name="platform/system/media"/>
+  <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="54c32c2ddef066fbdf611d29e4b7c47e0363599e"/>
   <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="48835395daa6a49b281db62c50805bd6ca24077e"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="3c4f041e3e3dc676f2111caf20a186ec0467dbdb"/>
--- 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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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": "4763231f664a8a94cbea98b35b84749b51a961c9", 
+    "revision": "5b7182e0e489747ff07ca952f5a99dc21a0226c9", 
     "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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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="cddf7f505c4c280bacb74c22af3fa4959ccb555a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ae6598f3ab7b0c34ac42a73083ddb74266affba"/>
   <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/dom/icc/IccCardLockError.cpp
+++ b/dom/icc/IccCardLockError.cpp
@@ -7,38 +7,35 @@
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(IccCardLockError, DOMError)
 
 /* static */ already_AddRefed<IccCardLockError>
 IccCardLockError::Constructor(const GlobalObject& aGlobal,
-                              const nsAString& aLockType,
                               const nsAString& aName,
                               int16_t aRetryCount,
                               ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<IccCardLockError> result =
-    new IccCardLockError(window, aName, aLockType, aRetryCount);
+    new IccCardLockError(window, aName, aRetryCount);
   return result.forget();
 }
 
 IccCardLockError::IccCardLockError(nsPIDOMWindow* aWindow,
                                    const nsAString& aName,
-                                   const nsAString& aLockType,
                                    int16_t aRetryCount)
   : DOMError(aWindow, aName)
-  , mLockType(aLockType)
   , mRetryCount(aRetryCount)
 {
 }
 
 JSObject*
 IccCardLockError::WrapObject(JSContext* aCx)
 {
   return IccCardLockErrorBinding::Wrap(aCx, this);
--- a/dom/icc/IccCardLockError.h
+++ b/dom/icc/IccCardLockError.h
@@ -11,44 +11,36 @@ namespace mozilla {
 namespace dom {
 
 class IccCardLockError MOZ_FINAL : public DOMError
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   IccCardLockError(nsPIDOMWindow* aWindow, const nsAString& aName,
-                   const nsAString& aLockType, int16_t aRetryCount);
+                   int16_t aRetryCount);
 
   static already_AddRefed<IccCardLockError>
-  Constructor(const GlobalObject& aGlobal, const nsAString& aLockType,
-              const nsAString& aName, int16_t aRetryCount,
-              ErrorResult& aRv);
+  Constructor(const GlobalObject& aGlobal, const nsAString& aName,
+              int16_t aRetryCount, ErrorResult& aRv);
 
   virtual JSObject*
   WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   // WebIDL interface
 
-  void
-  GetLockType(nsString& aLockType) const
-  {
-    aLockType = mLockType;
-  }
-
   int16_t
   RetryCount() const
   {
     return mRetryCount;
   }
 
 private:
   ~IccCardLockError() {}
 
 private:
-  nsString mLockType;
   int16_t mRetryCount;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_IccCardLockError_h
--- a/dom/icc/tests/marionette/test_icc_card_lock.js
+++ b/dom/icc/tests/marionette/test_icc_card_lock.js
@@ -12,17 +12,16 @@ taskHelper.push(function testPinChangeFa
      pin: "1111",
      newPin: "0000"});
 
   ok(request instanceof DOMRequest,
      "request instanceof " + request.constructor);
 
   request.onerror = function onerror() {
     is(request.error.name, "IncorrectPassword");
-    is(request.error.lockType, "pin");
     // The default pin retries is 3, failed once becomes to 2
     is(request.error.retryCount, 2);
 
     // Reset pin retries by passing correct pin code.
     let resetRequest = icc.setCardLock(
       {lockType: "pin",
        pin: "0000",
        newPin: "0000"});
--- a/dom/icc/tests/marionette/test_stk_display_text.js
+++ b/dom/icc/tests/marionette/test_stk_display_text.js
@@ -184,11 +184,45 @@ let tests = [
             "1E" +
             "F6" + // 8BIT + Class 2
             "546F6F6C6B697420546573742047524F" +
             "55503A307846302C2038424954",
    func: testDisplayText,
    expect: {name: "display_text_cmd_20",
             commandQualifier: 0x00,
             text: "Toolkit Test GROUP:0xF0, 8BIT"}},
+  // Bug 1088573: this test case is to ensure that we provide |length| argument
+  // in |integer| format to GsmPDUHelper.readSeptetsToString().
+  {command: "D0" +
+            "81" + // 2-byte length encoded:
+            "FC" + // 252
+            "810301210082028102" +
+            "8D" +
+            "81" + // 2-byte length encoded:
+            "F0" + // 240
+            "00" + // 7BIT
+            "C332A85D9ECFC3E732685E068DDF6DF8" +
+            "7B5E0691CB20D96D061A87E5E131BD2C" +
+            "2FCF416537A8FD269741E3771B2E2FCF" +
+            "E76517685806B5CBF379F85C0695E774" +
+            "50D86C4E8FD165D0BC2E07C1D9F579BA" +
+            "5C97CF41E5B13CEC9E83CA7490BB0C22" +
+            "BFD374103C3C0795E9F232882E7FBBE3" +
+            "F5B20B24BBCD40E5391DC42E83DCEFB6" +
+            "585E06B5C3F874BBDE0691CBA071581E" +
+            "1ED3CBF2F21C14369BD3637458CC2EBB" +
+            "40C3329D5E0699DFEE313DFD76BBC3EC" +
+            "34BD0C0A83CAF432280C87CBDF757BB9" +
+            "0C8287E5207619346D1E73A0783D0D9A" +
+            "9FCA733A885C96BFEBEC32280C9A6689" +
+            "CE621654768382D529551A64268B2E",
+   func: testDisplayText,
+   expect: {name: "display_text_cmd_21",
+            commandQualifier: 0x00,
+            text: "Ce message se compose de 273 caracteres en mode " +
+                  "compresse. Ce message est affiche sur plusieurs " +
+                  "ecrans et ne doit pas etre tronque. 273 est le " +
+                  "nombre maximum de caracteres affichable. Cette " +
+                  "fonctionnalite a ete approuvee par le SMG9 qui s'est " +
+                  "deroule a SYDNEY en AUSTRALIE."}},
 ];
 
 runNextTest();
--- a/dom/mobileconnection/Assertions.cpp
+++ b/dom/mobileconnection/Assertions.cpp
@@ -23,10 +23,29 @@ ASSERT_NETWORK_SELECTION_MODE_EQUALITY(M
 
 ASSERT_MOBILE_RADIO_STATE_EQUALITY(Enabling, MOBILE_RADIO_STATE_ENABLING);
 ASSERT_MOBILE_RADIO_STATE_EQUALITY(Enabled, MOBILE_RADIO_STATE_ENABLED);
 ASSERT_MOBILE_RADIO_STATE_EQUALITY(Disabling, MOBILE_RADIO_STATE_DISABLING);
 ASSERT_MOBILE_RADIO_STATE_EQUALITY(Disabled, MOBILE_RADIO_STATE_DISABLED);
 
 #undef ASSERT_MOBILE_RADIO_STATE_EQUALITY
 
+#define ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(webidlState, xpidlState) \
+  static_assert(static_cast<int32_t>(MobilePreferredNetworkType::webidlState) == nsIMobileConnection::xpidlState, \
+                "MobilePreferredNetworkType::" #webidlState " should equal to nsIMobileConnection::" #xpidlState)
+
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Wcdma_gsm, PREFERRED_NETWORK_TYPE_WCDMA_GSM);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Gsm, PREFERRED_NETWORK_TYPE_GSM_ONLY);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Wcdma, PREFERRED_NETWORK_TYPE_WCDMA_ONLY);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Wcdma_gsm_auto, PREFERRED_NETWORK_TYPE_WCDMA_GSM_AUTO);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Cdma_evdo, PREFERRED_NETWORK_TYPE_CDMA_EVDO);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Cdma, PREFERRED_NETWORK_TYPE_CDMA_ONLY);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Evdo, PREFERRED_NETWORK_TYPE_EVDO_ONLY);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Wcdma_gsm_cdma_evdo, PREFERRED_NETWORK_TYPE_WCDMA_GSM_CDMA_EVDO);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Lte_cdma_evdo, PREFERRED_NETWORK_TYPE_LTE_CDMA_EVDO);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Lte_wcdma_gsm, PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Lte_wcdma_gsm_cdma_evdo, PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM_CDMA_EVDO);
+ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY(Lte, PREFERRED_NETWORK_TYPE_LTE_ONLY);
+
+#undef ASSERT_PREFERRED_NETWORK_TYPE_EQUALITY
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobileconnection/MobileConnection.cpp
+++ b/dom/mobileconnection/MobileConnection.cpp
@@ -472,18 +472,17 @@ already_AddRefed<DOMRequest>
 MobileConnection::SetPreferredNetworkType(MobilePreferredNetworkType& aType,
                                           ErrorResult& aRv)
 {
   if (!mMobileConnection) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  nsAutoString type;
-  CONVERT_ENUM_TO_STRING(MobilePreferredNetworkType, aType, type);
+  int32_t type = static_cast<int32_t>(aType);
 
   nsRefPtr<DOMRequest> request = new DOMRequest(GetOwner());
   nsRefPtr<MobileConnectionCallback> requestCallback =
     new MobileConnectionCallback(GetOwner(), request);
 
   nsresult rv =
     mMobileConnection->SetPreferredNetworkType(type, requestCallback);
   if (NS_FAILED(rv)) {
--- a/dom/mobileconnection/MobileConnectionCallback.cpp
+++ b/dom/mobileconnection/MobileConnectionCallback.cpp
@@ -10,16 +10,23 @@
 #include "mozilla/dom/ToJSValue.h"
 #include "nsJSUtils.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace mobileconnection {
 
+#define CONVERT_ENUM_TO_STRING(_enumType, _enum, _string)               \
+{                                                                       \
+  uint32_t index = uint32_t(_enum);                                     \
+  _string.AssignASCII(_enumType##Values::strings[index].value,          \
+                      _enumType##Values::strings[index].length);        \
+}
+
 NS_IMPL_ISUPPORTS(MobileConnectionCallback, nsIMobileConnectionCallback)
 
 MobileConnectionCallback::MobileConnectionCallback(nsPIDOMWindow* aWindow,
                                                    DOMRequest* aRequest)
   : mWindow(aWindow)
   , mRequest(aRequest)
 {
 }
@@ -345,16 +352,28 @@ MobileConnectionCallback::NotifyGetClirS
     JS_ClearPendingException(cx);
     return NS_ERROR_TYPE_ERR;
   }
 
   return NotifySuccess(jsResult);
 };
 
 NS_IMETHODIMP
+MobileConnectionCallback::NotifyGetPreferredNetworkTypeSuccess(int32_t aType)
+{
+  MOZ_ASSERT(aType < static_cast<int32_t>(MobilePreferredNetworkType::EndGuard_));
+  MobilePreferredNetworkType type = static_cast<MobilePreferredNetworkType>(aType);
+
+  nsAutoString typeString;
+  CONVERT_ENUM_TO_STRING(MobilePreferredNetworkType, type, typeString);
+
+  return NotifySuccessWithString(typeString);
+};
+
+NS_IMETHODIMP
 MobileConnectionCallback::NotifyError(const nsAString& aName,
                                       const nsAString& aMessage,
                                       const nsAString& aServiceCode,
                                       uint16_t aInfo,
                                       uint8_t aArgc)
 {
   nsCOMPtr<nsIDOMRequestService> rs = do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
--- a/dom/mobileconnection/gonk/MobileConnectionService.js
+++ b/dom/mobileconnection/gonk/MobileConnectionService.js
@@ -769,17 +769,17 @@ MobileConnectionProvider.prototype = {
 
     this._radioInterface.sendWorkerMessage("getPreferredNetworkType", null,
                                            (function(aResponse) {
       if (aResponse.errorMsg) {
         aCallback.notifyError(aResponse.errorMsg);
         return false;
       }
 
-      aCallback.notifySuccessWithString(aResponse.type);
+      aCallback.notifyGetPreferredNetworkTypeSuccess(aResponse.type);
       return false;
     }).bind(this));
   },
 
   setRoamingPreference: function(aMode, aCallback) {
     this._radioInterface.sendWorkerMessage("setRoamingPreference",
                                            {mode: aMode},
                                            (function(aResponse) {
--- a/dom/mobileconnection/interfaces/nsIMobileConnectionService.idl
+++ b/dom/mobileconnection/interfaces/nsIMobileConnectionService.idl
@@ -119,17 +119,17 @@ interface nsIMobileConnectionListener : 
    */
   void notifyNetworkSelectionModeChanged();
 };
 
 %{C++
 #define NO_ADDITIONAL_INFORMATION 0
 %}
 
-[scriptable, builtinclass, uuid(05568ae9-9873-46c6-9acd-0f6994cde756)]
+[scriptable, builtinclass, uuid(413e8bff-9f65-41a0-953f-b82e6cdbc00d)]
 interface nsIMobileConnectionCallback : nsISupports
 {
   /**
    * notify*Success*() will be called, when request is succeed.
    */
   void notifySuccess();
 
   void notifySuccessWithString(in DOMString result);
@@ -161,16 +161,18 @@ interface nsIMobileConnectionCallback : 
 
 
   void notifyGetCallBarringSuccess(in unsigned short program,
                                    in boolean enabled,
                                    in unsigned short serviceClass);
 
   void notifyGetClirStatusSuccess(in unsigned short n, in unsigned short m);
 
+  void notifyGetPreferredNetworkTypeSuccess(in long type);
+
   /**
    * notifyError() will be called, when request is failed.
    */
   [optional_argc]
   void notifyError(in DOMString name,
                    [optional] in DOMString message,
                    [optional] in DOMString serviceCode,
                    [optional] in unsigned short additionalInformation);
@@ -228,17 +230,17 @@ interface nsIMobileConnectionService : n
 
 %{C++
 template<typename T> struct already_AddRefed;
 
 already_AddRefed<nsIMobileConnectionService>
 NS_CreateMobileConnectionService();
 %}
 
-[scriptable, uuid(99818dc7-e770-4249-87e2-2de0a928ed08)]
+[scriptable, uuid(d6b15551-d290-4e38-9749-d21eb35cdaf1)]
 interface nsIMobileConnection : nsISupports
 {
   /*
    * ICC service class.
    */
   const long ICC_SERVICE_CLASS_NONE       = 0; // not available
   const long ICC_SERVICE_CLASS_VOICE      = (1 << 0);
   const long ICC_SERVICE_CLASS_DATA       = (1 << 1);
@@ -305,16 +307,32 @@ interface nsIMobileConnection : nsISuppo
    * Mobile Radio State.
    */
   const long MOBILE_RADIO_STATE_UNKNOWN   = -1;
   const long MOBILE_RADIO_STATE_ENABLING  = 0;
   const long MOBILE_RADIO_STATE_ENABLED   = 1;
   const long MOBILE_RADIO_STATE_DISABLING = 2;
   const long MOBILE_RADIO_STATE_DISABLED  = 3;
 
+  /**
+   * Preferred network type.
+   */
+  const long PREFERRED_NETWORK_TYPE_WCDMA_GSM               = 0;
+  const long PREFERRED_NETWORK_TYPE_GSM_ONLY                = 1;
+  const long PREFERRED_NETWORK_TYPE_WCDMA_ONLY              = 2;
+  const long PREFERRED_NETWORK_TYPE_WCDMA_GSM_AUTO          = 3;
+  const long PREFERRED_NETWORK_TYPE_CDMA_EVDO               = 4;
+  const long PREFERRED_NETWORK_TYPE_CDMA_ONLY               = 5;
+  const long PREFERRED_NETWORK_TYPE_EVDO_ONLY               = 6;
+  const long PREFERRED_NETWORK_TYPE_WCDMA_GSM_CDMA_EVDO     = 7;
+  const long PREFERRED_NETWORK_TYPE_LTE_CDMA_EVDO           = 8;
+  const long PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM           = 9;
+  const long PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM_CDMA_EVDO = 10;
+  const long PREFERRED_NETWORK_TYPE_LTE_ONLY                = 11;
+
   readonly attribute unsigned long serviceId;
 
   /**
    * Called when any one who is interested in receiving unsolicited messages
    * from this nsIMobileConnection instance.
    */
   void registerListener(in nsIMobileConnectionListener listener);
   void unregisterListener(in nsIMobileConnectionListener listener);
@@ -413,44 +431,39 @@ interface nsIMobileConnection : nsISuppo
    * 'GenericFailure'.
    */
   void selectNetworkAutomatically(in nsIMobileConnectionCallback requestCallback);
 
   /**
    * Set preferred network type.
    *
    * @param type
-   *        DOMString indicates the desired preferred network type.
-   *        Possible values: 'wcdma/gsm', 'gsm', 'wcdma', 'wcdma/gsm-auto',
-   *        'cdma/evdo', 'cdma', 'evdo', 'wcdma/gsm/cdma/evdo',
-   *        'lte/cdma/evdo', 'lte/wcdma/gsm', 'lte/wcdma/gsm/cdma/evdo' or
-   *        'lte'.
+   *        One of the nsIMobileConnection.PREFERRED_NETWORK_TYPE_* values.
    * @param requestCallback
    *        Called when request is finished.
    *
    * If successful, the notifySuccess() will be called.
    *
    * Otherwise, the notifyError() will be called, and the error will be either
    * 'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter',
    * 'IllegalSIMorME', or 'GenericFailure'.
    */
-  void setPreferredNetworkType(in DOMString type,
+  void setPreferredNetworkType(in long type,
                                in nsIMobileConnectionCallback requestCallback);
 
   /**
    * Query current preferred network type.
    *
    * @param requestCallback
    *        Called when request is finished.
    *
-   * If successful, the notifySuccessString() will be called. And the result
-   * will be a string indicating the current preferred network type. The value
-   * will be either 'wcdma/gsm', 'gsm', 'wcdma', 'wcdma/gsm-auto', 'cdma/evdo',
-   * 'cdma', 'evdo', 'wcdma/gsm/cdma/evdo', 'lte/cdma/evdo', 'lte/wcdma/gsm',
-   * 'lte/wcdma/gsm/cdma/evdo' or 'lte'.
+   * If successful, the notifyGetPreferredNetworkTypeSuccess() will be called,
+   * and the result 'type' will be one of the
+   * nsIMobileConnection.PREFERRED_NETWORK_TYPE_* values, indicating the current
+   * preferred network type.
    *
    * Otherwise, the notifyError() will be called, and the error will be either
    * 'RadioNotAvailable', 'RequestNotSupported', 'IllegalSIMorME', or
    * 'GenericFailure'.
    */
   void getPreferredNetworkType(in nsIMobileConnectionCallback requestCallback);
 
   /**
--- a/dom/mobileconnection/ipc/MobileConnectionChild.cpp
+++ b/dom/mobileconnection/ipc/MobileConnectionChild.cpp
@@ -173,21 +173,20 @@ NS_IMETHODIMP
 MobileConnectionChild::SelectNetworkAutomatically(nsIMobileConnectionCallback* aCallback)
 {
   return SendRequest(SelectNetworkAutoRequest(), aCallback)
     ? NS_OK : NS_ERROR_FAILURE;
 }
 
 
 NS_IMETHODIMP
-MobileConnectionChild::SetPreferredNetworkType(const nsAString& aType,
+MobileConnectionChild::SetPreferredNetworkType(int32_t aType,
                                                nsIMobileConnectionCallback* aCallback)
 {
-  return SendRequest(SetPreferredNetworkTypeRequest(nsAutoString(aType)),
-                     aCallback)
+  return SendRequest(SetPreferredNetworkTypeRequest(aType), aCallback)
     ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 MobileConnectionChild::GetPreferredNetworkType(nsIMobileConnectionCallback* aCallback)
 {
   return SendRequest(GetPreferredNetworkTypeRequest(), aCallback)
     ? NS_OK : NS_ERROR_FAILURE;
@@ -657,16 +656,22 @@ MobileConnectionRequestChild::DoReply(co
 bool
 MobileConnectionRequestChild::DoReply(const MobileConnectionReplySuccessClirStatus& aReply)
 {
   return NS_SUCCEEDED(mRequestCallback->NotifyGetClirStatusSuccess(aReply.n(),
                                                                    aReply.m()));
 }
 
 bool
+MobileConnectionRequestChild::DoReply(const MobileConnectionReplySuccessPreferredNetworkType& aReply)
+{
+  return NS_SUCCEEDED(mRequestCallback->NotifyGetPreferredNetworkTypeSuccess(aReply.type()));
+}
+
+bool
 MobileConnectionRequestChild::DoReply(const MobileConnectionReplyError& aReply)
 {
   return NS_SUCCEEDED(mRequestCallback->NotifyError(aReply.message()));
 }
 
 bool
 MobileConnectionRequestChild::DoReply(const MobileConnectionReplyErrorMmi& aReply)
 {
@@ -710,16 +715,18 @@ MobileConnectionRequestChild::Recv__dele
     case MobileConnectionReply::TMobileConnectionReplySuccessMmi:
       return DoReply(aReply.get_MobileConnectionReplySuccessMmi());
     case MobileConnectionReply::TMobileConnectionReplySuccessCallForwarding:
       return DoReply(aReply.get_MobileConnectionReplySuccessCallForwarding());
     case MobileConnectionReply::TMobileConnectionReplySuccessCallBarring:
       return DoReply(aReply.get_MobileConnectionReplySuccessCallBarring());
     case MobileConnectionReply::TMobileConnectionReplySuccessClirStatus:
       return DoReply(aReply.get_MobileConnectionReplySuccessClirStatus());
+    case MobileConnectionReply::TMobileConnectionReplySuccessPreferredNetworkType:
+      return DoReply(aReply.get_MobileConnectionReplySuccessPreferredNetworkType());
     case MobileConnectionReply::TMobileConnectionReplyError:
       return DoReply(aReply.get_MobileConnectionReplyError());
     case MobileConnectionReply::TMobileConnectionReplyErrorMmi:
       return DoReply(aReply.get_MobileConnectionReplyErrorMmi());
     default:
       MOZ_CRASH("Received invalid response type!");
   }
 
--- a/dom/mobileconnection/ipc/MobileConnectionChild.h
+++ b/dom/mobileconnection/ipc/MobileConnectionChild.h
@@ -159,16 +159,19 @@ public:
 
   bool
   DoReply(const MobileConnectionReplySuccessCallBarring& aReply);
 
   bool
   DoReply(const MobileConnectionReplySuccessClirStatus& aReply);
 
   bool
+  DoReply(const MobileConnectionReplySuccessPreferredNetworkType& aReply);
+
+  bool
   DoReply(const MobileConnectionReplyError& aReply);
 
   bool
   DoReply(const MobileConnectionReplyErrorMmi& aReply);
 
 protected:
   virtual
   ~MobileConnectionRequestChild()
--- a/dom/mobileconnection/ipc/MobileConnectionParent.cpp
+++ b/dom/mobileconnection/ipc/MobileConnectionParent.cpp
@@ -638,16 +638,22 @@ MobileConnectionRequestParent::NotifyGet
 NS_IMETHODIMP
 MobileConnectionRequestParent::NotifyGetClirStatusSuccess(uint16_t aN,
                                                           uint16_t aM)
 {
   return SendReply(MobileConnectionReplySuccessClirStatus(aN, aM));
 }
 
 NS_IMETHODIMP
+MobileConnectionRequestParent::NotifyGetPreferredNetworkTypeSuccess(int32_t aType)
+{
+  return SendReply(MobileConnectionReplySuccessPreferredNetworkType(aType));
+}
+
+NS_IMETHODIMP
 MobileConnectionRequestParent::NotifyError(const nsAString& aName,
                                            const nsAString& aMessage,
                                            const nsAString& aServiceCode,
                                            uint16_t aInfo,
                                            uint8_t aArgc)
 {
   if (aArgc == 0) {
     nsAutoString error(aName);
--- a/dom/mobileconnection/ipc/PMobileConnection.ipdl
+++ b/dom/mobileconnection/ipc/PMobileConnection.ipdl
@@ -67,17 +67,17 @@ struct SelectNetworkRequest
 };
 
 struct SelectNetworkAutoRequest
 {
 };
 
 struct SetPreferredNetworkTypeRequest
 {
-  nsString type;
+  int32_t type;
 };
 
 struct GetPreferredNetworkTypeRequest
 {
 };
 
 struct SetRoamingPreferenceRequest
 {
--- a/dom/mobileconnection/ipc/PMobileConnectionRequest.ipdl
+++ b/dom/mobileconnection/ipc/PMobileConnectionRequest.ipdl
@@ -65,16 +65,21 @@ struct MobileConnectionReplySuccessCallB
 };
 
 struct MobileConnectionReplySuccessClirStatus
 {
   uint16_t n;
   uint16_t m;
 };
 
+struct MobileConnectionReplySuccessPreferredNetworkType
+{
+  int32_t type;
+};
+
 // Error
 struct MobileConnectionReplyError
 {
   nsString message;
 };
 
 struct MobileConnectionReplyErrorMmi
 {
@@ -90,16 +95,17 @@ union MobileConnectionReply
   MobileConnectionReplySuccess;
   MobileConnectionReplySuccessString;
   MobileConnectionReplySuccessBoolean;
   MobileConnectionReplySuccessNetworks;
   MobileConnectionReplySuccessMmi;
   MobileConnectionReplySuccessCallForwarding;
   MobileConnectionReplySuccessCallBarring;
   MobileConnectionReplySuccessClirStatus;
+  MobileConnectionReplySuccessPreferredNetworkType;
   // Error
   MobileConnectionReplyError;
   MobileConnectionReplyErrorMmi;
 };
 
 } // namespace mobileconnection
 } // namespace dom
 } // namespace mozilla
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -27,32 +27,36 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsINetworkManager.h"
 #include "nsIObserverService.h"
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "prtime.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_B2G_RIL
 #include "nsIIccInfo.h"
 #include "nsIMobileConnectionInfo.h"
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileCellInfo.h"
 #include "nsIRadioInterfaceLayer.h"
 #endif
 
 #ifdef AGPS_TYPE_INVALID
 #define AGPS_HAVE_DUAL_APN
 #endif
 
 #define FLUSH_AIDE_DATA 0
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 static const int kDefaultPeriod = 1000; // ms
 static bool gDebug_isLoggingEnabled = false;
 static bool gDebug_isGPSLocationIgnored = false;
 static const char* kNetworkConnStateChangedTopic = "network-connection-state-changed";
 static const char* kMozSettingsChangedTopic = "mozsettings-changed";
 // Both of these settings can be toggled in the Gaia Developer settings screen.
 static const char* kSettingDebugEnabled = "geolocation.debugging.enabled";
@@ -395,19 +399,31 @@ void
 GonkGPSGeolocationProvider::RequestSettingValue(const char* aKey)
 {
   MOZ_ASSERT(aKey);
   nsCOMPtr<nsISettingsService> ss = do_GetService("@mozilla.org/settingsService;1");
   if (!ss) {
     MOZ_ASSERT(ss);
     return;
   }
+
   nsCOMPtr<nsISettingsServiceLock> lock;
-  ss->CreateLock(nullptr, getter_AddRefs(lock));
-  lock->Get(aKey, this);
+  nsresult rv = ss->CreateLock(nullptr, getter_AddRefs(lock));
+  if (NS_FAILED(rv)) {
+    nsContentUtils::LogMessageToConsole(
+      "geo: error while createLock setting '%s': %d\n", aKey, rv);
+    return;
+  }
+
+  rv = lock->Get(aKey, this);
+  if (NS_FAILED(rv)) {
+    nsContentUtils::LogMessageToConsole(
+      "geo: error while get setting '%s': %d\n", aKey, rv);
+    return;
+  }
 }
 
 #ifdef MOZ_B2G_RIL
 void
 GonkGPSGeolocationProvider::RequestDataConnection()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -809,32 +825,32 @@ GonkGPSGeolocationProvider::NetworkLocat
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::Startup()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mStarted) {
+    return NS_OK;
+  }
+
   RequestSettingValue(kSettingDebugEnabled);
   RequestSettingValue(kSettingDebugGpsIgnored);
 
   // Setup an observer to watch changes to the setting.
   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
   if (observerService) {
     nsresult rv = observerService->AddObserver(this, kMozSettingsChangedTopic, false);
     if (NS_FAILED(rv)) {
       NS_WARNING("geo: Gonk GPS AddObserver failed");
     }
   }
 
-  if (mStarted) {
-    return NS_OK;
-  }
-
   if (!mInitThread) {
     nsresult rv = NS_NewThread(getter_AddRefs(mInitThread));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mInitThread->Dispatch(NS_NewRunnableMethod(this, &GonkGPSGeolocationProvider::Init),
                         NS_DISPATCH_NORMAL);
 
@@ -863,28 +879,36 @@ GonkGPSGeolocationProvider::Watch(nsIGeo
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mStarted) {
     return NS_OK;
   }
+
   mStarted = false;
   if (mNetworkLocationProvider) {
     mNetworkLocationProvider->Shutdown();
     mNetworkLocationProvider = nullptr;
   }
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (obs) {
+    nsresult rv;
 #ifdef MOZ_B2G_RIL
-    obs->RemoveObserver(this, kNetworkConnStateChangedTopic);
+    rv = obs->RemoveObserver(this, kNetworkConnStateChangedTopic);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("geo: Gonk GPS network state RemoveObserver failed");
+    }
 #endif
-    obs->RemoveObserver(this, kMozSettingsChangedTopic);
+    rv = obs->RemoveObserver(this, kMozSettingsChangedTopic);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("geo: Gonk GPS mozsettings RemoveObserver failed");
+    }
   }
 
   mInitThread->Dispatch(NS_NewRunnableMethod(this, &GonkGPSGeolocationProvider::ShutdownGPS),
                         NS_DISPATCH_NORMAL);
 
   return NS_OK;
 }
 
@@ -986,18 +1010,40 @@ GonkGPSGeolocationProvider::Observe(nsIS
       return NS_OK;
     }
 
     RequestSettingValue("ril.supl.apn");
   }
 #endif
 
   if (!strcmp(aTopic, kMozSettingsChangedTopic)) {
-    RequestSettingValue(kSettingDebugEnabled);
-    RequestSettingValue(kSettingDebugGpsIgnored);
+    // Read changed setting value
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    RootedDictionary<SettingChangeNotification> setting(cx);
+    if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+      return NS_OK;
+    }
+
+    if (setting.mKey.EqualsASCII(kSettingDebugGpsIgnored)) {
+      nsContentUtils::LogMessageToConsole("geo: received mozsettings-changed: ignoring\n");
+      gDebug_isGPSLocationIgnored =
+        setting.mValue.isBoolean() ? setting.mValue.toBoolean() : false;
+      if (gDebug_isLoggingEnabled) {
+        nsContentUtils::LogMessageToConsole("geo: Debug: GPS ignored %d\n",
+                                            gDebug_isGPSLocationIgnored);
+      }
+      return NS_OK;
+    } else if (setting.mKey.EqualsASCII(kSettingDebugEnabled)) {
+      nsContentUtils::LogMessageToConsole("geo: received mozsettings-changed: logging\n");
+      gDebug_isLoggingEnabled =
+        setting.mValue.isBoolean() ? setting.mValue.toBoolean() : false;
+      return NS_OK;
+    }
   }
 
   return NS_OK;
 }
 
 /** nsISettingsServiceCallback **/
 
 NS_IMETHODIMP
@@ -1015,28 +1061,18 @@ GonkGPSGeolocationProvider::Handle(const
       nsAutoJSString apn;
       if (!apn.init(cx, aResult.toString())) {
         return NS_ERROR_FAILURE;
       }
       if (!apn.IsEmpty()) {
         SetAGpsDataConn(apn);
       }
     }
-  } else
+  }
 #endif // MOZ_B2G_RIL
-  if (aName.EqualsASCII(kSettingDebugGpsIgnored)) {
-    gDebug_isGPSLocationIgnored = aResult.isBoolean() ? aResult.toBoolean() : false;
-    if (gDebug_isLoggingEnabled) {
-      nsContentUtils::LogMessageToConsole("geo: Debug: GPS ignored %d\n", gDebug_isGPSLocationIgnored);
-    }
-    return NS_OK;
-  } else if (aName.EqualsASCII(kSettingDebugEnabled)) {
-    gDebug_isLoggingEnabled = aResult.isBoolean() ? aResult.toBoolean() : false;
-    return NS_OK;
-  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::HandleError(const nsAString& aErrorMessage)
 {
   return NS_OK;
 }
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -678,18 +678,17 @@ RILContentHelper.prototype = {
         delete this._windowsMap[requestId];
 
         if (data.success) {
           let result = new MobileIccCardLockResult(data);
           this.fireRequestSuccess(requestId, result);
         } else {
           if (data.rilMessageType == "iccSetCardLock" ||
               data.rilMessageType == "iccUnlockCardLock") {
-            let cardLockError = new requestWindow.IccCardLockError(data.lockType,
-                                                                   data.errorMsg,
+            let cardLockError = new requestWindow.IccCardLockError(data.errorMsg,
                                                                    data.retryCount);
             this.fireRequestDetailedError(requestId, cardLockError);
           } else {
             this.fireRequestError(requestId, data.errorMsg);
           }
         }
         break;
       }
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1328,18 +1328,18 @@ RilObject.prototype = {
 
   /**
    * Set the preferred network type.
    *
    * @param options An object contains a valid value of
    *                RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `type` attribute.
    */
   setPreferredNetworkType: function(options) {
-    let networkType = RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(options.type);
-    if (networkType < 0) {
+    let networkType = options.type;
+    if (networkType < 0 || networkType >= RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.length) {
       options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
       this.sendChromeMessage(options);
       return;
     }
 
     let Buf = this.context.Buf;
     Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
     Buf.writeInt32(1);
@@ -5869,16 +5869,22 @@ RilObject.prototype[REQUEST_SET_CALL_FOR
   }
   this.sendChromeMessage(options);
 };
 RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
   function REQUEST_QUERY_CALL_WAITING(length, options) {
   options.success = (options.rilRequestError === 0);
   if (!options.success) {
     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+
+    if (options.callback) {
+      // Prevent DataCloneError when sending chrome messages.
+      delete options.callback;
+    }
+
     this.sendChromeMessage(options);
     return;
   }
 
   if (options.callback) {
     options.callback.call(this, options);
     return;
   }
@@ -5889,16 +5895,22 @@ RilObject.prototype[REQUEST_QUERY_CALL_W
                      ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
   this.sendChromeMessage(options);
 };
 
 RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
   options.success = (options.rilRequestError === 0);
   if (!options.success) {
     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+
+    if (options.callback) {
+      // Prevent DataCloneError when sending chrome messages.
+      delete options.callback;
+    }
+
     this.sendChromeMessage(options);
     return;
   }
 
   if (options.callback) {
     options.callback.call(this, options);
     return;
   }
@@ -6287,18 +6299,17 @@ RilObject.prototype[REQUEST_SET_PREFERRE
 };
 RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
   if (options.rilRequestError) {
     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
     this.sendChromeMessage(options);
     return;
   }
 
-  let networkType = this.context.Buf.readInt32List()[0];
-  options.type = RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[networkType];
+  options.type = this.context.Buf.readInt32List()[0];
   this.sendChromeMessage(options);
 };
 RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = function REQUEST_GET_NEIGHBORING_CELL_IDS(length, options) {
   if (options.rilRequestError) {
     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
     this.sendChromeMessage(options);
     return;
   }
@@ -8514,17 +8525,17 @@ GsmPDUHelperObject.prototype = {
       }
     };
 
     msg.body = null;
     msg.data = null;
     switch (msg.encoding) {
       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
         msg.body = this.readSeptetsToString.call(bufAdapter,
-                                                 (length * 8 / 7), 0,
+                                                 Math.floor(length * 8 / 7), 0,
                                                  PDU_NL_IDENTIFIER_DEFAULT,
                                                  PDU_NL_IDENTIFIER_DEFAULT);
         if (msg.hasLanguageIndicator) {
           msg.language = msg.body.substring(0, 2);
           msg.body = msg.body.substring(3);
         }
         break;
 
@@ -8611,17 +8622,17 @@ GsmPDUHelperObject.prototype = {
     Buf.seekIncoming(-numOfPages * (CB_MSG_PAGE_INFO_SIZE + 1));
 
     switch (msg.encoding) {
       case PDU_DCS_MSG_CODING_7BITS_ALPHABET: {
         let body;
         msg.body = "";
         for (let i = 0; i < numOfPages; i++) {
           body = this.readSeptetsToString.call(bufAdapter,
-                                               (pageLengths[i] * 8 / 7),
+                                               Math.floor(pageLengths[i] * 8 / 7),
                                                0,
                                                PDU_NL_IDENTIFIER_DEFAULT,
                                                PDU_NL_IDENTIFIER_DEFAULT);
           if (msg.hasLanguageIndicator) {
             if (!msg.language) {
               msg.language = body.substring(0, 2);
             }
             body = body.substring(3);
@@ -8841,17 +8852,17 @@ GsmPDUHelperObject.prototype = {
     let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
     let spareBits = codingInfo & 0x07;
     let resultString;
 
     switch (textEncoding) {
     case 0:
       // GSM Default alphabet.
       resultString = this.readSeptetsToString(
-        ((len - 1) * 8 - spareBits) / 7, 0,
+        Math.floor(((len - 1) * 8 - spareBits) / 7), 0,
         PDU_NL_IDENTIFIER_DEFAULT,
         PDU_NL_IDENTIFIER_DEFAULT);
       break;
     case 1:
       // UCS2 encoded.
       resultString = this.readUCS2String(len - 1);
       break;
     default:
@@ -11256,17 +11267,18 @@ StkProactiveCmdHelperObject.prototype = 
     let GsmPDUHelper = this.context.GsmPDUHelper;
     let text = {
       codingScheme: GsmPDUHelper.readHexOctet()
     };
 
     length--; // -1 for the codingScheme.
     switch (text.codingScheme & 0x0c) {
       case STK_TEXT_CODING_GSM_7BIT_PACKED:
-        text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
+        text.textString =
+          GsmPDUHelper.readSeptetsToString(Math.floor(length * 8 / 7), 0, 0, 0);
         break;
       case STK_TEXT_CODING_GSM_8BIT:
         text.textString =
           this.context.ICCPDUHelper.read8BitUnpackedToString(length);
         break;
       case STK_TEXT_CODING_UCS2:
         text.textString = GsmPDUHelper.readUCS2String(length);
         break;
--- a/dom/tv/FakeTVService.cpp
+++ b/dom/tv/FakeTVService.cpp
@@ -1,28 +1,122 @@
 /* -*- 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 "mozilla/dom/TVTypes.h"
 #include "nsCOMPtr.h"
 #include "nsIMutableArray.h"
+#include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
+#include "prtime.h"
 #include "FakeTVService.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_ISUPPORTS(FakeTVService, nsITVService)
+NS_IMPL_CYCLE_COLLECTION_CLASS(FakeTVService)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FakeTVService)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceListener)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTuners)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannels)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrograms)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEITBroadcastedTimer)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScanCompleteTimer)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FakeTVService)
+  tmp->Shutdown();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceListener)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTuners)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannels)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrograms)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEITBroadcastedTimer)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mScanCompleteTimer)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeTVService)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeTVService)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeTVService)
+  NS_INTERFACE_MAP_ENTRY(nsITVService)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FakeTVService::FakeTVService()
+{
+  Init();
+}
+
+FakeTVService::~FakeTVService()
+{
+  Shutdown();
+}
+
+void
+FakeTVService::Init()
+{
+  const char* sourceTypes1[2] = {"dvb-t", "dvb-c"};
+  nsCOMPtr<nsITVTunerData> tunerData1 = MockTuner(NS_LITERAL_STRING("1"), 2, sourceTypes1);
+  mTuners.AppendElement(tunerData1);
+  const char* sourceTypes2[1] = {"dvb-s"};
+  nsCOMPtr<nsITVTunerData> tunerData2 = MockTuner(NS_LITERAL_STRING("2"), 1, sourceTypes2);
+  mTuners.AppendElement(tunerData2);
+
+  nsCOMPtr<nsITVChannelData> channelData1 =
+    MockChannel(NS_LITERAL_STRING("networkId1"), NS_LITERAL_STRING("transportStreamId1"),
+                NS_LITERAL_STRING("serviceId1"), NS_LITERAL_STRING("tv"),
+                NS_LITERAL_STRING("1"), NS_LITERAL_STRING("name1"), true, true);
+  mChannels.AppendElement(channelData1);
+  nsCOMPtr<nsITVChannelData> channelData2 =
+    MockChannel(NS_LITERAL_STRING("networkId2"), NS_LITERAL_STRING("transportStreamId2"),
+                NS_LITERAL_STRING("serviceId2"), NS_LITERAL_STRING("radio"),
+                NS_LITERAL_STRING("2"), NS_LITERAL_STRING("name2"), true, true);
+  mChannels.AppendElement(channelData2);
+
+  uint64_t now = PR_Now();
+  const char* audioLanguages1[2] = {"eng", "jpn"};
+  const char* subtitleLanguages1[2] = {"fre", "spa"};
+  nsCOMPtr<nsITVProgramData> programData1 =
+    MockProgram(NS_LITERAL_STRING("eventId1"), NS_LITERAL_STRING("title1"),
+                now - 1, 3600000,
+                NS_LITERAL_STRING("description1"), NS_LITERAL_STRING("rating1"),
+                2, audioLanguages1, 2, subtitleLanguages1);
+  mPrograms.AppendElement(programData1);
+  nsCOMPtr<nsITVProgramData> programData2 =
+      MockProgram(NS_LITERAL_STRING("eventId2"), NS_LITERAL_STRING("title2"),
+                  now + 3600000 , 3600000,
+                  NS_LITERAL_STRING(""), NS_LITERAL_STRING(""),
+                  0, nullptr, 0, nullptr);
+  mPrograms.AppendElement(programData2);
+}
+
+void
+FakeTVService::Shutdown()
+{
+  if (mEITBroadcastedTimer) {
+    mEITBroadcastedTimer->Cancel();
+  }
+  if (mScanCompleteTimer) {
+    mScanCompleteTimer->Cancel();
+  }
+}
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::GetSourceListener(nsITVSourceListener** aSourceListener)
 {
+  if (!mSourceListener) {
+    *aSourceListener = nullptr;
+    return NS_OK;
+  }
+
   *aSourceListener = mSourceListener;
   NS_ADDREF(*aSourceListener);
   return NS_OK;
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::SetSourceListener(nsITVSourceListener* aSourceListener)
 {
@@ -37,76 +131,210 @@ FakeTVService::GetTuners(nsITVServiceCal
     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.
+  for (uint32_t i = 0; i < mTuners.Length(); i++) {
+    tunerDataList->AppendElement(mTuners[i], false);
+  }
 
   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.
+  for (uint32_t i = 0; i < mTuners.Length(); i++) {
+    nsString tunerId;
+    mTuners[i]->GetId(tunerId);
+    if (aTunerId.Equals(tunerId)) {
+      uint32_t sourceTypeCount;
+      char** sourceTypes;
+      mTuners[i]->GetSupportedSourceTypes(&sourceTypeCount, &sourceTypes);
+      for (uint32_t j = 0; j < sourceTypeCount; j++) {
+        nsString sourceType;
+        sourceType.AssignASCII(sourceTypes[j]);
+        if (aSourceType.Equals(sourceType)) {
+          NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes);
+          nsCOMPtr<nsIRunnable> runnable =
+            new TVServiceNotifyRunnable(aCallback, nullptr);
+          return NS_DispatchToCurrentThread(runnable);
+        }
+      }
+      NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes);
+    }
+  }
 
   nsCOMPtr<nsIRunnable> runnable =
-    new TVServiceNotifyRunnable(aCallback, nullptr);
+    new TVServiceNotifyRunnable(aCallback, nullptr, nsITVServiceCallback::TV_ERROR_FAILURE);
   return NS_DispatchToCurrentThread(runnable);
 }
 
+class EITBroadcastedCallback : public nsITimerCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  EITBroadcastedCallback(const nsAString& aTunerId,
+                         const nsAString& aSourceType,
+                         nsITVSourceListener* aSourceListener,
+                         nsITVChannelData* aChannelData)
+    : mTunerId(aTunerId)
+    , mSourceType(aSourceType)
+    , mSourceListener(aSourceListener)
+    , mChannelData(aChannelData)
+  {}
+
+  NS_IMETHODIMP
+  Notify(nsITimer* aTimer)
+  {
+    // Notify mock EIT broadcasting.
+    nsITVProgramData** programDataList =
+      static_cast<nsITVProgramData **>(NS_Alloc(1 * sizeof(nsITVProgramData*)));
+    programDataList[0] = new TVProgramData();
+    programDataList[0]->SetEventId(NS_LITERAL_STRING("eventId"));
+    programDataList[0]->SetTitle(NS_LITERAL_STRING("title"));
+    programDataList[0]->SetStartTime(PR_Now() + 3600000);
+    programDataList[0]->SetDuration(3600000);
+    programDataList[0]->SetDescription(NS_LITERAL_STRING("description"));
+    programDataList[0]->SetRating(NS_LITERAL_STRING("rating"));
+    programDataList[0]->SetAudioLanguages(0, nullptr);
+    programDataList[0]->SetSubtitleLanguages(0, nullptr);
+    nsresult rv = mSourceListener->NotifyEITBroadcasted(mTunerId, mSourceType,
+                                                        mChannelData,
+                                                        programDataList, 1);
+    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(1, programDataList);
+    return rv;
+  }
+
+private:
+  ~EITBroadcastedCallback() {}
+
+  nsString mTunerId;
+  nsString mSourceType;
+  nsCOMPtr<nsITVSourceListener> mSourceListener;
+  nsCOMPtr<nsITVChannelData> mChannelData;
+};
+
+NS_IMPL_ISUPPORTS(EITBroadcastedCallback, nsITimerCallback)
+
+class ScanCompleteCallback : public nsITimerCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  ScanCompleteCallback(const nsAString& aTunerId,
+                       const nsAString& aSourceType,
+                       nsITVSourceListener* aSourceListener)
+    : mTunerId(aTunerId)
+    , mSourceType(aSourceType)
+    , mSourceListener(aSourceListener)
+  {}
+
+  NS_IMETHODIMP
+  Notify(nsITimer* aTimer)
+  {
+    return mSourceListener->NotifyChannelScanComplete(mTunerId, mSourceType);
+  }
+
+private:
+  ~ScanCompleteCallback() {}
+
+  nsString mTunerId;
+  nsString mSourceType;
+  nsCOMPtr<nsITVSourceListener> mSourceListener;
+};
+
+NS_IMPL_ISUPPORTS(ScanCompleteCallback, nsITimerCallback)
+
 /* 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);
+  nsresult rv = NS_DispatchToCurrentThread(runnable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (IsAllowed(aTunerId, aSourceType)) {
+    rv = mSourceListener->NotifyChannelScanned(aTunerId, aSourceType, mChannels[0]);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Set a timer. |notifyEITBroadcasted| will be called after the timer
+    // fires (10ms). (The timer could be canceled if |StopScanningChannels| gets
+    // called before firing.)
+    mEITBroadcastedTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    NS_ENSURE_TRUE(mEITBroadcastedTimer, NS_ERROR_OUT_OF_MEMORY);
+    nsRefPtr<EITBroadcastedCallback> eitBroadcastedCb =
+      new EITBroadcastedCallback(aTunerId, aSourceType, mSourceListener, mChannels[0]);
+    rv = mEITBroadcastedTimer->InitWithCallback(eitBroadcastedCb, 10,
+                                                nsITimer::TYPE_ONE_SHOT);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Set a timer. |notifyChannelScanComplete| will be called after the timer
+    // fires (20ms). (The timer could be canceled if |StopScanningChannels| gets
+    // called before firing.)
+    mScanCompleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    NS_ENSURE_TRUE(mScanCompleteTimer, NS_ERROR_OUT_OF_MEMORY);
+    nsRefPtr<ScanCompleteCallback> scanCompleteCb =
+      new ScanCompleteCallback(aTunerId, aSourceType, mSourceListener);
+    rv = mScanCompleteTimer->InitWithCallback(scanCompleteCb, 20,
+                                              nsITimer::TYPE_ONE_SHOT);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
 }
 
 /* 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.
+  if (mEITBroadcastedTimer) {
+    mEITBroadcastedTimer->Cancel();
+    mEITBroadcastedTimer = nullptr;
+  }
+  if (mScanCompleteTimer) {
+    mScanCompleteTimer->Cancel();
+    mScanCompleteTimer = nullptr;
+  }
+  nsresult rv = mSourceListener->NotifyChannelScanStopped(aTunerId, aSourceType);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIRunnable> runnable =
     new TVServiceNotifyRunnable(aCallback, nullptr);
   return NS_DispatchToCurrentThread(runnable);
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::ClearScannedChannelsCache()
 {
-  // TODO Implement in follow-up patches.
-
+  // Fake service doesn't support channel cache, so there's nothing to do here.
   return NS_OK;
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::SetChannel(const nsAString& aTunerId,
                           const nsAString& aSourceType,
                           const nsAString& aChannelNumber,
                           nsITVServiceCallback* aCallback)
@@ -115,20 +343,36 @@ FakeTVService::SetChannel(const nsAStrin
     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.
+  if (IsAllowed(aTunerId, aSourceType)) {
+    for (uint32_t i = 0; i < mChannels.Length(); i++) {
+      nsString channelNumber;
+      mChannels[i]->GetNumber(channelNumber);
+      if (aChannelNumber.Equals(channelNumber)) {
+        channelDataList->AppendElement(mChannels[i], false);
+        break;
+      }
+    }
+  }
 
-  nsCOMPtr<nsIRunnable> runnable =
-    new TVServiceNotifyRunnable(aCallback, channelDataList);
+  uint32_t length;
+  nsresult rv = channelDataList->GetLength(&length);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIRunnable> runnable = new TVServiceNotifyRunnable(
+    aCallback,
+    (length == 1) ? channelDataList : nullptr,
+    (length == 1) ? nsITVServiceCallback::TV_ERROR_OK : nsITVServiceCallback::TV_ERROR_FAILURE
+  );
   return NS_DispatchToCurrentThread(runnable);
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::GetChannels(const nsAString& aTunerId,
                            const nsAString& aSourceType,
                            nsITVServiceCallback* aCallback)
 {
@@ -136,17 +380,21 @@ FakeTVService::GetChannels(const nsAStri
     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.
+  if (IsAllowed(aTunerId, aSourceType)) {
+    for (uint32_t i = 0; i < mChannels.Length(); i++) {
+      channelDataList->AppendElement(mChannels[i], false);
+    }
+  }
 
   nsCOMPtr<nsIRunnable> runnable =
     new TVServiceNotifyRunnable(aCallback, channelDataList);
   return NS_DispatchToCurrentThread(runnable);
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::GetPrograms(const nsAString& aTunerId,
@@ -160,17 +408,24 @@ FakeTVService::GetPrograms(const nsAStri
     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.
+  // Only return mock programs for the first channel.
+  nsString channelNumber;
+  mChannels[0]->GetNumber(channelNumber);
+  if (IsAllowed(aTunerId, aSourceType) && aChannelNumber.Equals(channelNumber)) {
+    for (uint32_t i = 0; i < mPrograms.Length(); i++) {
+      programDataList->AppendElement(mPrograms[i], false);
+    }
+  }
 
   nsCOMPtr<nsIRunnable> runnable =
     new TVServiceNotifyRunnable(aCallback, programDataList);
   return NS_DispatchToCurrentThread(runnable);
 }
 
 /* virtual */ NS_IMETHODIMP
 FakeTVService::GetOverlayId(const nsAString& aTunerId,
@@ -187,10 +442,91 @@ FakeTVService::GetOverlayId(const nsAStr
 
   // TODO Implement in follow-up patches.
 
   nsCOMPtr<nsIRunnable> runnable =
     new TVServiceNotifyRunnable(aCallback, overlayIds);
   return NS_DispatchToCurrentThread(runnable);
 }
 
+bool
+FakeTVService::IsAllowed(const nsAString& aTunerId,
+                         const nsAString& aSourceType)
+{
+  // Only allow for the first source of the first tuner.
+  nsString tunerId;
+  mTuners[0]->GetId(tunerId);
+  if (!aTunerId.Equals(tunerId)) {
+    return false;
+  }
+
+  uint32_t sourceTypeCount;
+  char** sourceTypes;
+  mTuners[0]->GetSupportedSourceTypes(&sourceTypeCount, &sourceTypes);
+  nsString sourceType;
+  sourceType.AssignASCII(sourceTypes[0]);
+  NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes);
+  if (!aSourceType.Equals(sourceType)) {
+    return false;
+  }
+
+  return true;
+}
+
+already_AddRefed<nsITVTunerData>
+FakeTVService::MockTuner(const nsAString& aId,
+                         uint32_t aSupportedSourceTypeCount,
+                         const char** aSupportedSourceTypes)
+{
+  nsCOMPtr<nsITVTunerData> tunerData = new TVTunerData();
+  tunerData->SetId(aId);
+  tunerData->SetSupportedSourceTypes(aSupportedSourceTypeCount, aSupportedSourceTypes);
+  return tunerData.forget();
+}
+
+already_AddRefed<nsITVChannelData>
+FakeTVService::MockChannel(const nsAString& aNetworkId,
+                           const nsAString& aTransportStreamId,
+                           const nsAString& aServiceId,
+                           const nsAString& aType,
+                           const nsAString& aNumber,
+                           const nsAString& aName,
+                           bool aIsEmergency,
+                           bool aIsFree)
+{
+  nsCOMPtr<nsITVChannelData> channelData = new TVChannelData();
+  channelData->SetNetworkId(aNetworkId);
+  channelData->SetTransportStreamId(aTransportStreamId);
+  channelData->SetServiceId(aServiceId);
+  channelData->SetType(aType);
+  channelData->SetNumber(aNumber);
+  channelData->SetName(aName);
+  channelData->SetIsEmergency(aIsEmergency);
+  channelData->SetIsFree(aIsFree);
+  return channelData.forget();
+}
+
+already_AddRefed<nsITVProgramData>
+FakeTVService::MockProgram(const nsAString& aEventId,
+                           const nsAString& aTitle,
+                           uint64_t aStartTime,
+                           uint64_t aDuration,
+                           const nsAString& aDescription,
+                           const nsAString& aRating,
+                           uint32_t aAudioLanguageCount,
+                           const char** aAudioLanguages,
+                           uint32_t aSubtitleLanguageCount,
+                           const char** aSubtitleLanguages)
+{
+  nsCOMPtr<nsITVProgramData> programData = new TVProgramData();
+  programData->SetEventId(aEventId);
+  programData->SetTitle(aTitle);
+  programData->SetStartTime(aStartTime);
+  programData->SetDuration(aDuration);
+  programData->SetDescription(aDescription);
+  programData->SetRating(aRating);
+  programData->SetAudioLanguages(aAudioLanguageCount, aAudioLanguages);
+  programData->SetSubtitleLanguages(aSubtitleLanguageCount, aSubtitleLanguages);
+  return programData.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/tv/FakeTVService.h
+++ b/dom/tv/FakeTVService.h
@@ -3,38 +3,82 @@
 /* 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 "nsCycleCollectionParticipant.h"
 #include "nsITVService.h"
+#include "nsTArray.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 } }
 
+class nsITimer;
+class nsITVTunerData;
+class nsITVChannelData;
+class nsITVProgramData;
+
 namespace mozilla {
 namespace dom {
 
 class FakeTVService MOZ_FINAL : public nsITVService
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(FakeTVService)
   NS_DECL_NSITVSERVICE
 
-  FakeTVService() {}
-
-  // TODO More members might be added in follow-up patches to help testing.
+  FakeTVService();
 
 private:
-  ~FakeTVService() {}
+  ~FakeTVService();
+
+  void Init();
+
+  void Shutdown();
+
+  bool IsAllowed(const nsAString& aTunerId,
+                 const nsAString& aSourceType);
+
+  already_AddRefed<nsITVTunerData> MockTuner(const nsAString& aId,
+                                             uint32_t aSupportedSourceTypeCount,
+                                             const char** aSupportedSourceTypes);
+
+  already_AddRefed<nsITVChannelData> MockChannel(const nsAString& aNetworkId,
+                                                 const nsAString& aTransportStreamId,
+                                                 const nsAString& aServiceId,
+                                                 const nsAString& aType,
+                                                 const nsAString& aNumber,
+                                                 const nsAString& aName,
+                                                 bool aIsEmergency,
+                                                 bool aIsFree);
+
+  already_AddRefed<nsITVProgramData> MockProgram(const nsAString& aEventId,
+                                                 const nsAString& aTitle,
+                                                 uint64_t aStartTime,
+                                                 uint64_t aDuration,
+                                                 const nsAString& aDescription,
+                                                 const nsAString& aRating,
+                                                 uint32_t aAudioLanguageCount,
+                                                 const char** aAudioLanguages,
+                                                 uint32_t aSubtitleLanguageCount,
+                                                 const char** aSubtitleLanguages);
 
   nsCOMPtr<nsITVSourceListener> mSourceListener;
+
+  // The real implementation may want to use more efficient data structures.
+  nsTArray<nsCOMPtr<nsITVTunerData>> mTuners;
+  nsTArray<nsCOMPtr<nsITVChannelData>> mChannels;
+  nsTArray<nsCOMPtr<nsITVProgramData>> mPrograms;
+  nsCOMPtr<nsITimer> mEITBroadcastedTimer;
+  nsCOMPtr<nsITimer> mScanCompleteTimer;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FakeTVService_h
--- a/dom/tv/TVListeners.cpp
+++ b/dom/tv/TVListeners.cpp
@@ -7,49 +7,40 @@
 #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)
+NS_IMPL_CYCLE_COLLECTION(TVSourceListener, mSources)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TVSourceListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TVSourceListener)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TVSourceListener)
+  NS_INTERFACE_MAP_ENTRY(nsITVSourceListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
 
 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);
+  mSources.AppendElement(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;
+  for (uint32_t i = 0; i < mSources.Length(); i++) {
+    if (mSources[i] == aSource) {
+      mSources.RemoveElementsAt(i, 1);
+    }
   }
-
-  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);
@@ -86,20 +77,26 @@ TVSourceListener::NotifyEITBroadcasted(c
   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;
+  for (uint32_t i = 0; i < mSources.Length(); i++) {
+    nsString tunerId;
+    nsRefPtr<TVTuner> tuner = mSources[i]->Tuner();
+    tuner->GetId(tunerId);
+
+    nsString sourceType = ToTVSourceTypeStr(mSources[i]->Type());
+
+    if (aTunerId.Equals(tunerId) && aSourceType.Equals(sourceType)) {
+      nsRefPtr<TVSource> source = mSources[i];
+      return source.forget();
+    }
   }
 
-  nsRefPtr<TVSource> source;
-  tunerSources->Get(aSourceType, getter_AddRefs(source));
-  return source.forget();
+  return nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/tv/TVListeners.h
+++ b/dom/tv/TVListeners.h
@@ -2,42 +2,40 @@
 /* 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 "mozilla/dom/TVSource.h"
+#include "nsCycleCollectionParticipant.h"
 #include "nsITVService.h"
-#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 
-class TVSource;
-
 class TVSourceListener : public nsITVSourceListener
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(TVSourceListener)
   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;
+  nsTArray<nsRefPtr<TVSource>> mSources;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TVListeners_h
--- a/dom/tv/TVTypes.cpp
+++ b/dom/tv/TVTypes.cpp
@@ -12,16 +12,22 @@ namespace mozilla {
 namespace dom {
 
 /*
  * Implementation of TVTunerData
  */
 
 NS_IMPL_ISUPPORTS(TVTunerData, nsITVTunerData)
 
+TVTunerData::TVTunerData()
+  : mSupportedSourceTypes(nullptr)
+  , mCount(0)
+{
+}
+
 TVTunerData::~TVTunerData()
 {
   if (mSupportedSourceTypes) {
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mSupportedSourceTypes);
   }
 }
 
 /* virtual */ NS_IMETHODIMP
@@ -92,16 +98,26 @@ TVTunerData::SetSupportedSourceTypes(uin
 
 
 /*
  * Implementation of TVChannelData
  */
 
 NS_IMPL_ISUPPORTS(TVChannelData, nsITVChannelData)
 
+TVChannelData::TVChannelData()
+  : mIsEmergency(false)
+  , mIsFree(false)
+{
+}
+
+TVChannelData::~TVChannelData()
+{
+}
+
 /* virtual */ NS_IMETHODIMP
 TVChannelData::GetNetworkId(nsAString& aNetworkId)
 {
   aNetworkId = mNetworkId;
   return NS_OK;
 }
 
 /* virtual */ NS_IMETHODIMP
@@ -239,16 +255,24 @@ TVChannelData::SetIsFree(bool aIsFree)
 
 
 /*
  * Implementation of TVProgramData
  */
 
 NS_IMPL_ISUPPORTS(TVProgramData, nsITVProgramData)
 
+TVProgramData::TVProgramData()
+  : mAudioLanguages(nullptr)
+  , mAudioLanguageCount(0)
+  , mSubtitleLanguages(nullptr)
+  , mSubtitleLanguageCount(0)
+{
+}
+
 TVProgramData::~TVProgramData()
 {
   if (mAudioLanguages) {
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mAudioLanguageCount, mAudioLanguages);
   }
   if (mSubtitleLanguages) {
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSubtitleLanguageCount, mSubtitleLanguages);
   }
--- a/dom/tv/TVTypes.h
+++ b/dom/tv/TVTypes.h
@@ -13,32 +13,36 @@ namespace mozilla {
 namespace dom {
 
 class TVTunerData MOZ_FINAL : public nsITVTunerData
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITVTUNERDATA
 
+  TVTunerData();
+
 private:
   ~TVTunerData();
 
   nsString mId;
   char** mSupportedSourceTypes;
   uint32_t mCount;
 };
 
 class TVChannelData MOZ_FINAL : public nsITVChannelData
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITVCHANNELDATA
 
+  TVChannelData();
+
 private:
-  ~TVChannelData() {}
+  ~TVChannelData();
 
   nsString mNetworkId;
   nsString mTransportStreamId;
   nsString mServiceId;
   nsString mType;
   nsString mNumber;
   nsString mName;
   bool mIsEmergency;
@@ -46,16 +50,18 @@ private:
 };
 
 class TVProgramData MOZ_FINAL : public nsITVProgramData
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITVPROGRAMDATA
 
+  TVProgramData();
+
 private:
   ~TVProgramData();
 
   nsString mEventId;
   nsString mTitle;
   uint64_t mStartTime;
   uint64_t mDuration;
   nsString mDescription;
--- a/dom/tv/moz.build
+++ b/dom/tv/moz.build
@@ -1,16 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-TEST_DIRS += ['test']
-
 EXPORTS.mozilla.dom += [
     'FakeTVService.h',
     'TVChannel.h',
     'TVListeners.h',
     'TVManager.h',
     'TVProgram.h',
     'TVServiceCallbacks.h',
     'TVServiceFactory.h',
@@ -35,11 +33,15 @@ UNIFIED_SOURCES += [
 ]
 
 XPIDL_SOURCES += [
     'nsITVService.idl',
 ]
 
 XPIDL_MODULE = 'dom_tv'
 
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
rename from dom/tv/test/file_app.sjs
rename to dom/tv/test/mochitest/file_app.sjs
--- a/dom/tv/test/file_app.sjs
+++ b/dom/tv/test/mochitest/file_app.sjs
@@ -1,9 +1,9 @@
-var gBasePath = "tests/dom/tv/test/";
+var gBasePath = "tests/dom/tv/test/mochitest/";
 
 function handleRequest(request, response) {
   var query = getQuery(request);
 
   var testToken = '';
   if ('testToken' in query) {
     testToken = query.testToken;
   }
rename from dom/tv/test/file_app.template.webapp
rename to dom/tv/test/mochitest/file_app.template.webapp
--- a/dom/tv/test/file_app.template.webapp
+++ b/dom/tv/test/mochitest/file_app.template.webapp
@@ -1,6 +1,6 @@
 {
   "name": "TV Test App (hosted)",
   "description": "Hosted TV test app used for mochitest.",
-  "launch_path": "/tests/dom/tv/test/TESTTOKEN",
+  "launch_path": "/tests/dom/tv/test/mochitest/TESTTOKEN",
   "icons": { "128": "default_icon" }
 }
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_channels.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test GetChannels for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+
+          aSources[0].getChannels().then(
+            function(aChannels) {
+              ok(aChannels.length > 0, "Got at least 1 channel.");
+
+              for (var i = 0; i < aChannels.length; i++) {
+                var channel = aChannels[i];
+                ok(channel instanceof TVChannel, "Channel " + i + " should be in right type.")
+                ok('source' in channel, "Channel " + i + " should have a source.");
+                ok('networkId' in channel, "Channel " + i + " should have a network ID.");
+                ok('transportStreamId' in channel, "Channel " + i + " should have a transport stream ID.");
+                ok('serviceId' in channel, "Channel " + i + " should have a service ID.");
+                ok('type' in channel, "Channel " + i + " should have a type.");
+                ok('name' in channel, "Channel " + i + " should have a name.");
+                ok('number' in channel, "Channel " + i + " should have a number.");
+              }
+
+              finish();
+            },
+            function(aError) {
+              ok(false, "Error occurred when getting channels: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_channels_during_scanning.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test an error case for GetChannels during scanning for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+  function(aTuners) {
+    ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+    aTuners[0].getSources().then(
+      function(aSources) {
+        ok(aSources.length > 0, "Got at least 1 source.");
+        var source = aSources[0];
+
+        // TODO Bug 1088818 - Modify the behavior of channel scanning.
+        source.startScanning({}).then(
+          function() {
+            source.getChannels().then(
+              function() {
+                ok(false, "Getting channels during scanning should get error.");
+                  finish();
+                },
+                function(aError) {
+                  is(aError.name, "InvalidStateError",
+                     "InvalidStateError should be expected.");
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when starting scanning: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_current_program.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test GetCurrentProgram for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+
+          aSources[0].getChannels().then(
+            function(aChannels) {
+              ok(aChannels.length > 0, "Got at least 1 channel.");
+
+              aChannels[0].getCurrentProgram().then(
+                function(aCurrentProgram) {
+                  ok(aCurrentProgram, "Got the current program.");
+                  ok(aCurrentProgram instanceof TVProgram, "The current program should be in the right type.")
+                  ok('channel' in aCurrentProgram, "The current program should have a channel.");
+                  ok('eventId' in aCurrentProgram, "The current program should have an event ID.");
+                  ok('title' in aCurrentProgram, "The current program should have a title.");
+                  ok('startTime' in aCurrentProgram, "The current program should have start time.");
+                  ok('duration' in aCurrentProgram, "The current program should have duration.");
+                  ok(aCurrentProgram.getAudioLanguages().length >= 0,
+                     "The current program may have audio language(s).");
+                  ok(aCurrentProgram.getSubtitleLanguages().length >= 0,
+                     "The current program may have subtitle language(s).");
+
+                  finish();
+                },
+                function(aError) {
+                  ok(false, "Error occurred when getting programs: " + aError);
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when getting channels: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_programs.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test GetPrograms for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+
+          aSources[0].getChannels().then(
+            function(aChannels) {
+              ok(aChannels.length > 0, "Got at least 1 channel.");
+
+              aChannels[0].getPrograms().then(
+                function(aPrograms) {
+                  ok(aPrograms.length > 0, "Got at least 1 program.");
+
+                  for (var i = 0; i < aPrograms.length; i++) {
+                    var program = aPrograms[i];
+                    ok(program instanceof TVProgram, "Program " + i + " should be in the right type.")
+                    ok('channel' in program, "Program " + i + " should have a channel.");
+                    ok('eventId' in program, "Program " + i + " should have an event ID.");
+                    ok('title' in program, "Program " + i + " should have a title.");
+                    ok('startTime' in program, "Program " + i + " should have start time.");
+                    ok('duration' in program, "Program " + i + " should have duration.");
+                    ok(program.getAudioLanguages().length >= 0,
+                       "Program " + i + " may have audio language(s).");
+                    ok(program.getSubtitleLanguages().length >= 0,
+                       "Program " + i + " may have subtitle language(s).");
+                  }
+
+                  finish();
+                },
+                function(aError) {
+                  ok(false, "Error occurred when getting programs: " + aError);
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when getting channels: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_sources.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test GetSources for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+          for (var i = 0; i < aSources.length; i++) {
+            var source = aSources[i];
+            ok(source instanceof TVSource, "Source " + i + " should be in the right type.");
+            ok('tuner' in source, "Source " + i + " should have a tuner.");
+            ok('type' in source, "Source " + i + " should have a type.");
+            ok('isScanning' in source, "Source " + i + " should have isScanning.");
+            ok(!source.isScanning,
+               "Source " + i + " should not be scanning by default.");
+            ok(!source.currentChannel,
+               "Source " + i + " should have no current channel by default.");
+          }
+
+          finish();
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_get_tuners.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test GetTuners for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+      for (var i = 0; i < aTuners.length; i++) {
+        var tuner = aTuners[i];
+        ok(tuner instanceof TVTuner, "Tuner " + i + " should be in the right type.");
+        ok('id' in tuner, "Tuner " + i + " should have an ID.");
+        ok(tuner.getSupportedSourceTypes().length > 0,
+           "Tuner " + i + " should have supported source type(s).");
+        ok(!tuner.currentSource,
+           "Tuner " + i + " should have no current source by default.");
+        ok(!tuner.stream, "Tuner " + i + " should have no stream by default.");
+      }
+
+      finish();
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
rename from dom/tv/test/file_tv_non_permitted_app.html
rename to dom/tv/test/mochitest/file_tv_non_permitted_app.html
--- a/dom/tv/test/file_tv_non_permitted_app.html
+++ b/dom/tv/test/mochitest/file_tv_non_permitted_app.html
@@ -1,24 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test the availability of TV Manager API for non-permitted Apps</title>
 </head>
 <body>
 <div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
   <script type="application/javascript;version=1.7">
 
-  function ok(a, msg) {
-    alert((a ? 'OK' : 'KO')+ ' ' + msg)
-  }
-
-  function finish() {
-    alert('DONE');
-  }
-
   ok(!('tv' in navigator),
      "navigator.tv should not exist for non-permitted app.");
   finish();
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_permitted_app.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test the availability of TV Manager API for permitted Apps</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist for permitted app.");
+  finish();
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_scan_channels_completed.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test channel scanning complete for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  var isScannedEventFired = false;
+  
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+          var source = aSources[0];
+
+          source.oneitbroadcasted = function(aEvent) {
+            info("Received EIT broadcasted event.");
+
+            var programs = aEvent.programs;
+            for (var i = 0; i < programs.length; i++) {
+              ok(programs[i], "Program " + i + " should be set.")
+            }
+          };
+
+          source.onscanningstatechanged = function(aEvent) {
+            if (aEvent.state === 'scanned') {
+              isScannedEventFired = true;
+              info("Received channel scanned event.");
+              ok(aEvent.channel, "Scanned channel should be set.");
+            } else if (aEvent.state === 'completed') {
+              ok(isScannedEventFired, "Received channel scanning completed event after channel scanned event.");
+              finish();
+            }
+          };
+
+          // TODO Bug 1088818 - Modify the behavior of channel scanning.
+          source.startScanning({}).then(
+            function() {},
+            function(aError) {
+              ok(false, "Error occurred when starting scanning: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_scan_channels_stopped.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test StartScanning and StopScanning for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  var isClearedEventFired = false;
+  
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+          var source = aSources[0];
+
+          source.onscanningstatechanged = function(aEvent) {
+            if (aEvent.state === 'cleared') {
+              isClearedEventFired = true;
+              info("Received channel cache cleared event.");
+            } else if (aEvent.state === 'stopped') {
+              ok(isClearedEventFired, "Received channel scanning stopped event after cleared event.");
+              finish();
+            }
+          };
+
+          // TODO Bug 1088818 - Modify the behavior of channel scanning.
+          source.startScanning({ isRescanned: true }).then(
+            function() {
+              source.stopScanning().then(
+                function() {},
+                function(aError) {
+                  ok(false, "Error occurred when stopping scanning: " + aError);
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when starting scanning: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_set_current_channel.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test SetCurrentChannel for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+      var tuner = aTuners[0];
+      var selectedSourceType = tuner.getSupportedSourceTypes()[0];
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+          var source = aSources[0];
+
+          source.getChannels().then(
+            function(aChannels) {
+              ok(aChannels.length > 0, "Got at least 1 channel.");
+              var selectedChannelNumber = aChannels[0].number;
+
+              source.oncurrentchannelchanged = function(aEvent) {
+                ok(aEvent instanceof TVCurrentChannelChangedEvent,
+                   "The event is in the right type");
+                ok(aEvent.channel instanceof TVChannel,
+                   "The channel is in the right type.");
+                is(aEvent.channel, source.currentChannel,
+                  "The current channel is set.");
+                is(source.currentChannel.number, selectedChannelNumber,
+                   "The current channel number is correct.");
+                finish();
+              };
+
+              source.setCurrentChannel(selectedChannelNumber).then(
+                function() {},
+                function(aError) {
+                  ok(false, "Error occurred when setting current channel: " + aError);
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when getting channels: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_set_current_channel_during_scanning.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test an error case for SetCurrentChannel during scanning for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners){
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+          var source = aSources[0];
+
+          // TODO Bug 1088818 - Modify the behavior of channel scanning.
+          source.startScanning({}).then(
+            function() {
+              source.setCurrentChannel("1").then(
+                function() {
+                  ok(false, "Setting current channel during scanning should get error.");
+                  finish();
+                },
+                function(aError) {
+                  is(aError.name, "InvalidStateError",
+                     "InvalidStateError should be expected.");
+                  finish();
+                }
+              );
+            },
+            function(aError) {
+              ok(false, "Error occurred when starting scanning: " + aError);
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_set_current_source.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test SetCurrentSource for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+      var tuner = aTuners[0];
+      var selectedSourceType = tuner.getSupportedSourceTypes()[0];
+
+      tuner.oncurrentsourcechanged = function(aEvent) {
+        ok(aEvent instanceof TVCurrentSourceChangedEvent,
+           "The event is in the right type");
+        ok(aEvent.source instanceof TVSource,
+           "The source is in the right type.");
+        is(aEvent.source, tuner.currentSource,
+           "The current source is set.");
+        is(tuner.currentSource.type, selectedSourceType,
+           "The current source type is correct.");
+        finish();
+      };
+
+      tuner.setCurrentSource(selectedSourceType).then(
+        function() {},
+        function(aError) {
+          ok(false, "Error occurred when setting current source: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_set_invalid_current_channel.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test an error case for SetCurrentChannel for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+      var tuner = aTuners[0];
+      var selectedSourceType = tuner.getSupportedSourceTypes()[0];
+
+      aTuners[0].getSources().then(
+        function(aSources) {
+          ok(aSources.length > 0, "Got at least 1 source.");
+
+          aSources[0].setCurrentChannel("NonExistentChannelNumber").then(
+            function() {
+              ok(false, "Setting an invalid current channel should get error.");
+              finish();
+            },
+            function(aError) {
+              is(aError.name, "AbortError", "AbortError should be expected.");
+              finish();
+            }
+          );
+        },
+        function(aError) {
+          ok(false, "Error occurred when getting sources: " + aError);
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/file_tv_set_invalid_current_source.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test an error case for SetCurrentSource for TV API</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript" src="./test_helpers.js"></script>
+  <script type="application/javascript;version=1.7">
+
+  ok('tv' in navigator, "navigator.tv should exist.");
+
+  navigator.tv.getTuners().then(
+    function(aTuners) {
+      ok(aTuners.length > 0, "Got at least 1 tuner.");
+
+      aTuners[0].setCurrentSource("InvalidSourceType").then(
+        function() {
+          ok(false, "Setting an invalid current source should get error.");
+          finish();
+        },
+        function(aError) {
+          is(aError.name, "TypeError", "TypeError should be expected.");
+          finish();
+        }
+      );
+    },
+    function(aError) {
+      ok(false, "Error occurred when getting tuners: " + aError);
+      finish();
+    }
+  );
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/head.js
@@ -0,0 +1,97 @@
+"use strict";
+
+var gAppsService = SpecialPowers.Cc["@mozilla.org/AppsService;1"]
+                   .getService(SpecialPowers.Ci.nsIAppsService);
+
+
+var gApp;
+
+function cbError(e) {
+  ok(false, "Error callback invoked: " + this.error.name);
+  SimpleTest.finish();
+}
+
+function installApp(aTestToken, aTemplate) {
+  var hostedManifestURL = window.location.origin +
+                          '/tests/dom/tv/test/mochitest/file_app.sjs?testToken='
+                          + aTestToken + '&template=' + aTemplate;
+  var request = navigator.mozApps.install(hostedManifestURL);
+  request.onerror = cbError;
+  request.onsuccess = function() {
+    gApp = request.result;
+
+    var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
+    SpecialPowers.addPermission("tv", true, { url: gApp.origin,
+                                              appId: appId,
+                                              isInBrowserElement: false });
+
+    runTest();
+  }
+}
+
+function uninstallApp() {
+  var request = navigator.mozApps.mgmt.uninstall(gApp);
+  request.onerror = cbError;
+  request.onsuccess = function() {
+    // All done.
+    info("All done");
+    runTest();
+  }
+}
+
+function testApp() {
+  var ifr = document.createElement('iframe');
+  ifr.setAttribute('mozbrowser', 'true');
+  ifr.setAttribute('mozapp', gApp.manifestURL);
+  ifr.setAttribute('src', gApp.manifest.launch_path);
+  var domParent = document.getElementById('content');
+
+  // Set us up to listen for messages from the app.
+  var listener = function(e) {
+    var message = e.detail.message;
+    if (/^OK/.exec(message)) {
+      ok(true, "Message from app: " + message);
+    } else if (/KO/.exec(message)) {
+      ok(false, "Message from app: " + message);
+    } else if (/^INFO/.exec(message)) {
+      info("Message from app: " + message);
+    } else if (/DONE/.exec(message)) {
+      ok(true, "Messaging from app complete");
+      ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+      domParent.removeChild(ifr);
+      runTest();
+    }
+  }
+
+  // This event is triggered when the app calls "alert".
+  ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+  domParent.appendChild(ifr);
+}
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+function setupPrefsAndPermissions() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.tv.enabled", true],
+                                     ["dom.testing.tv_enabled_for_hosted_apps", true]]}, function() {
+    SpecialPowers.pushPermissions(
+      [{ "type": "browser", "allow": true, "context": document },
+       { "type": "embed-apps", "allow": true, "context": document },
+       { "type": "webapps-manage", "allow": true, "context": document }],
+      function() {
+        SpecialPowers.setAllAppsLaunchable(true);
+        SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+        // No confirmation needed when an app is installed and uninstalled.
+        SpecialPowers.autoConfirmAppInstall(() => {
+          SpecialPowers.autoConfirmAppUninstall(runTest);
+        });
+      });
+  });
+}
rename from dom/tv/test/mochitest.ini
rename to dom/tv/test/mochitest/mochitest.ini
--- a/dom/tv/test/mochitest.ini
+++ b/dom/tv/test/mochitest/mochitest.ini
@@ -1,7 +1,37 @@
 [DEFAULT]
 support-files =
   file_app.sjs
   file_app.template.webapp
   file_tv_non_permitted_app.html
+  file_tv_permitted_app.html
+  file_tv_get_tuners.html
+  file_tv_get_sources.html
+  file_tv_get_channels.html
+  file_tv_get_channels_during_scanning.html
+  file_tv_get_programs.html
+  file_tv_get_current_program.html
+  file_tv_set_current_source.html
+  file_tv_set_invalid_current_source.html
+  file_tv_set_current_channel.html
+  file_tv_set_current_channel_during_scanning.html
+  file_tv_set_invalid_current_channel.html
+  file_tv_scan_channels_stopped.html
+  file_tv_scan_channels_completed.html
+  head.js
+  test_helpers.js
 
 [test_tv_non_permitted_app.html]
+[test_tv_permitted_app.html]
+[test_tv_get_tuners.html]
+[test_tv_get_sources.html]
+[test_tv_get_channels.html]
+[test_tv_get_channels_during_scanning.html]
+[test_tv_get_programs.html]
+[test_tv_get_current_program.html]
+[test_tv_set_current_source.html]
+[test_tv_set_invalid_current_source.html]
+[test_tv_set_current_channel.html]
+[test_tv_set_current_channel_during_scanning.html]
+[test_tv_set_invalid_current_channel.html]
+[test_tv_scan_channels_stopped.html]
+[test_tv_scan_channels_completed.html]
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_helpers.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function is(a, b, msg) {
+  alert((a === b ? 'OK' : 'KO') + ' ' + msg);
+}
+
+function ok(a, msg) {
+  alert((a ? 'OK' : 'KO')+ ' ' + msg);
+}
+
+function info(msg) {
+  alert('INFO ' + msg);
+}
+
+function finish() {
+  alert('DONE');
+}
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_channels.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GetChannels for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_channels.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_channels_during_scanning.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test an error case for GetChannels during scanning for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_channels_during_scanning.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_current_program.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GetCurrentProgram for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_current_program.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_programs.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GetPrograms for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_programs.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_sources.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GetSources for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+"use strict";
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_sources.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_get_tuners.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GetTuners for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_get_tuners.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
rename from dom/tv/test/test_tv_non_permitted_app.html
rename to dom/tv/test/mochitest/test_tv_non_permitted_app.html
--- a/dom/tv/test/test_tv_non_permitted_app.html
+++ b/dom/tv/test/mochitest/test_tv_non_permitted_app.html
@@ -4,109 +4,46 @@
   <title>Test Non-Permitted Application for TV API</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test">
+<script type="application/javascript" src="./head.js"></script>
 <script type="application/javascript">
 
-"use strict";
-
-var gHostedManifestURL = 'http://test/tests/dom/tv/test/file_app.sjs?testToken=file_tv_non_permitted_app.html&template=file_app.template.webapp';
-var gApp;
-
-function cbError(e) {
-  ok(false, "Error callback invoked: " + this.error.name);
-  SimpleTest.finish();
-}
-
-function installApp() {
-  var request = navigator.mozApps.install(gHostedManifestURL);
-  request.onerror = cbError;
-  request.onsuccess = function() {
-    gApp = request.result;
-    runTest();
-  }
-}
-
-function uninstallApp() {
-  var request = navigator.mozApps.mgmt.uninstall(gApp);
-  request.onerror = cbError;
-  request.onsuccess = function() {
-    // All done.
-    info("All done");
-    runTest();
-  }
-}
+var tests = [
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.tv.enabled", true]]}, function() {
+      SpecialPowers.pushPermissions(
+        [{ "type": "browser", "allow": true, "context": document },
+         { "type": "embed-apps", "allow": true, "context": document },
+         { "type": "webapps-manage", "allow": true, "context": document }],
+        function() {
+          SpecialPowers.setAllAppsLaunchable(true);
+          SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+          // No confirmation needed when an app is installed and uninstalled.
+          SpecialPowers.autoConfirmAppInstall(() => {
+            SpecialPowers.autoConfirmAppUninstall(runTest);
+          });
+        });
+    });
+  },
 
-function testApp() {
-  var ifr = document.createElement('iframe');
-  ifr.setAttribute('mozbrowser', 'true');
-  ifr.setAttribute('mozapp', gApp.manifestURL);
-  ifr.setAttribute('src', gApp.manifest.launch_path);
-  var domParent = document.getElementById('content');
-
-  // Set us up to listen for messages from the app.
-  var listener = function(e) {
-    var message = e.detail.message;
-    if (/^OK/.exec(message)) {
-      ok(true, "Message from app: " + message);
-    } else if (/KO/.exec(message)) {
-      ok(false, "Message from app: " + message);
-    } else if (/DONE/.exec(message)) {
-      ok(true, "Messaging from app complete");
-      ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
-      domParent.removeChild(ifr);
-      runTest();
-    }
-  }
-
-  // This event is triggered when the app calls "alert".
-  ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
-  domParent.appendChild(ifr);
-}
-
-var tests = [
   // Installing the app
-  installApp,
+  installApp.bind(this, "file_tv_non_permitted_app.html", "file_app.template.webapp"),
 
   // Run tests in app
   testApp,
 
   // Uninstall the app
   uninstallApp
 ];
 
-function runTest() {
-  if (!tests.length) {
-    SimpleTest.finish();
-    return;
-  }
-
-  var test = tests.shift();
-  test();
-}
-
 SimpleTest.waitForExplicitFinish();
-
-SpecialPowers.pushPrefEnv({"set": [["dom.tv.enabled", true]]}, function() {
-  SpecialPowers.pushPermissions(
-    [{ "type": "tv", "allow": true, "context": document },
-     { "type": "browser", "allow": true, "context": document },
-     { "type": "embed-apps", "allow": true, "context": document },
-     { "type": "webapps-manage", "allow": true, "context": document }],
-    function(){
-      SpecialPowers.setAllAppsLaunchable(true);
-      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
-      // No confirmation needed when an app is installed
-      SpecialPowers.autoConfirmAppInstall(() => {
-        SpecialPowers.autoConfirmAppUninstall(runTest);
-      });
-    });
-});
+runTest();
 
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_permitted_app.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Permitted Application for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_permitted_app.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_scan_channels_completed.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test channel scanning complete for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_scan_channels_completed.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.expectAssertions(0, 2);
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_scan_channels_stopped.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test StartScanning and StopScanning for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_scan_channels_stopped.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_set_current_channel.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test SetCurrentChannel for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_set_current_channel.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_set_current_channel_during_scanning.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test an error case for SetCurrentChannel during scanning for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_set_current_channel_during_scanning.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_set_current_source.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test SetCurrentSource for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_set_current_source.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_set_invalid_current_channel.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test an error case for SetCurrentChannel for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_set_invalid_current_channel.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest/test_tv_set_invalid_current_source.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test an error case for SetCurrentSource for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript" src="./head.js"></script>
+<script type="application/javascript">
+
+var tests = [
+  // Setup preferences and permissions
+  setupPrefsAndPermissions,
+
+  // Installing the app
+  installApp.bind(this, "file_tv_set_invalid_current_source.html", "file_app.template.webapp"),
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
deleted file mode 100644
--- a/dom/tv/test/moz.build
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-MOCHITEST_MANIFESTS += ['mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/xpcshell/test_tv_channel_data.js
@@ -0,0 +1,169 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_valid_network_id() {
+  var networkId = "networkId";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.networkId = networkId;
+
+  equal(data.networkId, networkId);
+
+  run_next_test();
+});
+
+add_test(function test_empty_network_id() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.networkId = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_transport_stream_id() {
+  var transportStreamId = "transportStreamId";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.transportStreamId = transportStreamId;
+
+  equal(data.transportStreamId, transportStreamId);
+
+  run_next_test();
+});
+
+add_test(function test_empty_transport_stream_id() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.transportStreamId = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_service_id() {
+  var serviceId = "serviceId";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.serviceId = serviceId;
+
+  equal(data.serviceId, serviceId);
+
+  run_next_test();
+});
+
+add_test(function test_empty_service_id() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.serviceId = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_type() {
+  var type = "tv";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.type = type;
+
+  equal(data.type, type);
+
+  run_next_test();
+});
+
+add_test(function test_empty_type() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.type = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_invalid_type() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.type = "invalid";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_number() {
+  var number = "number";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.number = number;
+
+  equal(data.number, number);
+
+  run_next_test();
+});
+
+add_test(function test_empty_number() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.number = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_name() {
+  var name = "name";
+
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.name = name;
+
+  equal(data.name, name);
+
+  run_next_test();
+});
+
+add_test(function test_empty_name() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  Assert.throws(function() {
+    data.name = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_is_emergency() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.isEmergency = true;
+
+  ok(data.isEmergency);
+
+  run_next_test();
+});
+
+add_test(function test_is_free() {
+  var data = Cc["@mozilla.org/tv/tvchanneldata;1"].
+             createInstance(Ci.nsITVChannelData);
+  data.isFree = true;
+
+  ok(data.isFree);
+
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/xpcshell/test_tv_program_data.js
@@ -0,0 +1,197 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_valid_event_id() {
+  var eventId = "eventId";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.eventId = eventId;
+
+  equal(data.eventId, eventId);
+
+  run_next_test();
+});
+
+add_test(function test_empty_event_id() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  Assert.throws(function() {
+    data.eventId = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_title() {
+  var title = "title";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.title = title;
+
+  equal(data.title, title);
+
+  run_next_test();
+});
+
+add_test(function test_empty_title() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  Assert.throws(function() {
+    data.title = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_start_time() {
+  var startTime = 1;
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.startTime = startTime;
+
+  equal(data.startTime, startTime);
+
+  run_next_test();
+});
+
+add_test(function test_duration() {
+  var duration = 1;
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.duration = duration;
+
+  equal(data.duration, duration);
+
+  run_next_test();
+});
+
+add_test(function test_valid_description() {
+  var description = "description";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.description = description;
+
+  equal(data.description, description);
+
+  run_next_test();
+});
+
+add_test(function test_empty_description() {
+  var description = "";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.description = description;
+
+  equal(data.description, description);
+
+  run_next_test();
+});
+
+add_test(function test_valid_rating() {
+  var rating = "rating";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.rating = rating;
+
+  equal(data.rating, rating);
+
+  run_next_test();
+});
+
+add_test(function test_empty_rating() {
+  var rating = "";
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.rating = rating;
+
+  equal(data.rating, rating);
+
+  run_next_test();
+});
+
+add_test(function test_valid_audio_languages() {
+  var languages = ["eng", "jpn"];
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.setAudioLanguages(languages.length, languages);
+
+  var audioLanguages = data.getAudioLanguages();
+  equal(audioLanguages.length, languages.length);
+  for (var i = 0; i < audioLanguages.length; i++) {
+    equal(audioLanguages[i], languages[i]);
+  }
+
+  run_next_test();
+});
+
+add_test(function test_empty_audio_languages() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.setAudioLanguages(0, []);
+
+  var audioLanguages = data.getAudioLanguages();
+  equal(audioLanguages.length, 0);
+
+  run_next_test();
+});
+
+add_test(function test_mismatched_audio_languages() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  Assert.throws(function() {
+    data.setAudioLanguages(1, []);
+  }, /NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_subtitle_languages() {
+  var languages = ["eng", "jpn"];
+
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.setSubtitleLanguages(languages.length, languages);
+
+  var subtitleLanguages = data.getSubtitleLanguages();
+  equal(subtitleLanguages.length, languages.length);
+  for (var i = 0; i < subtitleLanguages.length; i++) {
+    equal(subtitleLanguages[i], languages[i]);
+  }
+
+  run_next_test();
+});
+
+add_test(function test_empty_subtitle_languages() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  data.setSubtitleLanguages(0, []);
+
+  var subtitleLanguages = data.getSubtitleLanguages();
+  equal(subtitleLanguages.length, 0);
+
+  run_next_test();
+});
+
+add_test(function test_mismatched_subtitle_languages() {
+  var data = Cc["@mozilla.org/tv/tvprogramdata;1"].
+             createInstance(Ci.nsITVProgramData);
+  Assert.throws(function() {
+    data.setSubtitleLanguages(1, []);
+  }, /NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY/i);
+
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/xpcshell/test_tv_tuner_data.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_valid_id() {
+  var id = "id";
+
+  var data = Cc["@mozilla.org/tv/tvtunerdata;1"].
+             createInstance(Ci.nsITVTunerData);
+  data.id = id;
+
+  equal(data.id, id);
+
+  run_next_test();
+});
+
+add_test(function test_empty_id() {
+  var data = Cc["@mozilla.org/tv/tvtunerdata;1"].
+             createInstance(Ci.nsITVTunerData);
+  Assert.throws(function() {
+    data.id = "";
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_valid_supported_source_types() {
+  var sourceTypes = ["dvb-t", "dvb-s"];
+
+  var data = Cc["@mozilla.org/tv/tvtunerdata;1"].
+             createInstance(Ci.nsITVTunerData);
+  data.setSupportedSourceTypes(sourceTypes.length, sourceTypes);
+
+  var types = data.getSupportedSourceTypes();
+  equal(types.length, sourceTypes.length);
+  for (var i = 0; i < types.length; i++) {
+    equal(types[i], sourceTypes[i]);
+  }
+
+  run_next_test();
+});
+
+add_test(function test_empty_supported_source_types() {
+  var data = Cc["@mozilla.org/tv/tvtunerdata;1"].
+             createInstance(Ci.nsITVTunerData);
+  Assert.throws(function() {
+    data.setSupportedSourceTypes(0, []);
+  }, /NS_ERROR_ILLEGAL_VALUE/i);
+
+  run_next_test();
+});
+
+add_test(function test_mismatched_supported_source_types() {
+  var data = Cc["@mozilla.org/tv/tvtunerdata;1"].
+             createInstance(Ci.nsITVTunerData);
+  Assert.throws(function() {
+    data.setSupportedSourceTypes(1, []);
+  }, /NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY/i);
+
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/xpcshell/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+
+[test_tv_tuner_data.js]
+[test_tv_channel_data.js]
+[test_tv_program_data.js]
--- a/dom/webidl/IccCardLockError.webidl
+++ b/dom/webidl/IccCardLockError.webidl
@@ -1,11 +1,10 @@
 /* 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/.
  */
 
-[Constructor(DOMString lockType, DOMString errorName, short retryCount),
+[Constructor(DOMString errorName, short retryCount),
  Pref="dom.icc.enabled"]
 interface IccCardLockError : DOMError {
-  readonly attribute DOMString lockType;
   readonly attribute short retryCount;
 };
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -213,16 +213,40 @@ typedef mozilla::gfx::Matrix4x4 Matrix4x
  * "apz.fling_accel_base_mult"
  * "apz.fling_accel_supplemental_mult"
  * When applying an acceleration on a fling, the new computed velocity is
  * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
  * The base_mult and supplemental_mult multiplier values are controlled by
  * these prefs. Note that "old_velocity" here is the initial velocity of the
  * previous fling _after_ acceleration was applied to it (if applicable).
  *
+ * "apz.fling_curve_function_x1"
+ * "apz.fling_curve_function_y1"
+ * "apz.fling_curve_function_x2"
+ * "apz.fling_curve_function_y2"
+ * "apz.fling_curve_threshold_inches_per_ms"
+ * These five parameters define a Bezier curve function and threshold used to
+ * increase the actual velocity relative to the user's finger velocity. When the
+ * finger velocity is below the threshold (or if the threshold is not positive),
+ * the velocity is used as-is. If the finger velocity exceeds the threshold
+ * velocity, then the function defined by the curve is applied on the part of
+ * the velocity that exceeds the threshold. Note that the upper bound of the
+ * velocity is still specified by the apz.max_velocity_inches_per_ms pref, and
+ * the function will smoothly curve the velocity from the threshold to the
+ * max. In general the function parameters chosen should define an ease-out
+ * curve in order to increase the velocity in this range, or an ease-in curve to
+ * decrease the velocity. A straight-line curve is equivalent to disabling the
+ * curve entirely by setting the threshold to -1. The max velocity pref must
+ * also be set in order for the curving to take effect, as it defines the upper
+ * bound of the velocity curve.
+ * The points (x1, y1) and (x2, y2) used as the two intermediate control points
+ * in the cubic bezier curve; the first and last points are (0,0) and (1,1).
+ * Some example values for these prefs can be found at
+ * http://mxr.mozilla.org/mozilla-central/source/layout/style/nsStyleStruct.cpp?rev=21282be9ad95#2462
+ *
  * "apz.fling_friction"
  * Amount of friction applied during flings.
  *
  * "apz.fling_repaint_interval"
  * Maximum amount of time flinging before sending a viewport change. This will
  * asynchronously repaint the page.
  * Units: milliseconds
  *
@@ -346,17 +370,22 @@ typedef mozilla::gfx::Matrix4x4 Matrix4x
  * "apz.zoom_animation_duration_ms"
  * This controls how long the zoom-to-rect animation takes.
  * Units: ms
  */
 
 /**
  * Computed time function used for sampling frames of a zoom to animation.
  */
-StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction;
+StaticAutoPtr<ComputedTimingFunction> gZoomAnimationFunction;
+
+/**
+ * Computed time function used for curving up velocity when it gets high.
+ */
+StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
 
 /**
  * Maximum zoom amount, always used, even if a page asks for higher.
  */
 static const CSSToScreenScale MAX_ZOOM(8.0f);
 
 /**
  * Minimum zoom amount, always used, even if a page asks for lower.
@@ -633,17 +662,17 @@ public:
     if (animPosition >= 1.0) {
       aFrameMetrics.SetZoom(mEndZoom);
       aFrameMetrics.SetScrollOffset(mEndOffset);
       return false;
     }
 
     // Sample the zoom at the current time point.  The sampled zoom
     // will affect the final computed resolution.
-    float sampledPosition = gComputedTimingFunction->GetValue(animPosition);
+    float sampledPosition = gZoomAnimationFunction->GetValue(animPosition);
 
     // We scale the scrollOffset linearly with sampledPosition, so the zoom
     // needs to scale inversely to match.
     aFrameMetrics.SetZoom(CSSToScreenScale(1 /
       (sampledPosition / mEndZoom.scale +
       (1 - sampledPosition) / mStartZoom.scale)));
 
     aFrameMetrics.SetScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point(
@@ -854,20 +883,27 @@ AsyncPanZoomController::InitializeGlobal
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool sInitialized = false;
   if (sInitialized)
     return;
   sInitialized = true;
 
-  gComputedTimingFunction = new ComputedTimingFunction();
-  gComputedTimingFunction->Init(
+  gZoomAnimationFunction = new ComputedTimingFunction();
+  gZoomAnimationFunction->Init(
     nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
-  ClearOnShutdown(&gComputedTimingFunction);
+  ClearOnShutdown(&gZoomAnimationFunction);
+  gVelocityCurveFunction = new ComputedTimingFunction();
+  gVelocityCurveFunction->Init(
+    nsTimingFunction(gfxPrefs::APZCurveFunctionX1(),
+                     gfxPrefs::APZCurveFunctionY2(),
+                     gfxPrefs::APZCurveFunctionX2(),
+                     gfxPrefs::APZCurveFunctionY2()));
+  ClearOnShutdown(&gVelocityCurveFunction);
 }
 
 AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
                                                APZCTreeManager* aTreeManager,
                                                const nsRefPtr<InputQueue>& aInputQueue,
                                                GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -3,42 +3,55 @@
 /* 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 "Axis.h"
 #include <math.h>                       // for fabsf, pow, powf
 #include <algorithm>                    // for max
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController
+#include "mozilla/dom/AnimationPlayer.h" // for ComputedTimingFunction
 #include "mozilla/layers/APZCTreeManager.h" // for APZCTreeManager
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "mozilla/Attributes.h"         // for MOZ_FINAL
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/FloatingPoint.h"      // for FuzzyEqualsAdditive
+#include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "nsMathUtils.h"                // for NS_lround
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsThreadUtils.h"              // for NS_DispatchToMainThread, etc
 #include "nscore.h"                     // for NS_IMETHOD
 #include "gfxPrefs.h"                   // for the preferences
 
+#define AXIS_LOG(...)
+// #define AXIS_LOG(...) printf_stderr("AXIS: " __VA_ARGS__)
+
 namespace mozilla {
 namespace layers {
 
+extern StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
+
 Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
   : mPos(0),
     mPosTimeMs(0),
     mVelocity(0.0f),
     mAxisLocked(false),
     mAsyncPanZoomController(aAsyncPanZoomController),
     mOverscroll(0)
 {
 }
 
+float Axis::ToLocalVelocity(float aVelocityInchesPerMs) {
+  ScreenPoint aVelocityPoint = MakePoint(aVelocityInchesPerMs * APZCTreeManager::GetDPI());
+  mAsyncPanZoomController->ToLocalScreenCoordinates(&aVelocityPoint, mAsyncPanZoomController->PanStart());
+  return aVelocityPoint.Length();
+}
+
 void Axis::UpdateWithTouchAtDevicePoint(ScreenCoord aPos, uint32_t aTimestampMs) {
   // mVelocityQueue is controller-thread only
   AsyncPanZoomController::AssertOnControllerThread();
 
   if (aTimestampMs == mPosTimeMs) {
     // This could be a duplicate event, or it could be a legitimate event
     // on some platforms that generate events really fast. As a compromise
     // update mPos so we don't run into problems like bug 1042734, even though
@@ -47,19 +60,31 @@ void Axis::UpdateWithTouchAtDevicePoint(
     return;
   }
 
   float newVelocity = mAxisLocked ? 0.0f : (float)(mPos - aPos) / (float)(aTimestampMs - mPosTimeMs);
   if (gfxPrefs::APZMaxVelocity() > 0.0f) {
     bool velocityIsNegative = (newVelocity < 0);
     newVelocity = fabs(newVelocity);
 
-    ScreenPoint maxVelocity = MakePoint(gfxPrefs::APZMaxVelocity() * APZCTreeManager::GetDPI());
-    mAsyncPanZoomController->ToLocalScreenCoordinates(&maxVelocity, mAsyncPanZoomController->PanStart());
-    newVelocity = std::min(newVelocity, maxVelocity.Length());
+    float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity());
+    newVelocity = std::min(newVelocity, maxVelocity);
+
+    if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) {
+      float curveThreshold = ToLocalVelocity(gfxPrefs::APZCurveThreshold());
+      if (newVelocity > curveThreshold) {
+        // here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply the curve
+        float scale = maxVelocity - curveThreshold;
+        float funcInput = (newVelocity - curveThreshold) / scale;
+        float funcOutput = gVelocityCurveFunction->GetValue(funcInput);
+        float curvedVelocity = (funcOutput * scale) + curveThreshold;
+        AXIS_LOG("Curving up velocity from %f to %f\n", newVelocity, curvedVelocity);
+        newVelocity = curvedVelocity;
+      }
+    }
 
     if (velocityIsNegative) {
       newVelocity = -newVelocity;
     }
   }
 
   mVelocity = newVelocity;
   mPos = aPos;
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -227,16 +227,19 @@ protected:
   // accessed on the controller/UI thread.
   nsTArray<std::pair<uint32_t, float> > mVelocityQueue;
 
   const FrameMetrics& GetFrameMetrics() const;
 
   // Adjust a requested overscroll amount for resistance, yielding a smaller
   // actual overscroll amount.
   ScreenCoord ApplyResistance(ScreenCoord aOverscroll) const;
+
+  // Convert a velocity from global inches/ms into local ScreenCoords per ms
+  float ToLocalVelocity(float aVelocityInchesPerMs);
 };
 
 class AxisX : public Axis {
 public:
   explicit AxisX(AsyncPanZoomController* mAsyncPanZoomController);
   virtual ScreenCoord GetPointOffset(const ScreenPoint& aPoint) const MOZ_OVERRIDE;
   virtual ScreenCoord GetRectLength(const ScreenRect& aRect) const MOZ_OVERRIDE;
   virtual ScreenCoord GetRectOffset(const ScreenRect& aRect) const MOZ_OVERRIDE;
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -60,16 +60,20 @@
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/StaticPtr.h"
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "ProfilerMarkers.h"
 #endif
 #include "mozilla/VsyncDispatcher.h"
 
+#ifdef MOZ_WIDGET_GONK
+#include "GeckoTouchDispatcher.h"
+#endif
+
 namespace mozilla {
 namespace layers {
 
 using namespace base;
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace std;
 
@@ -268,16 +272,18 @@ CompositorVsyncObserver::Composite(TimeS
   if (mNeedsComposite && mCompositorParent) {
     mNeedsComposite = false;
     mCompositorParent->CompositeCallback(aVsyncTimestamp);
   } else {
     // We're getting vsync notifications but we don't need to composite so
     // unregister the vsync.
     UnobserveVsync();
   }
+
+  DispatchTouchEvents(aVsyncTimestamp);
 }
 
 bool
 CompositorVsyncObserver::NeedsComposite()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   return mNeedsComposite;
 }
@@ -297,16 +303,26 @@ CompositorVsyncObserver::ObserveVsync()
 void
 CompositorVsyncObserver::UnobserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
   VsyncDispatcher::GetInstance()->RemoveCompositorVsyncObserver(this);
   mIsObservingVsync = false;
 }
 
+void
+CompositorVsyncObserver::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
+{
+#ifdef MOZ_WIDGET_GONK
+  if (gfxPrefs::TouchResampling()) {
+    GeckoTouchDispatcher::NotifyVsync(aVsyncTimestamp);
+  }
+#endif
+}
+
 void CompositorParent::StartUp()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!");
   MOZ_ASSERT(!sCompositorThreadHolder, "The compositor thread has already been started!");
 
   sCompositorThreadHolder = new CompositorThreadHolder();
 }
 
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -108,16 +108,17 @@ public:
  
 private:
   virtual ~CompositorVsyncObserver();
 
   void Composite(TimeStamp aVsyncTimestamp);
   void NotifyCompositeTaskExecuted();
   void ObserveVsync();
   void UnobserveVsync();
+  void DispatchTouchEvents(TimeStamp aVsyncTimestamp);
 
   bool mNeedsComposite;
   bool mIsObservingVsync;
   nsRefPtr<CompositorParent> mCompositorParent;
 
   mozilla::Monitor mCurrentCompositeTaskMonitor;
   CancelableTask* mCurrentCompositeTask;
 };
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -141,16 +141,21 @@ private:
   DECL_GFX_PREF(Live, "apz.content_response_timeout",          APZContentResponseTimeout, int32_t, 300);
   DECL_GFX_PREF(Live, "apz.cross_slide.enabled",               APZCrossSlideEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
   DECL_GFX_PREF(Live, "apz.danger_zone_y",                     APZDangerZoneY, int32_t, 100);
   DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped",  APZEnlargeDisplayPortWhenClipped, bool, false);
   DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms",           APZFlingAccelInterval, int32_t, 500);
   DECL_GFX_PREF(Live, "apz.fling_accel_base_mult",             APZFlingAccelBaseMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult",     APZFlingAccelSupplementalMultiplier, float, 1.0f);
+  DECL_GFX_PREF(Once, "apz.fling_curve_function_x1",           APZCurveFunctionX1, float, 0.0f);
+  DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
+  DECL_GFX_PREF(Once, "apz.fling_curve_function_x2",           APZCurveFunctionX2, float, 1.0f);
+  DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
+  DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_repaint_interval",            APZFlingRepaintInterval, int32_t, 75);
   DECL_GFX_PREF(Once, "apz.fling_stop_on_tap_threshold",       APZFlingStopOnTapThreshold, float, 0.05f);
   DECL_GFX_PREF(Once, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Once, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.num_paint_duration_samples",        APZNumPaintDurationSamples, int32_t, 3);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -461,16 +461,21 @@ pref("apz.axis_lock.direct_pan_angle", "
 pref("apz.content_response_timeout", 300);
 pref("apz.cross_slide.enabled", false);
 pref("apz.danger_zone_x", 50);
 pref("apz.danger_zone_y", 100);
 pref("apz.enlarge_displayport_when_clipped", false);
 pref("apz.fling_accel_base_mult", "1.0");
 pref("apz.fling_accel_interval_ms", 500);
 pref("apz.fling_accel_supplemental_mult", "1.0");
+pref("apz.fling_curve_function_x1", "0.0");
+pref("apz.fling_curve_function_y1", "0.0");
+pref("apz.fling_curve_function_x2", "1.0");
+pref("apz.fling_curve_function_y2", "1.0");
+pref("apz.fling_curve_threshold_inches_per_ms", "-1.0");
 pref("apz.fling_friction", "0.002");
 pref("apz.fling_stop_on_tap_threshold", "0.05");
 pref("apz.fling_stopped_threshold", "0.01");
 pref("apz.max_velocity_inches_per_ms", "-1.0");
 pref("apz.max_velocity_queue_size", 5);
 pref("apz.min_skate_speed", "1.0");
 pref("apz.num_paint_duration_samples", 3);
 pref("apz.overscroll.enabled", false);
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
@@ -719,81 +719,76 @@ bool LoadSymbols(const string& obj_file,
     // Failed, but maybe there's a .gnu_debuglink section?
     if (read_gnu_debug_link) {
       const Shdr* gnu_debuglink_section
           = FindElfSectionByName<ElfClass>(".gnu_debuglink", SHT_PROGBITS,
                                            sections, names,
                                            names_end, elf_header->e_shnum);
       if (gnu_debuglink_section) {
         if (!info->debug_dirs().empty()) {
+          found_debug_info_section = true;
+
           const char* debuglink_contents =
               GetOffset<ElfClass, char>(elf_header,
                                         gnu_debuglink_section->sh_offset);
           string debuglink_file
               = ReadDebugLink<ElfClass>(debuglink_contents,
                                         gnu_debuglink_section->sh_size,
                                         obj_file, info->debug_dirs());
           info->set_debuglink_file(debuglink_file);
         } else {
           fprintf(stderr, ".gnu_debuglink section found in '%s', "
                   "but no debug path specified.\n", obj_file.c_str());
         }
       } else {
         fprintf(stderr, "%s does not contain a .gnu_debuglink section.\n",
                 obj_file.c_str());
       }
-    } else {
-      if (symbol_data != ONLY_CFI) {
-        // The caller doesn't want to consult .gnu_debuglink.
-        // See if there are export symbols available.
-        const Shdr* dynsym_section =
-          FindElfSectionByName<ElfClass>(".dynsym", SHT_DYNSYM,
-                                         sections, names, names_end,
-                                         elf_header->e_shnum);
-        const Shdr* dynstr_section =
-          FindElfSectionByName<ElfClass>(".dynstr", SHT_STRTAB,
-                                         sections, names, names_end,
-                                         elf_header->e_shnum);
-        if (dynsym_section && dynstr_section) {
-          info->LoadedSection(".dynsym");
-
-          const uint8_t* dynsyms =
-              GetOffset<ElfClass, uint8_t>(elf_header,
-                                           dynsym_section->sh_offset);
-          const uint8_t* dynstrs =
-              GetOffset<ElfClass, uint8_t>(elf_header,
-                                           dynstr_section->sh_offset);
-          bool result =
-              ELFSymbolsToModule(dynsyms,
-                                 dynsym_section->sh_size,
-                                 dynstrs,
-                                 dynstr_section->sh_size,
-                                 big_endian,
-                                 ElfClass::kAddrSize,
-                                 module);
-          found_usable_info = found_usable_info || result;
-        }
-      }
-
-      // Return true if some usable information was found, since
-      // the caller doesn't want to use .gnu_debuglink.
-      BPLOG(INFO) << "LoadSymbols: " 
-                  << (found_usable_info ? "SUCCESS " : "FAILURE ")
-                  << obj_file;
-      return found_usable_info;
     }
-
-    // No debug info was found, let the user try again with .gnu_debuglink
-    // if present.
-    BPLOG(INFO) << "LoadSymbols: FAILURE " << obj_file;
-    return false;
   }
 
-  BPLOG(INFO) << "LoadSymbols: SUCCESS " << obj_file;
-  return true;
+  if (symbol_data != ONLY_CFI) {
+    const Shdr* dynsym_section =
+      FindElfSectionByName<ElfClass>(".dynsym", SHT_DYNSYM,
+                                     sections, names, names_end,
+                                     elf_header->e_shnum);
+    const Shdr* dynstr_section =
+      FindElfSectionByName<ElfClass>(".dynstr", SHT_STRTAB,
+                                     sections, names, names_end,
+                                     elf_header->e_shnum);
+    if (dynsym_section && dynstr_section) {
+      info->LoadedSection(".dynsym");
+
+      const uint8_t* dynsyms =
+          GetOffset<ElfClass, uint8_t>(elf_header,
+                                       dynsym_section->sh_offset);
+      const uint8_t* dynstrs =
+          GetOffset<ElfClass, uint8_t>(elf_header,
+                                       dynstr_section->sh_offset);
+      bool result =
+          ELFSymbolsToModule(dynsyms,
+                             dynsym_section->sh_size,
+                             dynstrs,
+                             dynstr_section->sh_size,
+                             big_endian,
+                             ElfClass::kAddrSize,
+                             module);
+      found_usable_info = found_usable_info || result;
+    }
+  }
+
+  if (read_gnu_debug_link) {
+    return found_debug_info_section;
+  }
+
+  // Return true if some usable information was found
+  BPLOG(INFO) << "LoadSymbols: "
+              << (found_usable_info ? "SUCCESS " : "FAILURE ")
+              << obj_file;
+  return found_usable_info;
 }
 
 // Return the breakpad symbol file identifier for the architecture of
 // ELF_HEADER.
 template<typename ElfClass>
 const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) {
   typedef typename ElfClass::Half Half;
   Half arch = elf_header->e_machine;
--- a/toolkit/crashreporter/google-breakpad/src/common/module.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/module.cc
@@ -100,20 +100,31 @@ void Module::AddStackFrameEntry(StackFra
   if (!ret.second) {
     // Free the duplicate that was not inserted because this Module
     // now owns it.
     delete stack_frame_entry;
   }
 }
 
 void Module::AddExtern(Extern *ext) {
-  std::pair<ExternSet::iterator,bool> ret = externs_.insert(ext);
-  if (!ret.second) {
-    // Free the duplicate that was not inserted because this Module
-    // now owns it.
+  Function func;
+  func.name = ext->name;
+  func.address = ext->address;
+
+  // Since parsing debug section and public info are not necessarily
+  // mutually exclusive, check if the symbol has already been read
+  // as a function to avoid duplicates.
+  if (functions_.find(&func) == functions_.end()) {
+    std::pair<ExternSet::iterator,bool> ret = externs_.insert(ext);
+    if (!ret.second) {
+      // Free the duplicate that was not inserted because this Module
+      // now owns it.
+      delete ext;
+    }
+  } else {
     delete ext;
   }
 }
 
 void Module::GetFunctions(vector<Function *> *vec,
                           vector<Function *>::iterator i) {
   vec->insert(i, functions_.begin(), functions_.end());
 }