Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Oct 2014 16:19:07 -0400
changeset 213239 bc78d64a2a148dd236cadec6e9d6ad1d1c8cd81a
parent 213238 acd029ee3d13abf6106c559b8c8909139891e9cf (current diff)
parent 213208 e0b505a37b1c0bdeb1fd9523a49eb36b58d2133d (diff)
child 213240 af652f97938d6dd3807d8a7009f3010b798e0cc8
push id27745
push usercbook@mozilla.com
push dateFri, 31 Oct 2014 13:09:12 +0000
treeherdermozilla-central@6bd2071b373f [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 m-c to inbound. 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/mochitest/file_app.sjs
dom/tv/test/mochitest/file_app.template.webapp
dom/tv/test/mochitest/file_tv_non_permitted_app.html
dom/tv/test/mochitest/mochitest.ini
dom/tv/test/mochitest/test_tv_non_permitted_app.html
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/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -309,19 +309,19 @@ skip-if = os == "mac" || e10s # bug 9670
 [browser_ctrlTab.js]
 skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
 [browser_customize_popupNotification.js]
 skip-if = e10s
 [browser_datareporting_notification.js]
 run-if = datareporting
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
-skip-if = buildapp == 'mulet' || os == "linux" || e10s # linux: bug 976544 & bug 1060315; e10s: Bug 973001 - appears user media notifications only happen in the child and don't make their way to the parent?
+skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: bug 1071623
 [browser_devices_get_user_media_about_urls.js]
-skip-if = e10s # Bug 973001 - appears user media notifications only happen in the child and don't make their way to the parent?
+skip-if = e10s # Bug 1071623
 [browser_discovery.js]
 skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
 [browser_double_close_tab.js]
 skip-if = e10s
 [browser_duplicateIDs.js]
 [browser_drag.js]
 skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 [browser_favicon_change.js]
--- a/browser/base/content/test/general/browser_devices_get_user_media.js
+++ b/browser/base/content/test/general/browser_devices_get_user_media.js
@@ -173,59 +173,59 @@ function getMediaCaptureState() {
     return "CameraAndMicrophone";
   if (hasVideo.value)
     return "Camera";
   if (hasAudio.value)
     return "Microphone";
   return "none";
 }
 
-function closeStream(aAlreadyClosed) {
+function* closeStream(aAlreadyClosed) {
   expectNoObserverCalled();
 
   info("closing the stream");
   content.wrappedJSObject.closeStream();
 
   if (!aAlreadyClosed)
     yield promiseObserverCalled("recording-device-events");
 
   yield promiseNoPopupNotification("webRTC-sharingDevices");
   if (!aAlreadyClosed)
     expectObserverCalled("recording-window-ended");
 
-  assertWebRTCIndicatorStatus(null);
+  yield* assertWebRTCIndicatorStatus(null);
 }
 
 function checkDeviceSelectors(aAudio, aVideo) {
   let micSelector = document.getElementById("webRTC-selectMicrophone");
   if (aAudio)
     ok(!micSelector.hidden, "microphone selector visible");
   else
     ok(micSelector.hidden, "microphone selector hidden");
 
   let cameraSelector = document.getElementById("webRTC-selectCamera");
   if (aVideo)
     ok(!cameraSelector.hidden, "camera selector visible");
   else
     ok(cameraSelector.hidden, "camera selector hidden");
 }
 
-function checkSharingUI(aExpected) {
+function* checkSharingUI(aExpected) {
   yield promisePopupNotification("webRTC-sharingDevices");
 
-  assertWebRTCIndicatorStatus(aExpected);
+  yield* assertWebRTCIndicatorStatus(aExpected);
 }
 
-function checkNotSharing() {
+function* checkNotSharing() {
   is(getMediaCaptureState(), "none", "expected nothing to be shared");
 
   ok(!PopupNotifications.getNotification("webRTC-sharingDevices"),
      "no webRTC-sharingDevices popup notification");
 
-  assertWebRTCIndicatorStatus(null);
+  yield* assertWebRTCIndicatorStatus(null);
 }
 
 const permissionError = "error: PermissionDeniedError: The user did not grant permission for the operation.";
 
 let gTests = [
 
 {
   desc: "getUserMedia audio+video",
@@ -388,17 +388,17 @@ let gTests = [
     });
 
     // reset the menuitems to have no impact on the following tests.
     enableDevice("Camera", true);
     enableDevice("Microphone", true);
 
     expectObserverCalled("getUserMedia:response:deny");
     expectObserverCalled("recording-window-ended");
-    checkNotSharing();
+    yield checkNotSharing();
   }
 },
 
 {
   desc: "getUserMedia audio+video, user clicks \"Don't Share\"",
   run: function checkDontShare() {
     yield promisePopupNotificationShown("webRTC-shareDevices", () => {
       info("requesting devices");
@@ -408,17 +408,17 @@ let gTests = [
     checkDeviceSelectors(true, true);
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     expectObserverCalled("getUserMedia:response:deny");
     expectObserverCalled("recording-window-ended");
-    checkNotSharing();
+    yield checkNotSharing();
   }
 },
 
 {
   desc: "getUserMedia audio+video: stop sharing",
   run: function checkStopSharing() {
     yield promisePopupNotificationShown("webRTC-shareDevices", () => {
       info("requesting devices");
@@ -447,17 +447,17 @@ let gTests = [
     expectObserverCalled("recording-window-ended");
 
     if (gObservedTopics["recording-device-events"] == 1) {
       todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
       gObservedTopics["recording-device-events"] = 0;
     }
 
     expectNoObserverCalled();
-    checkNotSharing();
+    yield checkNotSharing();
 
     // the stream is already closed, but this will do some cleanup anyway
     yield closeStream(true);
   }
 },
 
 {
   desc: "getUserMedia prompt: Always/Never Share",
--- a/browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
+++ b/browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
@@ -105,30 +105,30 @@ function getMediaCaptureState() {
     return "CameraAndMicrophone";
   if (hasVideo.value)
     return "Camera";
   if (hasAudio.value)
     return "Microphone";
   return "none";
 }
 
-function closeStream(aAlreadyClosed) {
+function* closeStream(aAlreadyClosed) {
   expectNoObserverCalled();
 
   info("closing the stream");
   content.wrappedJSObject.closeStream();
 
   if (!aAlreadyClosed)
     yield promiseObserverCalled("recording-device-events");
 
   yield promiseNoPopupNotification("webRTC-sharingDevices");
   if (!aAlreadyClosed)
     expectObserverCalled("recording-window-ended");
 
-  assertWebRTCIndicatorStatus(null);
+  yield* assertWebRTCIndicatorStatus(null);
 }
 
 function loadPage(aUrl) {
   let deferred = Promise.defer();
 
   gTab.linkedBrowser.addEventListener("load", function onload() {
     gTab.linkedBrowser.removeEventListener("load", onload, true);
 
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -662,17 +662,32 @@ function assertWebRTCIndicatorStatus(exp
     is(menu && !menu.hidden, !!expected, "WebRTC menu should be " + expectedState);
   }
 
   if (!("nsISystemStatusBar" in Ci)) {
     let indicator = Services.wm.getEnumerator("Browser:WebRTCGlobalIndicator");
     let hasWindow = indicator.hasMoreElements();
     is(hasWindow, !!expected, "popup " + msg);
     if (hasWindow) {
-      let docElt = indicator.getNext().document.documentElement;
+      let document = indicator.getNext().document;
+      let docElt = document.documentElement;
+
+      if (document.readyState != "complete") {
+        info("Waiting for the sharing indicator's document to load");
+        let deferred = Promise.defer();
+        document.addEventListener("readystatechange",
+                                  function onReadyStateChange() {
+          if (document.readyState != "complete")
+            return;
+          document.removeEventListener("readystatechange", onReadyStateChange);
+          deferred.resolve();
+        });
+        yield deferred.promise;
+      }
+
       for (let item of ["video", "audio", "screen"]) {
         let expectedValue = (expected && expected[item]) ? "true" : "";
         is(docElt.getAttribute("sharing" + item), expectedValue,
            item + " global indicator attribute as expected");
       }
 
       ok(!indicator.hasMoreElements(), "only one global indicator window");
     }
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -64,29 +64,29 @@ skip-if = os == "linux"
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_945739_showInPrivateBrowsing_customize_mode.js]
 [browser_947914_button_addons.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_copy.js]
-skip-if = os == "linux" # Intermittent failures
+skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
 [browser_947914_button_cut.js]
-skip-if = os == "linux" # Intermittent failures
+skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
 [browser_947914_button_find.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_history.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_newPrivateWindow.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_newWindow.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_paste.js]
-skip-if = os == "linux" # Intermittent failures
+skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561
 [browser_947914_button_print.js]
 skip-if = os == "linux" || (os == "win" && e10s) # Intermittent failures on Linux, e10s issues on Windows (bug 1088714)
 [browser_947914_button_savePage.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_zoomIn.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_zoomOut.js]
 skip-if = os == "linux" # Intermittent failures
--- a/browser/devtools/canvasdebugger/test/browser.ini
+++ b/browser/devtools/canvasdebugger/test/browser.ini
@@ -14,17 +14,16 @@ support-files =
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
 [browser_canvas-actor-test-08.js]
 [browser_canvas-actor-test-09.js]
 [browser_canvas-actor-test-10.js]
-skip-if = e10s # Bug 1058879 - canvas debugger tests disabled with e10s
 [browser_canvas-frontend-call-highlight.js]
 [browser_canvas-frontend-call-list.js]
 [browser_canvas-frontend-call-search.js]
 [browser_canvas-frontend-call-stack-01.js]
 [browser_canvas-frontend-call-stack-02.js]
 [browser_canvas-frontend-call-stack-03.js]
 [browser_canvas-frontend-clear.js]
 [browser_canvas-frontend-img-screenshots.js]
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js
@@ -3,18 +3,17 @@
 
 /**
  * Tests that the correct framebuffer, renderbuffer and textures are re-bound
  * after generating screenshots using the actor.
  */
 
 function ifTestingSupported() {
   let { target, front } = yield initCanvasDebuggerBackend(WEBGL_BINDINGS_URL);
-  // XXX - use of |debuggee| here is incompatible with e10s - bug 1058879.
-  let debuggee = target.window.wrappedJSObject
+  loadFrameScripts();
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
@@ -32,30 +31,36 @@ function ifTestingSupported() {
     "The first screenshot has the correct width.");
   is(firstScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
     "The first screenshot has the correct height.");
   is(firstScreenshot.flipped, true,
     "The first screenshot has the correct 'flipped' flag.");
   is(firstScreenshot.pixels.length, 0,
     "The first screenshot should be empty.");
 
-  let gl = debuggee.gl;
-  is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
+  is((yield evalInDebuggee("gl.getParameter(gl.FRAMEBUFFER_BINDING) === customFramebuffer")),
+    true,
     "The debuggee's gl context framebuffer wasn't changed.");
-  is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
+  is((yield evalInDebuggee("gl.getParameter(gl.RENDERBUFFER_BINDING) === customRenderbuffer")),
+    true,
     "The debuggee's gl context renderbuffer wasn't changed.");
-  is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
+  is((yield evalInDebuggee("gl.getParameter(gl.TEXTURE_BINDING_2D) === customTexture")),
+    true,
     "The debuggee's gl context texture binding wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[0], 128,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[0]")),
+    128,
     "The debuggee's gl context viewport's left coord. wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[1], 256,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[1]")),
+    256,
     "The debuggee's gl context viewport's left coord. wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[2], 384,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[2]")),
+    384,
     "The debuggee's gl context viewport's left coord. wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[3], 512,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[3]")),
+    512,
     "The debuggee's gl context viewport's left coord. wasn't changed.");
 
   let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
   is(secondScreenshot.index, 1,
     "The second screenshot has the correct index.");
   is(secondScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
     "The second screenshot has the correct width.");
   is(secondScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
@@ -70,27 +75,33 @@ function ifTestingSupported() {
     "The second screenshot has the correct red component.");
   is(new Uint8Array(secondScreenshot.pixels.buffer)[1], 0,
     "The second screenshot has the correct green component.");
   is(new Uint8Array(secondScreenshot.pixels.buffer)[2], 255,
     "The second screenshot has the correct blue component.");
   is(new Uint8Array(secondScreenshot.pixels.buffer)[3], 255,
     "The second screenshot has the correct alpha component.");
 
-  gl = debuggee.gl;
-  is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
+  is((yield evalInDebuggee("gl.getParameter(gl.FRAMEBUFFER_BINDING) === customFramebuffer")),
+    true,
     "The debuggee's gl context framebuffer still wasn't changed.");
-  is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
+  is((yield evalInDebuggee("gl.getParameter(gl.RENDERBUFFER_BINDING) === customRenderbuffer")),
+    true,
     "The debuggee's gl context renderbuffer still wasn't changed.");
-  is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
+  is((yield evalInDebuggee("gl.getParameter(gl.TEXTURE_BINDING_2D) === customTexture")),
+    true,
     "The debuggee's gl context texture binding still wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[0], 128,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[0]")),
+    128,
     "The debuggee's gl context viewport's left coord. still wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[1], 256,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[1]")),
+    256,
     "The debuggee's gl context viewport's left coord. still wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[2], 384,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[2]")),
+    384,
     "The debuggee's gl context viewport's left coord. still wasn't changed.");
-  is(gl.getParameter(gl.VIEWPORT)[3], 512,
+  is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[3]")),
+    512,
     "The debuggee's gl context viewport's left coord. still wasn't changed.");
 
   yield removeTab(target.tab);
   finish();
 }
--- a/browser/devtools/canvasdebugger/test/head.js
+++ b/browser/devtools/canvasdebugger/test/head.js
@@ -6,29 +6,31 @@ const { classes: Cc, interfaces: Ci, uti
 
 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Disable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
+let { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
-
 let { CallWatcherFront } = devtools.require("devtools/server/actors/call-watcher");
 let { CanvasFront } = devtools.require("devtools/server/actors/canvas");
 let TiltGL = devtools.require("devtools/tilt/tilt-gl");
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
+let mm = null
 
+const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/";
 const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
 const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
 const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
 const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
 const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html";
 const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html";
 
@@ -43,16 +45,25 @@ registerCleanupFunction(() => {
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", gToolEnabled);
 
   // Some of yhese tests use a lot of memory due to GL contexts, so force a GC
   // to help fragmentation.
   info("Forcing GC after canvas debugger test.");
   Cu.forceGC();
 });
 
+/**
+ * Call manually in tests that use frame script utils after initializing
+ * the shader editor. Call after init but before navigating to different pages.
+ */
+function loadFrameScripts () {
+  mm = gBrowser.selectedBrowser.messageManager;
+  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
+
 function addTab(aUrl, aWindow) {
   info("Adding tab: " + aUrl);
 
   let deferred = promise.defer();
   let targetWindow = aWindow || window;
   let targetBrowser = targetWindow.gBrowser;
 
   targetWindow.focus();
@@ -227,8 +238,35 @@ function initCanvasDebuggerFrontend(aUrl
 function teardown(aPanel) {
   info("Destroying the specified canvas debugger.");
 
   return promise.all([
     once(aPanel, "destroyed"),
     removeTab(aPanel.target.tab)
   ]);
 }
+
+/**
+ * Takes a string `script` and evaluates it directly in the content
+ * in potentially a different process.
+ */
+function evalInDebuggee (script) {
+  let deferred = promise.defer();
+
+  if (!mm) {
+    throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
+  }
+
+  let id = generateUUID().toString();
+  mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
+  mm.addMessageListener("devtools:test:eval:response", handler);
+
+  function handler ({ data }) {
+    if (id !== data.id) {
+      return;
+    }
+
+    mm.removeMessageListener("devtools:test:eval:response", handler);
+    deferred.resolve(data.value);
+  }
+
+  return deferred.promise;
+}
--- a/browser/devtools/commandline/test/browser_cmd_settings.js
+++ b/browser/devtools/commandline/test/browser_cmd_settings.js
@@ -20,59 +20,59 @@ function spawnTest() {
   // Setup
   let options = yield helpers.openTab(TEST_URI);
 
   require("devtools/commandline/commands-index");
   let gcli = require("gcli/index");
   yield gcli.load();
   let settings = gcli.settings;
 
-  let tiltEnabled = settings.get("devtools.tilt.enabled");
+  let hideIntroEnabled = settings.get("devtools.gcli.hideIntro");
   let tabSize = settings.get("devtools.editor.tabsize");
   let remoteHost = settings.get("devtools.debugger.remote-host");
 
-  let tiltEnabledOrig = prefBranch.getBoolPref("devtools.tilt.enabled");
+  let hideIntroOrig = prefBranch.getBoolPref("devtools.gcli.hideIntro");
   let tabSizeOrig = prefBranch.getIntPref("devtools.editor.tabsize");
   let remoteHostOrig = prefBranch.getComplexValue(
           "devtools.debugger.remote-host",
           Components.interfaces.nsISupportsString).data;
 
-  info("originally: devtools.tilt.enabled = " + tiltEnabledOrig);
+  info("originally: devtools.gcli.hideIntro = " + hideIntroOrig);
   info("originally: devtools.editor.tabsize = " + tabSizeOrig);
   info("originally: devtools.debugger.remote-host = " + remoteHostOrig);
 
   // Actual tests
-  is(tiltEnabled.value, tiltEnabledOrig, "tiltEnabled default");
+  is(hideIntroEnabled.value, hideIntroOrig, "hideIntroEnabled default");
   is(tabSize.value, tabSizeOrig, "tabSize default");
   is(remoteHost.value, remoteHostOrig, "remoteHost default");
 
-  tiltEnabled.setDefault();
+  hideIntroEnabled.setDefault();
   tabSize.setDefault();
   remoteHost.setDefault();
 
-  let tiltEnabledDefault = tiltEnabled.value;
+  let hideIntroEnabledDefault = hideIntroEnabled.value;
   let tabSizeDefault = tabSize.value;
   let remoteHostDefault = remoteHost.value;
 
-  tiltEnabled.value = false;
+  hideIntroEnabled.value = false;
   tabSize.value = 42;
-  remoteHost.value = "example.com"
+  remoteHost.value = "example.com";
 
-  is(tiltEnabled.value, false, "tiltEnabled basic");
+  is(hideIntroEnabled.value, false, "hideIntroEnabled basic");
   is(tabSize.value, 42, "tabSize basic");
   is(remoteHost.value, "example.com", "remoteHost basic");
 
-  function tiltEnabledCheck(ev) {
-    is(ev.setting, tiltEnabled, "tiltEnabled event setting");
-    is(ev.value, true, "tiltEnabled event value");
-    is(ev.setting.value, true, "tiltEnabled event setting value");
+  function hideIntroEnabledCheck(ev) {
+    is(ev.setting, hideIntroEnabled, "hideIntroEnabled event setting");
+    is(ev.value, true, "hideIntroEnabled event value");
+    is(ev.setting.value, true, "hideIntroEnabled event setting value");
   }
-  tiltEnabled.onChange.add(tiltEnabledCheck);
-  tiltEnabled.value = true;
-  is(tiltEnabled.value, true, "tiltEnabled change");
+  hideIntroEnabled.onChange.add(hideIntroEnabledCheck);
+  hideIntroEnabled.value = true;
+  is(hideIntroEnabled.value, true, "hideIntroEnabled change");
 
   function tabSizeCheck(ev) {
     is(ev.setting, tabSize, "tabSize event setting");
     is(ev.value, 1, "tabSize event value");
     is(ev.setting.value, 1, "tabSize event setting value");
   }
   tabSize.onChange.add(tabSizeCheck);
   tabSize.value = 1;
@@ -82,39 +82,39 @@ function spawnTest() {
     is(ev.setting, remoteHost, "remoteHost event setting");
     is(ev.value, "y.com", "remoteHost event value");
     is(ev.setting.value, "y.com", "remoteHost event setting value");
   }
   remoteHost.onChange.add(remoteHostCheck);
   remoteHost.value = "y.com";
   is(remoteHost.value, "y.com", "remoteHost change");
 
-  tiltEnabled.onChange.remove(tiltEnabledCheck);
+  hideIntroEnabled.onChange.remove(hideIntroEnabledCheck);
   tabSize.onChange.remove(tabSizeCheck);
   remoteHost.onChange.remove(remoteHostCheck);
 
   function remoteHostReCheck(ev) {
     is(ev.setting, remoteHost, "remoteHost event reset");
     is(ev.value, null, "remoteHost event revalue");
     is(ev.setting.value, null, "remoteHost event setting revalue");
   }
   remoteHost.onChange.add(remoteHostReCheck);
 
-  tiltEnabled.setDefault();
+  hideIntroEnabled.setDefault();
   tabSize.setDefault();
   remoteHost.setDefault();
 
   remoteHost.onChange.remove(remoteHostReCheck);
 
-  is(tiltEnabled.value, tiltEnabledDefault, "tiltEnabled reset");
+  is(hideIntroEnabled.value, hideIntroEnabledDefault, "hideIntroEnabled reset");
   is(tabSize.value, tabSizeDefault, "tabSize reset");
   is(remoteHost.value, remoteHostDefault, "remoteHost reset");
 
   // Cleanup
-  prefBranch.setBoolPref("devtools.tilt.enabled", tiltEnabledOrig);
+  prefBranch.setBoolPref("devtools.gcli.hideIntro", hideIntroOrig);
   prefBranch.setIntPref("devtools.editor.tabsize", tabSizeOrig);
   supportsString.data = remoteHostOrig;
   prefBranch.setComplexValue("devtools.debugger.remote-host",
           Components.interfaces.nsISupportsString,
           supportsString);
 
   yield helpers.closeTab(options);
 }
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -340,16 +340,20 @@ TabTarget.prototype = {
   get isAddon() {
     return !!(this._form && this._form.addonActor);
   },
 
   get isLocalTab() {
     return !!this._tab;
   },
 
+  get isMultiProcess() {
+    return !this.window;
+  },
+
   get isThreadPaused() {
     return !!this._isThreadPaused;
   },
 
   /**
    * Adds remote protocol capabilities to the target, so that it can be used
    * for tools that support the Remote Debugging Protocol even for local
    * connections.
--- a/browser/devtools/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/browser/devtools/framework/test/browser_toolbox_options_disable_buttons.js
@@ -69,37 +69,42 @@ function testPreferenceAndUIStateIsConsi
     let check = checkNodes.filter(node=>node.id === tool.id)[0];
     is (check.checked, isVisible, "Checkbox should be selected based on current pref for " + tool.id);
   }
 }
 
 function testToggleToolboxButtons() {
   let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
   let toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
-  let visibleButtons = toolboxButtonNodes.filter(button=>!button.hasAttribute("hidden"));
   let toggleableTools = toolbox.toolboxButtons;
 
+  // Tilt is disabled in E10S mode so we skip the tilt button if E10S is
+  // enabled.
+  if (toolbox.target.isMultiProcess) {
+    toolboxButtonNodes = [...doc.querySelectorAll(".command-button:not(#command-button-tilt)")];
+  }
+
   is (checkNodes.length, toggleableTools.length, "All of the buttons are toggleable." );
   is (checkNodes.length, toolboxButtonNodes.length, "All of the DOM buttons are toggleable." );
 
   for (let tool of toggleableTools) {
     let id = tool.id;
     let matchedCheckboxes = checkNodes.filter(node=>node.id === id);
     let matchedButtons = toolboxButtonNodes.filter(button=>button.id === id);
     ok (matchedCheckboxes.length === 1,
       "There should be a single toggle checkbox for: " + id);
     ok (matchedButtons.length === 1,
       "There should be a DOM button for: " + id);
     is (matchedButtons[0], tool.button,
       "DOM buttons should match for: " + id);
 
     is (matchedCheckboxes[0].getAttribute("label"), tool.label,
-      "The label for checkbox matches the tool definition.")
+      "The label for checkbox matches the tool definition.");
     is (matchedButtons[0].getAttribute("tooltiptext"), tool.label,
-      "The tooltip for button matches the tool definition.")
+      "The tooltip for button matches the tool definition.");
   }
 
   // Store modified pref names so that they can be cleared on error.
   for (let tool of toggleableTools) {
     let pref = tool.visibilityswitch;
     modifiedPrefs.push(pref);
   }
 
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -177,16 +177,20 @@ OptionsPanel.prototype = {
       checkbox.setAttribute("id", tool.id);
       checkbox.setAttribute("label", tool.label);
       checkbox.setAttribute("checked", InfallibleGetBoolPref(tool.visibilityswitch));
       checkbox.addEventListener("command", onCheckboxClick.bind(this, checkbox));
       return checkbox;
     };
 
     for (let tool of toggleableButtons) {
+      if (this.toolbox.target.isMultiProcess && tool.id === "command-button-tilt") {
+        continue;
+      }
+
       enabledToolbarButtonsBox.appendChild(createCommandCheckbox(tool));
     }
   },
 
   setupToolsList: function() {
     let defaultToolsBox = this.panelDoc.getElementById("default-tools-box");
     let additionalToolsBox = this.panelDoc.getElementById("additional-tools-box");
     let toolsNotSupportedLabel = this.panelDoc.getElementById("tools-not-supported-label");
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -686,24 +686,31 @@ Toolbox.prototype = {
    */
   get toolboxButtons() {
     return ToolboxButtons.map(options => {
       let button = this.doc.getElementById(options.id);
       // Some buttons may not exist inside of Browser Toolbox
       if (!button) {
         return false;
       }
+
+      // Disable tilt in E10S mode. Removing it from the list of toolbox buttons
+      // allows a bunch of tests to pass without modification.
+      if (this.target.isMultiProcess && options.id === "command-button-tilt") {
+        return false;
+      }
+
       return {
         id: options.id,
         button: button,
         label: button.getAttribute("tooltiptext"),
         visibilityswitch: "devtools." + options.id + ".enabled",
         isTargetSupported: options.isTargetSupported ? options.isTargetSupported
                                                      : target => target.isLocalTab
-      }
+      };
     }).filter(button=>button);
   },
 
   /**
    * Ensure the visibility of each toolbox button matches the
    * preference value.  Simply hide buttons that are preffed off.
    */
   setToolboxButtonsVisibility: function() {
@@ -719,16 +726,28 @@ Toolbox.prototype = {
       if (button) {
         if (on) {
           button.removeAttribute("hidden");
         } else {
           button.setAttribute("hidden", "true");
         }
       }
     });
+
+    // Tilt is handled separately because it is disabled in E10S mode. Because
+    // we have removed tilt from toolboxButtons we have to deal with it here.
+    let tiltEnabled = !this.target.isMultiProcess &&
+                      Services.prefs.getBoolPref("devtools.command-button-tilt.enabled");
+    let tiltButton = this.doc.getElementById("command-button-tilt");
+
+    if (tiltEnabled) {
+      tiltButton.removeAttribute("hidden");
+    } else {
+      tiltButton.setAttribute("hidden", "true");
+    }
   },
 
   /**
    * Build a tab for one tool definition and add to the toolbox
    *
    * @param {string} toolDefinition
    *        Tool definition of the tool to build a tab for.
    */
--- a/browser/devtools/shared/frame-script-utils.js
+++ b/browser/devtools/shared/frame-script-utils.js
@@ -17,11 +17,20 @@ addMessageListener("devtools:test:reload
   content.location.reload(data.forceget);
 });
 
 addMessageListener("devtools:test:console", function ({ data }) {
   let method = data.shift();
   content.console[method].apply(content.console, data);
 });
 
+// To eval in content, look at `evalInDebuggee` in the head.js of canvasdebugger
+// for an example.
+addMessageListener("devtools:test:eval", function ({ data }) {
+  sendAsyncMessage("devtools:test:eval:response", {
+    value: content.eval(data.script),
+    id: data.id
+  });
+});
+
 addEventListener("load", function() {
   sendAsyncMessage("devtools:test:load");
 }, true);
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -44,16 +44,18 @@ support-files =
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
 skip-if = buildapp == 'mulet'
 [browser_telemetry_button_paintflashing.js]
 [browser_telemetry_button_responsive.js]
 [browser_telemetry_button_scratchpad.js]
 [browser_telemetry_button_tilt.js]
+skip-if = e10s # Bug 1086492 - Disable tilt for e10s
+               # Bug 937166 - Make tilt work in E10S mode
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_canvasdebugger.js]
 [browser_telemetry_toolboxtabs_inspector.js]
 [browser_telemetry_toolboxtabs_jsdebugger.js]
 [browser_telemetry_toolboxtabs_jsprofiler.js]
 [browser_telemetry_toolboxtabs_netmonitor.js]
 [browser_telemetry_toolboxtabs_options.js]
--- a/browser/devtools/tilt/test/browser.ini
+++ b/browser/devtools/tilt/test/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
+skip-if = e10s # Bug 1086492 - Disable tilt for e10s
+               # Bug 937166 - Make tilt work in E10S mode
 subsuite = devtools
 support-files = head.js
 
 [browser_tilt_01_lazy_getter.js]
 [browser_tilt_02_notifications-seq.js]
 [browser_tilt_02_notifications-tabs.js]
 [browser_tilt_02_notifications.js]
 [browser_tilt_03_tab_switch.js]
--- a/browser/devtools/tilt/tilt-commands.js
+++ b/browser/devtools/tilt/tilt-commands.js
@@ -15,23 +15,29 @@ Object.defineProperty(this, "TiltManager
   },
   enumerable: true
 });
 
 exports.items = [
 {
   name: 'tilt',
   description: gcli.lookup("tiltDesc"),
-  manual: gcli.lookup("tiltManual")
+  manual: gcli.lookup("tiltManual"),
+  hidden: true
 },
 {
   name: 'tilt open',
   description: gcli.lookup("tiltOpenDesc"),
   manual: gcli.lookup("tiltOpenManual"),
+  hidden: true,
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (!Tilt.currentInstance) {
       Tilt.toggle();
     }
   }
 },
 {
@@ -54,25 +60,30 @@ exports.items = [
       if (aTarget.tab) {
         let browserWindow = aTarget.tab.ownerDocument.defaultView;
         let tilt = TiltManager.getTiltForBrowser(browserWindow);
         tilt.off("change", aChangeHandler);
       }
     },
   },
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     Tilt.toggle();
   }
 },
 {
   name: 'tilt translate',
   description: gcli.lookup("tiltTranslateDesc"),
   manual: gcli.lookup("tiltTranslateManual"),
+  hidden: true,
   params: [
     {
       name: "x",
       type: "number",
       defaultValue: 0,
       description: gcli.lookup("tiltTranslateXDesc"),
       manual: gcli.lookup("tiltTranslateXManual")
     },
@@ -80,27 +91,32 @@ exports.items = [
       name: "y",
       type: "number",
       defaultValue: 0,
       description: gcli.lookup("tiltTranslateYDesc"),
       manual: gcli.lookup("tiltTranslateYManual")
     }
   ],
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.translate([args.x, args.y]);
     }
   }
 },
 {
   name: 'tilt rotate',
   description: gcli.lookup("tiltRotateDesc"),
   manual: gcli.lookup("tiltRotateManual"),
+  hidden: true,
   params: [
     {
       name: "x",
       type: { name: 'number', min: -360, max: 360, step: 10 },
       defaultValue: 0,
       description: gcli.lookup("tiltRotateXDesc"),
       manual: gcli.lookup("tiltRotateXManual")
     },
@@ -115,61 +131,84 @@ exports.items = [
       name: "z",
       type: { name: 'number', min: -360, max: 360, step: 10 },
       defaultValue: 0,
       description: gcli.lookup("tiltRotateZDesc"),
       manual: gcli.lookup("tiltRotateZManual")
     }
   ],
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.rotate([args.x, args.y, args.z]);
     }
   }
 },
 {
   name: 'tilt zoom',
   description: gcli.lookup("tiltZoomDesc"),
   manual: gcli.lookup("tiltZoomManual"),
+  hidden: true,
   params: [
     {
       name: "zoom",
       type: { name: 'number' },
       description: gcli.lookup("tiltZoomAmountDesc"),
       manual: gcli.lookup("tiltZoomAmountManual")
     }
   ],
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.zoom(-args.zoom);
     }
   }
 },
 {
   name: 'tilt reset',
   description: gcli.lookup("tiltResetDesc"),
   manual: gcli.lookup("tiltResetManual"),
+  hidden: true,
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.reset();
     }
   }
 },
 {
   name: 'tilt close',
   description: gcli.lookup("tiltCloseDesc"),
   manual: gcli.lookup("tiltCloseManual"),
+  hidden: true,
   exec: function(args, context) {
+    if (isMultiProcess(context)) {
+      return gcli.lookupFormat("notAvailableInE10S", [this.name]);
+    }
+
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     Tilt.destroy(Tilt.currentWindowId);
   }
 }
 ];
+
+function isMultiProcess(context) {
+  return !context.environment.window;
+}
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -15,16 +15,20 @@
 # LOCALIZATION NOTE (helpDesc) A very short string used to describe the
 # function of the help command.
 helpDesc=Get help on the available commands
 
 # LOCALIZATION NOTE (helpAvailable) Used in the output of the help command to
 # explain the contents of the command help table.
 helpAvailable=Available Commands
 
+# LOCALIZATION NOTE (notAvailableInE10S) Used in the output of any command that
+# is not compatible with multiprocess mode (E10S).
+notAvailableInE10S=The command '%1$S' is not available in multiprocess mode (E10S)
+
 # LOCALIZATION NOTE (consoleDesc) A very short string used to describe the
 # function of the console command.
 consoleDesc=Commands to control the console
 
 # LOCALIZATION NOTE (consoleManual) A longer description describing the
 # set of commands that control the console.
 consoleManual=Filter, clear and close the web console
 
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -96,16 +96,18 @@ this.UITour = {
                                                         "class",
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
     ["loop",        {query: "#loop-call-button"}],
+    ["devtools",    {query: "#developer-button"}],
+    ["webide",      {query: "#webide-button"}],
     ["forget", {
       query: "#panic-button",
       widgetName: "panic-button",
       allowAdd: true }],
     ["privateWindow",  {query: "#privatebrowsing-button"}],
     ["quit",        {query: "#PanelUI-quit"}],
     ["search",      {
       query: "#searchbar",
--- a/browser/modules/test/browser_UITour_availableTargets.js
+++ b/browser/modules/test/browser_UITour_availableTargets.js
@@ -4,16 +4,18 @@
 "use strict";
 
 let gTestTab;
 let gContentAPI;
 let gContentWindow;
 
 Components.utils.import("resource:///modules/UITour.jsm");
 
+let hasWebIDE = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
+
 function test() {
   requestLongerTimeout(2);
   UITourTest();
 }
 
 function searchEngineTargets() {
   let engines = Services.search.getVisibleEngines();
   return ["searchEngine-" + engine.identifier
@@ -29,23 +31,27 @@ let tests = [
         "addons",
         "appMenu",
         "backForward",
         "bookmarks",
         "customize",
         "help",
         "home",
         "loop",
+        "devtools",
         "pinnedTab",
         "privateWindow",
         "quit",
         "search",
         "searchProvider",
         "urlbar",
-      ].concat(searchEngineTargets()));
+        ...searchEngineTargets(),
+        ...(hasWebIDE ? ["webide"] : [])
+      ]);
+
       ok(UITour.availableTargetsCache.has(window),
          "Targets should now be cached");
       done();
     });
   },
 
   function test_availableTargets_changeWidgets(done) {
     CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
@@ -55,24 +61,28 @@ let tests = [
       ok_targets(data, [
         "accountStatus",
         "addons",
         "appMenu",
         "backForward",
         "customize",
         "help",
         "loop",
+        "devtools",
         "home",
         "pinnedTab",
         "privateWindow",
         "quit",
         "search",
         "searchProvider",
         "urlbar",
-      ].concat(searchEngineTargets()));
+        ...searchEngineTargets(),
+        ...(hasWebIDE ? ["webide"] : [])
+      ]);
+
       ok(UITour.availableTargetsCache.has(window),
          "Targets should now be cached again");
       CustomizableUI.reset();
       ok(!UITour.availableTargetsCache.has(window),
          "Targets should not be cached after reset");
       done();
     });
   },
@@ -88,21 +98,24 @@ let tests = [
         "addons",
         "appMenu",
         "backForward",
         "bookmarks",
         "customize",
         "help",
         "home",
         "loop",
+        "devtools",
         "pinnedTab",
         "privateWindow",
         "quit",
         "urlbar",
+        ...(hasWebIDE ? ["webide"] : [])
       ]);
+
       CustomizableUI.reset();
       done();
     });
   },
 ];
 
 function ok_targets(actualData, expectedTargets) {
   // Depending on how soon after page load this is called, the selected tab icon
--- 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/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -383,16 +383,19 @@ size. -->
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
 
 <!ENTITY home_top_sites_title "Top Sites">
 <!-- Localization note (home_top_sites_add): This string is used as placeholder
      text underneath empty thumbnails in the Top Sites page on about:home. -->
 <!ENTITY home_top_sites_add "Add a site">
 
+<!-- Localization note (home_title): This string should be kept in sync
+     with the page title defined in aboutHome.dtd -->
+<!ENTITY home_title "&brandShortName; Home">
 <!ENTITY home_history_title "History">
 <!ENTITY home_clear_history_button "Clear browsing history">
 <!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
 <!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
 <!ENTITY home_closed_tabs_title "Recently closed tabs">
 <!ENTITY home_last_tabs_title "Tabs from last time">
 <!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
 <!ENTITY home_open_all "Open all">
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/newtablet/res/color-large-v11/new_tablet_tab_item_title.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_checked="true"
+          android:color="@color/text_color_primary_inverse"/>
+
+    <item android:state_pressed="true"
+          android:color="@color/text_color_primary_inverse"/>
+
+    <item android:color="@color/new_tablet_tab_grid_text_unselected"/>
+
+</selector>
new file mode 100644
index 0000000000000000000000000000000000000000..ca09ae09adbd7242fd2489cdc31205b9f2bdfe42
GIT binary patch
literal 353
zc$@)Y0iOPeP)<h;3K|Lk000e1NJLTq000gE000gM1^@s6A4o0H00001b5ch_0Itp)
z=>Px$8%ab#R45g7lEI3DFc5}KKq=Oo1)+U_rMEss@7-q*AIO6TU!gZ2qt~9xA_~%i
zLUgIhcJsGP36lJWgqh5jnM{V|d434P@H9=6Y?@{t$MKJ2P@1NjD2mRW=ZU(mkE|$)
zU*Gpvh?P?IRaJd0VL(b3Aw+<<zV9D=97hQQ3W8v*ZoOn#wtagXX9>ZN>KK9$42Xg$
z^E}T_JrmAxR+i<C!LY$kB`Qyck|eplKpPA{i829#?*qaxWrlwV!N;+X@Fs|CednD2
z>Gmh1se5`LHH^OM1)3lP0yK3SFtb5uIm)wr6~`FaT8^7BNtB%`P$PQgm_2%nse2~a
zbzQ+Ru$aBJZSN?L{)sNbPH@+Cw_z9_iuwKr#WwOu!#h6=00000NkvXXu0mjfmeiB+
new file mode 100644
index 0000000000000000000000000000000000000000..e24adb4995993aa353b02b52322471f1aec57c98
GIT binary patch
literal 272
zc$@(c0q_2aP)<h;3K|Lk000e1NJLTq000R9000RH1^@s6;E@Ip00001b5ch_0Itp)
z=>Px#%1J~)R2Ug`kUa{6KoEuF5{L*|i|8RdK$;-QIV|)9X_5n^$q{TUy+b4rQl!lp
z{6TCjM1w{Az=1T`W|(<z-+r@NmSr!~H2u1+4{;nnR?A6}Wa~K2t?RnC!eAK2#`C-!
z0T0!6-8*y3vV29tFbtEf>uZEGO^;OiaU7>v-)p}&5ti3h7>c3@Ku`>$D0;SSdlCb+
zX_~Vz{!Id*s;V6Unm`Hz3ASxt=*6g5Qw%VKVR$XeGLU6vJy-lg-}fJZga;Ok%+bFO
W^oJsPr}$m~0000<MNUMnLSTaVl4@=M
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_tab_item_close_button.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- pressed state -->
+    <item android:state_pressed="true"
+          android:drawable="@drawable/new_tablet_tab_close_active"/>
+
+    <item android:state_checked="true"
+          android:drawable="@drawable/new_tablet_tab_close_active"/>
+
+    <!-- normal mode -->
+    <item android:drawable="@drawable/new_tablet_tab_close"/>
+
+</selector>
--- a/mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_tab_strip_item_bg.xml
+++ b/mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_tab_strip_item_bg.xml
@@ -26,27 +26,27 @@
         <shape>
             <solid android:color="@color/highlight_dark_focused" />
         </shape>
     </item>
 
     <item android:state_pressed="true"
           android:state_checked="true">
         <shape>
-            <solid android:color="@color/highlight_nav" />
+            <solid android:color="@color/new_tablet_highlight" />
         </shape>
     </item>
 
     <item android:state_checked="true">
         <shape>
             <solid android:color="@color/background_normal" />
         </shape>
     </item>
 
     <item android:state_pressed="true">
         <shape>
-            <solid android:color="@color/highlight_dark" />
+            <solid android:color="@color/new_tablet_highlight_dark" />
         </shape>
     </item>
 
     <item android:drawable="@android:color/transparent"/>
 
 </selector>
new file mode 100644
index 0000000000000000000000000000000000000000..cb997070af278fbd6cfd1b207227bac8d46f59c8
GIT binary patch
literal 417
zc$@*D0bc%zP)<h;3K|Lk000e1NJLTq000sI000sQ1^@s6R?d!B00001b5ch_0Itp)
z=>Px$TS-JgR5%fhl+9{`KoG~f+Xqk(RD6d*p_I_(8Yt~q5Bh$hK0pp7&{Jti$)PQM
z2SxA$k3vfK9~jwec2oD@%+ANpnROi&MbTv#hFi<BuG_ZVBuVmkF=M4^x^^7rVHk$*
zuIm=8D2koyx;K<tRn>bO$M>|?$vn?rJ<nT`udeG)j!MV?>HGd&mSu0UP1aQie70?0
zakL``LN~#55X3i4^TME@0whD1m)xcaB4R$oQ7jH1B%gyZ#(w29p_dT^`w4`N2?n3K
zWC(mmAQ<MrREQSzA4~^94#z`XI1|+-?e~&CsVA+OzRr{(6j9a5r9A@8fe=uIO|6+A
zI1?lhfTVh94;7t*;Ibjv^fjq6hQBxnf+u<tNmN1*vYB9ISw3@&0tzIlgTRNWpleew
zRvhz$0FWjK#pqyt-~Vt7CCrm?9QUHcjoJ<YWC<ABL5|4%5}?~Z(yih{^hqE<00000
LNkvXXu0mjf$;Ph-
new file mode 100644
index 0000000000000000000000000000000000000000..8e8f299f092c23dba9bd21551f7a127fa6da9210
GIT binary patch
literal 606
zc$@)V0-^nhP)<h;3K|Lk000e1NJLTq000{R000{Z1^@s6jnwp200001b5ch_0Itp)
z=>Px%7)eAyR7ee_mfuPOQ4q%4Qy{o);GYN)@nQ*j0Uw}<h@gw;0lX0L6}%E6=*A}y
z(MPDeA}@M@dV**#6j8B2mL)+!=zP*F>$>aCt_O77IcMhQob&BrlpQ(h^}3qR=id{F
z#L09zwZ`M|O}Sit!(5cnYPC+&>GV@P9zU2&CZCpN6-uR&r2wSsx*<VFlz29qExO(A
zRi#qtkdJGd&E{DymwS!HVtW)Lf#33@i~ovj5EjUVyVC#MXf!S;fMdNth?9jJYMS;T
zuytOE!UFw3lslPB9t)fiIoy^D3<4qVuN(qmz!|wg^c)1_fadf0Vl*1v3aAcZhyvt-
z`u+Yj#HLUM5CcSkO33H7PL+lMN!h<3mkq-M7dRXa?=qQ8P5v=k-iUV&Y#k~h5aw23
zaBzTFw`q8Ruzn2&=Qcn;kS{DMa1&@FEG#exgnR;Degkra82sj)e)%3fIK|+cqu-uq
zMw1+Q(^}_iK|Z(jg@pl8(jcHGEDDGNT!U=`3IeVOgad-=dcEF#l)Kc1aY49(ip63r
z%H_S>#xNkKyg4_GQ#1;ilWaEI345v1`EY@Q!QfJ$3yKN?5HCJwOH--Tqd>bw|I%$a
zR}dGds_MQ#U+0G4nG2%Wpb91DOQ3;7Uq?YvKosi-G)?ozFpR=djZ!4Ct<yfxZlMc8
sbAmuh64tic?Q@(Fwhk2mRIOIcKYT~ozl0)7e*gdg07*qoM6N<$g7G*B(*OVf
--- a/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml
+++ b/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml
@@ -5,18 +5,19 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <org.mozilla.gecko.tabs.TabStripView
         android:id="@+id/tab_strip"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:paddingTop="8dp"
-        android:paddingLeft="2dp"/>
+        android:requiresFadingEdge="horizontal"
+        android:fadingEdgeLength="10dp"
+        android:paddingTop="8dp"/>
 
     <ImageButton
         android:id="@+id/add_tab"
         style="@style/UrlBar.ImageButton"
         android:layout_width="@dimen/new_tablet_tab_strip_height"
         android:src="@drawable/tab_new_level"
         android:contentDescription="@string/new_tab"
         android:background="@drawable/action_bar_button_inverse"/>
--- a/mobile/android/base/newtablet/res/layout-large-v11/tab_strip_item.xml
+++ b/mobile/android/base/newtablet/res/layout-large-v11/tab_strip_item.xml
@@ -5,10 +5,10 @@
 
 <!-- The paddings are asymmetric here to compensate the padding around the
      the close button within the TabStripItemView -->
 <org.mozilla.gecko.tabs.TabStripItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/new_tablet_tab_strip_item_width"
     android:layout_height="match_parent"
     android:background="@drawable/new_tablet_tab_strip_item_bg"
-    android:paddingLeft="25dp"
-    android:paddingRight="15dp"/>
+    android:paddingLeft="28dp"
+    android:paddingRight="12dp"/>
--- a/mobile/android/base/newtablet/res/layout-large-v11/tab_strip_item_view.xml
+++ b/mobile/android/base/newtablet/res/layout-large-v11/tab_strip_item_view.xml
@@ -5,17 +5,17 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
    <ImageView
         android:id="@+id/favicon"
         android:layout_width="@dimen/new_tablet_tab_strip_favicon_size"
         android:layout_height="match_parent"
-        android:layout_marginRight="9dp"
+        android:layout_marginRight="8dp"
         android:scaleType="centerInside"
         android:duplicateParentState="true"/>
 
     <org.mozilla.gecko.widget.FadedTextView
         android:id="@+id/title"
         android:layout_width="0dip"
         android:layout_height="match_parent"
         android:layout_weight="1.0"
--- a/mobile/android/base/resources/layout/new_tablet_tabs_item_cell.xml
+++ b/mobile/android/base/resources/layout/new_tablet_tabs_item_cell.xml
@@ -4,53 +4,53 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
                                            style="@style/TabsItem"
                                            android:focusable="true"
                                            android:id="@+id/info"
                                            android:layout_width="wrap_content"
                                            android:layout_height="wrap_content"
-                                           android:paddingTop="6dip"
-                                           android:paddingBottom="6dip"
-                                           android:paddingLeft="1dip"
-                                           android:paddingRight="1dip"
                                            android:gravity="center"
-                                           android:orientation="vertical">
+                                           android:orientation="vertical"
+                                           android:paddingBottom="@dimen/new_tablet_tab_panel_grid_padding">
 
-    <LinearLayout android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
+    <LinearLayout android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
                   android:orientation="horizontal"
-                  android:padding="3dip">
+                  android:duplicateParentState="true"
+                  android:paddingLeft="@dimen/new_tablet_tab_highlight_stroke_width"
+                  android:paddingRight="@dimen/new_tablet_tab_highlight_stroke_width"
+                  android:paddingBottom="@dimen/new_tablet_tab_highlight_stroke_width">
 
         <TextView android:id="@+id/title"
                   android:layout_width="0dip"
                   android:layout_height="wrap_content"
                   android:layout_weight="1.0"
-                  android:paddingTop="4dip"
                   style="@style/TabLayoutItemTextAppearance"
-                  android:textSize="12sp"
-                  android:textColor="#FFFFFFFF"
+                  android:textSize="14sp"
+                  android:textColor="@color/new_tablet_tab_item_title"
                   android:singleLine="true"
                   android:duplicateParentState="true"/>
 
         <ImageButton android:id="@+id/close"
                      style="@style/TabsItemClose"
                      android:layout_width="wrap_content"
                      android:layout_height="match_parent"
-                     android:background="@drawable/action_bar_button_inverse"
                      android:scaleType="center"
+                     android:background="@android:color/transparent"
                      android:contentDescription="@string/close_tab"
-                     android:src="@drawable/new_tablet_tab_close"/>
+                     android:src="@drawable/new_tablet_tab_item_close_button"
+                     android:duplicateParentState="true"/>
 
     </LinearLayout>
 
     <RelativeLayout android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:padding="3dp"
+                    android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
                     android:background="@drawable/tab_thumbnail"
                     android:duplicateParentState="true">
 
         <org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
                                                 android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
                                                 android:layout_height="@dimen/new_tablet_tab_thumbnail_height"
                                                 android:src="@drawable/tab_thumbnail_default"/>
 
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -13,16 +13,17 @@
   <color name="highlight_dark">#33FFFFFF</color>
   <color name="highlight_dark_focused">#1AFFFFFF</color>
 
   <!-- (bug 1077195) Focused state values are temporary. -->
   <color name="new_tablet_highlight">#D7D7DC</color>
   <color name="new_tablet_highlight_focused">#C0C9D0</color>
   <color name="new_tablet_highlight_pb">#222222</color>
   <color name="new_tablet_highlight_focused_pb">#363B40</color>
+  <color name="new_tablet_highlight_dark">#45494E</color>
 
   <!-- highlight on shaped button: 20% white over background_tabs -->
   <color name="highlight_shaped">#FF696D71</color>
 
   <!-- highlight-focused on shaped button: 10% white over background_tabs -->
   <color name="highlight_shaped_focused">#FF565B60</color>
 
   <!-- highlight on nav button: 20% black over background_normal -->
@@ -146,9 +147,13 @@
 
   <!-- Canvas delegate paint color -->
   <color name="canvas_delegate_paint">#FFFF0000</color>
 
   <!-- Top sites thumbnail colors -->
   <color name="top_site_default">#FFECF0F3</color>
   <color name="top_site_border">#FFCFD9E1</color>
 
+  <!-- Tab grid text highlight colour -->
+  <color name="new_tablet_tab_grid_text_unselected">#FFAFB1B3</color>
+
+
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -18,18 +18,18 @@
     <dimen name="browser_toolbar_shadow_size">2dp</dimen>
 
     <!-- If you update one of these values, update the others. -->
     <dimen name="new_tablet_nav_button_width">42dp</dimen>
     <dimen name="new_tablet_nav_button_width_half">21dp</dimen>
     <dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
 
     <dimen name="new_tablet_tab_strip_height">48dp</dimen>
-    <dimen name="new_tablet_tab_strip_item_width">250dp</dimen>
-    <dimen name="new_tablet_tab_strip_item_margin">-30dp</dimen>
+    <dimen name="new_tablet_tab_strip_item_width">208dp</dimen>
+    <dimen name="new_tablet_tab_strip_item_margin">-28dp</dimen>
     <dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
     <dimen name="new_tablet_site_security_height">60dp</dimen>
     <dimen name="new_tablet_site_security_width">34dp</dimen>
     <!-- We primarily use padding (instead of margins) to increase the hit area. -->
     <dimen name="new_tablet_site_security_padding_vertical">21dp</dimen>
     <dimen name="new_tablet_site_security_padding_horizontal">8dp</dimen>
     <dimen name="new_tablet_site_security_right_margin">1dp</dimen>
     <dimen name="new_tablet_browser_toolbar_height">60dp</dimen>
@@ -123,19 +123,23 @@
     <dimen name="text_selection_handle_height">58dp</dimen>
     <dimen name="text_selection_handle_shadow">11dp</dimen>
     <dimen name="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
     <dimen name="url_bar_offset_left">32dp</dimen>
     <dimen name="history_tab_indicator_height">50dp</dimen>
 
 
-    <dimen name="new_tablet_tab_thumbnail_height">180dp</dimen>
-    <dimen name="new_tablet_tab_thumbnail_width">180dp</dimen>
-    <dimen name="new_tablet_tab_panel_grid_padding">24dp</dimen>
+    <dimen name="new_tablet_tab_thumbnail_width">168dp</dimen>
+    <dimen name="new_tablet_tab_thumbnail_height">140dp</dimen>
+    <dimen name="new_tablet_tab_panel_column_width">178dp</dimen>
+    <dimen name="new_tablet_tab_panel_grid_padding">19dp</dimen>
+    <dimen name="new_tablet_tab_panel_grid_padding_top">24dp</dimen>
+
+    <dimen name="new_tablet_tab_highlight_stroke_width">5dp</dimen>
 
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
 
     <!-- Icon Grid -->
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -338,16 +338,17 @@
   <string name="button_ok">&button_ok;</string>
   <string name="button_cancel">&button_cancel;</string>
   <string name="button_clear_data">&button_clear_data;</string>
   <string name="button_set">&button_set;</string>
   <string name="button_clear">&button_clear;</string>
   <string name="button_yes">&button_yes;</string>
   <string name="button_no">&button_no;</string>
 
+  <string name="home_title">&home_title;</string>
   <string name="home_top_sites_title">&home_top_sites_title;</string>
   <string name="home_top_sites_add">&home_top_sites_add;</string>
   <string name="home_history_title">&home_history_title;</string>
   <string name="home_clear_history_button">&home_clear_history_button;</string>
   <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
   <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
   <string name="home_closed_tabs_title">&home_closed_tabs_title;</string>
   <string name="home_last_tabs_title">&home_last_tabs_title;</string>
--- a/mobile/android/base/tabs/TabStripItemView.java
+++ b/mobile/android/base/tabs/TabStripItemView.java
@@ -1,30 +1,32 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 /* 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/. */
 
 package org.mozilla.gecko.tabs;
 
+import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.ThemedImageButton;
 import org.mozilla.gecko.widget.ThemedLinearLayout;
 import org.mozilla.gecko.widget.ThemedTextView;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Region;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.Checkable;
 import android.widget.ImageView;
 
 public class TabStripItemView extends ThemedLinearLayout
@@ -194,21 +196,35 @@ public class TabStripItemView extends Th
     }
 
     void updateFromTab(Tab tab) {
         if (tab == null) {
             return;
         }
 
         id = tab.getId();
+
+        updateTitle(tab);
         updateFavicon(tab.getFavicon());
-        titleView.setText(tab.getDisplayTitle());
         setPrivateMode(tab.isPrivate());
     }
 
+    private void updateTitle(Tab tab) {
+        final String title;
+
+        // Avoid flickering the about:home URL on every load given how often
+        // this page is used in the UI.
+        if (AboutPages.isAboutHome(tab.getURL())) {
+            titleView.setText(R.string.home_title);
+        } else {
+            titleView.setText(tab.getDisplayTitle());
+        }
+
+    }
+
     private void updateFavicon(final Bitmap favicon) {
         if (favicon == null) {
             lastFavicon = null;
             faviconView.setImageResource(R.drawable.new_tablet_default_favicon);
             return;
         }
         if (favicon == lastFavicon) {
             return;
--- a/mobile/android/base/tabs/TabsGridLayout.java
+++ b/mobile/android/base/tabs/TabsGridLayout.java
@@ -63,21 +63,22 @@ class TabsGridLayout extends GridView
         });
 
         setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
         setStretchMode(GridView.STRETCH_SPACING);
         setGravity(Gravity.CENTER);
         setNumColumns(GridView.AUTO_FIT);
 
         final Resources resources = getResources();
-        final int columnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_thumbnail_width);
+        final int columnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
         setColumnWidth(columnWidth);
 
         final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
-        setPadding(padding, 0, padding, 0);
+        final int paddingTop = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding_top);
+        setPadding(padding, paddingTop, padding, padding);
     }
 
     private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
 
         final private Button.OnClickListener mCloseClickListener;
         final private View.OnClickListener mSelectClickListener;
 
         public TabsGridLayoutAdapter (Context context) {
--- a/mobile/android/base/tabs/TabsLayoutItemView.java
+++ b/mobile/android/base/tabs/TabsLayoutItemView.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.widget.TabThumb
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.Checkable;
 import android.widget.ImageButton;
 import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class TabsLayoutItemView extends LinearLayout
                                 implements Checkable {
     private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
     private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
     private boolean mChecked;
@@ -90,17 +91,17 @@ public class TabsLayoutItemView extends 
         if (tab == null) {
             return;
         }
 
         mTabId = tab.getId();
 
         Drawable thumbnailImage = tab.getThumbnail();
         if (thumbnailImage != null) {
-            mThumbnail.setImageDrawable(thumbnailImage);
+            setThumbnail(thumbnailImage);
         } else {
             mThumbnail.setImageResource(R.drawable.tab_thumbnail_default);
         }
         if (mThumbnailWrapper != null) {
             mThumbnailWrapper.setRecording(tab.isRecording());
         }
         mTitle.setText(tab.getDisplayTitle());
         mCloseButton.setTag(this);
--- a/mobile/android/base/widget/TwoWayView.java
+++ b/mobile/android/base/widget/TwoWayView.java
@@ -1108,16 +1108,120 @@ public class TwoWayView extends AdapterV
                     return mFirstPosition + i;
                 }
             }
         }
         return INVALID_POSITION;
     }
 
     @Override
+    protected float getTopFadingEdgeStrength() {
+        if (!mIsVertical) {
+            return 0f;
+        }
+
+        final float fadingEdge = super.getTopFadingEdgeStrength();
+
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return fadingEdge;
+        } else {
+            if (mFirstPosition > 0) {
+                return 1.0f;
+            }
+
+            final int top = getChildAt(0).getTop();
+            final int paddingTop = getPaddingTop();
+
+            final float length = (float) getVerticalFadingEdgeLength();
+
+            return (top < paddingTop ? (float) -(top - paddingTop) / length : fadingEdge);
+        }
+    }
+
+    @Override
+    protected float getBottomFadingEdgeStrength() {
+        if (!mIsVertical) {
+            return 0f;
+        }
+
+        final float fadingEdge = super.getBottomFadingEdgeStrength();
+
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return fadingEdge;
+        } else {
+            if (mFirstPosition + childCount - 1 < mItemCount - 1) {
+                return 1.0f;
+            }
+
+            final int bottom = getChildAt(childCount - 1).getBottom();
+            final int paddingBottom = getPaddingBottom();
+
+            final int height = getHeight();
+            final float length = (float) getVerticalFadingEdgeLength();
+
+            return (bottom > height - paddingBottom ?
+                    (float) (bottom - height + paddingBottom) / length : fadingEdge);
+        }
+    }
+
+    @Override
+    protected float getLeftFadingEdgeStrength() {
+        if (mIsVertical) {
+            return 0f;
+        }
+
+        final float fadingEdge = super.getLeftFadingEdgeStrength();
+
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return fadingEdge;
+        } else {
+            if (mFirstPosition > 0) {
+                return 1.0f;
+            }
+
+            final int left = getChildAt(0).getLeft();
+            final int paddingLeft = getPaddingLeft();
+
+            final float length = (float) getHorizontalFadingEdgeLength();
+
+            return (left < paddingLeft ? (float) -(left - paddingLeft) / length : fadingEdge);
+        }
+    }
+
+    @Override
+    protected float getRightFadingEdgeStrength() {
+        if (mIsVertical) {
+            return 0f;
+        }
+
+        final float fadingEdge = super.getRightFadingEdgeStrength();
+
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return fadingEdge;
+        } else {
+            if (mFirstPosition + childCount - 1 < mItemCount - 1) {
+                return 1.0f;
+            }
+
+            final int right = getChildAt(childCount - 1).getRight();
+            final int paddingRight = getPaddingRight();
+
+            final int width = getWidth();
+            final float length = (float) getHorizontalFadingEdgeLength();
+
+            return (right > width - paddingRight ?
+                    (float) (right - width + paddingRight) / length : fadingEdge);
+        }
+    }
+
+    @Override
     protected int computeVerticalScrollExtent() {
         final int count = getChildCount();
         if (count == 0) {
             return 0;
         }
 
         int extent = count * 100;
 
@@ -1809,17 +1913,17 @@ public class TwoWayView extends AdapterV
 
         if (nextPage < 0) {
             return false;
         }
 
         final int position = lookForSelectablePosition(nextPage, forward);
         if (position >= 0) {
             mLayoutMode = LAYOUT_SPECIFIC;
-            mSpecificStart = (mIsVertical ? getPaddingTop() : getPaddingLeft());
+            mSpecificStart = getStartEdge() + getFadingEdgeLength();
 
             if (forward && position > mItemCount - getChildCount()) {
                 mLayoutMode = LAYOUT_FORCE_BOTTOM;
             }
 
             if (!forward && position < getChildCount()) {
                 mLayoutMode = LAYOUT_FORCE_TOP;
             }
@@ -2068,28 +2172,32 @@ public class TwoWayView extends AdapterV
         final View newFocus;
         final int searchPoint;
 
         if (selectedView != null && selectedView.hasFocus()) {
             View oldFocus = selectedView.findFocus();
             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
         } else {
             if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
-                final int start = getStartEdge();
+                boolean fadingEdgeShowing = (mFirstPosition > 0);
+                final int start = getStartEdge() +
+                        (fadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
 
                 final int selectedStart;
                 if (selectedView != null) {
                     selectedStart = getChildStartEdge(selectedView);
                 } else {
                     selectedStart = start;
                 }
 
                 searchPoint = Math.max(selectedStart, start);
             } else {
-                final int end = getEndEdge();
+                final boolean fadingEdgeShowing =
+                        (mFirstPosition + getChildCount() - 1) < mItemCount;
+                final int end = getEndEdge() - (fadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
 
                 final int selectedEnd;
                 if (selectedView != null) {
                     selectedEnd = getChildEndEdge(selectedView);
                 } else {
                     selectedEnd = end;
                 }
 
@@ -2152,22 +2260,17 @@ public class TwoWayView extends AdapterV
     public int getMaxScrollAmount() {
         return (int) (MAX_SCROLL_FACTOR * getSize());
     }
 
     /**
      * @return The amount to preview next items when arrow scrolling.
      */
     private int getArrowScrollPreviewLength() {
-        // FIXME: TwoWayView has no fading edge support just yet but using it
-        // makes it convenient for defining the next item's previous length.
-        int fadingEdgeLength =
-                (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength());
-
-        return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, fadingEdgeLength);
+        return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, getFadingEdgeLength());
     }
 
     /**
      * @param newFocus The view that would have focus.
      * @return the position that contains newFocus
      */
     private int positionOfNewFocus(View newFocus) {
         final int numChildren = getChildCount();
@@ -2953,16 +3056,40 @@ public class TwoWayView extends AdapterV
     private int getChildSize(View child) {
         return (mIsVertical ? child.getHeight() : child.getWidth());
     }
 
     private int getChildMeasuredSize(View child) {
         return (mIsVertical ? child.getMeasuredHeight() : child.getMeasuredWidth());
     }
 
+    private int getFadingEdgeLength() {
+        return (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength());
+    }
+
+    private int getMinSelectionPixel(int start, int fadingEdgeLength, int selectedPosition) {
+        // First pixel we can draw the selection into.
+        int selectionPixelStart = start;
+        if (selectedPosition > 0) {
+            selectionPixelStart += fadingEdgeLength;
+        }
+
+        return selectionPixelStart;
+    }
+
+    private int getMaxSelectionPixel(int end, int fadingEdgeLength,
+                                     int selectedPosition) {
+        int selectionPixelEnd = end;
+        if (selectedPosition != mItemCount - 1) {
+            selectionPixelEnd -= fadingEdgeLength;
+        }
+
+        return selectionPixelEnd;
+    }
+
     private boolean contentFits() {
         final int childCount = getChildCount();
         if (childCount == 0) {
             return true;
         }
 
         if (childCount != mItemCount) {
             return false;
@@ -4186,21 +4313,25 @@ public class TwoWayView extends AdapterV
             } else {
                 child.offsetLeftAndRight(offset);
             }
         }
     }
 
     private View moveSelection(View oldSelected, View newSelected, int delta, int start,
             int end) {
+        final int fadingEdgeLength = getFadingEdgeLength();
         final int selectedPosition = mSelectedPosition;
 
         final int oldSelectedStart = getChildStartEdge(oldSelected);
         final int oldSelectedEnd = getChildEndEdge(oldSelected);
 
+        final int minStart = getMinSelectionPixel(start, fadingEdgeLength, selectedPosition);
+        final int maxEnd = getMaxSelectionPixel(end, fadingEdgeLength, selectedPosition);
+
         View selected = null;
 
         if (delta > 0) {
             /*
              * Case 1: Scrolling down.
              */
 
             /*
@@ -4228,20 +4359,20 @@ public class TwoWayView extends AdapterV
             selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true);
 
             final int selectedStart = getChildStartEdge(selected);
             final int selectedEnd = getChildEndEdge(selected);
 
             // Some of the newly selected item extends below the bottom of the list
             if (selectedEnd > end) {
                 // Find space available above the selection into which we can scroll upwards
-                final int spaceBefore = selectedStart - start;
+                final int spaceBefore = selectedStart - minStart;
 
                 // Find space required to bring the bottom of the selected item fully into view
-                final int spaceAfter = selectedEnd - end;
+                final int spaceAfter = selectedEnd - maxEnd;
 
                 // Don't scroll more than half the size of the list
                 final int halfSpace = (end - start) / 2;
                 int offset = Math.min(spaceBefore, spaceAfter);
                 offset = Math.min(offset, halfSpace);
 
                 if (mIsVertical) {
                     oldSelected.offsetTopAndBottom(-offset);
@@ -4286,22 +4417,22 @@ public class TwoWayView extends AdapterV
                 // it above the oldSelected (B)
                 selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true);
             }
 
             final int selectedStart = getChildStartEdge(selected);
             final int selectedEnd = getChildEndEdge(selected);
 
             // Some of the newly selected item extends above the top of the list
-            if (selectedStart < start) {
+            if (selectedStart < minStart) {
                 // Find space required to bring the top of the selected item fully into view
-                final int spaceBefore = start - selectedStart;
+                final int spaceBefore = minStart - selectedStart;
 
                // Find space available below the selection into which we can scroll downwards
-                final int spaceAfter = end - selectedEnd;
+                final int spaceAfter = maxEnd - selectedEnd;
 
                 // Don't scroll more than half the height of the list
                 final int halfSpace = (end - start) / 2;
                 int offset = Math.min(spaceBefore, spaceAfter);
                 offset = Math.min(offset, halfSpace);
 
                 if (mIsVertical) {
                     selected.offsetTopAndBottom(offset);
@@ -4510,59 +4641,80 @@ public class TwoWayView extends AdapterV
         final int childCount = getChildCount();
         if (childCount <= 0) {
             return false;
         }
 
         int selectedStart = 0;
         int selectedPosition;
 
-        final int start = getStartEdge();
-        final int end = getEndEdge();
+        int start = getStartEdge();
+        int end = getEndEdge();
 
         final int firstPosition = mFirstPosition;
         final int toPosition = mResurrectToPosition;
         boolean down = true;
 
         if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
             selectedPosition = toPosition;
 
             final View selected = getChildAt(selectedPosition - mFirstPosition);
             selectedStart = getChildStartEdge(selected);
+
+            final int selectedEnd = getChildEndEdge(selected);
+
+            // We are scrolled, don't get in the fade
+            if (selectedStart < start) {
+                selectedStart = start + getFadingEdgeLength();
+            } else if (selectedEnd > end) {
+                selectedStart = end - getChildMeasuredSize(selected) - getFadingEdgeLength();
+            }
         } else if (toPosition < firstPosition) {
             // Default to selecting whatever is first
             selectedPosition = firstPosition;
 
             for (int i = 0; i < childCount; i++) {
                 final View child = getChildAt(i);
                 final int childStart = getChildStartEdge(child);
 
                 if (i == 0) {
                     // Remember the position of the first item
                     selectedStart = childStart;
+
+                    // See if we are scrolled at all
+                    if (firstPosition > 0 || childStart < start) {
+                        // If we are scrolled, don't select anything that is
+                        // in the fade region
+                        start += getFadingEdgeLength();
+                    }
                 }
 
                 if (childStart >= start) {
                     // Found a view whose top is fully visible
                     selectedPosition = firstPosition + i;
                     selectedStart = childStart;
                     break;
                 }
             }
         } else {
+            final int itemCount = mItemCount;
             selectedPosition = firstPosition + childCount - 1;
             down = false;
 
             for (int i = childCount - 1; i >= 0; i--) {
                 final View child = getChildAt(i);
                 final int childStart = getChildStartEdge(child);
                 final int childEnd = getChildEndEdge(child);
 
                 if (i == childCount - 1) {
                     selectedStart = childStart;
+
+                    if (firstPosition + childCount < itemCount || childEnd > end) {
+                        end -= getFadingEdgeLength();
+                    }
                 }
 
                 if (childEnd <= end) {
                     selectedPosition = firstPosition + i;
                     selectedStart = childStart;
                     break;
                 }
             }
@@ -5088,45 +5240,49 @@ public class TwoWayView extends AdapterV
 
         adjustViewsStartOrEnd();
 
         final int offsetAfter = getChildEndEdge(selected) + mItemMargin;
         fillAfter(position + 1, offsetAfter);
     }
 
     private View fillFromSelection(int selectedTop, int start, int end) {
+        int fadingEdgeLength = getFadingEdgeLength();
         final int selectedPosition = mSelectedPosition;
 
+        final int minStart = getMinSelectionPixel(start, fadingEdgeLength, selectedPosition);
+        final int maxEnd = getMaxSelectionPixel(end, fadingEdgeLength, selectedPosition);
+
         View selected = makeAndAddView(selectedPosition, selectedTop, true, true);
 
         final int selectedStart = getChildStartEdge(selected);
         final int selectedEnd = getChildEndEdge(selected);
 
         // Some of the newly selected item extends below the bottom of the list
-        if (selectedEnd > end) {
+        if (selectedEnd > maxEnd) {
             // Find space available above the selection into which we can scroll
             // upwards
-            final int spaceAbove = selectedStart - start;
+            final int spaceAbove = selectedStart - minStart;
 
             // Find space required to bring the bottom of the selected item
             // fully into view
-            final int spaceBelow = selectedEnd - end;
+            final int spaceBelow = selectedEnd - maxEnd;
 
             final int offset = Math.min(spaceAbove, spaceBelow);
 
             // Now offset the selected item to get it into view
             selected.offsetTopAndBottom(-offset);
-        } else if (selectedStart < start) {
+        } else if (selectedStart < minStart) {
             // Find space required to bring the top of the selected item fully
             // into view
-            final int spaceAbove = start - selectedStart;
+            final int spaceAbove = minStart - selectedStart;
 
             // Find space available below the selection into which we can scroll
             // downwards
-            final int spaceBelow = end - selectedEnd;
+            final int spaceBelow = maxEnd - selectedEnd;
 
             final int offset = Math.min(spaceAbove, spaceBelow);
 
             // Offset the selected item to get it into view
             selected.offsetTopAndBottom(offset);
         }
 
         // Fill in views above and below
--- a/mobile/android/chrome/content/WebcompatReporter.js
+++ b/mobile/android/chrome/content/WebcompatReporter.js
@@ -34,17 +34,18 @@ var WebcompatReporter = {
         this.menuItemEnabled = true;
       } else if (this.menuItemEnabled && !this.isReportableUrl(currentURI)) {
         NativeWindow.menu.update(this.menuItem, {enabled: false});
         this.menuItemEnabled = false;
       }
     } else if (topic === "DesktopMode:Change") {
       let args = JSON.parse(data);
       let tab = BrowserApp.getTabForId(args.tabId);
-      if (args.desktopMode && tab !== null) {
+      let currentURI = tab.browser.currentURI.spec;
+      if (args.desktopMode && this.isReportableUrl(currentURI)) {
         this.reportDesktopModePrompt();
       }
     }
   },
 
   addMenuItem: function() {
     this.menuItem = NativeWindow.menu.add({
       name: this.strings.GetStringFromName("webcompat.menu.name"),
@@ -52,20 +53,20 @@ var WebcompatReporter = {
         let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
         this.reportIssue(currentURI);
       },
       enabled: false,
     });
   },
 
   isReportableUrl: function(url) {
-    return url !== null && !(url.startsWith("about") ||
-                             url.startsWith("chrome") ||
-                             url.startsWith("file") ||
-                             url.startsWith("resource"));
+    return url && !(url.startsWith("about") ||
+                    url.startsWith("chrome") ||
+                    url.startsWith("file") ||
+                    url.startsWith("resource"));
   },
 
   reportDesktopModePrompt: function() {
     let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
     let message = this.strings.GetStringFromName("webcompat.reportDesktopMode.message");
     let options = {
       button: {
         label: this.strings.GetStringFromName("webcompat.reportDesktopModeYes.label"),
--- a/mobile/android/locales/en-US/chrome/aboutHome.dtd
+++ b/mobile/android/locales/en-US/chrome/aboutHome.dtd
@@ -1,5 +1,7 @@
 <!-- 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/. -->
 
+<!-- This string should be kept in sync with the home_title string
+     in android_strings.dtd -->
 <!ENTITY abouthome.title                  "&brandShortName; Home">
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -464,16 +464,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());
 }
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -149,24 +149,37 @@ types.addType = function(name, typeObjec
     name: name,
     primitive: !(typeObject.read || typeObject.write),
     read: identityWrite,
     write: identityWrite
   }, typeObject);
 
   registeredTypes.set(name, type);
 
-  if (!options.thawed) {
-    Object.freeze(type);
-  }
-
   return type;
 };
 
 /**
+ * Remove a type previously registered with the system.
+ * Primarily useful for types registered by addons.
+ */
+types.removeType = function(name) {
+  // This type may still be referenced by other types, make sure
+  // those references don't work.
+  let type = registeredTypes.get(name);
+
+  type.name = "DEFUNCT:" + name;
+  type.category = "defunct";
+  type.primitive = false;
+  type.read = type.write = function() { throw new Error("Using defunct type: " + name); };
+
+  registeredTypes.delete(name);
+}
+
+/**
  * Add an array type to the type system.
  *
  * getType() will call this function if provided an "array:<type>"
  * typestring.
  *
  * @param type subtype
  *    The subtype to be held by the array.
  */
@@ -294,20 +307,16 @@ types.addActorType = function(name) {
       }
 
       if (!(formAttr in type.actorSpec)) {
         throw new Error("No type defined for " + formAttr);
       }
 
       return type.actorSpec[formAttr];
     }
-  }, {
-    // We usually freeze types, but actor types are updated when clients are
-    // created, so don't freeze yet.
-    thawed: true
   });
   return type;
 }
 
 types.addNullableType = function(subtype) {
   subtype = types.getType(subtype);
   return types.addType("nullable:" + subtype.name, {
     category: "nullable",
@@ -365,16 +374,24 @@ types.addActorDetail = function(name, ac
 types.addLifetime = function(name, prop) {
   if (registeredLifetimes.has(name)) {
     throw Error("Lifetime '" + name + "' already registered.");
   }
   registeredLifetimes.set(name, prop);
 }
 
 /**
+ * Remove a previously-registered lifetime.  Useful for lifetimes registered
+ * in addons.
+ */
+types.removeLifetime = function(name) {
+  registeredLifetimes.delete(name);
+}
+
+/**
  * Register a lifetime type.  This creates an actor type tied to the given
  * lifetime.
  *
  * This is called by getType() when passed a '<lifetimeType>:<actorType>'
  * typestring.
  *
  * @param string lifetime
  *    A lifetime string previously regisered with addLifetime()
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_protocol_unregister.js
@@ -0,0 +1,44 @@
+const {types} = devtools.require("devtools/server/protocol");
+
+
+function run_test()
+{
+  types.addType("test", {
+    read: (v) => { return "successful read: " + v },
+    write: (v) => { return "successful write: " + v }
+  });
+
+  // Verify the type registered correctly.
+
+  let type = types.getType("test");
+  let arrayType = types.getType("array:test");
+  do_check_eq(type.read("foo"), "successful read: foo");
+  do_check_eq(arrayType.read(["foo"])[0], "successful read: foo");
+
+  types.removeType("test");
+
+  do_check_eq(type.name, "DEFUNCT:test");
+  try {
+    types.getType("test");
+    do_check_true(false, "getType should fail");
+  } catch(ex) {
+    do_check_eq(ex.toString(), "Error: Unknown type: test");
+  }
+
+  try {
+    type.read("foo");
+    do_check_true(false, "type.read should have thrown an exception.");
+  } catch(ex) {
+    do_check_eq(ex.toString(), "Error: Using defunct type: test");
+  }
+
+  try {
+    arrayType.read(["foo"]);
+    do_check_true(false, "array:test.read should have thrown an exception.");
+  } catch(ex) {
+    do_check_eq(ex.toString(), "Error: Using defunct type: test");
+  }
+
+}
+
+
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -57,20 +57,21 @@ support-files =
 [test_nativewrappers.js]
 [test_nodelistactor.js]
 [test_eval-01.js]
 [test_eval-02.js]
 [test_eval-03.js]
 [test_eval-04.js]
 [test_eval-05.js]
 [test_protocol_async.js]
-[test_protocol_simple.js]
-[test_protocol_longstring.js]
 [test_protocol_children.js]
 [test_protocol_formtype.js]
+[test_protocol_longstring.js]
+[test_protocol_simple.js]
+[test_protocol_unregister.js]
 [test_breakpoint-01.js]
 [test_register_actor.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpoint-02.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpoint-03.js]
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -50,16 +50,26 @@ XPCOMUtils.defineLazyGetter(this, "gCert
   let temp = { };
   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
   return temp;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
                                   "resource://gre/modules/UpdateChannel.jsm");
 
+/**
+ * Number of milliseconds after which we need to cancel `checkForAddons`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `checkForAddons` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
+
 function getScopedLogger(prefix) {
   // `PARENT_LOGGER_ID.` being passed here effectively links this logger
   // to the parentLogger.
   return Log.repository.getLogger(PARENT_LOGGER_ID + "." + prefix);
 }
 
 /**
  * Manages preferences for GMP addons
@@ -364,18 +374,21 @@ GMPInstallManager.prototype = {
     // The Cache-Control header is only interpreted by proxies and the
     // final destination. It does not help if a resource is already
     // cached locally.
     this._request.setRequestHeader("Cache-Control", "no-cache");
     // HTTP/1.0 servers might not implement Cache-Control and
     // might only implement Pragma: no-cache
     this._request.setRequestHeader("Pragma", "no-cache");
 
-    this._request.addEventListener("error", this.onErrorXML.bind(this) ,false);
-    this._request.addEventListener("load", this.onLoadXML.bind(this), false);
+    this._request.timeout = CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS;
+    this._request.addEventListener("error", event => this.onFailXML("onErrorXML", event), false);
+    this._request.addEventListener("abort", event => this.onFailXML("onAbortXML", event), false);
+    this._request.addEventListener("timeout", event => this.onFailXML("onTimeoutXML", event), false);
+    this._request.addEventListener("load", event => this.onLoadXML(event), false);
 
     log.info("sending request to: " + url);
     this._request.send(null);
 
     return this._deferred.promise;
   },
   /**
    * Installs the specified addon and calls a callback when done.
@@ -501,60 +514,73 @@ GMPInstallManager.prototype = {
   overrideLeaveDownloadedZip: false,
 
   /**
    * The XMLHttpRequest succeeded and the document was loaded.
    * @param event The nsIDOMEvent for the load
   */
   onLoadXML: function(event) {
     let log = getScopedLogger("onLoadXML");
-    log.info("request completed downloading document");
+    try {
+      log.info("request completed downloading document");
+      let certs = null;
+      if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
+          GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, undefined, true)) {
+        certs = gCertUtils.readCertPrefs(GMPPrefs.CERTS_BRANCH);
+      }
+
+      let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
+        undefined, true);
+      log.info("allowNonBuiltIn: " + allowNonBuiltIn);
 
-    let certs = null;
-    if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
-        GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, undefined, true)) {
-      certs = gCertUtils.readCertPrefs(GMPPrefs.CERTS_BRANCH);
+      gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
+
+      this.parseResponseXML();
+    } catch (ex) {
+      log.error("could not load xml: " + ex);
+      this._deferred.reject({
+        target: event.target,
+        status: this._getChannelStatus(event.target),
+        message: "" + ex,
+      });
+      delete this._deferred;
     }
-
-    let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
-                                        undefined, true);
-    log.info("allowNonBuiltIn: " + allowNonBuiltIn);
-    gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
-
-    this.parseResponseXML();
   },
 
   /**
    * Returns the status code for the XMLHttpRequest
    */
   _getChannelStatus: function(request) {
     let log = getScopedLogger("_getChannelStatus");
-    let status = 0;
+    let status = null;
     try {
       status = request.status;
       log.info("request.status is: " + request.status);
     }
     catch (e) {
     }
 
-    if (status == 0) {
+    if (status == null) {
       status = request.channel.QueryInterface(Ci.nsIRequest).status;
     }
     return status;
   },
 
   /**
-   * There was an error of some kind during the XMLHttpRequest
+   * There was an error of some kind during the XMLHttpRequest.  This
+   * error may have been caused by external factors (e.g. network
+   * issues) or internally (by a timeout).
+   *
    * @param event The nsIDOMEvent for the error
-  */
-  onErrorXML: function(event) {
-    let log = getScopedLogger("onErrorXML");
+   */
+  onFailXML: function(failure, event) {
+    let log = getScopedLogger(failure);
     let request = event.target;
     let status = this._getChannelStatus(request);
-    let message = "request.status: " + status;
+    let message = "request.status: " + status +  "(" + event.type + ")";
     log.warn(message);
     this._deferred.reject({
       target: request,
       status: status,
       message: message
     });
     delete this._deferred;
   },
--- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
+++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
@@ -139,16 +139,85 @@ add_test(function test_checkForAddons_40
   }, function(err) {
     do_check_true(!!err);
     do_check_eq(err.status, 404);
     installManager.uninit();
     run_next_test();
   });
 });
 
+/**
+ * Tests that a xhr abort() works as expected
+ */
+add_test(function test_checkForAddons_abort() {
+  let xhr = overrideXHR(200, "", { dropRequest: true} );
+  let installManager = new GMPInstallManager();
+  let promise = installManager.checkForAddons();
+  xhr.abort();
+  promise.then(function() {
+    do_throw("abort() should reject");
+  }, function(err) {
+    do_check_eq(err.status, 0);
+    installManager.uninit();
+    run_next_test();
+  });
+});
+
+/**
+ * Tests that a defensive timeout works as expected
+ */
+add_test(function test_checkForAddons_timeout() {
+  overrideXHR(200, "", { dropRequest: true, timeout: true });
+  let installManager = new GMPInstallManager();
+  let promise = installManager.checkForAddons();
+  promise.then(function() {
+    do_throw("Defensive timeout should reject");
+  }, function(err) {
+    do_check_eq(err.status, 0);
+    installManager.uninit();
+    run_next_test();
+  });
+});
+
+/**
+ * Tests that we throw correctly in case of ssl certification error.
+ */
+add_test(function test_checkForAddons_bad_ssl() {
+  //
+  // Add random stuff that cause CertUtil to require https.
+  //
+  let PREF_KEY_URL_OVERRIDE_BACKUP =
+    Preferences.get(GMPPrefs.KEY_URL_OVERRIDE, undefined);
+  Preferences.reset(GMPPrefs.KEY_URL_OVERRIDE);
+
+  let CERTS_BRANCH_DOT_ONE = GMPPrefs.CERTS_BRANCH + ".1";
+  let PREF_CERTS_BRANCH_DOT_ONE_BACKUP =
+    Preferences.get(CERTS_BRANCH_DOT_ONE, undefined);
+  Services.prefs.setCharPref(CERTS_BRANCH_DOT_ONE, "funky value");
+
+
+  overrideXHR(200, "");
+  let installManager = new GMPInstallManager();
+  let promise = installManager.checkForAddons();
+  promise.then(function() {
+    do_throw("Defensive timeout should reject");
+  }, function(err) {
+    do_check_true(err.message.contains("SSL is required and URI scheme is not https."));
+    installManager.uninit();
+    if (PREF_KEY_URL_OVERRIDE_BACKUP) {
+      Preferences.set(GMPPrefs.KEY_URL_OVERRIDE,
+        PREF_KEY_URL_OVERRIDE_BACKUP);
+    }
+    if (PREF_CERTS_BRANCH_DOT_ONE_BACKUP) {
+      Preferences.set(CERTS_BRANCH_DOT_ONE,
+        PREF_CERTS_BRANCH_DOT_ONE_BACKUP);
+    }
+    run_next_test();
+  });
+});
 
 /**
  * Tests that gettinga a funky non XML response works as expected
  */
 add_test(function test_checkForAddons_notXML() {
   overrideXHR(200, "3.141592653589793....");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
@@ -595,65 +664,110 @@ function makeHandler(aVal) {
   if (typeof aVal == "function")
     return { handleEvent: aVal };
   return aVal;
 }
 /**
  * Constructs a mock xhr which is used for testing different aspects
  * of responses.
  */
-function xhr(inputStatus, inputResponse) {
+function xhr(inputStatus, inputResponse, options) {
   this.inputStatus = inputStatus;
   this.inputResponse = inputResponse;
+  this.status = 0;
+  this.responseXML = null;
+  this._aborted = false;
+  this._onabort = null;
+  this._onprogress = null;
+  this._onerror = null;
+  this._onload = null;
+  this._onloadend = null;
+  this._ontimeout = null;
+  this._url = null;
+  this._method = null;
+  this._timeout = 0;
+  this._notified = false;
+  this._options = options || {};
 }
 xhr.prototype = {
   overrideMimeType: function(aMimetype) { },
   setRequestHeader: function(aHeader, aValue) { },
   status: null,
   channel: { set notificationCallbacks(aVal) { } },
-  _url: null,
-  _method: null,
   open: function(aMethod, aUrl) {
     this.channel.originalURI = Services.io.newURI(aUrl, null, null);
     this._method = aMethod; this._url = aUrl;
   },
   abort: function() {
+    this._dropRequest = true;
+    this._notify(["abort", "loadend"]);
   },
   responseXML: null,
   responseText: null,
   send: function(aBody) {
-    let self = this;
     do_execute_soon(function() {
-      self.status = self.inputStatus;
-      self.responseText = self.inputResponse;
       try {
-        let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
-                     createInstance(Ci.nsIDOMParser);
-        self.responseXML = parser.parseFromString(self.inputResponse,
-                                                  "application/xml");
-      } catch (e) {
-        self.responseXML = null;
+        if (this._options.dropRequest) {
+          if (this._timeout > 0 && this._options.timeout) {
+            this._notify(["timeout", "loadend"]);
+          }
+          return;
+        }
+        this.status = this.inputStatus;
+        this.responseText = this.inputResponse;
+        try {
+          let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+                createInstance(Ci.nsIDOMParser);
+          this.responseXML = parser.parseFromString(this.inputResponse,
+            "application/xml");
+        } catch (e) {
+          this.responseXML = null;
+        }
+        if (this.inputStatus === 200) {
+          this._notify(["load", "loadend"]);
+        } else {
+          this._notify(["error", "loadend"]);
+        }
+      } catch (ex) {
+        do_throw(ex);
       }
-      let e = { target: self };
-      if (self.inputStatus === 200) {
-        self.onload(e);
-      } else {
-        self.onerror(e);
-      }
-    });
+    }.bind(this));
   },
-  _onprogress: null,
+  set onabort(aValue) { this._onabort = makeHandler(aValue); },
+  get onabort() { return this._onabort; },
   set onprogress(aValue) { this._onprogress = makeHandler(aValue); },
   get onprogress() { return this._onprogress; },
-  _onerror: null,
   set onerror(aValue) { this._onerror = makeHandler(aValue); },
   get onerror() { return this._onerror; },
-  _onload: null,
   set onload(aValue) { this._onload = makeHandler(aValue); },
   get onload() { return this._onload; },
+  set onloadend(aValue) { this._onloadend = makeHandler(aValue); },
+  get onloadend() { return this._onloadend; },
+  set ontimeout(aValue) { this._ontimeout = makeHandler(aValue); },
+  get ontimeout() { return this._ontimeout; },
+  set timeout(aValue) { this._timeout = aValue; },
+  _notify: function(events) {
+    if (this._notified) {
+      return;
+    }
+    this._notified = true;
+    for (let item of events) {
+      let k = "on" + item;
+      if (this[k]) {
+        do_print("Notifying " + item);
+        let e = {
+          target: this,
+          type: item,
+        };
+        this[k](e);
+      } else {
+        do_print("Notifying " + item + ", but there are no listeners");
+      }
+    }
+  },
   addEventListener: function(aEvent, aValue, aCapturing) {
     eval("this._on" + aEvent + " = aValue");
   },
   flags: Ci.nsIClassInfo.SINGLETON,
   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
   getHelperForLanguage: function(aLanguage) null,
   getInterfaces: function(aCount) {
     let interfaces = [Ci.nsISupports];
@@ -678,26 +792,27 @@ xhr.prototype = {
 };
 
 /**
  * Helper used to overrideXHR requests (no matter to what URL) with the
  * specified status and response.
  * @param status The status you want to get back when an XHR request is made
  * @param response The response you want to get back when an XHR request is made
  */
-function overrideXHR(status, response) {
+function overrideXHR(status, response, options) {
   let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   if (overrideXHR.myxhr) {
     registrar.unregisterFactory(overrideXHR.myxhr.classID, overrideXHR.myxhr);
   }
-  overrideXHR.myxhr = new xhr(status, response);
+  overrideXHR.myxhr = new xhr(status, response, options);
   registrar.registerFactory(overrideXHR.myxhr.classID,
                             overrideXHR.myxhr.classDescription,
                             overrideXHR.myxhr.contractID,
                             overrideXHR.myxhr);
+  return overrideXHR.myxhr;
 }
 
 /**
  * Compares binary data of 2 arrays and returns true if they are the same
  *
  * @param arr1 The first array to compare
  * @param arr2 The second array to compare
 */
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2413,21 +2413,21 @@ this.AddonManagerPrivate = {
   },
 
   backgroundUpdateCheck: function AMP_backgroundUpdateCheck() {
     return AddonManagerInternal.backgroundUpdateCheck();
   },
 
   backgroundUpdateTimerHandler() {
     // Don't call through to the real update check if no checks are enabled.
-    let checkHotfix = this.hotfixID &&
+    let checkHotfix = AddonManagerInternal.hotfixID &&
                       Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) &&
                       Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO);
 
-    if (!this.updateEnabled && !checkHotfix) {
+    if (!AddonManagerInternal.updateEnabled && !checkHotfix) {
       logger.info("Skipping background update check");
       return;
     }
     // Don't return the promise here, since the caller doesn't care.
     AddonManagerInternal.backgroundUpdateCheck();
   },
 
   addStartupChange: function AMP_addStartupChange(aType, aID) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js
@@ -39,17 +39,18 @@ function run_test_1() {
     do_check_eq(aAddons.length, 0);
 
     Services.obs.addObserver(function() {
       Services.obs.removeObserver(arguments.callee, "addons-background-update-complete");
 
       do_execute_soon(run_test_2);
     }, "addons-background-update-complete", false);
 
-    AddonManagerPrivate.backgroundUpdateCheck();
+    // Trigger the background update timer handler
+    gInternalManager.notify(null);
   });
 }
 
 // Verify that with two add-ons installed both of which claim to have updates
 // available we get the notification after both updates attempted to start
 function run_test_2() {
   writeInstallRDFForExtension({
     id: "addon1@tests.mozilla.org",
@@ -115,10 +116,11 @@ function run_test_2() {
       completeCount++;
       if (completeCount == 3) {
         do_check_true(sawCompleteNotification);
         end_test();
       }
     }
   });
 
-  AddonManagerPrivate.backgroundUpdateCheck();
+  // Trigger the background update timer handler
+  gInternalManager.notify(null);
 }