Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 04 Dec 2015 12:02:27 +0100
changeset 309856 48f854ba48c09b38b05208ce8513cbc452af35fc
parent 309855 adbd5c905a16bec519550eb3b9f5f83cbe972608 (current diff)
parent 309756 e02b17a2b5b8df7bb84f325fc08eedd2f3cab755 (diff)
child 309857 c9865d59bbb8f5e861183caa51d2abeac48c76c0
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone45.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 mozilla-central to mozilla-inbound
--- a/.eslintignore
+++ b/.eslintignore
@@ -39,17 +39,39 @@ parser/**
 probes/**
 python/**
 rdf/**
 security/**
 services/**
 startupcache/**
 storage/**
 testing/**
-toolkit/**
+toolkit/components/**
+toolkit/content/**
+toolkit/crashreporter/**
+toolkit/forgetaboutsite/**
+toolkit/identity/**
+toolkit/library/**
+toolkit/locales/**
+toolkit/modules/**
+# This file contains preprocessor statements.
+toolkit/mozapps/extensions/internal/AddonConstants.jsm
+toolkit/mozapps/downloads/**
+toolkit/mozapps/handling/**
+toolkit/mozapps/installer/**
+toolkit/mozapps/preferences/**
+toolkit/mozapps/update/**
+toolkit/obsolete/**
+toolkit/pluginproblem/**
+toolkit/profile/**
+toolkit/system/**
+toolkit/themes/**
+toolkit/toolkit.mozbuild/**
+toolkit/webapps/**
+toolkit/xre/**
 tools/**
 uriloader/**
 view/**
 webapprt/**
 widget/**
 xpcom/**
 xpfe/**
 xulrunner/**
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- 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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/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 git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gecko and Gaia -->
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="android-development" path="development" remote="b2g" revision="2bdf22305b523af644e1891b4ddfd9229336d0ce"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- 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="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
--- 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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/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 git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gecko and Gaia -->
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="android-development" path="development" remote="b2g" revision="2bdf22305b523af644e1891b4ddfd9229336d0ce"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- 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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "23a94087e701c4bc592f2943e1007e25a9242f97", 
+        "git_revision": "e9419046f360dd05b2717c4994990608519b93e4", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "1a71a5a828091c47b1306ca850d8ac882c7c0c0d", 
+    "revision": "f8b0a6383f642ae3160fad856fc7c93496a9a1ae", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="23a94087e701c4bc592f2943e1007e25a9242f97"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1038,16 +1038,20 @@ pref("browser.sessionstore.restore_hidde
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 // The version at which we performed the latest upgrade backup
 pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
 // How many upgrade backups should be kept
 pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3);
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
+// Causes SessionStore to ignore non-final update messages from
+// browser tabs that were not caused by a flush from the parent.
+// This is a testing flag and should not be used by end-users.
+pref("browser.sessionstore.debug.no_auto_updates", false);
 // Forget closed windows/tabs after two weeks
 pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -28,17 +28,17 @@ function contextMenuObserver(subject, to
 
 // When a new contextMenu is opened, this function is called and
 // we populate the |xulMenu| with all the items from extensions
 // to be displayed. We always clear all the items again when
 // popuphidden fires. Since most of the info we need is already
 // calculated in nsContextMenu.jsm we simple reuse its flags here.
 // For remote processes there is a gContextMenuContentData where all
 // the important info is stored from the child process. We get
-// this data in |contentData|.
+// this data in |contextData|.
 var menuBuilder = {
   build: function(contextData) {
     // TODO: icons should be set for items
     let xulMenu = contextData.menu;
     xulMenu.addEventListener("popuphidden", this);
     let doc = xulMenu.ownerDocument;
     for (let [ext, menuItemMap] of contextMenuMap) {
       let parentMap = new Map();
@@ -93,17 +93,17 @@ var menuBuilder = {
       }
       if (topLevelItems.size > 1) {
         // If more than one top level items are visible, callopse them.
         let top = doc.createElement("menu");
         top.setAttribute("label", ext.name);
         top.setAttribute("ext-type", "top-level-menu");
         let menupopup = doc.createElement("menupopup");
         top.appendChild(menupopup);
-        for (i of topLevelItems) {
+        for (let i of topLevelItems) {
           menupopup.appendChild(i);
         }
         xulMenu.appendChild(top);
         this._itemsToCleanUp.add(top);
       } else if (topLevelItems.size == 1) {
         // If there is only one visible item, we can just append it.
         let singleItem = topLevelItems.values().next().value;
         xulMenu.appendChild(singleItem);
@@ -321,23 +321,23 @@ MenuItem.prototype = {
         break
       }
     }
     if (!enabled) {
       return false;
     }
 
     if (this.documentUrlMatchPattern &&
-        !this.documentUrlMatchPattern.matches(contentData.documentURIObject)) {
+        !this.documentUrlMatchPattern.matches(contextData.documentURIObject)) {
       return false;
     }
 
     if (this.targetUrlPatterns &&
         (contextData.onImage || contextData.onAudio || contextData.onVideo) &&
-        !this.targetUrlPatterns.matches(contentData.mediaURL)) {
+        !this.targetUrlPatterns.matches(contextData.mediaURL)) {
       // TODO: double check if mediaURL is always set when we need it
       return false;
     }
 
     return true;
   },
 };
 
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -468,17 +468,17 @@ global.WindowManager = {
       incognito: PrivateBrowsingUtils.isWindowPrivate(window),
 
       // We fudge on these next two.
       type: this.windowType(window),
       state: window.fullScreen ? "fullscreen" : "normal",
     };
 
     if (getInfo && getInfo.populate) {
-      results.tabs = TabManager.for(extension).getTabs(window);
+      result.tabs = TabManager.for(extension).getTabs(window);
     }
 
     return result;
   },
 };
 
 // Manages listeners for window opening and closing. A window is
 // considered open when the "load" event fires on it. A window is
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -40,16 +40,20 @@ XPCOMUtils.defineLazyGetter(this, 'gCont
                             () => { return new ContentRestore(this) });
 
 // The current epoch.
 var gCurrentEpoch = 0;
 
 // A bound to the size of data to store for DOM Storage.
 const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
 
+// This pref controls whether or not we send updates to the parent on a timeout
+// or not, and should only be used for tests or debugging.
+const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
+
 /**
  * Returns a lazy function that will evaluate the given
  * function |fn| only once and cache its return value.
  */
 function createLazy(fn) {
   let cached = false;
   let cachedValue = null;
 
@@ -660,31 +664,79 @@ var MessageQueue = {
 
   /**
    * The current timeout ID, null if there is no queue data. We use timeouts
    * to damp a flood of data changes and send lots of changes as one batch.
    */
   _timeout: null,
 
   /**
+   * Whether or not sending batched messages on a timer is disabled. This should
+   * only be used for debugging or testing. If you need to access this value,
+   * you should probably use the timeoutDisabled getter.
+   */
+  _timeoutDisabled: false,
+
+  /**
+   * True if batched messages are not being fired on a timer. This should only
+   * ever be true when debugging or during tests.
+   */
+  get timeoutDisabled() {
+    return this._timeoutDisabled;
+  },
+
+  /**
+   * Disables sending batched messages on a timer. Also cancels any pending
+   * timers.
+   */
+  set timeoutDisabled(val) {
+    this._timeoutDisabled = val;
+
+    if (!val && this._timeout) {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    }
+
+    return val;
+  },
+
+  init() {
+    this.timeoutDisabled =
+      Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+
+    Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
+  },
+
+  uninit() {
+    Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
+  },
+
+  observe(subject, topic, data) {
+    if (topic == TIMEOUT_DISABLED_PREF) {
+      this.timeoutDisabled =
+        Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+    }
+  },
+
+  /**
    * Pushes a given |value| onto the queue. The given |key| represents the type
    * of data that is stored and can override data that has been queued before
    * but has not been sent to the parent process, yet.
    *
    * @param key (string)
    *        A unique identifier specific to the type of data this is passed.
    * @param fn (function)
    *        A function that returns the value that will be sent to the parent
    *        process.
    */
   push: function (key, fn) {
     this._data.set(key, createLazy(fn));
     this._lastUpdated.set(key, this._id);
 
-    if (!this._timeout) {
+    if (!this._timeout && !this._timeoutDisabled) {
       // Wait a little before sending the message to batch multiple changes.
       this._timeout = setTimeout(() => this.send(), this.BATCH_DELAY_MS);
     }
   },
 
   /**
    * Sends queued data to the chrome process.
    *
@@ -794,16 +846,17 @@ MessageListener.init();
 FormDataListener.init();
 SyncHandler.init();
 PageStyleListener.init();
 SessionHistoryListener.init();
 SessionStorageListener.init();
 ScrollPositionListener.init();
 DocShellCapabilitiesListener.init();
 PrivacyListener.init();
+MessageQueue.init();
 
 function handleRevivedTab() {
   if (!content) {
     removeEventListener("pagehide", handleRevivedTab);
     return;
   }
 
   if (content.document.documentURI.startsWith("about:tabcrashed")) {
@@ -835,16 +888,17 @@ addEventListener("unload", () => {
   // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
   // event to be fired.
   handleRevivedTab();
 
   // Remove all registered nsIObservers.
   PageStyleListener.uninit();
   SessionStorageListener.uninit();
   SessionHistoryListener.uninit();
+  MessageQueue.uninit();
 
   // Remove progress listeners.
   gContentRestore.resetRestore();
 
   // We don't need to take care of any gFrameTree observers as the gFrameTree
   // will die with the content script. The same goes for the privacy transition
   // observer that will die with the docShell when the tab is closed.
 });
--- a/browser/components/sessionstore/test/browser_async_window_flushing.js
+++ b/browser/components/sessionstore/test/browser_async_window_flushing.js
@@ -1,26 +1,175 @@
+"use strict";
+
+const PAGE = "http://example.com/";
+
+/**
+ * Tests that if we initially discard a window as not interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed.
+ */
+add_task(function* test_add_interesting_window() {
+  // We want to suppress all non-final updates from the browser tabs
+  // so as to eliminate any racy-ness with this test.
+  yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+  // Depending on previous tests, we might already have some closed
+  // windows stored. We'll use its length to determine whether or not
+  // the window was added or not.
+  let initialClosedWindows = ss.getClosedWindowCount();
+
+  // Make sure we can actually store another closed window
+  yield pushPrefs(["browser.sessionstore.max_windows_undo",
+                   initialClosedWindows + 1]);
+
+  // Create a new browser window. Since the default window will start
+  // at about:blank, SessionStore should find this tab (and therefore the
+  // whole window) uninteresting, and should not initially put it into
+  // the closed windows array.
+  let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let browser = newWin.gBrowser.selectedBrowser;
+
+  // Send a message that will cause the content to change its location
+  // to someplace more interesting. We've disabled auto updates from
+  // the browser, so the parent won't know about this
+  yield ContentTask.spawn(browser, PAGE, function*(PAGE) {
+    content.location = PAGE;
+  });
+
+  // for e10s, this will cause a remoteness switch, since the
+  // initial browser in a newly opened window will not be remote.
+  // We need to wait for that remoteness change before we attach
+  // our OnHistoryReplaceEntry listener.
+  if (gMultiProcessBrowser) {
+    yield BrowserTestUtils.waitForEvent(newWin.gBrowser.selectedTab,
+                                        "TabRemotenessChange");
+  }
+
+  yield promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
+
+  // Clear out the userTypedValue so that the new window looks like
+  // it's really not worth restoring.
+  browser.userTypedValue = null;
+
+  // Once the domWindowClosed Promise resolves, the window should
+  // have closed, and SessionStore's onClose handler should have just
+  // run.
+  let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+  // Once this windowClosed Promise resolves, we should have finished
+  // the flush and revisited our decision to put this window into
+  // the closed windows array.
+  let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+  // Ok, let's close the window.
+  newWin.close();
+
+  yield domWindowClosed;
+  // OnClose has just finished running.
+  let currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows, initialClosedWindows,
+     "We should not have added the window to the closed windows array");
+
+  yield windowClosed;
+  // The window flush has finished
+  currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows,
+     initialClosedWindows + 1,
+     "We should have added the window to the closed windows array");
+});
+
+/**
+ * Tests that if we initially store a closed window as interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed, and stop storing a window that
+ * we've deemed no longer interesting.
+ */
+add_task(function* test_remove_uninteresting_window() {
+  // We want to suppress all non-final updates from the browser tabs
+  // so as to eliminate any racy-ness with this test.
+  yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+  // Depending on previous tests, we might already have some closed
+  // windows stored. We'll use its length to determine whether or not
+  // the window was added or not.
+  let initialClosedWindows = ss.getClosedWindowCount();
+
+  // Make sure we can actually store another closed window
+  yield pushPrefs(["browser.sessionstore.max_windows_undo",
+                   initialClosedWindows + 1]);
+
+  let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+  // Now browse the initial tab of that window to an interesting
+  // site.
+  let tab = newWin.gBrowser.selectedTab;
+  let browser = tab.linkedBrowser;
+  browser.loadURI(PAGE);
+
+  yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+  yield TabStateFlusher.flush(browser);
+
+  // Send a message that will cause the content to purge its
+  // history entries and make itself seem uninteresting.
+  yield ContentTask.spawn(browser, null, function*() {
+    // Epic hackery to make this browser seem suddenly boring.
+    Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+    docShell.setCurrentURI(BrowserUtils.makeURI("about:blank"));
+
+    let {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
+    sessionHistory.PurgeHistory(sessionHistory.count);
+  });
+
+  // Once the domWindowClosed Promise resolves, the window should
+  // have closed, and SessionStore's onClose handler should have just
+  // run.
+  let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+  // Once this windowClosed Promise resolves, we should have finished
+  // the flush and revisited our decision to put this window into
+  // the closed windows array.
+  let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+  // Ok, let's close the window.
+  newWin.close();
+
+  yield domWindowClosed;
+  // OnClose has just finished running.
+  let currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows, initialClosedWindows + 1,
+     "We should have added the window to the closed windows array");
+
+  yield windowClosed;
+  // The window flush has finished
+  currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows,
+     initialClosedWindows,
+     "We should have removed the window from the closed windows array");
+});
+
 /**
  * Tests that when we close a window, it is immediately removed from the
  * _windows array.
  */
 add_task(function* test_synchronously_remove_window_state() {
   // Depending on previous tests, we might already have some closed
   // windows stored. We'll use its length to determine whether or not
   // the window was added or not.
   let state = JSON.parse(ss.getBrowserState());
   ok(state, "Make sure we can get the state");
   let initialWindows = state.windows.length;
 
   // Open a new window and send the first tab somewhere
   // interesting.
   let newWin = yield BrowserTestUtils.openNewBrowserWindow();
   let browser = newWin.gBrowser.selectedBrowser;
-  browser.loadURI("http://example.com");
-  yield BrowserTestUtils.browserLoaded(browser);
+  browser.loadURI(PAGE);
+  yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
   yield TabStateFlusher.flush(browser);
 
   state = JSON.parse(ss.getBrowserState());
   is(state.windows.length, initialWindows + 1,
      "The new window to be in the state");
 
   // Now close the window, and make sure that the window was removed
   // from the windows list from the SessionState. We're specifically
--- a/configure.in
+++ b/configure.in
@@ -4862,16 +4862,23 @@ fi
 
 dnl =========================================================
 dnl = Whether to exclude hyphenations files in the build
 dnl =========================================================
 if test -n "$MOZ_EXCLUDE_HYPHENATION_DICTIONARIES"; then
     AC_DEFINE(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 fi
 
+dnl =========================================================
+dnl = Background service for downloading additional content at runtime.
+dnl =========================================================
+if test -n "$MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE"; then
+    AC_DEFINE(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
+fi
+
 dnl ========================================================
 dnl = Include install tracking on Android
 dnl ========================================================
 if test -n "$MOZ_INSTALL_TRACKING"; then
     AC_DEFINE(MOZ_INSTALL_TRACKING)
 fi
 
 dnl ========================================================
@@ -8566,16 +8573,17 @@ AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
 AC_SUBST(MOZ_ANDROID_TAB_QUEUE)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
 AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
 AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
+AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
 AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 AC_SUBST(MOZ_INSTALL_TRACKING)
 AC_SUBST(MOZ_SWITCHBOARD)
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(STRIP_FLAGS)
 AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -318,17 +318,21 @@ var DebuggerController = {
         fetchEventListeners();
       }
     });
 
     this.Workers.connect();
     this.ThreadState.connect();
     this.StackFrames.connect();
 
-    this._onNavigate();
+    // Load all of the sources. Note that the server will actually
+    // emit individual `newSource` notifications, which trigger
+    // separate actions, so this won't do anything other than force
+    // the server to traverse sources.
+    this.dispatch(actions.loadSources());
   },
 
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   disconnect: function() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
@@ -373,21 +377,22 @@ var DebuggerController = {
     clearNamedTimeout("event-breakpoints-update");
     clearNamedTimeout("event-listeners-fetch");
   },
 
   _onNavigate: function() {
     this.ThreadState.handleTabNavigation();
     this.StackFrames.handleTabNavigation();
 
-    // Load all of the sources. Note that the server will actually
-    // emit individual `newSource` notifications, which trigger
-    // separate actions, so this won't do anything other than force
-    // the server to traverse sources.
-
+    // TODO(jwl): We shouldn't need this call. We're already getting
+    // `newSource` notifications because we're already connected, but
+    // I'm not sure of the order those come in with regards to the
+    // navigation event.  Tests look for this action and it needs to
+    // indicate everything is done loading, so we should figure out
+    // another way to indicate that.
     this.dispatch(actions.loadSources());
   },
 
   /**
    * Called when the debugged tab is closed.
    */
   _onTabDetached: function() {
     this.shutdownDebugger();
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -3466,21 +3466,21 @@ VariablesView.stringifiers.byType = {
       return result;
     }
     return result.substr(0, result.length - 1) + ellipsis + '"';
   },
 
   object: function(aGrip, aOptions) {
     let {preview} = aGrip;
     let stringifier;
-    if (preview && preview.kind) {
-      stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
+    if (aGrip.class) {
+      stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
     }
-    if (!stringifier && aGrip.class) {
-      stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
+    if (!stringifier && preview && preview.kind) {
+      stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
     }
     if (stringifier) {
       return stringifier(aGrip, aOptions);
     }
     return null;
   },
 
   symbol: function(aGrip, aOptions) {
@@ -3516,28 +3516,23 @@ VariablesView.stringifiers.byObjectClass
 
     if (typeof preview.timestamp != "number") {
       return new Date(preview.timestamp).toString(); // invalid date
     }
 
     return "Date " + new Date(preview.timestamp).toISOString();
   },
 
-  String: function({displayString}) {
-    if (displayString === undefined) {
-      return null;
-    }
-    return VariablesView.getString(displayString);
-  },
-
-  Number: function({preview}) {
+  Number: function(aGrip) {
+    let {preview} = aGrip;
     if (preview === undefined) {
       return null;
     }
-    return VariablesView.getString(preview.value);
+    return aGrip.class + " { " + VariablesView.getString(preview.wrappedValue) +
+      " }";
   },
 }; // VariablesView.stringifiers.byObjectClass
 
 VariablesView.stringifiers.byObjectClass.Boolean =
   VariablesView.stringifiers.byObjectClass.Number;
 
 VariablesView.stringifiers.byObjectKind = {
   ArrayLike: function(aGrip, {concise}) {
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -3372,16 +3372,64 @@ Widgets.ObjectRenderers.add({
       }
     }
 
     this._renderObjectProperties(container, addedPromiseInternalProps);
     this._renderObjectSuffix();
   }
 }); // Widgets.ObjectRenderers.byClass.Promise
 
+/*
+ * A renderer used for wrapped primitive objects.
+ */
+
+function WrappedPrimitiveRenderer() {
+  let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
+  if ((!ownProperties && !safeGetterValues) || this.options.concise) {
+    this._renderConciseObject();
+    return;
+  }
+
+  this._renderObjectPrefix();
+
+  let elem =
+      this.message._renderValueGrip(this.objectActor.preview.wrappedValue);
+  this.element.appendChild(elem);
+
+  this._renderObjectProperties(this.element, true);
+  this._renderObjectSuffix();
+}
+
+/**
+ * The widget used for displaying Boolean previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "Boolean",
+
+  render: WrappedPrimitiveRenderer,
+});
+
+/**
+ * The widget used for displaying Number previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "Number",
+
+  render: WrappedPrimitiveRenderer,
+});
+
+/**
+ * The widget used for displaying String previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "String",
+
+  render: WrappedPrimitiveRenderer,
+});
+
 /**
  * The widget used for displaying generic JS object previews.
  */
 Widgets.ObjectRenderers.add({
   byKind: "Object",
 
   render: function()
   {
--- a/devtools/client/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
+++ b/devtools/client/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
@@ -17,28 +17,32 @@ var test = asyncTest(function*() {
   function* autocomplete(term) {
     let deferred = promise.defer();
 
     jsterm.setInputValue(term);
     jsterm.complete(jsterm.COMPLETE_HINT_ONLY, deferred.resolve);
 
     yield deferred.promise;
 
-    ok(popup.itemCount > 0, "There's suggestions for '" + term + "'");
+    ok(popup.itemCount > 0,
+       "There's " + popup.itemCount + " suggestions for '" + term + "'");
   }
 
   let { jsterm } = yield openConsole();
   let popup = jsterm.autocompletePopup;
 
   yield jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
 
-  // FIXMEshu: global lexicals can't be autocompleted without extra platform
-  // support. See bug 1207868.
-  //yield jsterm.execute("let testObject = {$$aaab: '', $$aaac: ''}");
-
   // Should work with bug 967468.
   yield autocomplete("Object.__d");
   yield autocomplete("testObject.$$a");
 
   // Here's when things go wrong in bug 967468.
   yield autocomplete("Object.__de");
   yield autocomplete("testObject.$$aa");
+
+  // Should work with bug 1207868.
+  yield jsterm.execute("let foobar = {a: ''}; const blargh = {a: 1};");
+  yield autocomplete("foobar");
+  yield autocomplete("blargh");
+  yield autocomplete("foobar.a");
+  yield autocomplete("blargh.a");
 });
--- a/devtools/client/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -20,17 +20,17 @@ function consoleOpened(HUD) {
   let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
 
   let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   tmp.addDebuggerToGlobal(tmp);
   let dbg = new tmp.Debugger();
 
   let jsterm = HUD.jsterm;
   let win = content.wrappedJSObject;
-  let dbgWindow = dbg.makeGlobalObjectReference(win);
+  let dbgWindow = dbg.addDebuggee(content);
   let container = win._container;
 
   // Make sure autocomplete does not walk through iterators and generators.
   let result = container.gen1.next();
   let completion = JSPropertyProvider(dbgWindow, null, "_container.gen1.");
   isnot(completion.matches.length, 0, "Got matches for gen1");
 
   is(result + 1, container.gen1.next(), "gen1.next() did not execute");
@@ -57,16 +57,17 @@ function consoleOpened(HUD) {
   let dbgContent = dbg.makeGlobalObjectReference(content);
   completion = JSPropertyProvider(dbgContent, null, "_container.iter2.");
   isnot(completion.matches.length, 0, "Got matches for iter2");
 
   completion = JSPropertyProvider(dbgWindow, null, "window._container.");
   ok(completion, "matches available for window._container");
   ok(completion.matches.length, "matches available for window (length)");
 
+  dbg.removeDebuggee(content);
   jsterm.clearOutput();
 
   jsterm.execute("window._container", (msg) => {
     jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
     let anchor = msg.querySelector(".message-body a");
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
   });
 }
--- a/devtools/client/webconsole/test/browser_webconsole_output_02.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_02.js
@@ -153,27 +153,27 @@ var inputTests = [
     inspectable: true,
     variablesViewLabel: "Map[3]",
   },
 
   // 15 - WeakSet
   {
     input: "window.weakset",
     // Need a regexp because the order may vary.
-    output: new RegExp("WeakSet \\[ (String\\[7\\], <head>|<head>, String\\[7\\]) \\]"),
+    output: new RegExp("WeakSet \\[ (String, <head>|<head>, String) \\]"),
     printOutput: "[object WeakSet]",
     inspectable: true,
     variablesViewLabel: "WeakSet[2]",
   },
 
   // 16 - WeakMap
   {
     input: "window.weakmap",
     // Need a regexp because the order may vary.
-    output: new RegExp("WeakMap { (String\\[7\\]: 23, HTMLCollection\\[2\\]: Object|HTMLCollection\\[2\\]: Object, String\\[7\\]: 23) }"),
+    output: new RegExp("WeakMap { (String: 23, HTMLCollection\\[2\\]: Object|HTMLCollection\\[2\\]: Object, String: 23) }"),
     printOutput: "[object WeakMap]",
     inspectable: true,
     variablesViewLabel: "WeakMap[2]",
   },
 ];
 
 function test() {
   requestLongerTimeout(2);
--- a/devtools/client/webconsole/test/browser_webconsole_output_05.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_05.js
@@ -45,18 +45,20 @@ var inputTests = [
   {
     input: "true",
     output: "true",
   },
 
   // 4
   {
     input: "new Boolean(false)",
-    output: "false",
+    output: "Boolean { false }",
+    printOutput: "false",
     inspectable: true,
+    variablesViewLabel: "Boolean { false }"
   },
 
   // 5
   {
     input: "new Date(" + testDate + ")",
     output: "Date " + (new Date(testDate)).toISOString(),
     printOutput: (new Date(testDate)).toString(),
     inspectable: true,
@@ -78,51 +80,73 @@ var inputTests = [
     printOutput: "Invalid Date",
     inspectable: true,
     variablesViewLabel: "Object",
   },
 
   // 8
   {
     input: "new Number(43)",
-    output: "43",
+    output: "Number { 43 }",
+    printOutput: "43",
     inspectable: true,
+    variablesViewLabel: "Number { 43 }"
   },
 
   // 9
   {
     input: "new String('hello')",
-    output: 'String [ "h", "e", "l", "l", "o" ]',
+    output: /String { "hello", 6 more.* }/,
+    printOutput: "hello",
+    inspectable: true,
+    variablesViewLabel: "String"
+  },
+
+  // 10
+  {
+    input: "(function () { var s = new String('hello'); s.whatever = 23; " +
+           " return s;})()",
+    output: /String { "hello", whatever: 23, 6 more.* }/,
     printOutput: "hello",
     inspectable: true,
-    variablesViewLabel: "String[5]"
+    variablesViewLabel: "String"
   },
 
-  // 9
+  // 11
+  {
+    input: "(function () { var s = new String('hello'); s[8] = 'x'; " +
+           " return s;})()",
+    output: /String { "hello", 8: "x", 6 more.* }/,
+    printOutput: "hello",
+    inspectable: true,
+    variablesViewLabel: "String"
+  },
+
+  // 12
   {
     // XXX: Can't test fulfilled and rejected promises, because promises get
     // settled on the next tick of the event loop.
     input: "new Promise(function () {})",
     output: 'Promise { <state>: "pending" }',
     printOutput: "[object Promise]",
     inspectable: true,
     variablesViewLabel: "Promise"
   },
 
-  // 10
+  // 13
   {
     input: "(function () { var p = new Promise(function () {}); " +
            "p.foo = 1; return p; }())",
     output: 'Promise { <state>: "pending", foo: 1 }',
     printOutput: "[object Promise]",
     inspectable: true,
     variablesViewLabel: "Promise"
   },
 
-  // 11
+  // 14
   {
     input: "new Object({1: 'this\\nis\\nsupposed\\nto\\nbe\\na\\nvery" +
            "\\nlong\\nstring\\n,shown\\non\\na\\nsingle\\nline', " +
            "2: 'a shorter string', 3: 100})",
     output: 'Object { 1: "this is supposed to be a very long ' + ELLIPSIS +
             '", 2: "a shorter string", 3: 100 }',
     printOutput: "[object Object]",
     inspectable: false,
--- a/devtools/client/webconsole/test/browser_webconsole_output_table.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_table.js
@@ -119,27 +119,27 @@ const TEST_DATA = [
         _value: "\"value associated with 'a string'\"" },
       { _index: 1, _key: "5", _value: "\"value associated with 5\"" },
     ],
     columns: { _index: "(iteration index)", _key: "Key", _value: "Values" }
   },
   {
     command: "console.table(weakset)",
     data: [
-      { _value: "String[7]" },
-      { _value: "String[7]" },
+      { _value: "String" },
+      { _value: "String" },
     ],
     columns: { _index: "(iteration index)", _value: "Values" },
     couldBeOutOfOrder: true,
   },
   {
     command: "console.table(weakmap)",
     data: [
-      { _key: "String[7]", _value: "\"oh no\"" },
-      { _key: "String[7]", _value: "23" },
+      { _key: "String", _value: "\"oh no\"" },
+      { _key: "String", _value: "23" },
     ],
     columns: { _index: "(iteration index)", _key: "Key", _value: "Values" },
     couldBeOutOfOrder: true,
   },
 ];
 
 add_task(function*() {
   const {tab} = yield loadTab(TEST_URI);
--- a/devtools/client/webconsole/test/browser_webconsole_property_provider.js
+++ b/devtools/client/webconsole/test/browser_webconsole_property_provider.js
@@ -16,17 +16,17 @@ function test() {
 
 function testPropertyProvider({browser}) {
   browser.removeEventListener("load", testPropertyProvider, true);
   let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
 
   let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   tmp.addDebuggerToGlobal(tmp);
   let dbg = new tmp.Debugger();
-  let dbgWindow = dbg.makeGlobalObjectReference(content);
+  let dbgWindow = dbg.addDebuggee(content);
 
   let completion = JSPropertyProvider(dbgWindow, null, "thisIsNotDefined");
   is(completion.matches.length, 0, "no match for 'thisIsNotDefined");
 
   // This is a case the PropertyProvider can't handle. Should return null.
   completion = JSPropertyProvider(dbgWindow, null, "window[1].acb");
   is(completion, null, "no match for 'window[1].acb");
 
@@ -36,10 +36,11 @@ function testPropertyProvider({browser})
   completion = JSPropertyProvider(dbgWindow, null, strComplete);
   ok(completion.matches.length == 2, "two matches found");
   ok(completion.matchProp == "locatio", "matching part is 'test'");
   let matches = completion.matches;
   matches.sort();
   ok(matches[0] == "location", "the first match is 'location'");
   ok(matches[1] == "locationbar", "the second match is 'locationbar'");
 
+  dbg.removeDebuggee(content);
   finishTest();
 }
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -871,62 +871,26 @@ PropertyIteratorActor.prototype.requestT
  *   - the raw JS object after calling Debugger.Object.unsafeDereference(). This
  *   argument is only provided if the object is safe for reading properties and
  *   executing methods. See DevToolsUtils.isSafeJSObject().
  *
  * Functions must return false if they cannot provide preview
  * information for the debugger object, or true otherwise.
  */
 DebuggerServer.ObjectActorPreviewers = {
-  String: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("String", String, obj, hooks);
-    let length = DevToolsUtils.getProperty(obj, "length");
-
-    if (!result || typeof length != "number") {
-      return false;
-    }
-
-    grip.preview = {
-      kind: "ArrayLike",
-      length: length
-    };
-
-    if (hooks.getGripDepth() > 1) {
-      return true;
-    }
-
-    let items = grip.preview.items = [];
-
-    const max = Math.min(result.value.length, OBJECT_PREVIEW_MAX_ITEMS);
-    for (let i = 0; i < max; i++) {
-      let value = hooks.createValueGrip(result.value[i]);
-      items.push(value);
-    }
-
-    return true;
+  String: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("String", String, objectActor, grip);
   }],
 
-  Boolean: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("Boolean", Boolean, obj, hooks);
-    if (result) {
-      grip.preview = result;
-      return true;
-    }
-
-    return false;
+  Boolean: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip);
   }],
 
-  Number: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("Number", Number, obj, hooks);
-    if (result) {
-      grip.preview = result;
-      return true;
-    }
-
-    return false;
+  Number: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("Number", Number, objectActor, grip);
   }],
 
   Function: [function({obj, hooks}, grip) {
     if (obj.name) {
       grip.name = obj.name;
     }
 
     if (obj.displayName) {
@@ -1213,52 +1177,115 @@ DebuggerServer.ObjectActorPreviewers = {
       }
     }
 
     return true;
   }],
 };
 
 /**
- * Generic previewer for "simple" classes like String, Number and Boolean.
+ * Generic previewer for classes wrapping primitives, like String,
+ * Number and Boolean.
  *
  * @param string className
  *        Class name to expect.
  * @param object classObj
  *        The class to expect, eg. String. The valueOf() method of the class is
  *        invoked on the given object.
- * @param Debugger.Object obj
- *        The debugger object we need to preview.
- * @param object hooks
- *        The thread actor to use to create a value grip.
- * @return object|null
- *         An object with one property, "value", which holds the value grip that
- *         represents the given object. Null is returned if we cant preview the
- *         object.
+ * @param ObjectActor objectActor
+ *        The object actor
+ * @param Object grip
+ *        The result grip to fill in
+ * @return Booolean true if the object was handled, false otherwise
  */
-function genericObjectPreviewer(className, classObj, obj, hooks) {
+function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) {
+  let {obj, hooks} = objectActor;
+
   if (!obj.proto || obj.proto.class != className) {
-    return null;
+    return false;
   }
 
   let raw = obj.unsafeDereference();
   let v = null;
   try {
     v = classObj.prototype.valueOf.call(raw);
   } catch (ex) {
     // valueOf() can throw if the raw JS object is "misbehaved".
-    return null;
+    return false;
+  }
+
+  if (v === null) {
+    return false;
+  }
+
+  let canHandle = GenericObject(objectActor, grip, className === "String");
+  if (!canHandle) {
+    return false;
+  }
+
+  grip.preview.wrappedValue =
+    hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
+  return true;
+}
+
+function GenericObject(objectActor, grip, specialStringBehavior = false) {
+  let {obj, hooks} = objectActor;
+  if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
+    return false;
+  }
+
+  let i = 0, names = [];
+  let preview = grip.preview = {
+    kind: "Object",
+    ownProperties: Object.create(null),
+  };
+
+  try {
+    names = obj.getOwnPropertyNames();
+  } catch (ex) {
+    // Calling getOwnPropertyNames() on some wrapped native prototypes is not
+    // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
   }
 
-  if (v !== null) {
-    v = hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
-    return { value: v };
+  preview.ownPropertiesLength = names.length;
+
+  let length;
+  if (specialStringBehavior) {
+    length = DevToolsUtils.getProperty(obj, "length");
+    if (typeof length != "number") {
+      specialStringBehavior = false;
+    }
   }
 
-  return null;
+  for (let name of names) {
+    if (specialStringBehavior && /^[0-9]+$/.test(name)) {
+      let num = parseInt(name, 10);
+      if (num.toString() === name && num >= 0 && num < length) {
+        continue;
+      }
+    }
+
+    let desc = objectActor._propertyDescriptor(name, true);
+    if (!desc) {
+      continue;
+    }
+
+    preview.ownProperties[name] = desc;
+    if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
+      break;
+    }
+  }
+
+  if (i < OBJECT_PREVIEW_MAX_ITEMS) {
+    preview.safeGetterValues = objectActor._findSafeGetterValues(
+      Object.keys(preview.ownProperties),
+      OBJECT_PREVIEW_MAX_ITEMS - i);
+  }
+
+  return true;
 }
 
 // Preview functions that do not rely on the object class.
 DebuggerServer.ObjectActorPreviewers.Object = [
   function TypedArray({obj, hooks}, grip) {
     if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
       return false;
     }
@@ -1617,57 +1644,17 @@ DebuggerServer.ObjectActorPreviewers.Obj
         let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
         items.push(hooks.createValueGrip(value));
       }
     }
 
     return true;
   },
 
-  function GenericObject(objectActor, grip) {
-    let {obj, hooks} = objectActor;
-    if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
-      return false;
-    }
-
-    let i = 0, names = [];
-    let preview = grip.preview = {
-      kind: "Object",
-      ownProperties: Object.create(null),
-    };
-
-    try {
-      names = obj.getOwnPropertyNames();
-    } catch (ex) {
-      // Calling getOwnPropertyNames() on some wrapped native prototypes is not
-      // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
-    }
-
-    preview.ownPropertiesLength = names.length;
-
-    for (let name of names) {
-      let desc = objectActor._propertyDescriptor(name, true);
-      if (!desc) {
-        continue;
-      }
-
-      preview.ownProperties[name] = desc;
-      if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
-        break;
-      }
-    }
-
-    if (i < OBJECT_PREVIEW_MAX_ITEMS) {
-      preview.safeGetterValues = objectActor._findSafeGetterValues(
-                                    Object.keys(preview.ownProperties),
-                                    OBJECT_PREVIEW_MAX_ITEMS - i);
-    }
-
-    return true;
-  },
+  GenericObject,
 ];
 
 /**
  * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap
  * the resulting `value` or `reason` properties in a Debugger.Object instance.
  *
  * See dom/webidl/PromiseDebugging.webidl
  *
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -900,36 +900,43 @@ WebConsoleActor.prototype =
    * @return object
    *         The response message - matched properties.
    */
   onAutocomplete: function WCA_onAutocomplete(aRequest)
   {
     let frameActorId = aRequest.frameActor;
     let dbgObject = null;
     let environment = null;
+    let hadDebuggee = false;
 
     // This is the case of the paused debugger
     if (frameActorId) {
       let frameActor = this.conn.getActor(frameActorId);
       if (frameActor) {
         let frame = frameActor.frame;
         environment = frame.environment;
       }
       else {
         DevToolsUtils.reportException("onAutocomplete",
           Error("The frame actor was not found: " + frameActorId));
       }
     }
     // This is the general case (non-paused debugger)
     else {
-      dbgObject = this.dbg.makeGlobalObjectReference(this.evalWindow);
+      hadDebuggee = this.dbg.hasDebuggee(this.evalWindow);
+      dbgObject = this.dbg.addDebuggee(this.evalWindow);
     }
 
     let result = JSPropertyProvider(dbgObject, environment, aRequest.text,
                                     aRequest.cursor, frameActorId) || {};
+
+    if (!hadDebuggee && dbgObject) {
+      this.dbg.removeDebuggee(this.evalWindow);
+    }
+
     let matches = result.matches || [];
     let reqText = aRequest.text.substr(0, aRequest.cursor);
 
     // We consider '$' as alphanumerc because it is used in the names of some
     // helper functions.
     let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText);
     if (!lastNonAlphaIsDot) {
       if (!this._webConsoleCommandsCache) {
--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -27,16 +27,20 @@ const STATE_DQUOTE = 3;
 const OPEN_BODY = "{[(".split("");
 const CLOSE_BODY = "}])".split("");
 const OPEN_CLOSE_BODY = {
   "{": "}",
   "[": "]",
   "(": ")",
 };
 
+function hasArrayIndex(str) {
+  return /\[\d+\]$/.test(str);
+}
+
 /**
  * Analyses a given string to find the last statement that is interesting for
  * later completion.
  *
  * @param   string aStr
  *          A string to analyse.
  *
  * @returns object
@@ -218,91 +222,98 @@ function JSPropertyProvider(aDbgObject, 
     }
   }
 
   // We are completing a variable / a property lookup.
   let properties = completionPart.split(".");
   let matchProp = properties.pop().trimLeft();
   let obj = aDbgObject;
 
-  // The first property must be found in the environment if the debugger is
-  // paused.
-  if (anEnvironment) {
-    if (properties.length == 0) {
-      return getMatchedPropsInEnvironment(anEnvironment, matchProp);
-    }
-    obj = getVariableInEnvironment(anEnvironment, properties.shift());
+  // The first property must be found in the environment of the paused debugger
+  // or of the global lexical scope.
+  let env = anEnvironment || obj.asEnvironment();
+
+  if (properties.length === 0) {
+    return getMatchedPropsInEnvironment(env, matchProp);
+  }
+
+  let firstProp = properties.shift().trim();
+  if (firstProp === "this") {
+    // Special case for 'this' - try to get the Object from the Environment.
+    // No problem if it throws, we will just not autocomplete.
+    try {
+      obj = env.object;
+    } catch(e) { }
+  }
+  else if (hasArrayIndex(firstProp)) {
+    obj = getArrayMemberProperty(null, env, firstProp);
+  } else {
+    obj = getVariableInEnvironment(env, firstProp);
   }
 
   if (!isObjectUsable(obj)) {
     return null;
   }
 
   // We get the rest of the properties recursively starting from the Debugger.Object
   // that wraps the first property
   for (let i = 0; i < properties.length; i++) {
     let prop = properties[i].trim();
     if (!prop) {
       return null;
     }
 
-    // Special case for 'this' since it's not part of the global's properties
-    // but we want autocompletion to work properly for it
-    if (prop === "this" && obj === aDbgObject && i === 0) {
-      continue;
-    }
-
-    if (/\[\d+\]$/.test(prop)) {
+    if (hasArrayIndex(prop)) {
       // The property to autocomplete is a member of array. For example
       // list[i][j]..[n]. Traverse the array to get the actual element.
-      obj = getArrayMemberProperty(obj, prop);
+      obj = getArrayMemberProperty(obj, null, prop);
     }
     else {
       obj = DevToolsUtils.getProperty(obj, prop);
     }
 
     if (!isObjectUsable(obj)) {
       return null;
     }
   }
 
   // If the final property is a primitive
   if (typeof obj != "object") {
     return getMatchedProps(obj, matchProp);
   }
 
-  let matchedProps = getMatchedPropsInDbgObject(obj, matchProp);
-  if (properties.length !== 0 || obj !== aDbgObject) {
-    let thisInd = matchedProps.matches.indexOf("this");
-    if (thisInd > -1) {
-      matchedProps.matches.splice(thisInd, 1)
-    }
-  }
-
-  return matchedProps;
+  return getMatchedPropsInDbgObject(obj, matchProp);
 }
 
 /**
  * Get the array member of aObj for the given aProp. For example, given
  * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
  *
  * @param object aObj
- *        The object to operate on.
+ *        The object to operate on. Should be null if aEnv is passed.
+ * @param object aEnv
+ *        The Environment to operate in. Should be null if aObj is passed.
  * @param string aProp
  *        The property to return.
  * @return null or Object
  *         Returns null if the property couldn't be located. Otherwise the array
  *         member identified by aProp.
  */
-function getArrayMemberProperty(aObj, aProp)
+function getArrayMemberProperty(aObj, aEnv, aProp)
 {
   // First get the array.
   let obj = aObj;
   let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
-  obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+
+  if (aEnv) {
+    obj = getVariableInEnvironment(aEnv, propWithoutIndices);
+  } else {
+    obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+  }
+
   if (!isObjectUsable(obj)) {
     return null;
   }
 
   // Then traverse the list of indices to get the actual element.
   let result;
   let arrayIndicesRegex = /\[[^\]]*\]/g;
   while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
@@ -491,27 +502,17 @@ var DebuggerObjectSupport = {
     while (aObj) {
       yield aObj;
       aObj = aObj.proto;
     }
   },
 
   getProperties: function(aObj)
   {
-    let names = aObj.getOwnPropertyNames();
-    // Include 'this' in results (in sorted order).  It will be removed
-    // in all cases except for the first property request on the global, but
-    // it needs to be added now so it can be filtered based on string input.
-    for (let i = 0; i < names.length; i++) {
-      if (i === names.length - 1 || names[i+1] > "this") {
-        names.splice(i+1, 0, "this");
-        break;
-      }
-    }
-    return names;
+    return aObj.getOwnPropertyNames();
   },
 
   getProperty: function(aObj, aName, aRootObj)
   {
     // This is left unimplemented in favor to DevToolsUtils.getProperty().
     throw "Unimplemented!";
   },
 };
@@ -522,26 +523,44 @@ var DebuggerEnvironmentSupport = {
     while (aObj) {
       yield aObj;
       aObj = aObj.parent;
     }
   },
 
   getProperties: function(aObj)
   {
-    return aObj.names();
+    let names = aObj.names();
+
+    // Include 'this' in results (in sorted order)
+    for (let i = 0; i < names.length; i++) {
+      if (i === names.length - 1 || names[i+1] > "this") {
+        names.splice(i+1, 0, "this");
+        break;
+      }
+    }
+
+    return names;
   },
 
   getProperty: function(aObj, aName)
   {
-    // TODO: we should use getVariableDescriptor() here - bug 725815.
-    let result = aObj.getVariable(aName);
+    let result;
+    // Try/catch since aName can be anything, and getVariable throws if
+    // it's not a valid ECMAScript identifier name
+    try {
+      // TODO: we should use getVariableDescriptor() here - bug 725815.
+      result = aObj.getVariable(aName);
+    } catch(e) { }
+
     // FIXME: Need actual UI, bug 941287.
     if (result === undefined || result.optimizedOut || result.missingArguments) {
       return null;
     }
     return { value: result };
   },
 };
 
 
 exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
 
+// Export a version that will throw (for tests)
+exports.FallibleJSPropertyProvider = JSPropertyProvider;
--- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js
+++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
@@ -1,122 +1,154 @@
 /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 
 "use strict";
 const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-const { JSPropertyProvider } = require("devtools/shared/webconsole/js-property-provider");
+const { FallibleJSPropertyProvider: JSPropertyProvider } =
+  require("devtools/shared/webconsole/js-property-provider");
 
 Components.utils.import("resource://gre/modules/jsdebugger.jsm");
 addDebuggerToGlobal(this);
 
 function run_test() {
-  const testArray = 'var testArray = [\
-    {propA: "A"},\
-    {\
-      propB: "B", \
-      propC: [\
-        {propD: "D"}\
-      ]\
-    },\
-    [\
-      {propE: "E"}\
-    ]\
-  ];'
+  const testArray = `var testArray = [
+    {propA: "A"},
+    {
+      propB: "B",
+      propC: [
+        "D"
+      ]
+    },
+    [
+      {propE: "E"}
+    ]
+  ]`;
 
   const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
   const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}';
+  const testLet = "let foobar = {a: ''}; const blargh = {a: 1};";
 
   let sandbox = Components.utils.Sandbox("http://example.com");
   let dbg = new Debugger;
   let dbgObject = dbg.addDebuggee(sandbox);
+  let dbgEnv = dbgObject.asEnvironment();
   Components.utils.evalInSandbox(testArray, sandbox);
   Components.utils.evalInSandbox(testObject, sandbox);
   Components.utils.evalInSandbox(testHyphenated, sandbox);
+  Components.utils.evalInSandbox(testLet, sandbox);
+
+  do_print("Running tests with dbgObject");
+  runChecks(dbgObject, null);
+
+  do_print("Running tests with dbgEnv");
+  runChecks(null, dbgEnv);
+
+}
+
+function runChecks(dbgObject, dbgEnv) {
+  do_print("Test that suggestions are given for 'this'");
+  let results = JSPropertyProvider(dbgObject, dbgEnv, "t");
+  test_has_result(results, "this");
+
+  if (dbgObject != null) {
+    do_print("Test that suggestions are given for 'this.'");
+    results = JSPropertyProvider(dbgObject, dbgEnv, "this.");
+    test_has_result(results, "testObject");
+
+    do_print("Test that no suggestions are given for 'this.this'");
+    results = JSPropertyProvider(dbgObject, dbgEnv, "this.this");
+    test_has_no_results(results);
+  }
+
+  do_print("Testing lexical scope issues (Bug 1207868)");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "foobar");
+  test_has_result(results, "foobar");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "foobar.");
+  test_has_result(results, "a");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "blargh");
+  test_has_result(results, "blargh");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "blargh.");
+  test_has_result(results, "a");
 
   do_print("Test that suggestions are given for 'foo[n]' where n is an integer.");
-  let results = JSPropertyProvider(dbgObject, null, "testArray[0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0].");
   test_has_result(results, "propA");
 
   do_print("Test that suggestions are given for multidimensional arrays.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[2][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[2][0].");
   test_has_result(results, "propE");
 
+  do_print("Test that suggestions are given for nested arrays.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[1].propC[0].");
+  test_has_result(results, "indexOf");
+
   do_print("Test that suggestions are given for literal arrays.");
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].");
   test_has_result(results, "indexOf");
 
   do_print("Test that suggestions are given for literal arrays with newlines.");
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3,\n4\n].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3,\n4\n].");
   test_has_result(results, "indexOf");
 
-  do_print("Test that suggestions are given for 'this'");
-  results = JSPropertyProvider(dbgObject, null, "t");
-  test_has_result(results, "this");
-
-  do_print("Test that suggestions are given for 'this.'");
-  results = JSPropertyProvider(dbgObject, null, "this.");
-  test_has_result(results, "testObject");
-
-  do_print("Test that no suggestions are given for 'this.this'");
-  results = JSPropertyProvider(dbgObject, null, "this.this");
-  test_has_no_results(results);
-
   do_print("Test that suggestions are given for literal strings.");
-  results = JSPropertyProvider(dbgObject, null, "'foo'.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'.");
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, '"foo".');
+  results = JSPropertyProvider(dbgObject, dbgEnv, '"foo".');
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, "`foo`.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "`foo`.");
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, "'[1,2,3]'.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2,3]'.");
   test_has_result(results, "charAt");
 
   do_print("Test that suggestions are not given for syntax errors.");
-  results = JSPropertyProvider(dbgObject, null, "'foo\"");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo\"");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,',2]");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,',2]");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "'[1,2].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2].");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "'foo'..");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'..");
   do_check_null(results);
 
   do_print("Test that suggestions are not given without a dot.");
-  results = JSPropertyProvider(dbgObject, null, "'foo'");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'");
   test_has_no_results(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3]");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]");
   test_has_no_results(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3].\n'foo'");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].\n'foo'");
   test_has_no_results(results);
 
   do_print("Test that suggestions are not given for numeric literals.");
-  results = JSPropertyProvider(dbgObject, null, "1.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "1.");
   do_check_null(results);
 
   do_print("Test that suggestions are not given for index that's out of bounds.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[10].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[10].");
   do_check_null(results);
 
   do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'][0].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testObject['propA'][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testObject['propA'][0].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testArray[][1].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[][1].");
   do_check_null(results);
 
   do_print("Test that suggestions are not given if there is an hyphen in the chain.");
-  results = JSPropertyProvider(dbgObject, null, "testHyphenated['prop-A'].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testHyphenated['prop-A'].");
   do_check_null(results);
 }
 
 /**
  * A helper that ensures an empty array of results were found.
  * @param Object aResults
  *        The results returned by JSPropertyProvider.
  */
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
@@ -451,17 +451,18 @@ Convert(uint8_t aIn, BluetoothStatus& aO
     [0x02] = STATUS_NOT_READY,
     [0x03] = STATUS_NOMEM,
     [0x04] = STATUS_BUSY,
     [0x05] = STATUS_DONE,
     [0x06] = STATUS_UNSUPPORTED,
     [0x07] = STATUS_PARM_INVALID,
     [0x08] = STATUS_UNHANDLED,
     [0x09] = STATUS_AUTH_FAILURE,
-    [0x0a] = STATUS_RMT_DEV_DOWN
+    [0x0a] = STATUS_RMT_DEV_DOWN,
+    [0x0b] = STATUS_AUTH_REJECTED
   };
   if (MOZ_HAL_IPC_CONVERT_WARN_IF(
         aIn >= MOZ_ARRAY_LENGTH(sStatus), uint8_t, BluetoothStatus)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   aOut = sStatus[aIn];
   return NS_OK;
 }
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -315,16 +315,17 @@ enum BluetoothStatus {
   STATUS_NOMEM,
   STATUS_BUSY,
   STATUS_DONE,
   STATUS_UNSUPPORTED,
   STATUS_PARM_INVALID,
   STATUS_UNHANDLED,
   STATUS_AUTH_FAILURE,
   STATUS_RMT_DEV_DOWN,
+  STATUS_AUTH_REJECTED,
   NUM_STATUS
 };
 
 enum BluetoothAclState {
   ACL_STATE_CONNECTED,
   ACL_STATE_DISCONNECTED
 };
 
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -169,16 +169,17 @@ skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_Close.html]
 [test_browserElement_inproc_CloseApp.html]
 skip-if = toolkit == 'android' || buildapp == 'b2g' # android(FAILS, bug 796982) androidx86(FAILS, bug 796982)
 [test_browserElement_inproc_CloseFromOpener.html]
 skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_ContextmenuEvents.html]
 [test_browserElement_inproc_CookiesNotThirdParty.html]
 [test_browserElement_inproc_CopyPaste.html]
+skip-if = (os == "android") # Disabled on Android, see bug 1230421
 [test_browserElement_inproc_DOMRequestError.html]
 [test_browserElement_inproc_DataURI.html]
 [test_browserElement_inproc_DisallowEmbedAppsInOOP.html]
 skip-if = os == "android" || toolkit == 'gonk' # embed-apps doesn't work in the mochitest app
 [test_browserElement_inproc_DocumentFirstPaint.html]
 [test_browserElement_inproc_Download.html]
 disabled = bug 1022281
 [test_browserElement_inproc_ExecuteScript.html]
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -259,31 +259,33 @@ NS_INTERFACE_MAP_END
 
 /******************************************************************/
 /* mozilla::EventStateManager                                     */
 /******************************************************************/
 
 static uint32_t sESMInstanceCount = 0;
 static bool sPointerEventEnabled = false;
 
+uint64_t EventStateManager::sUserInputCounter = 0;
 int32_t EventStateManager::sUserInputEventDepth = 0;
 bool EventStateManager::sNormalLMouseEventInProcess = false;
 EventStateManager* EventStateManager::sActiveESM = nullptr;
 nsIDocument* EventStateManager::sMouseOverDocument = nullptr;
 nsWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
 CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
 bool EventStateManager::sIsPointerLocked = false;
 // Reference to the pointer locked element.
 nsWeakPtr EventStateManager::sPointerLockedElement;
 // Reference to the document which requested pointer lock.
 nsWeakPtr EventStateManager::sPointerLockedDoc;
 nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
+TimeStamp EventStateManager::sLatestUserInputStart;
 TimeStamp EventStateManager::sHandlingInputStart;
 
 EventStateManager::WheelPrefs*
   EventStateManager::WheelPrefs::sInstance = nullptr;
 EventStateManager::DeltaAccumulator*
   EventStateManager::DeltaAccumulator::sInstance = nullptr;
 
 EventStateManager::EventStateManager()
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -190,41 +190,63 @@ public:
 
   nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
                      bool aHaveHotspot, float aHotspotX, float aHotspotY,
                      nsIWidget* aWidget, bool aLockCursor); 
 
   static void StartHandlingUserInput()
   {
     ++sUserInputEventDepth;
+    ++sUserInputCounter;
     if (sUserInputEventDepth == 1) {
-      sHandlingInputStart = TimeStamp::Now();
+      sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
     }
   }
 
   static void StopHandlingUserInput()
   {
     --sUserInputEventDepth;
     if (sUserInputEventDepth == 0) {
       sHandlingInputStart = TimeStamp();
     }
   }
 
   /**
-   * Returns true if the current code is being executed as a result of user input.
-   * This includes timers or anything else that is initiated from user input.
-   * However, mouse over events are not counted as user input, nor are
-   * page load events. If this method is called from asynchronously executed code,
-   * such as during layout reflows, it will return false. If more time has elapsed
-   * since the user input than is specified by the
-   * dom.event.handling-user-input-time-limit pref (default 1 second), this
-   * function also returns false.
+   * Returns true if the current code is being executed as a result of
+   * user input.  This includes anything that is initiated by user,
+   * with the exception of page load events or mouse over events. If
+   * this method is called from asynchronously executed code, such as
+   * during layout reflows, it will return false. If more time has
+   * elapsed since the user input than is specified by the
+   * dom.event.handling-user-input-time-limit pref (default 1 second),
+   * this function also returns false.
    */
   static bool IsHandlingUserInput();
 
+  /**
+   * Get the number of user inputs handled since process start. This
+   * includes anything that is initiated by user, with the exception
+   * of page load events or mouse over events.
+   */
+  static uint64_t UserInputCount()
+  {
+    return sUserInputCounter;
+  }
+
+  /**
+   * Get the timestamp at which the latest user input was handled.
+   *
+   * Guaranteed to be monotonic. Until the first user input, return
+   * the epoch.
+   */
+  static TimeStamp LatestUserInputStart()
+  {
+    return sLatestUserInputStart;
+  }
+
   nsPresContext* GetPresContext() { return mPresContext; }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EventStateManager,
                                            nsIObserver)
 
   static nsIDocument* sMouseOverDocument;
 
   static EventStateManager* GetActiveEventStateManager() { return sActiveESM; }
@@ -916,27 +938,42 @@ private:
   RefPtr<IMEContentObserver> mIMEContentObserver;
 
   uint32_t mLClickCount;
   uint32_t mMClickCount;
   uint32_t mRClickCount;
 
   bool m_haveShutdown;
 
-  // Time at which we began handling user input.
+  // Time at which we began handling user input. Reset to the epoch
+  // once we have finished handling user input.
   static TimeStamp sHandlingInputStart;
 
+  // Time at which we began handling the latest user input. Not reset
+  // at the end of the input.
+  static TimeStamp sLatestUserInputStart;
+
   RefPtr<OverOutElementsWrapper> mMouseEnterLeaveHelper;
   nsRefPtrHashtable<nsUint32HashKey, OverOutElementsWrapper> mPointersEnterLeaveHelper;
 
 public:
   static nsresult UpdateUserActivityTimer(void);
   // Array for accesskey support
   nsCOMArray<nsIContent> mAccessKeys;
 
+  // The number of user inputs handled since process start. This
+  // includes anything that is initiated by user, with the exception
+  // of page load events or mouse over events.
+  static uint64_t sUserInputCounter;
+
+  // The current depth of user inputs. This includes anything that is
+  // initiated by user, with the exception of page load events or
+  // mouse over events. Incremented whenever we start handling a user
+  // input, decremented when we have finished handling a user
+  // input. This depth is *not* reset in case of nested event loops.
   static int32_t sUserInputEventDepth;
   
   static bool sNormalLMouseEventInProcess;
 
   static EventStateManager* sActiveESM;
   
   static void ClearGlobalActiveContent(EventStateManager* aClearer);
 
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -491,16 +491,21 @@
             android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
         </service>
 
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.NotificationService">
         </service>
 
+        <service
+            android:exported="false"
+            android:name="org.mozilla.gecko.dlc.DownloadContentService">
+        </service>
+
 
 #include ../services/manifests/FxAccountAndroidManifest_services.xml.in
 #include ../services/manifests/HealthReportAndroidManifest_services.xml.in
 #include ../services/manifests/SyncAndroidManifest_services.xml.in
 
         <service
             android:name="org.mozilla.gecko.tabqueue.TabReceivedService"
             android:exported="false" />
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -345,9 +345,23 @@ public class AppConstants {
 //#else
     false;
 //#endif
 
     /**
      * Target CPU architecture: "armeabi-v7a", "x86, "mips", ..
      */
     public static final String ANDROID_CPU_ARCH = "@ANDROID_CPU_ARCH@";
+
+    public static final boolean MOZ_ANDROID_EXCLUDE_FONTS =
+//#ifdef MOZ_ANDROID_EXCLUDE_FONTS
+    true;
+//#else
+    false;
+//#endif
+
+    public static final boolean MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE =
+//#ifdef MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE
+    true;
+//#else
+    false;
+//#endif
 }
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -14,16 +14,17 @@ import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.TransitionsTracker;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.distribution.Distribution;
+import org.mozilla.gecko.dlc.DownloadContentService;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.firstrun.FirstrunPane;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
@@ -1802,16 +1803,20 @@ public class BrowserApp extends GeckoApp
                     ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
                         @Override
                         public void run() {
                              GeckoPreferences.broadcastStumblerPref(BrowserApp.this);
                         }
                     }, oneSecondInMillis);
                 }
 
+                if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
+                    DownloadContentService.startVerification(this);
+                }
+
                 super.handleMessage(event, message);
             } else if (event.equals("Gecko:Ready")) {
                 // Handle this message in GeckoApp, but also enable the Settings
                 // menuitem, which is specific to BrowserApp.
                 super.handleMessage(event, message);
                 final Menu menu = mMenu;
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -1,17 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.AdjustConstants;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.LocalBrowserDB;
+import org.mozilla.gecko.dlc.DownloadContentService;
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.mdns.MulticastDNSManager;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Application;
@@ -150,16 +153,20 @@ public class GeckoApplication extends Ap
                 // its own thing.
                 return new LocalBrowserDB(profileName);
             }
         });
 
         GeckoService.register();
 
         super.onCreate();
+
+        if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
+            DownloadContentService.startStudy(this);
+        }
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
     public LightweightTheme getLightweightTheme() {
         return mLightweightTheme;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/dlc/DownloadContentHelper.java
@@ -0,0 +1,288 @@
+/* -*- 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.dlc;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.background.nativecode.NativeCrypto;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.IOUtils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.support.v4.net.ConnectivityManagerCompat;
+import android.util.Log;
+
+import ch.boye.httpclientandroidlib.HttpEntity;
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.HttpStatus;
+import ch.boye.httpclientandroidlib.client.HttpClient;
+import ch.boye.httpclientandroidlib.client.methods.HttpGet;
+import ch.boye.httpclientandroidlib.impl.client.DefaultHttpRequestRetryHandler;
+import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.util.zip.GZIPInputStream;
+
+/* package-private */ class DownloadContentHelper {
+    private static final String LOGTAG = "GeckoDLCHelper";
+
+    public static final String ACTION_STUDY_CATALOG = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.STUDY";
+    public static final String ACTION_VERIFY_CONTENT = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.VERIFY";
+    public static final String ACTION_DOWNLOAD_CONTENT = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.DOWNLOAD";
+
+    private static final String CDN_BASE_URL = "https://mobile.cdn.mozilla.net/";
+
+    private static final String CACHE_DIRECTORY = "downloadContent";
+
+    /**
+     * Exception indicating a recoverable error has happened. Download of the content will be retried later.
+     */
+    /* package-private */ static class RecoverableDownloadContentException extends Exception {
+        private static final long serialVersionUID = -2246772819507370734L;
+
+        public RecoverableDownloadContentException(String message) {
+            super(message);
+        }
+
+        public RecoverableDownloadContentException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    /**
+     * If this exception is thrown the content will be marked as unrecoverable, permanently failed and we will not try
+     * downloading it again - until a newer version of the content is available.
+     */
+    /* package-private */ static class UnrecoverableDownloadContentException extends Exception {
+        private static final long serialVersionUID = 8956080754787367105L;
+
+        public UnrecoverableDownloadContentException(String message) {
+            super(message);
+        }
+
+        public UnrecoverableDownloadContentException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    /* package-private */ static HttpClient buildHttpClient() {
+        // TODO: Implement proxy support (Bug 1209496)
+        return HttpClientBuilder.create()
+            .setUserAgent(HardwareUtils.isTablet() ?
+                    AppConstants.USER_AGENT_FENNEC_TABLET :
+                    AppConstants.USER_AGENT_FENNEC_MOBILE)
+            .setRetryHandler(new DefaultHttpRequestRetryHandler())
+            .build();
+    }
+
+    /* package-private */ static File createTemporaryFile(Context context, DownloadContent content)
+            throws RecoverableDownloadContentException {
+        File cacheDirectory = new File(context.getCacheDir(), CACHE_DIRECTORY);
+
+        if (!cacheDirectory.exists() && !cacheDirectory.mkdirs()) {
+            // Recoverable: File system might not be mounted NOW and we didn't download anything yet anyways.
+            throw new RecoverableDownloadContentException("Could not create cache directory: " + cacheDirectory);
+        }
+
+        return new File(cacheDirectory, content.getDownloadChecksum() + "-" + content.getId());
+    }
+
+    /* package-private */ static void download(HttpClient client, String source, File temporaryFile)
+            throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        final HttpGet request = new HttpGet(source);
+
+        try {
+            final HttpResponse response = client.execute(request);
+            final int status = response.getStatusLine().getStatusCode();
+            if (status != HttpStatus.SC_OK) {
+                // We are trying to be smart and only retry if this is an error that might resolve in the future.
+                // TODO: This is guesstimating at best. We want to implement failure counters (Bug 1215106).
+                if (status >= 500) {
+                    // Recoverable: Server errors 5xx
+                    throw new RecoverableDownloadContentException("(Recoverable) Download failed. Status code: " + status);
+                } else if (status >= 400) {
+                    // Unrecoverable: Client errors 4xx - Unlikely that this version of the client will ever succeed.
+                    throw new UnrecoverableDownloadContentException("(Unrecoverable) Download failed. Status code: " + status);
+                } else {
+                    // Informational 1xx: They have no meaning to us.
+                    // Successful 2xx: We don't know how to handle anything but 200.
+                    // Redirection 3xx: HttpClient should have followed redirects if possible. We should not see those errors here.
+                    throw new UnrecoverableDownloadContentException("(Unrecoverable) Download failed. Status code: " + status);
+                }
+            }
+
+            final HttpEntity entity = response.getEntity();
+            if (entity == null) {
+                // Recoverable: Should not happen for a valid asset
+                throw new RecoverableDownloadContentException("Null entity");
+            }
+
+            inputStream = new BufferedInputStream(entity.getContent());
+            outputStream = new BufferedOutputStream(new FileOutputStream(temporaryFile));
+
+            IOUtils.copy(inputStream, outputStream);
+
+            inputStream.close();
+            outputStream.close();
+        } catch (IOException e) {
+            // TODO: Support resuming downloads using 'Range' header (Bug 1209513)
+            temporaryFile.delete();
+
+            // Recoverable: Just I/O discontinuation
+            throw new RecoverableDownloadContentException(e);
+        } finally {
+            IOUtils.safeStreamClose(inputStream);
+            IOUtils.safeStreamClose(outputStream);
+        }
+    }
+
+    /* package-private */ static boolean verify(File file, String expectedChecksum)
+            throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
+        InputStream inputStream = null;
+
+        try {
+            inputStream = new BufferedInputStream(new FileInputStream(file));
+
+            byte[] ctx = NativeCrypto.sha256init();
+            if (ctx == null) {
+                throw new RecoverableDownloadContentException("Could not create SHA-256 context");
+            }
+
+            byte[] buffer = new byte[4096];
+            int read;
+
+            while ((read = inputStream.read(buffer)) != -1) {
+                NativeCrypto.sha256update(ctx, buffer, read);
+            }
+
+            String actualChecksum = Utils.byte2Hex(NativeCrypto.sha256finalize(ctx));
+
+            if (!actualChecksum.equalsIgnoreCase(expectedChecksum)) {
+                Log.w(LOGTAG, "Checksums do not match. Expected=" + expectedChecksum + ", Actual=" + actualChecksum);
+                return false;
+            }
+
+            return true;
+        } catch (IOException e) {
+            // Recoverable: Just I/O discontinuation
+            throw new RecoverableDownloadContentException(e);
+        } finally {
+            IOUtils.safeStreamClose(inputStream);
+        }
+    }
+
+    /* package-private */ static void move(File temporaryFile, File destinationFile)
+            throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
+        if (!temporaryFile.renameTo(destinationFile)) {
+            Log.d(LOGTAG, "Could not move temporary file to destination. Trying to copy..");
+            copy(temporaryFile, destinationFile);
+            temporaryFile.delete();
+        }
+    }
+
+    /* package-private */ static void extract(File sourceFile, File destinationFile, String checksum)
+            throws UnrecoverableDownloadContentException, RecoverableDownloadContentException {
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+        File temporaryFile = null;
+
+        try {
+            File destinationDirectory = destinationFile.getParentFile();
+            if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
+                throw new IOException("Destination directory does not exist and cannot be created");
+            }
+
+            temporaryFile = new File(destinationDirectory, destinationFile.getName() + ".tmp");
+
+            inputStream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(sourceFile)));
+            outputStream = new BufferedOutputStream(new FileOutputStream(temporaryFile));
+
+            IOUtils.copy(inputStream, outputStream);
+
+            inputStream.close();
+            outputStream.close();
+
+            if (!verify(temporaryFile, checksum)) {
+                Log.w(LOGTAG, "Checksum of extracted file does not match.");
+                return;
+            }
+
+            move(temporaryFile, destinationFile);
+        } catch (IOException e) {
+            // We do not support resume yet (Bug 1209513). Therefore we have to treat this as unrecoverable: The
+            // temporarily file will be deleted and we want to avoid downloading and failing repeatedly.
+            throw new UnrecoverableDownloadContentException(e);
+        } finally {
+            IOUtils.safeStreamClose(inputStream);
+            IOUtils.safeStreamClose(outputStream);
+
+            if (temporaryFile != null && temporaryFile.exists()) {
+                temporaryFile.delete();
+            }
+        }
+    }
+
+    private static void copy(File temporaryFile, File destinationFile)
+            throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            File destinationDirectory = destinationFile.getParentFile();
+            if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
+                throw new IOException("Destination directory does not exist and cannot be created");
+            }
+
+            inputStream = new BufferedInputStream(new FileInputStream(temporaryFile));
+            outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile));
+
+            IOUtils.copy(inputStream, outputStream);
+
+            inputStream.close();
+            outputStream.close();
+        } catch (IOException e) {
+            // Meh. This is an awkward situation: We downloaded the content but we can't move it to its destination. We
+            // are treating this as "unrecoverable" error because we want to avoid downloading this again and again and
+            // then always failing to copy it to the destination. This will be fixed after we implement resuming
+            // downloads (Bug 1209513): We will keep the downloaded temporary file and just retry copying.
+            throw new UnrecoverableDownloadContentException(e);
+        } finally {
+            IOUtils.safeStreamClose(inputStream);
+            IOUtils.safeStreamClose(outputStream);
+        }
+    }
+
+    /* package-private */ static File getDestinationFile(Context context, DownloadContent content)  throws UnrecoverableDownloadContentException {
+        if (content.isFont()) {
+            return new File(new File(context.getApplicationInfo().dataDir, "fonts"), content.getFilename());
+        }
+
+        // Unrecoverable: We downloaded a file and we don't know what to do with it (Should not happen)
+        throw new UnrecoverableDownloadContentException("Can't determine destination for kind: " + content.getKind());
+    }
+
+    /* package-private */ static boolean isActiveNetworkMetered(Context context) {
+        return ConnectivityManagerCompat.isActiveNetworkMetered(
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
+    }
+
+    /* package-private */ static String createDownloadURL(DownloadContent content) {
+        return CDN_BASE_URL + content.getLocation();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/dlc/DownloadContentService.java
@@ -0,0 +1,224 @@
+/* -*- 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.dlc;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.dlc.DownloadContentHelper;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
+
+import android.app.IntentService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import ch.boye.httpclientandroidlib.client.HttpClient;
+
+import java.io.File;
+
+/**
+ * Service to handle downloadable content that did not ship with the APK.
+ */
+public class DownloadContentService extends IntentService {
+    private static final String LOGTAG = "GeckoDLCService";
+
+    public static void startStudy(Context context) {
+        Intent intent = new Intent(DownloadContentHelper.ACTION_STUDY_CATALOG);
+        intent.setComponent(new ComponentName(context, DownloadContentService.class));
+        context.startService(intent);
+    }
+
+    public static void startVerification(Context context) {
+        Intent intent = new Intent(DownloadContentHelper.ACTION_VERIFY_CONTENT);
+        intent.setComponent(new ComponentName(context, DownloadContentService.class));
+        context.startService(intent);
+    }
+
+    public static void startDownloads(Context context) {
+        Intent intent = new Intent(DownloadContentHelper.ACTION_DOWNLOAD_CONTENT);
+        intent.setComponent(new ComponentName(context, DownloadContentService.class));
+        context.startService(intent);
+    }
+
+    private DownloadContentCatalog catalog;
+
+    public DownloadContentService() {
+        super(LOGTAG);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        catalog = new DownloadContentCatalog(this);
+    }
+
+    protected void onHandleIntent(Intent intent) {
+        if (!AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
+            Log.w(LOGTAG, "Download content is not enabled. Stop.");
+            return;
+        }
+
+        if (intent == null) {
+            return;
+        }
+
+        switch (intent.getAction()) {
+            case DownloadContentHelper.ACTION_STUDY_CATALOG:
+                studyCatalog();
+                break;
+
+            case DownloadContentHelper.ACTION_DOWNLOAD_CONTENT:
+                downloadContent();
+                break;
+
+            case DownloadContentHelper.ACTION_VERIFY_CONTENT:
+                verifyCatalog();
+                break;
+
+            default:
+                Log.e(LOGTAG, "Unknown action: " + intent.getAction());
+        }
+
+        catalog.persistChanges();
+    }
+
+    /**
+     * Study: Scan the catalog for "new" content available for download.
+     */
+    private void studyCatalog() {
+        Log.d(LOGTAG, "Studying catalog..");
+
+        for (DownloadContent content : catalog.getContentWithoutState()) {
+            if (content.isAssetArchive() && content.isFont()) {
+                catalog.scheduleDownload(content);
+
+                Log.d(LOGTAG, "Scheduled download: " + content);
+            }
+        }
+
+        if (catalog.hasScheduledDownloads()) {
+            startDownloads(this);
+        }
+
+        Log.v(LOGTAG, "Done");
+    }
+
+    /**
+     * Verify: Validate downloaded content. Does it still exist and does it have the correct checksum?
+     */
+    private void verifyCatalog() {
+        Log.d(LOGTAG, "Verifying catalog..");
+
+        for (DownloadContent content : catalog.getDownloadedContent()) {
+            try {
+                File destinationFile = DownloadContentHelper.getDestinationFile(this, content);
+
+                if (!destinationFile.exists()) {
+                    Log.d(LOGTAG, "Downloaded content does not exist anymore: " + content);
+
+                    // This file does not exist even though it is marked as downloaded in the catalog. Scheduling a
+                    // download to fetch it again.
+                    catalog.scheduleDownload(content);
+                }
+
+                if (!DownloadContentHelper.verify(destinationFile, content.getChecksum())) {
+                    catalog.scheduleDownload(content);
+                    Log.d(LOGTAG, "Wrong checksum. Scheduling download: " + content);
+                    continue;
+                }
+
+                Log.v(LOGTAG, "Content okay: " + content);
+            } catch (DownloadContentHelper.UnrecoverableDownloadContentException e) {
+                Log.w(LOGTAG, "Unrecoverable exception while verifying downloaded file", e);
+            } catch (DownloadContentHelper.RecoverableDownloadContentException e) {
+                // That's okay, we are just verifying already existing content. No log.
+            }
+        }
+
+        if (catalog.hasScheduledDownloads()) {
+            startDownloads(this);
+        }
+
+        Log.v(LOGTAG, "Done");
+    }
+
+    /**
+     * Download content that has been scheduled during "study" or "verify".
+     */
+    private void downloadContent() {
+        Log.d(LOGTAG, "Downloading content..");
+
+        if (DownloadContentHelper.isActiveNetworkMetered(this)) {
+            Log.d(LOGTAG, "Network is metered. Postponing download.");
+            // TODO: Reschedule download (bug 1209498)
+            return;
+        }
+
+        HttpClient client = DownloadContentHelper.buildHttpClient();
+
+        for (DownloadContent content : catalog.getScheduledDownloads()) {
+            Log.d(LOGTAG, "Downloading: " + content);
+
+            File temporaryFile = null;
+
+            try {
+                File destinationFile = DownloadContentHelper.getDestinationFile(this, content);
+                if (destinationFile.exists() && DownloadContentHelper.verify(destinationFile, content.getChecksum())) {
+                    Log.d(LOGTAG, "Content already exists and is up-to-date.");
+                    continue;
+                }
+
+                temporaryFile = DownloadContentHelper.createTemporaryFile(this, content);
+
+                // TODO: Check space on disk before downloading content (bug 1220145)
+                final String url = DownloadContentHelper.createDownloadURL(content);
+                DownloadContentHelper.download(client, url, temporaryFile);
+
+                if (!DownloadContentHelper.verify(temporaryFile, content.getDownloadChecksum())) {
+                    Log.w(LOGTAG, "Wrong checksum after download, content=" + content.getId());
+                    temporaryFile.delete();
+                    continue;
+                }
+
+                if (!content.isAssetArchive()) {
+                    Log.e(LOGTAG, "Downloaded content is not of type 'asset-archive': " + content.getType());
+                    continue;
+                }
+
+                DownloadContentHelper.extract(temporaryFile, destinationFile, content.getChecksum());
+
+                catalog.markAsDownloaded(content);
+
+                Log.d(LOGTAG, "Successfully downloaded: " + content);
+
+                onContentDownloaded(content);
+            } catch (DownloadContentHelper.RecoverableDownloadContentException e) {
+                Log.w(LOGTAG, "Downloading content failed (Recoverable): " + content, e);
+                // TODO: Reschedule download (bug 1209498)
+            } catch (DownloadContentHelper.UnrecoverableDownloadContentException e) {
+                Log.w(LOGTAG, "Downloading content failed (Unrecoverable): " + content, e);
+
+                catalog.markAsPermanentlyFailed(content);
+            } finally {
+                if (temporaryFile != null && temporaryFile.exists()) {
+                    temporaryFile.delete();
+                }
+            }
+        }
+
+        Log.v(LOGTAG, "Done");
+    }
+
+    private void onContentDownloaded(DownloadContent content) {
+        if (content.isFont()) {
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Fonts:Reload", ""));
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/dlc/catalog/DownloadContent.java
@@ -0,0 +1,223 @@
+/* -*- 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.dlc.catalog;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringDef;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class DownloadContent {
+    private static final String KEY_ID = "id";
+    private static final String KEY_LOCATION = "location";
+    private static final String KEY_FILENAME = "filename";
+    private static final String KEY_CHECKSUM = "checksum";
+    private static final String KEY_DOWNLOAD_CHECKSUM = "download_checksum";
+    private static final String KEY_LAST_MODIFIED = "last_modified";
+    private static final String KEY_TYPE = "type";
+    private static final String KEY_KIND = "kind";
+    private static final String KEY_SIZE = "size";
+    private static final String KEY_STATE = "state";
+
+    @IntDef({STATE_NONE, STATE_SCHEDULED, STATE_DOWNLOADED, STATE_FAILED, STATE_IGNORED})
+    public @interface State {}
+    public static final int STATE_NONE = 0;
+    public static final int STATE_SCHEDULED = 1;
+    public static final int STATE_DOWNLOADED = 2;
+    public static final int STATE_FAILED = 3; // Permanently failed for this version of the content
+    public static final int STATE_IGNORED = 4;
+
+    @StringDef({TYPE_ASSET_ARCHIVE})
+    public @interface Type {}
+    public static final String TYPE_ASSET_ARCHIVE = "asset-archive";
+
+    @StringDef({KIND_FONT})
+    public @interface Kind {}
+    public static final String KIND_FONT = "font";
+
+    private final String id;
+    private final String location;
+    private final String filename;
+    private final String checksum;
+    private final String downloadChecksum;
+    private final long lastModified;
+    private final String type;
+    private final String kind;
+    private final long size;
+    private int state;
+
+    private DownloadContent(@NonNull String id, @NonNull String location, @NonNull String filename,
+                            @NonNull String checksum, @NonNull String downloadChecksum, @NonNull long lastModified,
+                            @NonNull String type, @NonNull String kind, long size) {
+        this.id = id;
+        this.location = location;
+        this.filename = filename;
+        this.checksum = checksum;
+        this.downloadChecksum = downloadChecksum;
+        this.lastModified = lastModified;
+        this.type = type;
+        this.kind = kind;
+        this.size = size;
+        this.state = STATE_NONE;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    /* package-private */ void setState(@State int state) {
+        this.state = state;
+    }
+
+    @State
+    public int getState() {
+        return state;
+    }
+
+    @Kind
+    public String getKind() {
+        return kind;
+    }
+
+    @Type
+    public String getType() {
+        return type;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String getFilename() {
+        return filename;
+    }
+
+    public String getChecksum() {
+        return checksum;
+    }
+
+    public String getDownloadChecksum() {
+        return downloadChecksum;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public boolean isFont() {
+        return KIND_FONT.equals(kind);
+    }
+
+    public boolean isAssetArchive() {
+        return TYPE_ASSET_ARCHIVE.equals(type);
+    }
+
+    public static DownloadContent fromJSON(JSONObject object) throws JSONException {
+        return new Builder()
+                .setId(object.getString(KEY_ID))
+                .setLocation(object.getString(KEY_LOCATION))
+                .setFilename(object.getString(KEY_FILENAME))
+                .setChecksum(object.getString(KEY_CHECKSUM))
+                .setDownloadChecksum(object.getString(KEY_DOWNLOAD_CHECKSUM))
+                .setLastModified(object.getLong(KEY_LAST_MODIFIED))
+                .setType(object.getString(KEY_TYPE))
+                .setKind(object.getString(KEY_KIND))
+                .setSize(object.getLong(KEY_SIZE))
+                .setState(object.getInt(KEY_STATE))
+                .build();
+    }
+
+    public JSONObject toJSON() throws JSONException {
+        JSONObject object = new JSONObject();
+        object.put(KEY_ID, id);
+        object.put(KEY_LOCATION, location);
+        object.put(KEY_FILENAME, filename);
+        object.put(KEY_CHECKSUM, checksum);
+        object.put(KEY_DOWNLOAD_CHECKSUM, downloadChecksum);
+        object.put(KEY_LAST_MODIFIED, lastModified);
+        object.put(KEY_TYPE, type);
+        object.put(KEY_KIND, kind);
+        object.put(KEY_SIZE, size);
+        object.put(KEY_STATE, state);
+        return object;
+    }
+
+    public String toString() {
+        return String.format("[%s,%s] %s (%d bytes) %s", getType(), getKind(), getId(), getSize(), getChecksum());
+    }
+
+    public static class Builder {
+        private String id;
+        private String location;
+        private String filename;
+        private String checksum;
+        private String downloadChecksum;
+        private long lastModified;
+        private String type;
+        private String kind;
+        private long size;
+        private int state;
+
+        public DownloadContent build() {
+            DownloadContent content = new DownloadContent(id, location, filename, checksum, downloadChecksum,
+                                                          lastModified, type, kind, size);
+            content.setState(state);
+            return content;
+        }
+
+        public Builder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder setLocation(String location) {
+            this.location = location;
+            return this;
+        }
+
+        public Builder setFilename(String filename) {
+            this.filename = filename;
+            return this;
+        }
+
+        public Builder setChecksum(String checksum) {
+            this.checksum = checksum;
+            return this;
+        }
+
+        public Builder setDownloadChecksum(String downloadChecksum) {
+            this.downloadChecksum = downloadChecksum;
+            return this;
+        }
+
+        public Builder setLastModified(long lastModified) {
+            this.lastModified = lastModified;
+            return this;
+        }
+
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder setKind(String kind) {
+            this.kind = kind;
+            return this;
+        }
+
+        public Builder setSize(long size) {
+            this.size = size;
+            return this;
+        }
+
+        public Builder setState(int state) {
+            this.state = state;
+            return this;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/dlc/catalog/DownloadContentBootstrap.java
@@ -0,0 +1,156 @@
+/* -*- 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.dlc.catalog;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/* package-private */ class DownloadContentBootstrap {
+    public static List<DownloadContent> createInitialDownloadContentList() {
+        if (!AppConstants.MOZ_ANDROID_EXCLUDE_FONTS) {
+            // We are packaging fonts. There's nothing we want to download;
+            return Collections.emptyList();
+        }
+
+        return Arrays.asList(
+                new DownloadContent.Builder()
+                        .setId("bff50e08-7bbc-4d77-a907-bb0a54434bee")
+                        .setLocation("fonts/CharisSILCompact-B.ttf.gz")
+                        .setFilename("CharisSILCompact-B.ttf")
+                        .setChecksum("699d958b492eda0cc2823535f8567d0393090e3842f6df3c36dbe7239cb80b6d")
+                        .setDownloadChecksum("ff7ecae7669a51d5fa6a5f8e703278ebda3a68f51bc49c4321bde4438020d639")
+                        .setSize(1676072)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("68c6472d-94a6-4fb2-8525-78e427b022fe")
+                        .setLocation("fonts/CharisSILCompact-BI.ttf.gz")
+                        .setFilename("CharisSILCompact-BI.ttf")
+                        .setChecksum("82465e747b4f41471dbfd942842b2ee810749217d44b55dbc43623b89f9c7d9b")
+                        .setDownloadChecksum("dfb6d583edd27d5e6d91d479e6c8a5706275662c940c65b70911493bb279904a")
+                        .setSize(1667812)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("33d0ce0d-9c48-4a37-8b74-81cce872061b")
+                        .setLocation("fonts/CharisSILCompact-I.ttf.gz")
+                        .setFilename("CharisSILCompact-I.ttf")
+                        .setChecksum("ab3ed6f2a4d4c2095b78227bd33155d7ccd05a879c107a291912640d4d64f767")
+                        .setDownloadChecksum("5a257ec3c5226e7be0be65e463f5b22eff108da853b9ff7bc47f1733b1ddacf2")
+                        .setSize(1693988)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("7e274cdc-4216-4dc0-b7a5-8ec333f0c287")
+                        .setLocation("fonts/CharisSILCompact-R.ttf.gz")
+                        .setFilename("CharisSILCompact-R.ttf")
+                        .setChecksum("4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067")
+                        .setDownloadChecksum("cab284228b8dfe8ef46c3f1af70b5b6f9e92878f05e741ecc611e5e750a4a3b3")
+                        .setSize(1727656)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("b144002f-d5de-448c-8952-da1e405e022f")
+                        .setLocation("fonts/ClearSans-Bold.ttf.gz")
+                        .setFilename("ClearSans-Bold.ttf")
+                        .setChecksum("385d0a293c1714770e198f7c762ab32f7905a0ed9d2993f69d640bd7232b4b70")
+                        .setDownloadChecksum("d95168996dc932e6504cb5448fcb759e0ee6e66c5c8603293b046d28ab589cce")
+                        .setSize(140136)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("f07502f5-e4c5-41a8-8788-89717397a98d")
+                        .setLocation("fonts/ClearSans-BoldItalic.ttf.gz")
+                        .setFilename("ClearSans-BoldItalic.ttf")
+                        .setChecksum("7bce66864e38eecd7c94b6657b9b38c35ebfacf8046bfb1247e08f07fe933198")
+                        .setDownloadChecksum("f5e18f4acc4ceaeca9e081b1be79cd6034e0dc7ad683fa240195fd6c838452e0")
+                        .setSize(156124)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("afafc7ef-f516-42da-88d4-d8435f65541b")
+                        .setLocation("fonts/ClearSans-Italic.ttf.gz")
+                        .setFilename("ClearSans-Italic.ttf")
+                        .setChecksum("87c13c5fbae832e4f85c3bd46fcbc175978234f39be5fe51c4937be4cbff3b68")
+                        .setDownloadChecksum("56d12114ac15d913d7d9876c698889cd25f26e14966a8bd7424aeb0f61ffaf87")
+                        .setSize(155672)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("28521d9b-ac2e-45d0-89b6-a4c9076dbf6d")
+                        .setLocation("fonts/ClearSans-Light.ttf.gz")
+                        .setFilename("ClearSans-Light.ttf")
+                        .setChecksum("e4885f6188e7a8587f5621c077c6c1f5e8d3739dffc8f4d055c2ba87568c750a")
+                        .setDownloadChecksum("1fc716662866b9c01e32dda3fc9c54ca3e57de8c6ac523f46305d8ae6c0a9cf4")
+                        .setSize(145976)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("13f01bf4-da71-4673-9c60-ec0e9a45c38c")
+                        .setLocation("fonts/ClearSans-Medium.ttf.gz")
+                        .setFilename("ClearSans-Medium.ttf")
+                        .setChecksum("5d0e0115f3a3ed4be3eda6d7eabb899bb9a361292802e763d53c72e00f629da1")
+                        .setDownloadChecksum("a29184ec6621dbd3bc6ae1e30bba70c479d1001bca647ea4a205ecb64d5a00a0")
+                        .setSize(148892)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("73104370-c7ee-4b5b-bb37-392a4e66f65a")
+                        .setLocation("fonts/ClearSans-MediumItalic.ttf.gz")
+                        .setFilename("ClearSans-MediumItalic.ttf")
+                        .setChecksum("937dda88b26469306258527d38e42c95e27e7ebb9f05bd1d7c5d706a3c9541d7")
+                        .setDownloadChecksum("a381a3d4060e993af440a7b72fed29fa3a488536cc451d7c435d5fae1256318b")
+                        .setSize(155228)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("274f3718-f6e0-40b4-b52a-44812ec3ea9e")
+                        .setLocation("fonts/ClearSans-Regular.ttf.gz")
+                        .setFilename("ClearSans-Regular.ttf")
+                        .setChecksum("9b91bbdb95ffa6663da24fdaa8ee06060cd0a4d2dceaf1ffbdda00e04915ee5b")
+                        .setDownloadChecksum("87dec7f0331e19b293fc510f2764b9bd1b94595ac279cf9414f8d03c5bf34dca")
+                        .setSize(142572)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build(),
+
+                new DownloadContent.Builder()
+                        .setId("77803858-3cfb-4a0d-a1d3-fa1bf8a6c604")
+                        .setLocation("fonts/ClearSans-Thin.ttf.gz")
+                        .setFilename("ClearSans-Thin.ttf")
+                        .setChecksum("07b0db85a3ad99afeb803f0f35631436a7b4c67ac66d0c7f77d26a47357c592a")
+                        .setDownloadChecksum("64300b48b2867e5642212690f0ff9ea3988f47790311c444a81d25213b4102aa")
+                        .setSize(147004)
+                        .setKind("font")
+                        .setType("asset-archive")
+                        .build()
+        );
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/dlc/catalog/DownloadContentCatalog.java
@@ -0,0 +1,228 @@
+/* -*- 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.dlc.catalog;
+
+import org.mozilla.gecko.dlc.catalog.DownloadContentBootstrap;
+
+import android.content.Context;
+import android.support.v4.util.AtomicFile;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Catalog of downloadable content (DLC).
+ *
+ * Changing elements returned by the catalog should be guarded by the catalog instance to guarantee visibility when
+ * persisting changes.
+ */
+public class DownloadContentCatalog {
+    private static final String LOGTAG = "GeckoDLCCatalog";
+    private static final String FILE_NAME = "download_content_catalog";
+
+    private final AtomicFile file;          // Guarded by 'file'
+    private List<DownloadContent> content;  // Guarded by 'this'
+    private boolean hasLoadedCatalog;       // Guarded by 'this
+    private boolean hasCatalogChanged;      // Guarded by 'this'
+
+    public DownloadContentCatalog(Context context) {
+        content = Collections.emptyList();
+        file = new AtomicFile(new File(context.getApplicationInfo().dataDir, FILE_NAME));
+
+        startLoadFromDisk();
+    }
+
+    public synchronized List<DownloadContent> getContentWithoutState() {
+        awaitLoadingCatalogLocked();
+
+        List<DownloadContent> contentWithoutState = new ArrayList<>();
+
+        for (DownloadContent content : this.content) {
+            if (DownloadContent.STATE_NONE == content.getState()) {
+                contentWithoutState.add(content);
+            }
+        }
+
+        return contentWithoutState;
+    }
+
+    public synchronized List<DownloadContent> getDownloadedContent() {
+        awaitLoadingCatalogLocked();
+
+        List<DownloadContent> downloadedContent = new ArrayList<>();
+        for (DownloadContent content : this.content) {
+            if (DownloadContent.STATE_DOWNLOADED == content.getState()) {
+                downloadedContent.add(content);
+            }
+        }
+
+        return downloadedContent;
+    }
+
+    public synchronized List<DownloadContent> getScheduledDownloads() {
+        awaitLoadingCatalogLocked();
+
+        List<DownloadContent> scheduledContent = new ArrayList<>();
+        for (DownloadContent content : this.content) {
+            if (DownloadContent.STATE_SCHEDULED == content.getState()) {
+                scheduledContent.add(content);
+            }
+        }
+
+        return scheduledContent;
+    }
+
+    public synchronized boolean hasScheduledDownloads() {
+        awaitLoadingCatalogLocked();
+
+        for (DownloadContent content : this.content) {
+            if (DownloadContent.STATE_SCHEDULED == content.getState()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public synchronized void scheduleDownload(DownloadContent content) {
+        content.setState(DownloadContent.STATE_SCHEDULED);
+        hasCatalogChanged = true;
+    }
+
+    public synchronized void markAsDownloaded(DownloadContent content) {
+        content.setState(DownloadContent.STATE_DOWNLOADED);
+        hasCatalogChanged = true;
+    }
+
+    public synchronized void markAsPermanentlyFailed(DownloadContent content) {
+        content.setState(DownloadContent.STATE_FAILED);
+        hasCatalogChanged = true;
+    }
+
+    public synchronized void markAsIgnored(DownloadContent content) {
+        content.setState(DownloadContent.STATE_IGNORED);
+        hasCatalogChanged = true;
+    }
+
+    public void persistChanges() {
+        new Thread(LOGTAG + "-Persist") {
+            public void run() {
+                writeToDisk();
+            }
+        }.start();
+    }
+
+    private void startLoadFromDisk() {
+        new Thread(LOGTAG + "-Load") {
+            public void run() {
+                loadFromDisk();
+            }
+        }.start();
+    }
+
+    private void awaitLoadingCatalogLocked() {
+        while (!hasLoadedCatalog) {
+            try {
+                Log.v(LOGTAG, "Waiting for catalog to be loaded");
+
+                wait();
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+    }
+
+    private synchronized void loadFromDisk() {
+        Log.d(LOGTAG, "Loading from disk");
+
+        if (hasLoadedCatalog) {
+            return;
+        }
+
+        List<DownloadContent> content = new ArrayList<DownloadContent>();
+
+        try {
+            JSONArray array;
+
+            synchronized (file) {
+                array = new JSONArray(new String(file.readFully(), "UTF-8"));
+            }
+
+            for (int i = 0; i < array.length(); i++) {
+                content.add(DownloadContent.fromJSON(array.getJSONObject(i)));
+            }
+        } catch (FileNotFoundException e) {
+            Log.d(LOGTAG, "Catalog file does not exist: Bootstrapping initial catalog");
+            content = DownloadContentBootstrap.createInitialDownloadContentList();
+        } catch (JSONException e) {
+            Log.w(LOGTAG, "Unable to parse catalog JSON. Re-creating catalog.", e);
+            // Catalog seems to be broken. Re-create catalog:
+            content = DownloadContentBootstrap.createInitialDownloadContentList();
+            hasCatalogChanged = true; // Indicate that we want to persist the new catalog
+        } catch (UnsupportedEncodingException e) {
+            AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
+            error.initCause(e);
+            throw error;
+        } catch (IOException e) {
+            Log.d(LOGTAG, "Can't read catalog due to IOException", e);
+        }
+
+        this.content = content;
+        this.hasLoadedCatalog = true;
+
+        notifyAll();
+
+        Log.d(LOGTAG, "Loaded " + content.size() + " elements");
+    }
+
+    private synchronized void writeToDisk() {
+        if (!hasCatalogChanged) {
+            Log.v(LOGTAG, "Not persisting: Catalog has not changed");
+            return;
+        }
+
+        Log.d(LOGTAG, "Writing to disk");
+
+        FileOutputStream outputStream = null;
+
+        synchronized (file) {
+            try {
+                outputStream = file.startWrite();
+
+                JSONArray array = new JSONArray();
+                for (DownloadContent content : this.content) {
+                    array.put(content.toJSON());
+                }
+
+                outputStream.write(array.toString().getBytes("UTF-8"));
+
+                file.finishWrite(outputStream);
+
+                hasCatalogChanged = false;
+            } catch (UnsupportedEncodingException e) {
+                AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
+                error.initCause(e);
+                throw error;
+            } catch (IOException | JSONException e) {
+                Log.e(LOGTAG, "IOException during writing catalog", e);
+
+                if (outputStream != null) {
+                    file.failWrite(outputStream);
+                }
+            }
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -242,16 +242,21 @@ gbjar.sources += [
     'db/TabsProvider.java',
     'db/TopSitesCursorWrapper.java',
     'db/URLMetadata.java',
     'db/URLMetadataTable.java',
     'DevToolsAuthHelper.java',
     'distribution/Distribution.java',
     'distribution/ReferrerDescriptor.java',
     'distribution/ReferrerReceiver.java',
+    'dlc/catalog/DownloadContent.java',
+    'dlc/catalog/DownloadContentBootstrap.java',
+    'dlc/catalog/DownloadContentCatalog.java',
+    'dlc/DownloadContentHelper.java',
+    'dlc/DownloadContentService.java',
     'DoorHangerPopup.java',
     'DownloadsIntegration.java',
     'DynamicToolbar.java',
     'EditBookmarkDialog.java',
     'EventDispatcher.java',
     'favicons/cache/FaviconCache.java',
     'favicons/cache/FaviconCacheElement.java',
     'favicons/cache/FaviconsForURL.java',
--- a/mobile/android/base/util/IOUtils.java
+++ b/mobile/android/base/util/IOUtils.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko.util;
 
 import android.util.Log;
 
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 
 /**
  * Static helper class containing useful methods for manipulating IO objects.
  */
 public class IOUtils {
     private static final String LOGTAG = "GeckoIOUtils";
 
     /**
@@ -111,9 +112,18 @@ public class IOUtils {
     }
 
     public static void safeStreamClose(Closeable stream) {
         try {
             if (stream != null)
                 stream.close();
         } catch (IOException e) {}
     }
+
+    public static void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[4096];
+        int len;
+
+        while ((len = in.read(buffer)) != -1) {
+            out.write(buffer, 0, len);
+        }
+    }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -115,16 +115,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
                                   "resource://gre/modules/Notifications.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
+  "@mozilla.org/gfx/fontenumerator;1",
+  "nsIFontEnumerator");
+
 var lazilyLoadedBrowserScripts = [
   ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
   ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
@@ -451,16 +455,18 @@ var BrowserApp = {
     Services.obs.addObserver(this, "webapps-ask-install", false);
     Services.obs.addObserver(this, "webapps-ask-uninstall", false);
     Services.obs.addObserver(this, "webapps-launch", false);
     Services.obs.addObserver(this, "webapps-runtime-uninstall", false);
     Services.obs.addObserver(this, "Webapps:AutoInstall", false);
     Services.obs.addObserver(this, "Webapps:Load", false);
     Services.obs.addObserver(this, "Webapps:AutoUninstall", false);
     Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
+    Services.obs.addObserver(this, "Fonts:Reload", false);
+
     Messaging.addListener(this.getHistory.bind(this), "Session:GetHistory");
 
     function showFullScreenWarning() {
       Snackbars.show(Strings.browser.GetStringFromName("alertFullScreenToast"), Snackbars.LENGTH_SHORT);
     }
 
     window.addEventListener("fullscreen", function() {
       Messaging.sendRequest({
@@ -2095,16 +2101,20 @@ var BrowserApp = {
 
       case "Tiles:Click":
         // Set the click data for the given tab to be handled on the next page load.
         let data = JSON.parse(aData);
         let tab = this.getTabForId(data.tabId);
         tab.tilesData = data.payload;
         break;
 
+      case "Fonts:Reload":
+        FontEnumerator.updateFontList();
+        break;
+
       default:
         dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
         break;
 
     }
   },
 
   /**
@@ -7635,8 +7645,9 @@ HTMLContextMenuItem.prototype = Object.c
         icon: elt.icon,
         label: elt.label,
         disabled: elt.disabled,
         menu: elt instanceof Ci.nsIDOMHTMLMenuElement
       };
     }
   },
 });
+
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4068,17 +4068,21 @@ pref("font.name.serif.x-unicode", "dt-in
 pref("font.name.sans-serif.x-unicode", "dt-interface system-ucs2.cjk_japan-0");
 pref("font.name.monospace.x-unicode", "dt-interface user-ucs2.cjk_japan-0");
 
 # AIX
 #endif
 
 // Login Manager prefs
 pref("signon.rememberSignons",              true);
+#ifdef NIGHTLY_BUILD
 pref("signon.rememberSignons.visibilityToggle", true);
+#else
+pref("signon.rememberSignons.visibilityToggle", false);
+#endif
 pref("signon.autofillForms",                true);
 pref("signon.autologin.proxy",              false);
 pref("signon.storeWhenAutocompleteOff",     true);
 pref("signon.ui.experimental",              false);
 pref("signon.debug",                        false);
 pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
 
 // Satchel (Form Manager) prefs
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -62,20 +62,20 @@ var Authentication = {
    *        The user's password
    */
   signIn: function signIn(account) {
     let cb = Async.makeSpinningCallback();
 
     Logger.AssertTrue(account["username"], "Username has been found");
     Logger.AssertTrue(account["password"], "Password has been found");
 
-    Logger.logInfo("Login user: " + account["username"] + '\n');
+    Logger.logInfo("Login user: " + account["username"]);
 
     let client = new FxAccountsClient();
-    client.signIn(account["username"], account["password"]).then(credentials => {
+    client.signIn(account["username"], account["password"], true).then(credentials => {
       return fxAccounts.setSignedInUser(credentials);
     }).then(() => {
       cb(null, true);
     }, error => {
       cb(error, false);
     });
 
     try {
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -103,42 +103,52 @@ var TPS = {
   _syncActive: false,
   _syncErrors: 0,
   _syncWipeAction: null,
   _tabsAdded: 0,
   _tabsFinished: 0,
   _test: null,
   _triggeredSync: false,
   _usSinceEpoch: 0,
+  _requestedQuit: false,
 
   _init: function TPS__init() {
     // Check if Firefox Accounts is enabled
     let service = Cc["@mozilla.org/weave/service;1"]
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
     this.fxaccounts_enabled = service.fxAccountsEnabled;
 
     this.delayAutoSync();
 
     OBSERVER_TOPICS.forEach(function (aTopic) {
       Services.obs.addObserver(this, aTopic, true);
     }, this);
 
+    // Configure some logging prefs for Sync itself.
+    Weave.Svc.Prefs.set("log.appender.dump", "Debug");
     // Import the appropriate authentication module
     if (this.fxaccounts_enabled) {
       Cu.import("resource://tps/auth/fxaccounts.jsm", module);
     }
     else {
       Cu.import("resource://tps/auth/sync.jsm", module);
     }
   },
 
-  DumpError: function TPS__DumpError(msg) {
+  DumpError(msg, exc = null) {
     this._errors++;
-    Logger.logError("[phase" + this._currentPhase + "] " + msg);
+    let errInfo;
+    if (exc) {
+      errInfo = Utils.exceptionStr(exc); // includes details and stack-trace.
+    } else {
+      // always write a stack even if no error passed.
+      errInfo = Utils.stackTrace(new Error());
+    }
+    Logger.logError(`[phase ${this._currentPhase}] ${msg} - ${errInfo}`);
     this.quit();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   observe: function TPS__observe(subject, topic, data) {
     try {
@@ -227,17 +237,17 @@ var TPS = {
           break;
 
         case "weave:engine:stop-tracking":
           this._isTracking = false;
           break;
       }
     }
     catch (e) {
-      this.DumpError("Exception caught: " + Utils.exceptionStr(e));
+      this.DumpError("Observer failed", e);
       return;
     }
   },
 
   /**
    * Given that we cannot complely disable the automatic sync operations, we
    * massively delay the next sync. Sync operations have to only happen when
    * directly called via TPS.Sync()!
@@ -260,16 +270,17 @@ var TPS = {
       this._currentAction++;
       Utils.nextTick(function() {
         this.RunNextTestAction();
       }, this);
     }
   },
 
   quit: function TPS__quit() {
+    this._requestedQuit = true;
     this.goQuitApplication();
   },
 
   HandleWindows: function (aWindow, action) {
     Logger.logInfo("executing action " + action.toUpperCase() +
                    " on window " + JSON.stringify(aWindow));
     switch(action) {
       case ACTION_ADD:
@@ -409,36 +420,36 @@ var TPS = {
   },
 
   HandlePasswords: function (passwords, action) {
     try {
       for (let password of passwords) {
         let password_id = -1;
         Logger.logInfo("executing action " + action.toUpperCase() +
                       " on password " + JSON.stringify(password));
-        var password = new Password(password);
+        let passwordOb = new Password(password);
         switch (action) {
           case ACTION_ADD:
-            Logger.AssertTrue(password.Create() > -1, "error adding password");
+            Logger.AssertTrue(passwordOb.Create() > -1, "error adding password");
             break;
           case ACTION_VERIFY:
-            Logger.AssertTrue(password.Find() != -1, "password not found");
+            Logger.AssertTrue(passwordOb.Find() != -1, "password not found");
             break;
           case ACTION_VERIFY_NOT:
-            Logger.AssertTrue(password.Find() == -1,
+            Logger.AssertTrue(passwordOb.Find() == -1,
               "password found, but it shouldn't exist");
             break;
           case ACTION_DELETE:
-            Logger.AssertTrue(password.Find() != -1, "password not found");
-            password.Remove();
+            Logger.AssertTrue(passwordOb.Find() != -1, "password not found");
+            passwordOb.Remove();
             break;
           case ACTION_MODIFY:
-            if (password.updateProps != null) {
-              Logger.AssertTrue(password.Find() != -1, "password not found");
-              password.Update();
+            if (passwordOb.updateProps != null) {
+              Logger.AssertTrue(passwordOb.Find() != -1, "password not found");
+              passwordOb.Update();
             }
             break;
           default:
             Logger.AssertTrue(false, "invalid action: " + action);
         }
       }
       Logger.logPass("executing action " + action.toUpperCase() +
                      " on passwords");
@@ -596,17 +607,25 @@ var TPS = {
 
       // if we're in an async operation, don't continue on to the next action
       if (this._operations_pending)
         return;
 
       this._currentAction++;
     }
     catch(e) {
-      this.DumpError("Exception caught: " + Utils.exceptionStr(e));
+      if (Async.isShutdownException(e)) {
+        if (this._requestedQuit) {
+          Logger.logInfo("Sync aborted due to requested shutdown");
+        } else {
+          this.DumpError("Sync aborted due to shutdown, but we didn't request it");
+        }
+      } else {
+        this.DumpError("RunNextTestAction failed", e);
+      }
       return;
     }
     this.RunNextTestAction();
   },
 
   /**
    * Runs a single test phase.
    *
@@ -653,17 +672,17 @@ var TPS = {
         this.waitForEvent("weave:service:ready");
       }
 
       // Always give Sync an extra tick to initialize. If we waited for the
       // service:ready event, this is required to ensure all handlers have
       // executed.
       Utils.nextTick(this._executeTestPhase.bind(this, file, phase, settings));
     } catch(e) {
-      this.DumpError("Exception caught: " + Utils.exceptionStr(e));
+      this.DumpError("RunTestPhase failed", e);
       return;
     }
   },
 
   /**
    * Executes a single test phase.
    *
    * This is called by RunTestPhase() after the environment is validated.
@@ -738,17 +757,17 @@ var TPS = {
         prefs.setCharPref('tps.account.password', this.config.sync_account.password);
         prefs.setCharPref('tps.account.passphrase', this.config.sync_account.passphrase);
       }
 
       // start processing the test actions
       this._currentAction = 0;
     }
     catch(e) {
-      this.DumpError("Exception caught: " + Utils.exceptionStr(e));
+      this.DumpError("_executeTestPhase failed", e);
       return;
     }
   },
 
   /**
    * Register a single phase with the test harness.
    *
    * This is called when loading individual test files.
@@ -814,20 +833,16 @@ var TPS = {
    */
   waitForEvent: function waitForEvent(aEventName) {
     Logger.logInfo("Waiting for " + aEventName + "...");
     let cb = Async.makeSpinningCallback();
     Svc.Obs.add(aEventName, cb);
     cb.wait();
     Svc.Obs.remove(aEventName, cb);
     Logger.logInfo(aEventName + " observed!");
-
-    cb = Async.makeSpinningCallback();
-    Utils.nextTick(cb);
-    cb.wait();
   },
 
 
   /**
    * Waits for Sync to logged in before returning
    */
   waitForSetupComplete: function waitForSetup() {
     if (!this._setupComplete) {
@@ -863,16 +878,23 @@ var TPS = {
 
     Logger.logInfo("Setting client credentials and login.");
     let account = this.fxaccounts_enabled ? this.config.fx_account
                                           : this.config.sync_account;
     Authentication.signIn(account);
     this.waitForSetupComplete();
     Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK");
     this.waitForTracking();
+    // If fxaccounts is enabled we get an initial sync at login time - let
+    // that complete.
+    if (this.fxaccounts_enabled) {
+      this._triggeredSync = true;
+      this.waitForEvent("weave:service:sync:start");
+      this.waitForSyncFinished();
+    }
   },
 
   /**
    * Triggers a sync operation
    *
    * @param {String} [wipeAction]
    *        Type of wipe to perform (resetClient, wipeClient, wipeRemote)
    *
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -303,16 +303,17 @@ user_pref("media.eme.enabled", true);
 
 user_pref("media.autoplay.enabled", true);
 
 #if defined(XP_WIN)
 user_pref("media.decoder.heuristic.dormant.timeout", 0);
 #endif
 
 // Don't prompt about e10s
+user_pref("browser.displayedE10SNotice", 5);
 user_pref("browser.displayedE10SPrompt.1", 5);
 // Don't use auto-enabled e10s
 user_pref("browser.tabs.remote.autostart.1", false);
 user_pref("browser.tabs.remote.autostart.2", false);
 // Don't forceably kill content processes after a timeout
 user_pref("dom.ipc.tabs.shutdownTimeoutSecs", 0);
 
 // Avoid performing Reader Mode intros during tests.
new file mode 100644
--- /dev/null
+++ b/testing/tps/.gitignore
@@ -0,0 +1,4 @@
+# These files are added by running the TPS test suite.
+build/
+dist/
+tps.egg-info/
new file mode 100644
--- /dev/null
+++ b/testing/tps/.hgignore
@@ -0,0 +1,4 @@
+# These files are added by running the TPS test suite.
+build/
+dist/
+tps.egg-info/
--- a/testing/tps/setup.py
+++ b/testing/tps/setup.py
@@ -8,19 +8,19 @@ import sys
 version = '0.5'
 
 deps = ['httplib2 == 0.7.3',
         'mozfile == 1.1',
         'mozhttpd == 0.7',
         'mozinfo == 0.7',
         'mozinstall == 1.10',
         'mozprocess == 0.19',
-        'mozprofile == 0.21',
+        'mozprofile == 0.27',
         'mozrunner == 6.0',
-        'mozversion == 0.6',
+        'mozversion == 1.4',
        ]
 
 # we only support python 2.6+ right now
 assert sys.version_info[0] == 2
 assert sys.version_info[1] >= 6
 
 setup(name='tps',
       version=version,
--- a/testing/tps/tps/cli.py
+++ b/testing/tps/tps/cli.py
@@ -64,16 +64,20 @@ def main():
                       default='tps_result.json',
                       help='path to the result file [default: %default]')
     parser.add_option('--testfile',
                       action='store',
                       type='string',
                       dest='testfile',
                       default='all_tests.json',
                       help='path to the test file to run [default: %default]')
+    parser.add_option('--stop-on-error',
+                      action='store_true',
+                      dest='stop_on_error',
+                      help='stop running tests after the first failure')
     (options, args) = parser.parse_args()
 
     configfile = options.configfile
     if configfile is None:
         virtual_env = os.environ.get('VIRTUAL_ENV')
         if virtual_env:
             configfile = os.path.join(virtual_env, 'config.json')
         if configfile is None or not os.access(configfile, os.F_OK):
@@ -108,16 +112,17 @@ def main():
                         config=config,
                         debug=options.debug,
                         ignore_unused_engines=options.ignore_unused_engines,
                         logfile=options.logfile,
                         mobile=options.mobile,
                         resultfile=options.resultfile,
                         rlock=rlock,
                         testfile=testfile,
+                        stop_on_error=options.stop_on_error,
                       )
     TPS.run_tests()
 
     if TPS.numfailed > 0 or TPS.numpassed == 0:
         sys.exit(1)
 
 if __name__ == '__main__':
     main()
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -67,16 +67,18 @@ class TPSTestRunner(object):
         'extensions.getAddons.get.url': 'http://127.0.0.1:4567/addons/api/%IDS%.xml',
         'extensions.update.enabled': False,
         # Don't open a dialog to show available add-on updates
         'extensions.update.notifyUser': False,
         'services.sync.addons.ignoreRepositoryChecking': True,
         'services.sync.firstSync': 'notReady',
         'services.sync.lastversion': '1.0',
         'toolkit.startup.max_resumed_crashes': -1,
+        # hrm - not sure what the release/beta channels will do?
+        'xpinstall.signatures.required': False,
     }
 
     debug_preferences = {
         'services.sync.log.appender.console': 'Trace',
         'services.sync.log.appender.dump': 'Trace',
         'services.sync.log.appender.file.level': 'Trace',
         'services.sync.log.appender.file.logOnSuccess': True,
         'services.sync.log.rootLogger': 'Trace',
@@ -111,27 +113,29 @@ class TPSTestRunner(object):
                  binary=None,
                  config=None,
                  debug=False,
                  ignore_unused_engines=False,
                  logfile='tps.log',
                  mobile=False,
                  rlock=None,
                  resultfile='tps_result.json',
-                 testfile=None):
+                 testfile=None,
+                 stop_on_error=False):
         self.binary = binary
         self.config = config if config else {}
         self.debug = debug
         self.extensions = []
         self.ignore_unused_engines = ignore_unused_engines
         self.logfile = os.path.abspath(logfile)
         self.mobile = mobile
         self.rlock = rlock
         self.resultfile = resultfile
         self.testfile = testfile
+        self.stop_on_error = stop_on_error
 
         self.addonversion = None
         self.branch = None
         self.changeset = None
         self.errorlogs = {}
         self.extensionDir = extensionDir
         self.firefoxRunner = None
         self.nightly = False
@@ -446,16 +450,19 @@ class TPSTestRunner(object):
             self.results.append({'state': result['state'],
                                  'name': result['name'],
                                  'message': result['message'],
                                  'logdata': result['logdata']})
             if result['state'] == 'TEST-PASS':
                 self.numpassed += 1
             else:
                 self.numfailed += 1
+                if self.stop_on_error:
+                    print '\nTest failed with --stop-on-error specified; not running any more tests.\n'
+                    break
 
         self.mozhttpd.stop()
 
         # generate the postdata we'll use to post the results to the db
         self.postdata = { 'tests': self.results,
                           'os': '%s %sbit' % (mozinfo.version, mozinfo.bits),
                           'testtype': 'crossweave',
                           'productversion': self.productversion,
--- a/toolkit/components/contentprefs/nsContentPrefService.js
+++ b/toolkit/components/contentprefs/nsContentPrefService.js
@@ -1065,17 +1065,19 @@ ContentPrefService.prototype = {
     if (!dbFile.exists())
       dbConnection = this._dbCreate(dbService, dbFile);
     else {
       try {
         dbConnection = dbService.openDatabase(dbFile);
       }
       // If the connection isn't ready after we open the database, that means
       // the database has been corrupted, so we back it up and then recreate it.
-      catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+      catch (e) {
+        if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
+          throw e;
         dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
                                                  dbConnection);
       }
 
       // Get the version of the schema in the file.
       var version = dbConnection.schemaVersion;
 
       // Try to migrate the schema in the database to the current schema used by
--- a/toolkit/components/contentprefs/tests/unit_cps2/AsyncRunner.jsm
+++ b/toolkit/components/contentprefs/tests/unit_cps2/AsyncRunner.jsm
@@ -19,43 +19,40 @@ function AsyncRunner(callbacks) {
 }
 
 AsyncRunner.prototype = {
 
   appendIterator: function AR_appendIterator(iter) {
     this._iteratorQueue.push(iter);
   },
 
-  next: function AR_next(/* ... */) {
+  next: function AR_next(arg) {
     if (!this._iteratorQueue.length) {
       this.destroy();
       this._callbacks.done();
       return;
     }
 
-    // send() discards all arguments after the first, so there's no choice here
-    // but to send only one argument to the yielder.
-    let args = [arguments.length <= 1 ? arguments[0] : Array.slice(arguments)];
     try {
-      var val = this._iteratorQueue[0].send.apply(this._iteratorQueue[0], args);
-    }
-    catch (err if err instanceof StopIteration) {
-      this._iteratorQueue.shift();
-      this.next();
-      return;
+      var { done, value } = this._iteratorQueue[0].next(arg);
+      if (done) {
+        this._iteratorQueue.shift();
+        this.next();
+        return;
+      }
     }
     catch (err) {
       this._callbacks.error(err);
     }
 
     // val is truthy => call next
     // val is an iterator => prepend it to the queue and start on it
-    if (val) {
-      if (typeof(val) != "boolean")
-        this._iteratorQueue.unshift(val);
+    if (value) {
+      if (typeof(value) != "boolean")
+        this._iteratorQueue.unshift(value);
       this.next();
     }
   },
 
   destroy: function AR_destroy() {
     Cc["@mozilla.org/consoleservice;1"].
       getService(Ci.nsIConsoleService).
       unregisterListener(this);
--- a/toolkit/components/contentprefs/tests/unit_cps2/head.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/head.js
@@ -57,17 +57,17 @@ function runAsyncTests(tests, dontResetB
   next = asyncRunner.next.bind(asyncRunner);
 
   do_register_cleanup(function () {
     asyncRunner.destroy();
     asyncRunner = null;
   });
 
   tests.forEach(function (test) {
-    function gen() {
+    function* gen() {
       do_print("Running " + test.name);
       yield test();
       yield reset();
     }
     asyncRunner.appendIterator(gen());
   });
 
   // reset() ends up calling asyncRunner.next(), starting the tests.
@@ -191,44 +191,44 @@ function prefOK(actual, expected, strict
   equal(actual.domain, expected.domain);
   equal(actual.name, expected.name);
   if (strict)
     strictEqual(actual.value, expected.value);
   else
     equal(actual.value, expected.value);
 }
 
-function getOK(args, expectedVal, expectedGroup, strict) {
+function* getOK(args, expectedVal, expectedGroup, strict) {
   if (args.length == 2)
     args.push(undefined);
   let expectedPrefs = expectedVal === undefined ? [] :
                       [{ domain: expectedGroup || args[0],
                          name: args[1],
                          value: expectedVal }];
   yield getOKEx("getByDomainAndName", args, expectedPrefs, strict);
 }
 
-function getSubdomainsOK(args, expectedGroupValPairs) {
+function* getSubdomainsOK(args, expectedGroupValPairs) {
   if (args.length == 2)
     args.push(undefined);
   let expectedPrefs = expectedGroupValPairs.map(function ([group, val]) {
     return { domain: group, name: args[1], value: val };
   });
   yield getOKEx("getBySubdomainAndName", args, expectedPrefs);
 }
 
-function getGlobalOK(args, expectedVal) {
+function* getGlobalOK(args, expectedVal) {
   if (args.length == 1)
     args.push(undefined);
   let expectedPrefs = expectedVal === undefined ? [] :
                       [{ domain: null, name: args[0], value: expectedVal }];
   yield getOKEx("getGlobal", args, expectedPrefs);
 }
 
-function getOKEx(methodName, args, expectedPrefs, strict, context) {
+function* getOKEx(methodName, args, expectedPrefs, strict, context) {
   let actualPrefs = [];
   args.push(makeCallback({
     handleResult: pref => actualPrefs.push(pref)
   }));
   yield cps[methodName].apply(cps, args);
   arraysOfArraysOK([actualPrefs], [expectedPrefs], function (actual, expected) {
     prefOK(actual, expected, strict);
   });
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_getCached.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_getCached.js
@@ -3,29 +3,29 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     getCachedOK(["a.com", "foo"], false, undefined);
     getCachedGlobalOK(["foo"], false, undefined);
     yield true;
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["http://a.com/huh", "foo"], true, 1, "a.com");
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     getCachedOK(["a.com", "foo"], true, 1);
 
     yield set("a.com", "bar", 2);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["a.com", "bar"], true, 2);
 
     yield setGlobal("foo", 3);
@@ -35,24 +35,24 @@ var tests = [
 
     yield setGlobal("bar", 4);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["a.com", "bar"], true, 2);
     getCachedGlobalOK(["foo"], true, 3);
     getCachedGlobalOK(["bar"], true, 4);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["b.a.com", "foo"], true, 2);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -65,27 +65,27 @@ var tests = [
 
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["a.com", "bar"], true, 2);
     getCachedGlobalOK(["foo"], true, 3);
     getCachedGlobalOK(["bar"], true, 4);
     getCachedOK(["b.com", "foo"], true, 5);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.getCachedByDomainAndName(null, "foo", null));
     do_check_throws(() => cps.getCachedByDomainAndName("", "foo", null));
     do_check_throws(() => cps.getCachedByDomainAndName("a.com", "", null));
     do_check_throws(() => cps.getCachedByDomainAndName("a.com", null, null));
     do_check_throws(() => cps.getCachedGlobal("", null));
     do_check_throws(() => cps.getCachedGlobal(null, null));
     yield true;
   },
 
-  function casts() {
+  function* casts() {
     // SQLite casts booleans to integers.  This makes sure the values stored in
     // the cache are the same as the casted values in the database.
 
     yield set("a.com", "foo", false);
     yield getOK(["a.com", "foo"], 0, "a.com", true);
     getCachedOK(["a.com", "foo"], true, 0, "a.com", true);
 
     yield set("a.com", "bar", true);
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_getCachedSubdomains.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_getCachedSubdomains.js
@@ -3,28 +3,28 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     getCachedSubdomainsOK(["a.com", "foo"], []);
     yield true;
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     getCachedSubdomainsOK(["http://a.com/huh", "foo"], [["a.com", 1]]);
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
 
     yield set("a.com", "bar", 2);
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
 
     yield setGlobal("foo", 3);
@@ -34,38 +34,38 @@ var tests = [
 
     yield setGlobal("bar", 4);
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
     getCachedGlobalOK(["foo"], true, 3);
     getCachedGlobalOK(["bar"], true, 4);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1], ["b.a.com", 2]]);
     getCachedSubdomainsOK(["b.a.com", "foo"], [["b.a.com", 2]]);
   },
 
-  function populateViaGet() {
+  function* populateViaGet() {
     yield cps.getByDomainAndName("a.com", "foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
 
     yield cps.getGlobal("foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
     getCachedGlobalOK(["foo"], true, undefined);
   },
 
-  function populateViaGetSubdomains() {
+  function* populateViaGetSubdomains() {
     yield cps.getBySubdomainAndName("a.com", "foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
   },
 
-  function populateViaRemove() {
+  function* populateViaRemove() {
     yield cps.removeByDomainAndName("a.com", "foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
 
     yield cps.removeBySubdomainAndName("b.com", "foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
     getCachedSubdomainsOK(["b.com", "foo"], [["b.com", undefined]]);
 
     yield cps.removeGlobal("foo", null, makeCallback());
@@ -92,17 +92,17 @@ var tests = [
     yield cps.removeGlobal("foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"],
                           [["a.com", undefined], ["b.a.com", undefined]]);
     getCachedSubdomainsOK(["b.com", "foo"], [["b.com", undefined]]);
     getCachedGlobalOK(["foo"], true, undefined);
     getCachedSubdomainsOK(["b.a.com", "foo"], [["b.a.com", undefined]]);
   },
 
-  function populateViaRemoveByDomain() {
+  function* populateViaRemoveByDomain() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield set("b.a.com", "foo", 3);
     yield set("b.a.com", "bar", 4);
     yield cps.removeByDomain("a.com", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"],
                           [["a.com", undefined], ["b.a.com", 3]]);
     getCachedSubdomainsOK(["a.com", "bar"],
@@ -118,29 +118,29 @@ var tests = [
 
     yield setGlobal("foo", 7);
     yield setGlobal("bar", 8);
     yield cps.removeAllGlobals(null, makeCallback());
     getCachedGlobalOK(["foo"], true, undefined);
     getCachedGlobalOK(["bar"], true, undefined);
   },
 
-  function populateViaRemoveAllDomains() {
+  function* populateViaRemoveAllDomains() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield set("b.com", "foo", 3);
     yield set("b.com", "bar", 4);
     yield cps.removeAllDomains(null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", undefined]]);
     getCachedSubdomainsOK(["b.com", "foo"], [["b.com", undefined]]);
     getCachedSubdomainsOK(["b.com", "bar"], [["b.com", undefined]]);
   },
 
-  function populateViaRemoveByName() {
+  function* populateViaRemoveByName() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield cps.removeByName("foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
     getCachedGlobalOK(["foo"], true, undefined);
@@ -148,17 +148,17 @@ var tests = [
 
     yield cps.removeByName("bar", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", undefined]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", undefined]]);
     getCachedGlobalOK(["foo"], true, undefined);
     getCachedGlobalOK(["bar"], true, undefined);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -171,16 +171,16 @@ var tests = [
 
     getCachedSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     getCachedSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
     getCachedGlobalOK(["foo"], true, 3);
     getCachedGlobalOK(["bar"], true, 4);
     getCachedSubdomainsOK(["b.com", "foo"], [["b.com", 5]]);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.getCachedBySubdomainAndName(null, "foo", null));
     do_check_throws(() => cps.getCachedBySubdomainAndName("", "foo", null));
     do_check_throws(() => cps.getCachedBySubdomainAndName("a.com", "", null));
     do_check_throws(() => cps.getCachedBySubdomainAndName("a.com", null, null));
     yield true;
   },
 ];
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_getSubdomains.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_getSubdomains.js
@@ -3,47 +3,47 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function get_nonexistent() {
+  function* get_nonexistent() {
     yield getSubdomainsOK(["a.com", "foo"], []);
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     yield getSubdomainsOK(["http://a.com/huh", "foo"], [["a.com", 1]]);
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
 
     yield set("a.com", "bar", 2);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     yield getSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
 
     yield setGlobal("foo", 3);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     yield getSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1], ["b.a.com", 2]]);
     yield getSubdomainsOK(["b.a.com", "foo"], [["b.a.com", 2]]);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -52,17 +52,17 @@ var tests = [
     yield getSubdomainsOK(["a.com", "bar", context], [["a.com", 2]]);
     yield getSubdomainsOK(["b.com", "foo", context], [["b.com", 5]]);
 
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 1]]);
     yield getSubdomainsOK(["a.com", "bar"], [["a.com", 2]]);
     yield getSubdomainsOK(["b.com", "foo"], [["b.com", 5]]);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.getBySubdomainAndName(null, "foo", null, {}));
     do_check_throws(() => cps.getBySubdomainAndName("", "foo", null, {}));
     do_check_throws(() => cps.getBySubdomainAndName("a.com", "", null, {}));
     do_check_throws(() => cps.getBySubdomainAndName("a.com", null, null, {}));
     do_check_throws(() => cps.getBySubdomainAndName("a.com", "foo", null, null));
     yield true;
   },
 ];
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_migrationToSchema4.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_migrationToSchema4.js
@@ -50,17 +50,17 @@ function run_test() {
   runAsyncTests(tests, true);
 }
 
 
 // WARNING: Database will reset after every test. This limitation comes from
 // the fact that we ContentPrefService constructor is run only once per test file
 // and so migration will be run only once.
 var tests = [
-  function testMigration() {
+  function* testMigration() {
     // Test migrated db content.
     schemaVersionIs(4);
     let dbExpectedState = [
       [null, "zoom-setting", 0.1],
       ["bar.com", "zoom-setting", 0.3],
       ["foo.com", "zoom-setting", 0.5],
       ["foo.com", "dir-setting", "/download/dir"],
     ];
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_observers.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_observers.js
@@ -3,31 +3,31 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function observerForName_set() {
+  function* observerForName_set() {
     yield set("a.com", "foo", 1);
     let args = yield on("Set", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [["a.com", "foo", 1]]);
     observerArgsOK(args.null, [["a.com", "foo", 1]]);
     observerArgsOK(args.bar, []);
 
     yield setGlobal("foo", 2);
     args = yield on("Set", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [[null, "foo", 2]]);
     observerArgsOK(args.null, [[null, "foo", 2]]);
     observerArgsOK(args.bar, []);
   },
 
-  function observerForName_remove() {
+  function* observerForName_remove() {
     yield set("a.com", "foo", 1);
     yield setGlobal("foo", 2);
 
     yield cps.removeByDomainAndName("a.com", "bogus", null, makeCallback());
     let args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, []);
     observerArgsOK(args.null, []);
     observerArgsOK(args.bar, []);
@@ -40,17 +40,17 @@ var tests = [
 
     yield cps.removeGlobal("foo", null, makeCallback());
     args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [[null, "foo"]]);
     observerArgsOK(args.null, [[null, "foo"]]);
     observerArgsOK(args.bar, []);
   },
 
-  function observerForName_removeByDomain() {
+  function* observerForName_removeByDomain() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "bar", 2);
     yield setGlobal("foo", 3);
 
     yield cps.removeByDomain("bogus", null, makeCallback());
     let args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, []);
     observerArgsOK(args.null, []);
@@ -64,17 +64,17 @@ var tests = [
 
     yield cps.removeAllGlobals(null, makeCallback());
     args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [[null, "foo"]]);
     observerArgsOK(args.null, [[null, "foo"]]);
     observerArgsOK(args.bar, []);
   },
 
-  function observerForName_removeAllDomainsSince() {
+  function* observerForName_removeAllDomainsSince() {
     yield setWithDate("a.com", "foo", 1, 100);
     yield setWithDate("b.com", "foo", 2, 200);
     yield setWithDate("c.com", "foo", 3, 300);
 
     yield setWithDate("a.com", "bar", 1, 000);
     yield setWithDate("b.com", "bar", 2, 100);
     yield setWithDate("c.com", "bar", 3, 200);
     yield setGlobal("foo", 2);
@@ -83,29 +83,29 @@ var tests = [
 
     let args = yield on("Removed", ["foo", "bar", null]);
 
     observerArgsOK(args.foo, [["b.com", "foo"], ["c.com", "foo"]]);
     observerArgsOK(args.bar, [["c.com", "bar"]]);
     observerArgsOK(args.null, [["b.com", "foo"], ["c.com", "bar"], ["c.com", "foo"]]);
   },
 
-  function observerForName_removeAllDomains() {
+  function* observerForName_removeAllDomains() {
     yield set("a.com", "foo", 1);
     yield setGlobal("foo", 2);
     yield set("b.com", "bar", 3);
 
     yield cps.removeAllDomains(null, makeCallback());
     let args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [["a.com", "foo"]]);
     observerArgsOK(args.null, [["a.com", "foo"], ["b.com", "bar"]]);
     observerArgsOK(args.bar, [["b.com", "bar"]]);
   },
 
-  function observerForName_removeByName() {
+  function* observerForName_removeByName() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
 
     yield cps.removeByName("bogus", null, makeCallback());
     let args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, []);
     observerArgsOK(args.null, []);
@@ -113,17 +113,17 @@ var tests = [
 
     yield cps.removeByName("foo", null, makeCallback());
     args = yield on("Removed", ["foo", null, "bar"]);
     observerArgsOK(args.foo, [["a.com", "foo"], [null, "foo"]]);
     observerArgsOK(args.null, [["a.com", "foo"], [null, "foo"]]);
     observerArgsOK(args.bar, []);
   },
 
-  function removeObserverForName() {
+  function* removeObserverForName() {
     let args = yield on("Set", ["foo", null, "bar"], true);
 
     cps.removeObserverForName("foo", args.foo.observer);
     yield set("a.com", "foo", 1);
     yield wait();
     observerArgsOK(args.foo, []);
     observerArgsOK(args.null, [["a.com", "foo", 1]]);
     observerArgsOK(args.bar, []);
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_remove.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_remove.js
@@ -3,17 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     yield set("a.com", "foo", 1);
     yield setGlobal("foo", 2);
 
     yield cps.removeByDomainAndName("a.com", "bogus", null, makeCallback());
     yield dbOK([
       ["a.com", "foo", 1],
       [null, "foo", 2],
     ]);
@@ -40,30 +40,30 @@ var tests = [
     yield dbOK([
       ["a.com", "foo", 1],
       [null, "foo", 2],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getGlobalOK(["foo"], 2);
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     yield cps.removeByDomainAndName("a.com", "foo", null, makeCallback());
     yield dbOK([]);
     yield getOK(["a.com", "foo"], undefined);
 
     yield set("a.com", "foo", 2);
     yield cps.removeByDomainAndName("http://a.com/huh", "foo", null,
                                     makeCallback());
     yield dbOK([]);
     yield getOK(["a.com", "foo"], undefined);
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
 
     yield cps.removeByDomainAndName("a.com", "foo", null, makeCallback());
     yield dbOK([
       ["a.com", "bar", 2],
@@ -98,17 +98,17 @@ var tests = [
     yield dbOK([
     ]);
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], undefined);
     yield getGlobalOK(["bar"], undefined);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     yield cps.removeByDomainAndName("a.com", "foo", null, makeCallback());
     yield dbOK([
       ["b.a.com", "foo", 2],
     ]);
     yield getSubdomainsOK(["a.com", "foo"], [["b.a.com", 2]]);
     yield getSubdomainsOK(["b.a.com", "foo"], [["b.a.com", 2]]);
@@ -133,17 +133,17 @@ var tests = [
     yield cps.removeBySubdomainAndName("b.a.com", "foo", null, makeCallback());
     yield dbOK([
       ["a.com", "foo", 4],
     ]);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 4]]);
     yield getSubdomainsOK(["b.a.com", "foo"], []);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield setGlobal("qux", 5);
     yield set("b.com", "foo", 6);
     yield set("b.com", "bar", 7);
 
@@ -171,52 +171,52 @@ var tests = [
     yield getOK(["a.com", "bar"], 2);
     yield getGlobalOK(["foo"], undefined);
     yield getGlobalOK(["bar"], 4);
     yield getGlobalOK(["qux"], undefined);
     yield getOK(["b.com", "foo"], undefined);
     yield getOK(["b.com", "bar"], 7);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.removeByDomainAndName(null, "foo", null));
     do_check_throws(() => cps.removeByDomainAndName("", "foo", null));
     do_check_throws(() => cps.removeByDomainAndName("a.com", "foo", null,
                                                     "bogus"));
     do_check_throws(() => cps.removeBySubdomainAndName(null, "foo",
                                                        null));
     do_check_throws(() => cps.removeBySubdomainAndName("", "foo", null));
     do_check_throws(() => cps.removeBySubdomainAndName("a.com", "foo",
                                                        null, "bogus"));
     do_check_throws(() => cps.removeGlobal("", null));
     do_check_throws(() => cps.removeGlobal(null, null));
     do_check_throws(() => cps.removeGlobal("foo", null, "bogus"));
     yield true;
   },
 
-  function removeByDomainAndName_invalidateCache() {
+  function* removeByDomainAndName_invalidateCache() {
     yield set("a.com", "foo", 1);
     getCachedOK(["a.com", "foo"], true, 1);
     cps.removeByDomainAndName("a.com", "foo", null, makeCallback());
     getCachedOK(["a.com", "foo"], false);
     yield;
   },
 
-  function removeBySubdomainAndName_invalidateCache() {
+  function* removeBySubdomainAndName_invalidateCache() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     getCachedSubdomainsOK(["a.com", "foo"], [
       ["a.com", 1],
       ["b.a.com", 2],
     ]);
     cps.removeBySubdomainAndName("a.com", "foo", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], []);
     yield;
   },
 
-  function removeGlobal_invalidateCache() {
+  function* removeGlobal_invalidateCache() {
     yield setGlobal("foo", 1);
     getCachedGlobalOK(["foo"], true, 1);
     cps.removeGlobal("foo", null, makeCallback());
     getCachedGlobalOK(["foo"], false);
     yield;
   },
 ];
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_removeAllDomains.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_removeAllDomains.js
@@ -3,26 +3,26 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     yield setGlobal("foo", 1);
     yield cps.removeAllDomains(null, makeCallback());
     yield dbOK([
       [null, "foo", 1],
     ]);
     yield getGlobalOK(["foo"], 1);
   },
 
-  function domains() {
+  function* domains() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
     yield set("b.com", "bar", 6);
 
     yield cps.removeAllDomains(null, makeCallback());
@@ -33,17 +33,17 @@ var tests = [
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], undefined);
     yield getOK(["b.com", "bar"], undefined);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -61,22 +61,22 @@ var tests = [
 
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], undefined);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.removeAllDomains(null, "bogus"));
     yield true;
   },
 
-  function invalidateCache() {
+  function* invalidateCache() {
     yield set("a.com", "foo", 1);
     yield set("b.com", "bar", 2);
     yield setGlobal("baz", 3);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["b.com", "bar"], true, 2);
     getCachedGlobalOK(["baz"], true, 3);
     cps.removeAllDomains(null, makeCallback());
     getCachedOK(["a.com", "foo"], false);
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_removeAllDomainsSince.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_removeAllDomainsSince.js
@@ -3,23 +3,23 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     yield setGlobal("foo", 1);
     yield cps.removeAllDomainsSince(0, null, makeCallback());
     yield getGlobalOK(["foo"], 1);
   },
 
-  function domainsAll() {
+  function* domainsAll() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
     yield set("b.com", "bar", 6);
 
     yield cps.removeAllDomainsSince(0, null, makeCallback());
@@ -30,17 +30,17 @@ var tests = [
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], undefined);
     yield getOK(["b.com", "bar"], undefined);
   },
 
-  function domainsWithDate() {
+  function* domainsWithDate() {
     yield setWithDate("a.com", "foobar", 0, 0);
     yield setWithDate("a.com", "foo", 1, 1000);
     yield setWithDate("a.com", "bar", 2, 4000);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield setWithDate("b.com", "foo", 5, 2000);
     yield setWithDate("b.com", "bar", 6, 3000);
     yield setWithDate("b.com", "foobar", 7, 1000);
@@ -50,17 +50,17 @@ var tests = [
       ["a.com", "foobar", 0],
       ["a.com", "foo", 1],
       [null, "foo", 3],
       [null, "bar", 4],
       ["b.com", "foobar", 7],
     ]);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -78,22 +78,22 @@ var tests = [
 
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], undefined);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.removeAllDomainsSince(null, "bogus"));
     yield true;
   },
 
-  function invalidateCache() {
+  function* invalidateCache() {
     yield setWithDate("a.com", "foobar", 0, 0);
     yield setWithDate("a.com", "foo", 1, 1000);
     yield setWithDate("a.com", "bar", 2, 4000);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield setWithDate("b.com", "foo", 5, 2000);
     yield setWithDate("b.com", "bar", 6, 3000);
     yield setWithDate("b.com", "foobar", 7, 1000);
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_removeByDomain.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_removeByDomain.js
@@ -3,17 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     yield set("a.com", "foo", 1);
     yield setGlobal("foo", 2);
 
     yield cps.removeByDomain("bogus", null, makeCallback());
     yield dbOK([
       ["a.com", "foo", 1],
       [null, "foo", 2],
     ]);
@@ -24,29 +24,29 @@ var tests = [
     yield dbOK([
       ["a.com", "foo", 1],
       [null, "foo", 2],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getGlobalOK(["foo"], 2);
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     yield cps.removeByDomain("a.com", null, makeCallback());
     yield dbOK([]);
     yield getOK(["a.com", "foo"], undefined);
 
     yield set("a.com", "foo", 2);
     yield cps.removeByDomain("http://a.com/huh", null, makeCallback());
     yield dbOK([]);
     yield getOK(["a.com", "foo"], undefined);
   },
 
-  function domains() {
+  function* domains() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
     yield set("b.com", "bar", 6);
 
     yield cps.removeByDomain("a.com", null, makeCallback());
@@ -81,17 +81,17 @@ var tests = [
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], undefined);
     yield getGlobalOK(["bar"], undefined);
     yield getOK(["b.com", "foo"], undefined);
     yield getOK(["b.com", "bar"], undefined);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     yield cps.removeByDomain("a.com", null, makeCallback());
     yield dbOK([
       ["b.a.com", "foo", 2],
     ]);
     yield getSubdomainsOK(["a.com", "foo"], [["b.a.com", 2]]);
     yield getSubdomainsOK(["b.a.com", "foo"], [["b.a.com", 2]]);
@@ -116,17 +116,17 @@ var tests = [
     yield cps.removeBySubdomain("b.a.com", null, makeCallback());
     yield dbOK([
       ["a.com", "foo", 4],
     ]);
     yield getSubdomainsOK(["a.com", "foo"], [["a.com", 4]]);
     yield getSubdomainsOK(["b.a.com", "foo"], []);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -144,51 +144,51 @@ var tests = [
 
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], undefined);
     yield getGlobalOK(["bar"], undefined);
     yield getOK(["b.com", "foo"], 5);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.removeByDomain(null, null));
     do_check_throws(() => cps.removeByDomain("", null));
     do_check_throws(() => cps.removeByDomain("a.com", null, "bogus"));
     do_check_throws(() => cps.removeBySubdomain(null, null));
     do_check_throws(() => cps.removeBySubdomain("", null));
     do_check_throws(() => cps.removeBySubdomain("a.com", null, "bogus"));
     do_check_throws(() => cps.removeAllGlobals(null, "bogus"));
     yield true;
   },
 
-  function removeByDomain_invalidateCache() {
+  function* removeByDomain_invalidateCache() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["a.com", "bar"], true, 2);
     cps.removeByDomain("a.com", null, makeCallback());
     getCachedOK(["a.com", "foo"], false);
     getCachedOK(["a.com", "bar"], false);
     yield;
   },
 
-  function removeBySubdomain_invalidateCache() {
+  function* removeBySubdomain_invalidateCache() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     getCachedSubdomainsOK(["a.com", "foo"], [
       ["a.com", 1],
       ["b.a.com", 2],
     ]);
     cps.removeBySubdomain("a.com", null, makeCallback());
     getCachedSubdomainsOK(["a.com", "foo"], []);
     yield;
   },
 
-  function removeAllGlobals_invalidateCache() {
+  function* removeAllGlobals_invalidateCache() {
     yield setGlobal("foo", 1);
     yield setGlobal("bar", 2);
     getCachedGlobalOK(["foo"], true, 1);
     getCachedGlobalOK(["bar"], true, 2);
     cps.removeAllGlobals(null, makeCallback());
     getCachedGlobalOK(["foo"], false);
     getCachedGlobalOK(["bar"], false);
     yield;
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_removeByName.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_removeByName.js
@@ -3,30 +3,30 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function nonexistent() {
+  function* nonexistent() {
     yield set("a.com", "foo", 1);
     yield setGlobal("foo", 2);
 
     yield cps.removeByName("bogus", null, makeCallback());
     yield dbOK([
       ["a.com", "foo", 1],
       [null, "foo", 2],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getGlobalOK(["foo"], 2);
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
     yield set("b.com", "bar", 6);
 
     yield cps.removeByName("foo", null, makeCallback());
@@ -38,17 +38,17 @@ var tests = [
     yield getOK(["a.com", "foo"], undefined);
     yield getOK(["a.com", "bar"], 2);
     yield getGlobalOK(["foo"], undefined);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], undefined);
     yield getOK(["b.com", "bar"], 6);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
     yield set("b.com", "bar", 6);
 
     let context = { usePrivateBrowsing: true };
@@ -71,24 +71,24 @@ var tests = [
     yield getOK(["a.com", "foo"], 1);
     yield getOK(["a.com", "bar"], undefined);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], undefined);
     yield getOK(["b.com", "foo"], 5);
     yield getOK(["b.com", "bar"], undefined);
   },
 
-  function erroneous() {
+  function* erroneous() {
     do_check_throws(() => cps.removeByName("", null));
     do_check_throws(() => cps.removeByName(null, null));
     do_check_throws(() => cps.removeByName("foo", null, "bogus"));
     yield true;
   },
 
-  function invalidateCache() {
+  function* invalidateCache() {
     yield set("a.com", "foo", 1);
     yield set("b.com", "foo", 2);
     getCachedOK(["a.com", "foo"], true, 1);
     getCachedOK(["b.com", "foo"], true, 2);
     cps.removeByName("foo", null, makeCallback());
     getCachedOK(["a.com", "foo"], false);
     getCachedOK(["b.com", "foo"], false);
     yield;
--- a/toolkit/components/contentprefs/tests/unit_cps2/test_setGet.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_setGet.js
@@ -3,38 +3,38 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function run_test() {
   runAsyncTests(tests);
 }
 
 var tests = [
 
-  function get_nonexistent() {
+  function* get_nonexistent() {
     yield getOK(["a.com", "foo"], undefined);
     yield getGlobalOK(["foo"], undefined);
   },
 
-  function isomorphicDomains() {
+  function* isomorphicDomains() {
     yield set("a.com", "foo", 1);
     yield dbOK([
       ["a.com", "foo", 1],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getOK(["http://a.com/huh", "foo"], 1, "a.com");
 
     yield set("http://a.com/huh", "foo", 2);
     yield dbOK([
       ["a.com", "foo", 2],
     ]);
     yield getOK(["a.com", "foo"], 2);
     yield getOK(["http://a.com/yeah", "foo"], 2, "a.com");
   },
 
-  function names() {
+  function* names() {
     yield set("a.com", "foo", 1);
     yield dbOK([
       ["a.com", "foo", 1],
     ]);
     yield getOK(["a.com", "foo"], 1);
 
     yield set("a.com", "bar", 2);
     yield dbOK([
@@ -62,28 +62,28 @@ var tests = [
       [null, "bar", 4],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getOK(["a.com", "bar"], 2);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
   },
 
-  function subdomains() {
+  function* subdomains() {
     yield set("a.com", "foo", 1);
     yield set("b.a.com", "foo", 2);
     yield dbOK([
       ["a.com", "foo", 1],
       ["b.a.com", "foo", 2],
     ]);
     yield getOK(["a.com", "foo"], 1);
     yield getOK(["b.a.com", "foo"], 2);
   },
 
-  function privateBrowsing() {
+  function* privateBrowsing() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield setGlobal("foo", 3);
     yield setGlobal("bar", 4);
     yield set("b.com", "foo", 5);
 
     let context = { usePrivateBrowsing: true };
     yield set("a.com", "foo", 6, context);
@@ -103,43 +103,43 @@ var tests = [
 
     yield getOK(["a.com", "foo"], 1);
     yield getOK(["a.com", "bar"], 2);
     yield getGlobalOK(["foo"], 3);
     yield getGlobalOK(["bar"], 4);
     yield getOK(["b.com", "foo"], 5);
   },
 
-  function set_erroneous() {
+  function* set_erroneous() {
     do_check_throws(() => cps.set(null, "foo", 1, null));
     do_check_throws(() => cps.set("", "foo", 1, null));
     do_check_throws(() => cps.set("a.com", "", 1, null));
     do_check_throws(() => cps.set("a.com", null, 1, null));
     do_check_throws(() => cps.set("a.com", "foo", undefined, null));
     do_check_throws(() => cps.set("a.com", "foo", 1, null, "bogus"));
     do_check_throws(() => cps.setGlobal("", 1, null));
     do_check_throws(() => cps.setGlobal(null, 1, null));
     do_check_throws(() => cps.setGlobal("foo", undefined, null));
     do_check_throws(() => cps.setGlobal("foo", 1, null, "bogus"));
     yield true;
   },
 
-  function get_erroneous() {
+  function* get_erroneous() {
     do_check_throws(() => cps.getByDomainAndName(null, "foo", null, {}));
     do_check_throws(() => cps.getByDomainAndName("", "foo", null, {}));
     do_check_throws(() => cps.getByDomainAndName("a.com", "", null, {}));
     do_check_throws(() => cps.getByDomainAndName("a.com", null, null, {}));
     do_check_throws(() => cps.getByDomainAndName("a.com", "foo", null, null));
     do_check_throws(() => cps.getGlobal("", null, {}));
     do_check_throws(() => cps.getGlobal(null, null, {}));
     do_check_throws(() => cps.getGlobal("foo", null, null));
     yield true;
   },
 
-  function set_invalidateCache() {
+  function* set_invalidateCache() {
     // (1) Set a pref and wait for it to finish.
     yield set("a.com", "foo", 1);
 
     // (2) It should be cached.
     getCachedOK(["a.com", "foo"], true, 1);
 
     // (3) Set the pref to a new value but don't wait for it to finish.
     cps.set("a.com", "foo", 2, null, {
@@ -164,17 +164,17 @@ var tests = [
         do_check_eq(fetchedPref.value, 2);
         next();
       },
     });
 
     yield;
   },
 
-  function get_nameOnly() {
+  function* get_nameOnly() {
     yield set("a.com", "foo", 1);
     yield set("a.com", "bar", 2);
     yield set("b.com", "foo", 3);
     yield setGlobal("foo", 4);
 
     yield getOKEx("getByName", ["foo", undefined], [
       {"domain": "a.com", "name": "foo", "value": 1},
       {"domain": "b.com", "name": "foo", "value": 3},
@@ -186,17 +186,17 @@ var tests = [
 
     yield getOKEx("getByName", ["foo", context], [
       {"domain": "a.com", "name": "foo", "value": 1},
       {"domain": null, "name": "foo", "value": 4},
       {"domain": "b.com", "name": "foo", "value": 5}
     ]);
   },
 
-  function setSetsCurrentDate() {
+  function* setSetsCurrentDate() {
     // Because Date.now() is not guaranteed to be monotonically increasing
     // we just do here rough sanity check with one minute tolerance.
     const MINUTE = 60 * 1000;
     let now = Date.now();
     let start = now - MINUTE;
     let end = now + MINUTE;
     yield set("a.com", "foo", 1);
     let timestamp = yield getDate("a.com", "foo");
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -265,29 +265,31 @@ this.CrashManager.prototype = Object.fre
 
               case this.EVENT_FILE_ERROR_UNKNOWN_EVENT:
                 break;
 
               default:
                 Cu.reportError("Unhandled crash event file return code. Please " +
                                "file a bug: " + result);
             }
-          } catch (ex if ex instanceof OS.File.Error) {
-            this._log.warn("I/O error reading " + entry.path + ": " +
-                           CommonUtils.exceptionStr(ex));
           } catch (ex) {
-            // We should never encounter an exception. This likely represents
-            // a coding error because all errors should be detected and
-            // converted to return codes.
-            //
-            // If we get here, report the error and delete the source file
-            // so we don't see it again.
-            Cu.reportError("Exception when processing crash event file: " +
-                           CommonUtils.exceptionStr(ex));
-            deletePaths.push(entry.path);
+            if (ex instanceof OS.File.Error) {
+              this._log.warn("I/O error reading " + entry.path + ": " +
+                             CommonUtils.exceptionStr(ex));
+            } else {
+              // We should never encounter an exception. This likely represents
+              // a coding error because all errors should be detected and
+              // converted to return codes.
+              //
+              // If we get here, report the error and delete the source file
+              // so we don't see it again.
+              Cu.reportError("Exception when processing crash event file: " +
+                             CommonUtils.exceptionStr(ex));
+              deletePaths.push(entry.path);
+            }
           }
         }
 
         if (needsSave) {
           let store = yield this._getStore();
           yield store.save();
         }
 
@@ -589,18 +591,21 @@ this.CrashManager.prototype = Object.fre
    *   path -- String filename
    *   id -- regexp.match()[1] (likely the crash ID)
    *   date -- Date mtime of the file
    */
   _getDirectoryEntries: function (path, re) {
     return Task.spawn(function* () {
       try {
         yield OS.File.stat(path);
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-          return [];
+      } catch (ex) {
+        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+          throw ex;
+        }
+        return [];
       }
 
       let it = new OS.File.DirectoryIterator(path);
       let entries = [];
 
       try {
         yield it.forEach((entry, index, it) => {
           if (entry.isDir) {
@@ -850,26 +855,27 @@ CrashStore.prototype = Object.freeze({
 
             // If we encountered more data in the payload than what the
             // data structure says, use the proper value.
             count = Math.max(count, actualCounts.get(key));
 
             this._countsByDay.get(day).set(type, count);
           }
         }
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
+      } catch (ex) {
         // Missing files (first use) are allowed.
-      } catch (ex) {
-        // If we can't load for any reason, mark a corrupt date in the instance
-        // and swallow the error.
-        //
-        // The marking of a corrupted file is intentionally not persisted to
-        // disk yet. Instead, we wait until the next save(). This is to give
-        // non-permanent failures the opportunity to recover on their own.
-        this._data.corruptDate = new Date();
+        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+          // If we can't load for any reason, mark a corrupt date in the instance
+          // and swallow the error.
+          //
+          // The marking of a corrupted file is intentionally not persisted to
+          // disk yet. Instead, we wait until the next save(). This is to give
+          // non-permanent failures the opportunity to recover on their own.
+          this._data.corruptDate = new Date();
+        }
       }
     }.bind(this));
   },
 
   /**
    * Save data to disk.
    *
    * @return Promise<null>
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -91,17 +91,17 @@ add_task(function* test_submitted_dumps(
 
   let entries = yield m.submittedDumps();
   Assert.equal(entries.length, COUNT, "proper number detected.");
 
   let hrID = yield m.createDummyDump(true, new Date(), true);
   entries = yield m.submittedDumps();
   Assert.equal(entries.length, COUNT + 1, "hr- in filename detected.");
 
-  let gotIDs = new Set([e.id for (e of entries)]);
+  let gotIDs = new Set(entries.map(e => e.id));
   Assert.ok(gotIDs.has(hrID));
 });
 
 // The store should expire after inactivity.
 add_task(function* test_store_expires() {
   let m = yield getManager();
 
   Object.defineProperty(m, "STORE_EXPIRATION_MS", {
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
@@ -52,17 +52,17 @@ function run_test() {
   run_next_test();
 }
 
 add_task(function* test_constructor() {
   let s = new CrashStore("/some/path");
   Assert.ok(s instanceof CrashStore);
 });
 
-add_task(function test_add_crash() {
+add_task(function* test_add_crash() {
   let s = yield getStore();
 
   Assert.equal(s.crashesCount, 0);
   let d = new Date(Date.now() - 5000);
   Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "id1", d));
 
   Assert.equal(s.crashesCount, 1);
 
@@ -74,26 +74,26 @@ add_task(function test_add_crash() {
   Assert.equal(c.crashDate.getTime(), d.getTime(), "Date set.");
 
   Assert.ok(
     s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "id2", new Date())
   );
   Assert.equal(s.crashesCount, 2);
 });
 
-add_task(function test_reset() {
+add_task(function* test_reset() {
   let s = yield getStore();
 
   Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "id1", DUMMY_DATE));
   Assert.equal(s.crashes.length, 1);
   s.reset();
   Assert.equal(s.crashes.length, 0);
 });
 
-add_task(function test_save_load() {
+add_task(function* test_save_load() {
   let s = yield getStore();
 
   yield s.save();
 
   let d1 = new Date();
   let d2 = new Date(d1.getTime() - 10000);
   Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "id1", d1));
   Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "id2", d2));
@@ -115,17 +115,17 @@ add_task(function test_save_load() {
   Assert.ok(!!c.submissions);
   let submission = c.submissions.get("sub1");
   Assert.ok(!!submission);
   Assert.equal(submission.requestDate.getTime(), d1.getTime());
   Assert.equal(submission.responseDate.getTime(), d2.getTime());
   Assert.equal(submission.result, SUBMISSION_RESULT_OK);
 });
 
-add_task(function test_corrupt_json() {
+add_task(function* test_corrupt_json() {
   let s = yield getStore();
 
   let buffer = new TextEncoder().encode("{bad: json-file");
   yield OS.File.writeAtomic(s._storePath, buffer, {compression: "lz4"});
 
   yield s.load();
   Assert.ok(s.corruptDate, "Corrupt date is defined.");
 
--- a/toolkit/components/crashmonitor/CrashMonitor.jsm
+++ b/toolkit/components/crashmonitor/CrashMonitor.jsm
@@ -93,17 +93,20 @@ var CrashMonitorInternal = {
    *
    * @return {Promise} A promise that resolves/rejects once loading is complete
    */
   loadPreviousCheckpoints: function () {
     this.previousCheckpoints = Task.spawn(function*() {
       let data;
       try {
         data = yield OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" });
-      } catch (ex if ex instanceof OS.File.Error) {
+      } catch (ex) {
+        if (!(ex instanceof OS.File.Error)) {
+          throw ex;
+        }
         if (!ex.becauseNoSuchFile) {
           Cu.reportError("Error while loading crash monitor data: " + ex.toString());
         }
 
         return null;
       }
 
       let notifications;
@@ -182,17 +185,17 @@ this.CrashMonitor = {
    *
    * Update checkpoint file for every new notification received.
    */
   observe: function (aSubject, aTopic, aData) {
     if (!(aTopic in CrashMonitorInternal.checkpoints)) {
       // If this is the first time this notification is received,
       // remember it and write it to file
       CrashMonitorInternal.checkpoints[aTopic] = true;
-      Task.spawn(function() {
+      Task.spawn(function* () {
         try {
           let data = JSON.stringify(CrashMonitorInternal.checkpoints);
 
           /* Write to the checkpoint file asynchronously, off the main
            * thread, for performance reasons. Note that this means
            * that there's not a 100% guarantee that the file will be
            * written by the time the notification completes. The
            * exception is profile-before-change which has a shutdown
--- a/toolkit/components/crashmonitor/test/unit/test_invalid_file.js
+++ b/toolkit/components/crashmonitor/test/unit/test_invalid_file.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Test with sessionCheckpoints.json containing invalid data
  */
-add_task(function test_invalid_file() {
+add_task(function* test_invalid_file() {
   // Write bogus data to checkpoint file
   let data = "1234";
   yield OS.File.writeAtomic(sessionCheckpointsPath, data,
                             {tmpPath: sessionCheckpointsPath + ".tmp"});
 
   // An invalid file will cause |init| to return null
   let status = yield CrashMonitor.init();
   do_check_true(status === null ? true : false);
--- a/toolkit/components/crashmonitor/test/unit/test_invalid_json.js
+++ b/toolkit/components/crashmonitor/test/unit/test_invalid_json.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Test with sessionCheckpoints.json containing invalid JSON data
  */
-add_task(function test_invalid_file() {
+add_task(function* test_invalid_file() {
   // Write bogus data to checkpoint file
   let data = "[}";
   yield OS.File.writeAtomic(sessionCheckpointsPath, data,
                             {tmpPath: sessionCheckpointsPath + ".tmp"});
 
   CrashMonitor.init();
   let checkpoints = yield CrashMonitor.previousCheckpoints;
   do_check_eq(checkpoints, null);
--- a/toolkit/components/crashmonitor/test/unit/test_missing_file.js
+++ b/toolkit/components/crashmonitor/test/unit/test_missing_file.js
@@ -1,13 +1,13 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Test with non-existing sessionCheckpoints.json
  */
-add_task(function test_missing_file() {
+add_task(function* test_missing_file() {
   CrashMonitor.init();
   let checkpoints = yield CrashMonitor.previousCheckpoints;
   do_check_eq(checkpoints, null);
 });
--- a/toolkit/components/crashmonitor/test/unit/test_valid_file.js
+++ b/toolkit/components/crashmonitor/test/unit/test_valid_file.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Test with sessionCheckpoints.json containing valid data
  */
-add_task(function test_valid_file() {
+add_task(function* test_valid_file() {
   // Write valid data to checkpoint file
   let data = JSON.stringify({"final-ui-startup": true});
   yield OS.File.writeAtomic(sessionCheckpointsPath, data,
                             {tmpPath: sessionCheckpointsPath + ".tmp"});
 
   CrashMonitor.init();
   let checkpoints = yield CrashMonitor.previousCheckpoints;
 
--- a/toolkit/components/downloads/test/schema_migration/test_migration_to_9.js
+++ b/toolkit/components/downloads/test/schema_migration/test_migration_to_9.js
@@ -66,9 +66,9 @@ function run_test()
   do_check_eq(data[i], stmt.getInt32(i++));
   do_check_eq(data[i], stmt.getInt32(i++));
   do_check_true(/^[a-zA-Z0-9\-_]{12}$/.test(stmt.getString(i++)));
 
   stmt.reset();
   stmt.finalize();
 
   cleanup();
-}
\ No newline at end of file
+}
--- a/toolkit/components/downloads/test/unit/tail_download_manager.js
+++ b/toolkit/components/downloads/test/unit/tail_download_manager.js
@@ -8,17 +8,17 @@
  * Provides infrastructure for automated download components tests.
  */
 
 "use strict";
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Termination functions common to all tests
 
-add_task(function test_common_terminate()
+add_task(function* test_common_terminate()
 {
   // Stop the HTTP server.  We must do this inside a task in "tail.js" until the
   // xpcshell testing framework supports asynchronous termination functions.
   let deferred = Promise.defer();
   gHttpServer.stop(deferred.resolve);
   yield deferred.promise;
 });
 
--- a/toolkit/components/downloads/test/unit/test_app_rep.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep.js
@@ -154,17 +154,19 @@ add_test(function test_nullSourceURI() {
 add_test(function test_nullCallback() {
   let counts = get_telemetry_counts();
   try {
     gAppRep.queryReputation({
       sourceURI: createURI("http://example.com"),
       fileSize: 12,
     }, null);
     do_throw("Callback cannot be null");
-  } catch (ex if ex.result == Cr.NS_ERROR_INVALID_POINTER) {
+  } catch (ex) {
+    if (ex.result != Cr.NS_ERROR_INVALID_POINTER)
+      throw ex;
     // We don't even increment the count here, because there's no callback.
     check_telemetry(counts.total, counts.shouldBlock, counts.listCounts);
     run_next_test();
   }
 });
 
 // Set up the local whitelist.
 add_test(function test_local_list() {
--- a/toolkit/components/downloads/test/unit/test_app_rep_maclinux.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep_maclinux.js
@@ -205,61 +205,61 @@ function promiseQueryReputation(query, e
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_eq(aShouldBlock, expectedShouldBlock);
     deferred.resolve(true);
   }
   gAppRep.queryReputation(query, onComplete);
   return deferred.promise;
 }
 
-add_task(function()
+add_task(function* ()
 {
   // Wait for Safebrowsing local list updates to complete.
   yield waitForUpdates();
 });
 
-add_task(function test_blocked_binary()
+add_task(function* test_blocked_binary()
 {
   // We should reach the remote server for a verdict.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   // evil.com should return a malware verdict from the remote server.
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 suggestedFileName: "noop.bat",
                                 fileSize: 12}, true);
 });
 
-add_task(function test_non_binary()
+add_task(function* test_non_binary()
 {
   // We should not reach the remote server for a verdict for non-binary files.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 suggestedFileName: "noop.txt",
                                 fileSize: 12}, false);
 });
 
-add_task(function test_good_binary()
+add_task(function* test_good_binary()
 {
   // We should reach the remote server for a verdict.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   // mozilla.com should return a not-guilty verdict from the remote server.
   yield promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
                                 suggestedFileName: "noop.bat",
                                 fileSize: 12}, false);
 });
 
-add_task(function test_disabled()
+add_task(function* test_disabled()
 {
   // Explicitly disable remote checks
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              false);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
   let query = {sourceURI: createURI("http://example.com"),
                suggestedFileName: "noop.bat",
@@ -271,17 +271,17 @@ add_task(function test_disabled()
       do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
       do_check_false(aShouldBlock);
       deferred.resolve(true);
     }
   );
   yield deferred.promise;
 });
 
-add_task(function test_disabled_through_lists()
+add_task(function* test_disabled_through_lists()
 {
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              false);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
   let query = {sourceURI: createURI("http://example.com"),
                suggestedFileName: "noop.bat",
@@ -292,12 +292,12 @@ add_task(function test_disabled_through_
       // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
       do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
       do_check_false(aShouldBlock);
       deferred.resolve(true);
     }
   );
   yield deferred.promise;
 });
-add_task(function test_teardown()
+add_task(function* test_teardown()
 {
   gStillRunning = false;
 });
--- a/toolkit/components/downloads/test/unit/test_app_rep_windows.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep_windows.js
@@ -160,17 +160,17 @@ function registerTableUpdate(aTable, aFi
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 function run_test()
 {
   run_next_test();
 }
 
-add_task(function test_setup()
+add_task(function* test_setup()
 {
   // Wait 10 minutes, that is half of the external xpcshell timeout.
   do_timeout(10 * 60 * 1000, function() {
     if (gStillRunning) {
       do_throw("Test timed out.");
     }
   });
   // Set up a local HTTP server to return bad verdicts.
@@ -305,23 +305,23 @@ function promiseQueryReputation(query, e
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_eq(aShouldBlock, expectedShouldBlock);
     deferred.resolve(true);
   }
   gAppRep.queryReputation(query, onComplete);
   return deferred.promise;
 }
 
-add_task(function()
+add_task(function* ()
 {
   // Wait for Safebrowsing local list updates to complete.
   yield waitForUpdates();
 });
 
-add_task(function test_signature_whitelists()
+add_task(function* test_signature_whitelists()
 {
   // We should never get to the remote server.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
 
   // Use BackgroundFileSaver to extract the signature on Windows.
@@ -342,55 +342,55 @@ add_task(function test_signature_whiteli
 
   // evil.com is not on the allowlist, but this binary is signed by an entity
   // whose certificate information is on the allowlist.
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 signatureInfo: saver.signatureInfo,
                                 fileSize: 12}, false);
 });
 
-add_task(function test_blocked_binary()
+add_task(function* test_blocked_binary()
 {
   // We should reach the remote server for a verdict.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   // evil.com should return a malware verdict from the remote server.
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 suggestedFileName: "noop.bat",
                                 fileSize: 12}, true);
 });
 
-add_task(function test_non_binary()
+add_task(function* test_non_binary()
 {
   // We should not reach the remote server for a verdict for non-binary files.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 suggestedFileName: "noop.txt",
                                 fileSize: 12}, false);
 });
 
-add_task(function test_good_binary()
+add_task(function* test_good_binary()
 {
   // We should reach the remote server for a verdict.
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              true);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   // mozilla.com should return a not-guilty verdict from the remote server.
   yield promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
                                 suggestedFileName: "noop.bat",
                                 fileSize: 12}, false);
 });
 
-add_task(function test_disabled()
+add_task(function* test_disabled()
 {
   // Explicitly disable remote checks
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              false);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
   let query = {sourceURI: createURI("http://example.com"),
                suggestedFileName: "noop.bat",
@@ -402,17 +402,17 @@ add_task(function test_disabled()
       do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
       do_check_false(aShouldBlock);
       deferred.resolve(true);
     }
   );
   yield deferred.promise;
 });
 
-add_task(function test_disabled_through_lists()
+add_task(function* test_disabled_through_lists()
 {
   Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.enabled",
                              false);
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
   let query = {sourceURI: createURI("http://example.com"),
                suggestedFileName: "noop.bat",
@@ -423,12 +423,12 @@ add_task(function test_disabled_through_
       // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
       do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
       do_check_false(aShouldBlock);
       deferred.resolve(true);
     }
   );
   yield deferred.promise;
 });
-add_task(function test_teardown()
+add_task(function* test_teardown()
 {
   gStillRunning = false;
 });
--- a/toolkit/components/downloads/test/unit/test_download_samename.js
+++ b/toolkit/components/downloads/test/unit/test_download_samename.js
@@ -94,17 +94,19 @@ function runNextTest()
 {
   if (currentTest == tests.length) {
     for (var file of DownloadListener.prevFiles) {
       try {
         file.remove(false);
       } catch (ex) {
         try {
           do_report_unexpected_exception(ex, "while removing " + file.path);
-        } catch (ex if ex == Components.results.NS_ERROR_ABORT) {
+        } catch (ex) {
+          if (ex != Components.results.NS_ERROR_ABORT)
+            throw ex;
           /* swallow */
         }
       }
     }
     httpserver.stop(do_test_finished);
     return;
   }
   let set = DownloadListener.set = tests[currentTest];
--- a/toolkit/components/downloads/test/unit/test_history_expiration.js
+++ b/toolkit/components/downloads/test/unit/test_history_expiration.js
@@ -28,17 +28,17 @@ function run_test()
 {
   if (oldDownloadManagerDisabled()) {
     return;
   }
 
   run_next_test();
 }
 
-add_task(function test_execute()
+add_task(function* test_execute()
 {
   // Like the code, we check to see if nav-history-service exists
   // (i.e MOZ_PLACES is enabled), so that we don't run this test if it doesn't.
   if (!("@mozilla.org/browser/nav-history-service;1" in Cc))
     return;
 
   let dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -345,17 +345,17 @@ var GlobalManager = {
     let context = new ExtensionPage(extension, {type: "tab", contentWindow, uri, docShell, incognito});
     inject(extension, context);
 
     let eventHandler = docShell.chromeEventHandler;
     let listener = event => {
       if (event.target != docShell.contentViewer.DOMDocument) {
         return;
       }
-      eventHandler.removeEventListener("unload", listener);
+      eventHandler.removeEventListener("unload", listener, true);
       context.unload();
     };
     eventHandler.addEventListener("unload", listener, true);
   },
 };
 
 // Represents the data contained in an extension, contained either
 // in a directory or a zip file, which may or may not be installed.
@@ -576,17 +576,17 @@ ExtensionData.prototype = {
     yield Promise.all(Array.from(locales.keys(),
                                  locale => this.readLocaleFile(locale)));
 
     let defaultLocale = this.defaultLocale;
     if (defaultLocale) {
       if (!locales.has(defaultLocale)) {
         this.manifestError('Value for "default_locale" property must correspond to ' +
                            'a directory in "_locales/". Not found: ' +
-                           JSON.stringify(`_locales/${default_locale}/`));
+                           JSON.stringify(`_locales/${this.manifest.default_locale}/`));
       }
     } else if (locales.size) {
       this.manifestError('The "default_locale" property is required when a ' +
                          '"_locales/" directory is present.');
     }
 
     return this.localeData.messages;
   }),
--- a/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
@@ -100,16 +100,17 @@ add_task(function* testAsyncConvert() {
   equal(result, "Foo <localized-xxx> bar <localized-yyy> baz");
 });
 
 
 // Test that attempting to initialize a converter with the URI of a
 // nonexistent WebExtension fails.
 add_task(function* testInvalidUUID() {
   let uri = NetUtil.newURI("moz-extension://eb4f3be8-41c9-4970-aa6d-b84d1ecc02b2/file.css");
+  let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz");
 
   Assert.throws(() => {
     convService.convert(stream, FROM_TYPE, TO_TYPE, uri);
   });
 
   Assert.throws(() => {
     let listener = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]) };
 
--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -428,17 +428,17 @@ this.Download.prototype = {
 
       if (changeMade) {
         this._notifyChange();
       }
     }
 
     // Now that we stored the promise in the download object, we can start the
     // task that will actually execute the download.
-    deferAttempt.resolve(Task.spawn(function task_D_start() {
+    deferAttempt.resolve(Task.spawn(function* task_D_start() {
       // Wait upon any pending operation before restarting.
       if (this._promiseCanceled) {
         yield this._promiseCanceled;
       }
       if (this._promiseRemovePartialData) {
         try {
           yield this._promiseRemovePartialData;
         } catch (ex) {
@@ -840,17 +840,17 @@ this.Download.prototype = {
     let promiseRemovePartialData = this._promiseRemovePartialData;
 
     if (!promiseRemovePartialData) {
       let deferRemovePartialData = Promise.defer();
       promiseRemovePartialData = deferRemovePartialData.promise;
       this._promiseRemovePartialData = promiseRemovePartialData;
 
       deferRemovePartialData.resolve(
-        Task.spawn(function task_D_removePartialData() {
+        Task.spawn(function* task_D_removePartialData() {
           try {
             // Wait upon any pending cancellation request.
             if (this._promiseCanceled) {
               yield this._promiseCanceled;
             }
             // Ask the saver object to remove any partial data.
             yield this.saver.removePartialData();
             // For completeness, clear the number of bytes transferred.
@@ -903,17 +903,17 @@ this.Download.prototype = {
    * moved or deleted the target file or its associated ".part" file.
    *
    * @return {Promise}
    * @resolves When the operation has completed.
    * @rejects Never.
    */
   refresh: function ()
   {
-    return Task.spawn(function () {
+    return Task.spawn(function* () {
       if (!this.stopped || this._finalized) {
         return;
       }
 
       if (this.succeeded) {
         let oldExists = this.target.exists;
         let oldSize = this.target.size;
         yield this.target.refresh();
@@ -937,17 +937,20 @@ this.Download.prototype = {
 
           // Update the bytes transferred and the related progress properties.
           this.currentBytes = stat.size;
           if (this.totalBytes > 0) {
             this.hasProgress = true;
             this.progress = Math.floor(this.currentBytes /
                                            this.totalBytes * 100);
           }
-        } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
+        } catch (ex) {
+          if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+            throw ex;
+          }
           // Ignore the result if the state has changed meanwhile.
           if (!this.stopped || this._finalized) {
             return;
           }
 
           this.hasBlockedData = false;
           this.hasPartialData = false;
         }
@@ -1704,18 +1707,21 @@ this.DownloadSaver.prototype = {
 
     // The start time is always available when we reach this point.
     let startPRTime = this.download.startTime.getTime() * 1000;
 
     try {
       gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
                                    targetUri);
     }
-    catch(ex if ex instanceof Components.Exception &&
-                ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+    catch(ex) {
+      if (!(ex instanceof Components.Exception) ||
+          ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+        throw ex;
+      }
       //
       // Under normal operation the download history service may not
       // be available. We don't want all downloads that are public to fail
       // when this happens so we'll ignore this error and this error only!
       //
     }
   },
 
@@ -1835,17 +1841,17 @@ this.DownloadCopySaver.prototype = {
 
     this._canceled = false;
 
     let download = this.download;
     let targetPath = download.target.path;
     let partFilePath = download.target.partFilePath;
     let keepPartialData = download.tryToKeepPartialData;
 
-    return Task.spawn(function task_DCS_execute() {
+    return Task.spawn(function* task_DCS_execute() {
       // Add the download to history the first time it is started in this
       // session.  If the download is restarted in a different session, a new
       // history visit will be added.  We do this just to avoid the complexity
       // of serializing this state between sessions, since adding a new visit
       // does not have any noticeable side effect.
       if (!this.alreadyAddedToHistory) {
         this.addToHistory();
         this.alreadyAddedToHistory = true;
@@ -1855,17 +1861,20 @@ this.DownloadCopySaver.prototype = {
       // file name, we should create a placeholder as soon as possible, before
       // starting the network request.  The placeholder is also required in case
       // we are using a ".part" file instead of the final target while the
       // download is in progress.
       try {
         // If the file already exists, don't delete its contents yet.
         let file = yield OS.File.open(targetPath, { write: true });
         yield file.close();
-      } catch (ex if ex instanceof OS.File.Error) {
+      } catch (ex) {
+        if (!(ex instanceof OS.File.Error)) {
+          throw ex;
+        }
         // Throw a DownloadError indicating that the operation failed because of
         // the target file.  We cannot translate this into a specific result
         // code, but we preserve the original message using the toString method.
         let error = new DownloadError({ message: ex.toString() });
         error.becauseTargetFailed = true;
         throw error;
       }
 
@@ -1925,18 +1934,21 @@ this.DownloadCopySaver.prototype = {
           let resumeFromBytes = 0;
           if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
               partFilePath && keepPartialData) {
             try {
               let stat = yield OS.File.stat(partFilePath);
               channel.resumeAt(stat.size, this.entityID);
               resumeAttempted = true;
               resumeFromBytes = stat.size;
-            } catch (ex if ex instanceof OS.File.Error &&
-                           ex.becauseNoSuchFile) { }
+            } catch (ex) {
+              if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+                throw ex;
+              }
+            }
           }
 
           channel.notificationCallbacks = {
             QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor]),
             getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
             onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
                                                  aProgressMax)
             {
@@ -1995,18 +2007,21 @@ this.DownloadCopySaver.prototype = {
 
               if (keepPartialData) {
                 // If the source is not resumable, don't keep partial data even
                 // if we were asked to try and do it.
                 if (aRequest instanceof Ci.nsIResumableChannel) {
                   try {
                     // If reading the ID succeeds, the source is resumable.
                     this.entityID = aRequest.entityID;
-                  } catch (ex if ex instanceof Components.Exception &&
-                                 ex.result == Cr.NS_ERROR_NOT_RESUMABLE) {
+                  } catch (ex) {
+                    if (!(ex instanceof Components.Exception) ||
+                        ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
+                      throw ex;
+                    }
                     keepPartialData = false;
                   }
                 } else {
                   keepPartialData = false;
                 }
               }
 
               // Enable hashing and signature verification before setting the
@@ -2147,21 +2162,25 @@ this.DownloadCopySaver.prototype = {
     }
   },
 
   /**
    * Implements "DownloadSaver.removePartialData".
    */
   removePartialData: function ()
   {
-    return Task.spawn(function task_DCS_removePartialData() {
+    return Task.spawn(function* task_DCS_removePartialData() {
       if (this.download.target.partFilePath) {
         try {
           yield OS.File.remove(this.download.target.partFilePath);
-        } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { }
+        } catch (ex) {
+          if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+            throw ex;
+          }
+        }
       }
     }.bind(this));
   },
 
   /**
    * Implements "DownloadSaver.toSerializable".
    */
   toSerializable: function ()
@@ -2329,18 +2348,22 @@ this.DownloadLegacySaver.prototype = {
   onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
   {
     // Store the entity ID to use for resuming if required.
     if (this.download.tryToKeepPartialData &&
         aRequest instanceof Ci.nsIResumableChannel) {
       try {
         // If reading the ID succeeds, the source is resumable.
         this.entityID = aRequest.entityID;
-      } catch (ex if ex instanceof Components.Exception &&
-                     ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { }
+      } catch (ex) {
+        if (!(ex instanceof Components.Exception) ||
+            ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
+          throw ex;
+        }
+      }
     }
 
     // For legacy downloads, we must update the referrer at this time.
     if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
       this.download.source.referrer = aRequest.referrer.spec;
     }
 
     if (!aAlreadyAddedToHistory) {
@@ -2406,17 +2429,17 @@ this.DownloadLegacySaver.prototype = {
         this.copySaver.entityID = this.entityID;
         this.copySaver.alreadyAddedToHistory = true;
       }
       return this.copySaver.execute.apply(this.copySaver, arguments);
     }
 
     this.setProgressBytesFn = aSetProgressBytesFn;
 
-    return Task.spawn(function task_DLS_execute() {
+    return Task.spawn(function* task_DLS_execute() {
       try {
         // Wait for the component that executes the download to finish.
         yield this.deferExecuted.promise;
 
         // At this point, the "request" property has been populated.  Ensure we
         // report the value of "Content-Length", if available, even if the
         // download didn't generate any progress events.
         if (!this.progressWasNotified &&
@@ -2436,17 +2459,21 @@ this.DownloadLegacySaver.prototype = {
         // source).  In this case, ensure that an empty file is created as
         // expected.
         if (!this.download.target.partFilePath) {
           try {
             // This atomic operation is more efficient than an existence check.
             let file = yield OS.File.open(this.download.target.path,
                                           { create: true });
             yield file.close();
-          } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { }
+          } catch (ex) {
+            if (!(ex instanceof OS.File.Error) || !ex.becauseExists) {
+              throw ex;
+            }
+          }
         }
 
         yield this._checkReputationAndMove();
 
       } catch (ex) {
         // Ensure we always remove the final target file on failure,
         // independently of which code path failed.  In some cases, the
         // component executing the download may have already removed the file.
@@ -2618,17 +2645,17 @@ this.DownloadPDFSaver.prototype = {
    */
   _webBrowserPrint: null,
 
   /**
    * Implements "DownloadSaver.execute".
    */
   execute: function (aSetProgressBytesFn, aSetPropertiesFn)
   {
-    return Task.spawn(function task_DCS_execute() {
+    return Task.spawn(function* task_DCS_execute() {
       if (!this.download.source.windowRef) {
         throw new DownloadError({
           message: "PDF saver must be passed an open window, and cannot be restarted.",
           becauseSourceFailed: true,
         });
       }
 
       let win = this.download.source.windowRef.get();
--- a/toolkit/components/jsdownloads/src/DownloadImport.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadImport.jsm
@@ -66,17 +66,17 @@ this.DownloadImport.prototype = {
    * format. Each imported download will be added to the DownloadList
    *
    * @return {Promise}
    * @resolves When the operation has completed (i.e., every download
    *           from the previous database has been read and added to
    *           the DownloadList)
    */
   import: function () {
-    return Task.spawn(function task_DI_import() {
+    return Task.spawn(function* task_DI_import() {
       let connection = yield Sqlite.openConnection({ path: this.path });
 
       try {
         let schemaVersion = yield connection.getSchemaVersion();
         // We don't support schemas older than version 7 (from 2007)
         // - Version 7 added the columns mimeType, preferredApplication
         //   and preferredAction in 2007
         // - Version 8 added the column autoResume in 2007
--- a/toolkit/components/jsdownloads/src/DownloadList.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadList.jsm
@@ -228,17 +228,17 @@ this.DownloadList.prototype = {
    *
    * @param aFilterFn
    *        The filter function is called with each download as its only
    *        argument, and should return true to remove the download and false
    *        to keep it.  This parameter may be null or omitted to have no
    *        additional filter.
    */
   removeFinished: function DL_removeFinished(aFilterFn) {
-    Task.spawn(function() {
+    Task.spawn(function* () {
       let list = yield this.getAll();
       for (let download of list) {
         // Remove downloads that have been canceled, even if the cancellation
         // operation hasn't completed yet so we don't check "stopped" here.
         // Failed downloads with partial data are also removed.
         if (download.stopped && (!download.hasPartialData || download.error) &&
             (!aFilterFn || aFilterFn(download))) {
           // Remove the download first, so that the views don't get the change
--- a/toolkit/components/jsdownloads/src/DownloadStore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadStore.jsm
@@ -98,21 +98,24 @@ this.DownloadStore.prototype = {
    * Loads persistent downloads from the file to the list.
    *
    * @return {Promise}
    * @resolves When the operation finished successfully.
    * @rejects JavaScript exception.
    */
   load: function DS_load()
   {
-    return Task.spawn(function task_DS_load() {
+    return Task.spawn(function* task_DS_load() {
       let bytes;
       try {
         bytes = yield OS.File.read(this.path);
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
+      } catch (ex) {
+        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+          throw ex;
+        }
         // If the file does not exist, there are no downloads to load.
         return;
       }
 
       let storeData = JSON.parse(gTextDecoder.decode(bytes));
 
       // Create live downloads based on the static snapshot.
       for (let downloadData of storeData.list) {
@@ -148,17 +151,17 @@ this.DownloadStore.prototype = {
    * If an error occurs, the previous file is not deleted.
    *
    * @return {Promise}
    * @resolves When the operation finished successfully.
    * @rejects JavaScript exception.
    */
   save: function DS_save()
   {
-    return Task.spawn(function task_DS_save() {
+    return Task.spawn(function* task_DS_save() {
       let downloads = yield this.list.getAll();
 
       // Take a static snapshot of the current state of all the downloads.
       let storeData = { list: [] };
       let atLeastOneDownload = false;
       for (let download of downloads) {
         try {
           if (!this.onsaveitem(download)) {
@@ -183,17 +186,20 @@ this.DownloadStore.prototype = {
         // Create or overwrite the file if there are downloads to save.
         let bytes = gTextEncoder.encode(JSON.stringify(storeData));
         yield OS.File.writeAtomic(this.path, bytes,
                                   { tmpPath: this.path + ".tmp" });
       } else {
         // Remove the file if there are no downloads to save at all.
         try {
           yield OS.File.remove(this.path);
-        } catch (ex if ex instanceof OS.File.Error &&
-                 (ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
+        } catch (ex) {
+          if (!(ex instanceof OS.File.Error) ||
+              !(ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
+            throw ex;
+          }
           // On Windows, we may get an access denied error instead of a no such
           // file error if the file existed before, and was recently deleted.
         }
       }
     }.bind(this));
   },
 };
--- a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
@@ -18,16 +18,17 @@ this.EXPORTED_SYMBOLS = [
 //// Globals
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
@@ -103,22 +104,22 @@ XPCOMUtils.defineLazyGetter(DownloadUIHe
  * Allows displaying prompts related to downloads.
  *
  * @param aParent
  *        The nsIDOMWindow to which prompts should be attached, or null to
  *        attach prompts to the most recently active window.
  */
 this.DownloadPrompter = function (aParent)
 {
-#ifdef MOZ_B2G
-  // On B2G there is no prompter implementation.
-  this._prompter = null;
-#else
-  this._prompter = Services.ww.getNewPrompter(aParent);
-#endif
+  if (AppConstants.MOZ_B2G) {
+    // On B2G there is no prompter implementation.
+    this._prompter = null;
+  } else {
+    this._prompter = Services.ww.getNewPrompter(aParent);
+  }
 }
 
 this.DownloadPrompter.prototype = {
   /**
    * Constants with the different type of prompts.
    */
   ON_QUIT: "prompt-on-quit",
   ON_OFFLINE: "prompt-on-offline",
@@ -205,27 +206,27 @@ this.DownloadPrompter.prototype = {
                       (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
     let okButton = aDownloadsCount > 1 ? s.cancelDownloadsOKTextMultiple(aDownloadsCount)
                                        : s.cancelDownloadsOKText;
     let title, message, cancelButton;
 
     switch (aPromptType) {
       case this.ON_QUIT:
         title = s.quitCancelDownloadsAlertTitle;
-#ifndef XP_MACOSX
-        message = aDownloadsCount > 1
-                  ? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
-                  : s.quitCancelDownloadsAlertMsg;
-        cancelButton = s.dontQuitButtonWin;
-#else
-        message = aDownloadsCount > 1
-                  ? s.quitCancelDownloadsAlertMsgMacMultiple(aDownloadsCount)
-                  : s.quitCancelDownloadsAlertMsgMac;
-        cancelButton = s.dontQuitButtonMac;
-#endif
+        if (AppConstants.platform != "macosx") {
+          message = aDownloadsCount > 1
+                    ? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
+                    : s.quitCancelDownloadsAlertMsg;
+          cancelButton = s.dontQuitButtonWin;
+        } else {
+          message = aDownloadsCount > 1
+                    ? s.quitCancelDownloadsAlertMsgMacMultiple(aDownloadsCount)
+                    : s.quitCancelDownloadsAlertMsgMac;
+          cancelButton = s.dontQuitButtonMac;
+        }
         break;
       case this.ON_OFFLINE:
         title = s.offlineCancelDownloadsAlertTitle;
         message = aDownloadsCount > 1
                   ? s.offlineCancelDownloadsAlertMsgMultiple(aDownloadsCount)
                   : s.offlineCancelDownloadsAlertMsg;
         cancelButton = s.dontGoOfflineButton;
         break;
--- a/toolkit/components/jsdownloads/src/Downloads.jsm
+++ b/toolkit/components/jsdownloads/src/Downloads.jsm
@@ -167,17 +167,17 @@ this.Downloads = {
    *
    * @return {Promise}
    * @resolves The requested DownloadList or DownloadCombinedList object.
    * @rejects JavaScript exception.
    */
   getList: function (aType)
   {
     if (!this._promiseListsInitialized) {
-      this._promiseListsInitialized = Task.spawn(function () {
+      this._promiseListsInitialized = Task.spawn(function* () {
         let publicList = new DownloadList();
         let privateList = new DownloadList();
         let combinedList = new DownloadCombinedList(publicList, privateList);
 
         try {
           yield DownloadIntegration.addListObservers(publicList, false);
           yield DownloadIntegration.addListObservers(privateList, true);
           yield DownloadIntegration.initializePublicDownloadList(publicList);
--- a/toolkit/components/jsdownloads/src/moz.build
+++ b/toolkit/components/jsdownloads/src/moz.build
@@ -14,21 +14,21 @@ EXTRA_COMPONENTS += [
 ]
 
 EXTRA_JS_MODULES += [
     'DownloadCore.jsm',
     'DownloadImport.jsm',
     'DownloadList.jsm',
     'Downloads.jsm',
     'DownloadStore.jsm',
+    'DownloadUIHelper.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'DownloadIntegration.jsm',
-    'DownloadUIHelper.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -46,17 +46,17 @@ function promiseStartDownload(aSourceUrl
  * This function uses either DownloadCopySaver or DownloadLegacySaver based on
  * the current test run.
  *
  * @return {Promise}
  * @resolves The newly created Download object, still in progress.
  * @rejects JavaScript exception.
  */
 function promiseStartDownload_tryToKeepPartialData() {
-  return Task.spawn(function () {
+  return Task.spawn(function* () {
     mustInterruptResponses();
 
     // Start a new download and configure it to keep partially downloaded data.
     let download;
     if (!gUseLegacySaver) {
       let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path;
       download = yield Downloads.createDownload({
         source: httpUrl("interruptible_resumable.txt"),
@@ -69,40 +69,43 @@ function promiseStartDownload_tryToKeepP
       // Start a download using nsIExternalHelperAppService, that is configured
       // to keep partially downloaded data by default.
       download = yield promiseStartExternalHelperAppServiceDownload();
     }
 
     yield promiseDownloadMidway(download);
     yield promisePartFileReady(download);
 
-    throw new Task.Result(download);
+    return download;
   });
 }
 
 /**
  * This function should be called after the progress notification for a download
  * is received, and waits for the worker thread of BackgroundFileSaver to
  * receive the data to be written to the ".part" file on disk.
  *
  * @return {Promise}
  * @resolves When the ".part" file has been written to disk.
  * @rejects JavaScript exception.
  */
 function promisePartFileReady(aDownload) {
-  return Task.spawn(function () {
+  return Task.spawn(function* () {
     // We don't have control over the file output code in BackgroundFileSaver.
     // After we receive the download progress notification, we may only check
     // that the ".part" file has been created, while its size cannot be
     // determined because the file is currently open.
     try {
       do {
         yield promiseTimeout(50);
       } while (!(yield OS.File.exists(aDownload.target.partFilePath)));
-    } catch (ex if ex instanceof OS.File.Error) {
+    } catch (ex) {
+      if (!(ex instanceof OS.File.Error)) {
+        throw ex;
+      }
       // This indicates that the file has been created and cannot be accessed.
       // The specific error might vary with the platform.
       do_print("Expected exception while checking existence: " + ex.toString());
       // Wait some more time to allow the write to complete.
       yield promiseTimeout(100);
     }
   });
 }
@@ -130,17 +133,17 @@ var promiseVerifyTarget = Task.async(fun
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 /**
  * Executes a download and checks its basic properties after construction.
  * The download is started by constructing the simplest Download object with
  * the "copy" saver, or using the legacy nsITransfer interface.
  */
-add_task(function test_basic()
+add_task(function* test_basic()
 {
   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
 
   let download;
   if (!gUseLegacySaver) {
     // When testing DownloadCopySaver, we have control over the download, thus
     // we can check its basic properties before it starts.
     download = yield Downloads.createDownload({
@@ -171,32 +174,32 @@ add_task(function test_basic()
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 });
 
 /**
  * Executes a download with the tryToKeepPartialData property set, and ensures
  * that the file is saved correctly.  When testing DownloadLegacySaver, the
  * download is executed using the nsIExternalHelperAppService component.
  */
-add_task(function test_basic_tryToKeepPartialData()
+add_task(function* test_basic_tryToKeepPartialData()
 {
   let download = yield promiseStartDownload_tryToKeepPartialData();
   continueResponses();
   yield promiseDownloadStopped(download);
 
   // The target file should now have been created, and the ".part" file deleted.
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
   do_check_false(yield OS.File.exists(download.target.partFilePath));
   do_check_eq(32, download.saver.getSha256Hash().length);
 });
 
 /**
  * Tests the permissions of the final target file once the download finished.
  */
-add_task(function test_unix_permissions()
+add_task(function* test_unix_permissions()
 {
   // This test is only executed on some Desktop systems.
   if (Services.appinfo.OS != "Darwin" && Services.appinfo.OS != "Linux" &&
       Services.appinfo.OS != "WINNT") {
     do_print("Skipping test.");
     return;
   }
 
@@ -249,17 +252,17 @@ add_task(function test_unix_permissions(
 
   // Clean up the changes to the preference.
   Services.prefs.clearUserPref(kDeleteTempFileOnExit);
 });
 
 /**
  * Checks the referrer for downloads.
  */
-add_task(function test_referrer()
+add_task(function* test_referrer()
 {
   let sourcePath = "/test_referrer.txt";
   let sourceUrl = httpUrl("test_referrer.txt");
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
 
   function cleanup() {
     gHttpServer.registerPathHandler(sourcePath, null);
   }
@@ -296,17 +299,17 @@ add_task(function test_referrer()
   yield download.start();
 
   cleanup();
 });
 
 /**
  * Checks initial and final state and progress for a successful download.
  */
-add_task(function test_initial_final_state()
+add_task(function* test_initial_final_state()
 {
   let download;
   if (!gUseLegacySaver) {
     // When testing DownloadCopySaver, we have control over the download, thus
     // we can check its state before it starts.
     download = yield promiseNewDownload();
 
     do_check_true(download.stopped);
@@ -334,17 +337,17 @@ add_task(function test_initial_final_sta
   do_check_true(isValidDate(download.startTime));
   do_check_true(download.target.exists);
   do_check_eq(download.target.size, TEST_DATA_SHORT.length);
 });
 
 /**
  * Checks the notification of the final download state.
  */
-add_task(function test_final_state_notified()
+add_task(function* test_final_state_notified()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   let onchangeNotified = false;
   let lastNotifiedStopped;
   let lastNotifiedProgress;
@@ -363,17 +366,17 @@ add_task(function test_final_state_notif
   do_check_true(onchangeNotified);
   do_check_true(lastNotifiedStopped);
   do_check_eq(lastNotifiedProgress, 100);
 });
 
 /**
  * Checks intermediate progress for a successful download.
  */
-add_task(function test_intermediate_progress()
+add_task(function* test_intermediate_progress()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   yield promiseDownloadMidway(download);
 
   do_check_true(download.hasProgress);
@@ -392,17 +395,17 @@ add_task(function test_intermediate_prog
   do_check_eq(download.progress, 100);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Downloads a file with a "Content-Length" of 0 and checks the progress.
  */
-add_task(function test_empty_progress()
+add_task(function* test_empty_progress()
 {
   let download = yield promiseStartDownload(httpUrl("empty.txt"));
   yield promiseDownloadStopped(download);
 
   do_check_true(download.stopped);
   do_check_true(download.hasProgress);
   do_check_eq(download.progress, 100);
   do_check_eq(download.currentBytes, 0);
@@ -415,17 +418,17 @@ add_task(function test_empty_progress()
   do_check_true(download.target.exists);
   do_check_eq(download.target.size, 0);
 });
 
 /**
  * Downloads a file with a "Content-Length" of 0 with the tryToKeepPartialData
  * property set, and ensures that the file is saved correctly.
  */
-add_task(function test_empty_progress_tryToKeepPartialData()
+add_task(function* test_empty_progress_tryToKeepPartialData()
 {
   // Start a new download and configure it to keep partially downloaded data.
   let download;
   if (!gUseLegacySaver) {
     let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path;
     download = yield Downloads.createDownload({
       source: httpUrl("empty.txt"),
       target: { path: targetFilePath,
@@ -448,17 +451,17 @@ add_task(function test_empty_progress_tr
 
   do_check_false(yield OS.File.exists(download.target.partFilePath));
   do_check_eq(32, download.saver.getSha256Hash().length);
 });
 
 /**
  * Downloads an empty file with no "Content-Length" and checks the progress.
  */
-add_task(function test_empty_noprogress()
+add_task(function* test_empty_noprogress()
 {
   let sourcePath = "/test_empty_noprogress.txt";
   let sourceUrl = httpUrl("test_empty_noprogress.txt");
   let deferRequestReceived = Promise.defer();
 
   // Register an interruptible handler that notifies us when the request occurs.
   function cleanup() {
     gHttpServer.registerPathHandler(sourcePath, null);
@@ -525,17 +528,17 @@ add_task(function test_empty_noprogress(
   do_check_eq(download.target.size, 0);
 
   do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
 });
 
 /**
  * Calls the "start" method two times before the download is finished.
  */
-add_task(function test_start_twice()
+add_task(function* test_start_twice()
 {
   mustInterruptResponses();
 
   let download;
   if (!gUseLegacySaver) {
     // When testing DownloadCopySaver, we have control over the download, thus
     // we can start the download later during the test.
     download = yield promiseNewDownload(httpUrl("interruptible.txt"));
@@ -562,17 +565,17 @@ add_task(function test_start_twice()
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Cancels a download and verifies that its state is reported correctly.
  */
-add_task(function test_cancel_midway()
+add_task(function* test_cancel_midway()
 {
   mustInterruptResponses();
 
   // In this test case, we execute different checks that are only possible with
   // DownloadCopySaver or DownloadLegacySaver respectively.
   let download;
   let options = {};
   if (!gUseLegacySaver) {
@@ -628,28 +631,31 @@ add_task(function test_cancel_midway()
   do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
   do_check_eq(download.currentBytes, TEST_DATA_SHORT.length);
 
   if (!gUseLegacySaver) {
     // The promise returned by "start" should have been rejected meanwhile.
     try {
       yield promiseAttempt;
       do_throw("The download should have been canceled.");
-    } catch (ex if ex instanceof Downloads.Error) {
+    } catch (ex) {
+      if (!(ex instanceof Downloads.Error)) {
+        throw ex;
+      }
       do_check_false(ex.becauseSourceFailed);
       do_check_false(ex.becauseTargetFailed);
     }
   }
 });
 
 /**
  * Cancels a download while keeping partially downloaded data, and verifies that
  * both the target file and the ".part" file are deleted.
  */
-add_task(function test_cancel_midway_tryToKeepPartialData()
+add_task(function* test_cancel_midway_tryToKeepPartialData()
 {
   let download = yield promiseStartDownload_tryToKeepPartialData();
 
   do_check_true(yield OS.File.exists(download.target.path));
   do_check_true(yield OS.File.exists(download.target.partFilePath));
 
   yield download.cancel();
   yield download.removePartialData();
@@ -660,17 +666,17 @@ add_task(function test_cancel_midway_try
 
   do_check_false(yield OS.File.exists(download.target.path));
   do_check_false(yield OS.File.exists(download.target.partFilePath));
 });
 
 /**
  * Cancels a download right after starting it.
  */
-add_task(function test_cancel_immediately()
+add_task(function* test_cancel_immediately()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   let promiseAttempt = download.start();
   do_check_false(download.stopped);
 
@@ -678,17 +684,20 @@ add_task(function test_cancel_immediatel
   do_check_true(download.canceled);
 
   // At this point, we don't know whether the download has already stopped or
   // is still waiting for cancellation.  We can wait on the promise returned
   // by the "start" method to know for sure.
   try {
     yield promiseAttempt;
     do_throw("The download should have been canceled.");
-  } catch (ex if ex instanceof Downloads.Error) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error)) {
+      throw ex;
+    }
     do_check_false(ex.becauseSourceFailed);
     do_check_false(ex.becauseTargetFailed);
   }
 
   do_check_true(download.stopped);
   do_check_true(download.canceled);
   do_check_true(download.error === null);
 
@@ -696,17 +705,17 @@ add_task(function test_cancel_immediatel
 
   // Check that the promise returned by the "cancel" method has been resolved.
   yield promiseCancel;
 });
 
 /**
  * Cancels and restarts a download sequentially.
  */
-add_task(function test_cancel_midway_restart()
+add_task(function* test_cancel_midway_restart()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   // The first time, cancel the download midway.
   yield promiseDownloadMidway(download);
   yield download.cancel();
@@ -738,17 +747,17 @@ add_task(function test_cancel_midway_res
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Cancels a download and restarts it from where it stopped.
  */
-add_task(function test_cancel_midway_restart_tryToKeepPartialData()
+add_task(function* test_cancel_midway_restart_tryToKeepPartialData()
 {
   let download = yield promiseStartDownload_tryToKeepPartialData();
   yield download.cancel();
 
   do_check_true(download.stopped);
   do_check_true(download.hasPartialData);
 
   // The target file should not exist, but we should have kept the partial data.
@@ -792,17 +801,17 @@ add_task(function test_cancel_midway_res
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
   do_check_false(yield OS.File.exists(download.target.partFilePath));
 });
 
 /**
  * Cancels a download while keeping partially downloaded data, then removes the
  * data and restarts the download from the beginning.
  */
-add_task(function test_cancel_midway_restart_removePartialData()
+add_task(function* test_cancel_midway_restart_removePartialData()
 {
   let download = yield promiseStartDownload_tryToKeepPartialData();
   yield download.cancel();
 
   do_check_true(download.hasPartialData);
   yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
   do_check_false(download.target.exists);
   do_check_eq(download.target.size, 0);
@@ -826,17 +835,17 @@ add_task(function test_cancel_midway_res
   do_check_false(yield OS.File.exists(download.target.partFilePath));
 });
 
 /**
  * Cancels a download while keeping partially downloaded data, then removes the
  * data and restarts the download from the beginning without keeping the partial
  * data anymore.
  */
-add_task(function test_cancel_midway_restart_tryToKeepPartialData_false()
+add_task(function* test_cancel_midway_restart_tryToKeepPartialData_false()
 {
   let download = yield promiseStartDownload_tryToKeepPartialData();
   yield download.cancel();
 
   download.tryToKeepPartialData = false;
 
   // The above property change does not affect existing partial data.
   do_check_true(download.hasPartialData);
@@ -879,17 +888,17 @@ add_task(function test_cancel_midway_res
   // The target file should now have been created, and the ".part" file deleted.
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
   do_check_false(yield OS.File.exists(download.target.partFilePath));
 });
 
 /**
  * Cancels a download right after starting it, then restarts it immediately.
  */
-add_task(function test_cancel_immediately_restart_immediately()
+add_task(function* test_cancel_immediately_restart_immediately()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   let promiseAttempt = download.start();
 
   do_check_false(download.stopped);
 
@@ -913,17 +922,20 @@ add_task(function test_cancel_immediatel
   // the canceled request was received by the server or not.
   continueResponses();
   try {
     yield promiseAttempt;
     // If we get here, it means that the first attempt actually succeeded.  In
     // fact, this could be a valid outcome, because the cancellation request may
     // not have been processed in time before the download finished.
     do_print("The download should have been canceled.");
-  } catch (ex if ex instanceof Downloads.Error) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error)) {
+      throw ex;
+    }
     do_check_false(ex.becauseSourceFailed);
     do_check_false(ex.becauseTargetFailed);
   }
 
   yield promiseRestarted;
 
   do_check_true(download.stopped);
   do_check_true(download.succeeded);
@@ -931,17 +943,17 @@ add_task(function test_cancel_immediatel
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Cancels a download midway, then restarts it immediately.
  */
-add_task(function test_cancel_midway_restart_immediately()
+add_task(function* test_cancel_midway_restart_immediately()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   let promiseAttempt = download.start();
 
   // The first time, cancel the download midway.
   yield promiseDownloadMidway(download);
@@ -961,17 +973,20 @@ add_task(function test_cancel_midway_res
   do_check_eq(download.totalBytes, 0);
   do_check_eq(download.currentBytes, 0);
 
   // The second request is allowed to complete.
   continueResponses();
   try {
     yield promiseAttempt;
     do_throw("The download should have been canceled.");
-  } catch (ex if ex instanceof Downloads.Error) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error)) {
+      throw ex;
+    }
     do_check_false(ex.becauseSourceFailed);
     do_check_false(ex.becauseTargetFailed);
   }
 
   yield promiseRestarted;
 
   do_check_true(download.stopped);
   do_check_true(download.succeeded);
@@ -979,17 +994,17 @@ add_task(function test_cancel_midway_res
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Calls the "cancel" method on a successful download.
  */
-add_task(function test_cancel_successful()
+add_task(function* test_cancel_successful()
 {
   let download = yield promiseStartDownload();
   yield promiseDownloadStopped(download);
 
   // The cancel method should succeed with no effect.
   yield download.cancel();
 
   do_check_true(download.stopped);
@@ -998,33 +1013,36 @@ add_task(function test_cancel_successful
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 });
 
 /**
  * Calls the "cancel" method two times in a row.
  */
-add_task(function test_cancel_twice()
+add_task(function* test_cancel_twice()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   let promiseAttempt = download.start();
   do_check_false(download.stopped);
 
   let promiseCancel1 = download.cancel();
   do_check_true(download.canceled);
   let promiseCancel2 = download.cancel();
 
   try {
     yield promiseAttempt;
     do_throw("The download should have been canceled.");
-  } catch (ex if ex instanceof Downloads.Error) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error)) {
+      throw ex;
+    }
     do_check_false(ex.becauseSourceFailed);
     do_check_false(ex.becauseTargetFailed);
   }
 
   // Both promises should now be resolved.
   yield promiseCancel1;
   yield promiseCancel2;
 
@@ -1034,17 +1052,17 @@ add_task(function test_cancel_twice()
   do_check_true(download.error === null);
 
   do_check_false(yield OS.File.exists(download.target.path));
 });
 
 /**
  * Checks the "refresh" method for succeeded downloads.
  */
-add_task(function test_refresh_succeeded()
+add_task(function* test_refresh_succeeded()
 {
   let download = yield promiseStartDownload();
   yield promiseDownloadStopped(download);
 
   // The DownloadTarget properties should be the same after calling "refresh".
   yield download.refresh();
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 
@@ -1059,17 +1077,17 @@ add_task(function test_refresh_succeeded
   yield OS.File.move(download.target.path + ".old", download.target.path);
   yield download.refresh();
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 });
 
 /**
  * Checks that a download cannot be restarted after the "finalize" method.
  */
-add_task(function test_finalize()
+add_task(function* test_finalize()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   let promiseFinalized = download.finalize();
 
   try {
@@ -1085,17 +1103,17 @@ add_task(function test_finalize()
   do_check_true(download.error === null);
 
   do_check_false(yield OS.File.exists(download.target.path));
 });
 
 /**
  * Checks that the "finalize" method can remove partially downloaded data.
  */
-add_task(function test_finalize_tryToKeepPartialData()
+add_task(function* test_finalize_tryToKeepPartialData()
 {
   // Check finalization without removing partial data.
   let download = yield promiseStartDownload_tryToKeepPartialData();
   yield download.finalize();
 
   do_check_true(download.hasPartialData);
   do_check_true(yield OS.File.exists(download.target.partFilePath));
 
@@ -1108,17 +1126,17 @@ add_task(function test_finalize_tryToKee
 
   do_check_false(download.hasPartialData);
   do_check_false(yield OS.File.exists(download.target.partFilePath));
 });
 
 /**
  * Checks that whenSucceeded returns a promise that is resolved after a restart.
  */
-add_task(function test_whenSucceeded_after_restart()
+add_task(function* test_whenSucceeded_after_restart()
 {
   mustInterruptResponses();
 
   let promiseSucceeded;
 
   let download;
   if (!gUseLegacySaver) {
     // When testing DownloadCopySaver, we have control over the download, thus
@@ -1149,17 +1167,17 @@ add_task(function test_whenSucceeded_aft
   do_check_true(download.error === null);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
 });
 
 /**
  * Ensures download error details are reported on network failures.
  */
-add_task(function test_error_source()
+add_task(function* test_error_source()
 {
   let serverSocket = startFakeServer();
   try {
     let sourceUrl = "http://localhost:" + serverSocket.port + "/source.txt";
 
     let download;
     try {
       if (!gUseLegacySaver) {
@@ -1174,17 +1192,20 @@ add_task(function test_error_source()
         // When testing DownloadLegacySaver, we cannot be sure whether we are
         // testing the promise returned by the "start" method or we are testing
         // the "error" property checked by promiseDownloadStopped.  This happens
         // because we don't have control over when the download is started.
         download = yield promiseStartLegacyDownload(sourceUrl);
         yield promiseDownloadStopped(download);
       }
       do_throw("The download should have failed.");
-    } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) {
+    } catch (ex) {
+      if (!(ex instanceof Downloads.Error) || !ex.becauseSourceFailed) {
+        throw ex;
+      }
       // A specific error object is thrown when reading from the source fails.
     }
 
     // Check the properties now that the download stopped.
     do_check_true(download.stopped);
     do_check_false(download.canceled);
     do_check_true(download.error !== null);
     do_check_true(download.error.becauseSourceFailed);
@@ -1197,17 +1218,17 @@ add_task(function test_error_source()
     serverSocket.close();
   }
 });
 
 /**
  * Ensures a download error is reported when receiving less bytes than what was
  * specified in the Content-Length header.
  */
-add_task(function test_error_source_partial()
+add_task(function* test_error_source_partial()
 {
   let sourceUrl = httpUrl("shorter-than-content-length-http-1-1.txt");
 
   let enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.http1");
   Services.prefs.setBoolPref("network.http.enforce-framing.http1", true);
 
   function cleanup() {
     Services.prefs.setBoolPref("network.http.enforce-framing.http1", enforcePref);
@@ -1228,17 +1249,20 @@ add_task(function test_error_source_part
       // When testing DownloadLegacySaver, we cannot be sure whether we are
       // testing the promise returned by the "start" method or we are testing
       // the "error" property checked by promiseDownloadStopped.  This happens
       // because we don't have control over when the download is started.
       download = yield promiseStartLegacyDownload(sourceUrl);
       yield promiseDownloadStopped(download);
     }
     do_throw("The download should have failed.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseSourceFailed) {
+      throw ex;
+    }
     // A specific error object is thrown when reading from the source fails.
   }
 
   // Check the properties now that the download stopped.
   do_check_true(download.stopped);
   do_check_false(download.canceled);
   do_check_true(download.error !== null);
   do_check_true(download.error.becauseSourceFailed);
@@ -1248,17 +1272,17 @@ add_task(function test_error_source_part
   do_check_false(yield OS.File.exists(download.target.path));
   do_check_false(download.target.exists);
   do_check_eq(download.target.size, 0);
 });
 
 /**
  * Ensures download error details are reported on local writing failures.
  */
-add_task(function test_error_target()
+add_task(function* test_error_target()
 {
   // Create a file without write access permissions before downloading.
   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
   targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0);
   try {
     let download;
     try {
       if (!gUseLegacySaver) {
@@ -1274,17 +1298,20 @@ add_task(function test_error_target()
         // testing the promise returned by the "start" method or we are testing
         // the "error" property checked by promiseDownloadStopped.  This happens
         // because we don't have control over when the download is started.
         download = yield promiseStartLegacyDownload(null,
                                                     { targetFile: targetFile });
         yield promiseDownloadStopped(download);
       }
       do_throw("The download should have failed.");
-    } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) {
+    } catch (ex) {
+      if (!(ex instanceof Downloads.Error) || !ex.becauseTargetFailed) {
+        throw ex;
+      }
       // A specific error object is thrown when writing to the target fails.
     }
 
     // Check the properties now that the download stopped.
     do_check_true(download.stopped);
     do_check_false(download.canceled);
     do_check_true(download.error !== null);
     do_check_true(download.error.becauseTargetFailed);
@@ -1296,17 +1323,17 @@ add_task(function test_error_target()
       targetFile.remove(false);
     }
   }
 });
 
 /**
  * Restarts a failed download.
  */
-add_task(function test_error_restart()
+add_task(function* test_error_restart()
 {
   let download;
 
   // Create a file without write access permissions before downloading.
   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
   targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0);
   try {
     // Use DownloadCopySaver or DownloadLegacySaver based on the test run,
@@ -1318,17 +1345,20 @@ add_task(function test_error_restart()
       });
       download.start();
     } else {
       download = yield promiseStartLegacyDownload(null,
                                                   { targetFile: targetFile });
     }
     yield promiseDownloadStopped(download);
     do_throw("The download should have failed.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseTargetFailed) {
+      throw ex;
+    }
     // A specific error object is thrown when writing to the target fails.
   } finally {
     // Restore the default permissions to allow deleting the file on Windows.
     if (targetFile.exists()) {
       targetFile.permissions = FileUtils.PERMS_FILE;
 
       // Also for Windows, rename the file before deleting.  This makes the
       // current file name available immediately for a new file, while deleting
@@ -1348,17 +1378,17 @@ add_task(function test_error_restart()
   do_check_eq(download.progress, 100);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 });
 
 /**
  * Executes download in both public and private modes.
  */
-add_task(function test_public_and_private()
+add_task(function* test_public_and_private()
 {
   let sourcePath = "/test_public_and_private.txt";
   let sourceUrl = httpUrl("test_public_and_private.txt");
   let testCount = 0;
 
   // Apply pref to allow all cookies.
   Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
 
@@ -1405,17 +1435,17 @@ add_task(function test_public_and_privat
   }
 
   cleanup();
 });
 
 /**
  * Checks the startTime gets updated even after a restart.
  */
-add_task(function test_cancel_immediately_restart_and_check_startTime()
+add_task(function* test_cancel_immediately_restart_and_check_startTime()
 {
   let download = yield promiseStartDownload();
 
   let startTime = download.startTime;
   do_check_true(isValidDate(download.startTime));
 
   yield download.cancel();
   do_check_eq(download.startTime.getTime(), startTime.getTime());
@@ -1425,17 +1455,17 @@ add_task(function test_cancel_immediatel
 
   yield download.start();
   do_check_true(download.startTime.getTime() > startTime.getTime());
 });
 
 /**
  * Executes download with content-encoding.
  */
-add_task(function test_with_content_encoding()
+add_task(function* test_with_content_encoding()
 {
   let sourcePath = "/test_with_content_encoding.txt";
   let sourceUrl = httpUrl("test_with_content_encoding.txt");
 
   function cleanup() {
     gHttpServer.registerPathHandler(sourcePath, null);
   }
   do_register_cleanup(cleanup);
@@ -1461,17 +1491,17 @@ add_task(function test_with_content_enco
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 
   cleanup();
 });
 
 /**
  * Checks that the file is not decoded if the extension matches the encoding.
  */
-add_task(function test_with_content_encoding_ignore_extension()
+add_task(function* test_with_content_encoding_ignore_extension()
 {
   let sourcePath = "/test_with_content_encoding_ignore_extension.gz";
   let sourceUrl = httpUrl("test_with_content_encoding_ignore_extension.gz");
 
   function cleanup() {
     gHttpServer.registerPathHandler(sourcePath, null);
   }
   do_register_cleanup(cleanup);
@@ -1500,17 +1530,17 @@ add_task(function test_with_content_enco
         String.fromCharCode.apply(String, TEST_DATA_SHORT_GZIP_ENCODED));
 
   cleanup();
 });
 
 /**
  * Cancels and restarts a download sequentially with content-encoding.
  */
-add_task(function test_cancel_midway_restart_with_content_encoding()
+add_task(function* test_cancel_midway_restart_with_content_encoding()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible_gzip.txt"));
 
   // The first time, cancel the download midway.
   let deferCancel = Promise.defer();
   let onchange = function () {
@@ -1538,17 +1568,17 @@ add_task(function test_cancel_midway_res
   do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
 
   yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
 });
 
 /**
  * Download with parental controls enabled.
  */
-add_task(function test_blocked_parental_controls()
+add_task(function* test_blocked_parental_controls()
 {
   function cleanup() {
     DownloadIntegration.shouldBlockInTest = false;
   }
   do_register_cleanup(cleanup);
   DownloadIntegration.shouldBlockInTest = true;
 
   let download;
@@ -1562,59 +1592,65 @@ add_task(function test_blocked_parental_
       // When testing DownloadLegacySaver, we cannot be sure whether we are
       // testing the promise returned by the "start" method or we are testing
       // the "error" property checked by promiseDownloadStopped.  This happens
       // because we don't have control over when the download is started.
       download = yield promiseStartLegacyDownload();
       yield promiseDownloadStopped(download);
     }
     do_throw("The download should have blocked.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseBlocked) {
+      throw ex;
+    }
     do_check_true(ex.becauseBlockedByParentalControls);
     do_check_true(download.error.becauseBlockedByParentalControls);
   }
 
   // Now that the download stopped, the target file should not exist.
   do_check_false(yield OS.File.exists(download.target.path));
 
   cleanup();
 });
 
 /**
  * Test a download that will be blocked by Windows parental controls by
  * resulting in an HTTP status code of 450.
  */
-add_task(function test_blocked_parental_controls_httpstatus450()
+add_task(function* test_blocked_parental_controls_httpstatus450()
 {
   let download;
   try {
     if (!gUseLegacySaver) {
       download = yield promiseNewDownload(httpUrl("parentalblocked.zip"));
       yield download.start();
     }
     else {
       download = yield promiseStartLegacyDownload(httpUrl("parentalblocked.zip"));
       yield promiseDownloadStopped(download);
     }
     do_throw("The download should have blocked.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseBlocked) {
+      throw ex;
+    }
     do_check_true(ex.becauseBlockedByParentalControls);
     do_check_true(download.error.becauseBlockedByParentalControls);
     do_check_true(download.stopped);
   }
 
   do_check_false(yield OS.File.exists(download.target.path));
 });
 
 /**
  * Check that DownloadCopySaver can always retrieve the hash.
  * DownloadLegacySaver can only retrieve the hash when
  * nsIExternalHelperAppService is invoked.
  */
-add_task(function test_getSha256Hash()
+add_task(function* test_getSha256Hash()
 {
   if (!gUseLegacySaver) {
     let download = yield promiseStartDownload(httpUrl("source.txt"));
     yield promiseDownloadStopped(download);
     do_check_true(download.stopped);
     do_check_eq(32, download.saver.getSha256Hash().length);
   }
 });
@@ -1653,34 +1689,37 @@ var promiseBlockedDownload = Task.async(
     } else {
       download = yield promiseNewDownload();
       yield download.start();
       do_throw("The download should have blocked.");
     }
 
     yield promiseDownloadStopped(download);
     do_throw("The download should have blocked.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseBlocked) {
+      throw ex;
+    }
     do_check_true(ex.becauseBlockedByReputationCheck);
     do_check_true(download.error.becauseBlockedByReputationCheck);
   }
 
   do_check_true(download.stopped);
   do_check_false(download.succeeded);
   do_check_false(yield OS.File.exists(download.target.path));
 
   cleanup();
   return download;
 });
 
 /**
  * Checks that application reputation blocks the download and the target file
  * does not exist.
  */
-add_task(function test_blocked_applicationReputation()
+add_task(function* test_blocked_applicationReputation()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: false,
     keepBlockedData: false,
   });
 
   // Now that the download is blocked, the target file should not exist.
   do_check_false(yield OS.File.exists(download.target.path));
@@ -1690,17 +1729,17 @@ add_task(function test_blocked_applicati
   // There should also be no blocked data in this case
   do_check_false(download.hasBlockedData);
 });
 
 /**
  * Checks that application reputation blocks the download but maintains the
  * blocked data, which will be deleted when the block is confirmed.
  */
-add_task(function test_blocked_applicationReputation_confirmBlock()
+add_task(function* test_blocked_applicationReputation_confirmBlock()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: true,
     keepBlockedData: true,
   });
 
   do_check_true(download.hasBlockedData);
   do_check_true(yield OS.File.exists(download.target.partFilePath));
@@ -1717,17 +1756,17 @@ add_task(function test_blocked_applicati
   do_check_false(download.target.exists);
   do_check_eq(download.target.size, 0);
 });
 
 /**
  * Checks that application reputation blocks the download but maintains the
  * blocked data, which will be used to complete the download when unblocking.
  */
-add_task(function test_blocked_applicationReputation_unblock()
+add_task(function* test_blocked_applicationReputation_unblock()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: true,
     keepBlockedData: true,
   });
 
   do_check_true(download.hasBlockedData);
   do_check_true(yield OS.File.exists(download.target.partFilePath));
@@ -1747,17 +1786,17 @@ add_task(function test_blocked_applicati
   do_check_true(download.error instanceof Downloads.Error);
   do_check_true(download.error.becauseBlocked);
   do_check_true(download.error.becauseBlockedByReputationCheck);
 });
 
 /**
  * Check that calling cancel on a blocked download will not cause errors
  */
-add_task(function test_blocked_applicationReputation_cancel()
+add_task(function* test_blocked_applicationReputation_cancel()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: true,
     keepBlockedData: true,
   });
 
   // This call should succeed on a blocked download.
   yield download.cancel();
@@ -1768,17 +1807,17 @@ add_task(function test_blocked_applicati
   do_check_true(download.stopped);
   do_check_false(download.succeeded);
   do_check_true(download.hasBlockedData);
 });
 
 /**
  * Checks that unblock and confirmBlock cannot race on a blocked download
  */
-add_task(function test_blocked_applicationReputation_decisionRace()
+add_task(function* test_blocked_applicationReputation_decisionRace()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: true,
     keepBlockedData: true,
   });
 
   let unblockPromise = download.unblock();
   let confirmBlockPromise = download.confirmBlock();
@@ -1821,17 +1860,17 @@ add_task(function test_blocked_applicati
   do_check_false(download.target.exists);
   do_check_eq(download.target.size, 0);
 });
 
 /**
  * Checks that unblocking a blocked download fails if the blocked data has been
  * removed.
  */
-add_task(function test_blocked_applicationReputation_unblock()
+add_task(function* test_blocked_applicationReputation_unblock()
 {
   let download = yield promiseBlockedDownload({
     keepPartialData: true,
     keepBlockedData: true,
   });
 
   do_check_true(download.hasBlockedData);
   do_check_true(yield OS.File.exists(download.target.partFilePath));
@@ -1851,30 +1890,33 @@ add_task(function test_blocked_applicati
   do_check_false(download.succeeded);
   do_check_false(download.target.exists);
   do_check_eq(download.target.size, 0);
 });
 
 /**
  * download.showContainingDirectory() action
  */
-add_task(function test_showContainingDirectory() {
+add_task(function* test_showContainingDirectory() {
   DownloadIntegration._deferTestShowDir = Promise.defer();
 
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
 
   let download = yield Downloads.createDownload({
     source: { url: httpUrl("source.txt") },
     target: ""
   });
 
   try {
     yield download.showContainingDirectory();
     do_throw("Should have failed because of an invalid path.");
-  } catch (ex if ex instanceof Components.Exception) {
+  } catch (ex) {
+    if (!(ex instanceof Components.Exception)) {
+      throw ex;
+    }
     // Invalid paths on Windows are reported with NS_ERROR_FAILURE,
     // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
     let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
                       ex.result == Cr.NS_ERROR_FAILURE;
     do_check_true(validResult);
   }
 
   download = yield Downloads.createDownload({
@@ -1887,17 +1929,17 @@ add_task(function test_showContainingDir
   download.showContainingDirectory();
   let result = yield DownloadIntegration._deferTestShowDir.promise;
   do_check_eq(result, "success");
 });
 
 /**
  * download.launch() action
  */
-add_task(function test_launch() {
+add_task(function* test_launch() {
   let customLauncher = getTempFile("app-launcher");
 
   // Test both with and without setting a custom application.
   for (let launcherPath of [null, customLauncher.path]) {
     let download;
     if (!gUseLegacySaver) {
       // When testing DownloadCopySaver, we have control over the download, thus
       // we can test that file is not launched if download.succeeded is not set.
@@ -1945,43 +1987,46 @@ add_task(function test_launch() {
                           .executable.equals(customLauncher));
     }
   }
 });
 
 /**
  * Test passing an invalid path as the launcherPath property.
  */
-add_task(function test_launcherPath_invalid() {
+add_task(function* test_launcherPath_invalid() {
   let download = yield Downloads.createDownload({
     source: { url: httpUrl("source.txt") },
     target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
     launcherPath: " "
   });
 
   DownloadIntegration._deferTestOpenFile = Promise.defer();
   yield download.start();
   try {
     download.launch();
     result = yield DownloadIntegration._deferTestOpenFile.promise;
     do_throw("Can't launch file with invalid custom launcher")
-  } catch (ex if ex instanceof Components.Exception) {
+  } catch (ex) {
+    if (!(ex instanceof Components.Exception)) {
+      throw ex;
+    }
     // Invalid paths on Windows are reported with NS_ERROR_FAILURE,
     // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
     let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
                       ex.result == Cr.NS_ERROR_FAILURE;
     do_check_true(validResult);
   }
 });
 
 /**
  * Tests that download.launch() is automatically called after
  * the download finishes if download.launchWhenSucceeded = true
  */
-add_task(function test_launchWhenSucceeded() {
+add_task(function* test_launchWhenSucceeded() {
   let customLauncher = getTempFile("app-launcher");
 
   // Test both with and without setting a custom application.
   for (let launcherPath of [null, customLauncher.path]) {
     DownloadIntegration._deferTestOpenFile = Promise.defer();
 
     if (!gUseLegacySaver) {
       let download = yield Downloads.createDownload({
@@ -2013,28 +2058,28 @@ add_task(function test_launchWhenSucceed
                           .executable.equals(customLauncher));
     }
   }
 });
 
 /**
  * Tests that the proper content type is set for a normal download.
  */
-add_task(function test_contentType() {
+add_task(function* test_contentType() {
   let download = yield promiseStartDownload(httpUrl("source.txt"));
   yield promiseDownloadStopped(download);
 
   do_check_eq("text/plain", download.contentType);
 });
 
 /**
  * Tests that the serialization/deserialization of the startTime Date
  * object works correctly.
  */
-add_task(function test_toSerializable_startTime()
+add_task(function* test_toSerializable_startTime()
 {
   let download1 = yield promiseStartDownload(httpUrl("source.txt"));
   yield promiseDownloadStopped(download1);
 
   let serializable = download1.toSerializable();
   let reserialized = JSON.parse(JSON.stringify(serializable));
 
   let download2 = yield Downloads.createDownload(reserialized);
@@ -2044,17 +2089,17 @@ add_task(function test_toSerializable_st
   do_check_eq(download1.startTime.toJSON(), download2.startTime.toJSON());
 });
 
 /**
  * This test will call the platform specific operations within
  * DownloadPlatform::DownloadDone. While there is no test to verify the
  * specific behaviours, this at least ensures that there is no error or crash.
  */
-add_task(function test_platform_integration()
+add_task(function* test_platform_integration()
 {
   let downloadFiles = [];
   let oldDeviceStorageEnabled = false;
   try {
      oldDeviceStorageEnabled = Services.prefs.getBoolPref("device.storage.enabled");
   } catch (e) {
     // This happens if the pref doesn't exist.
   }
@@ -2115,17 +2160,17 @@ add_task(function test_platform_integrat
 
     yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
   }
 });
 
 /**
  * Checks that downloads are added to browsing history when they start.
  */
-add_task(function test_history()
+add_task(function* test_history()
 {
   mustInterruptResponses();
 
   // We will wait for the visit to be notified during the download.
   yield PlacesTestUtils.clearHistory();
   let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
 
   // Start a download that is not allowed to finish yet.
@@ -2145,17 +2190,17 @@ add_task(function test_history()
   // The restart should not have added a new history visit.
   do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
 });
 
 /**
  * Checks that downloads started by nsIHelperAppService are added to the
  * browsing history when they start.
  */
-add_task(function test_history_tryToKeepPartialData()
+add_task(function* test_history_tryToKeepPartialData()
 {
   // We will wait for the visit to be notified during the download.
   yield PlacesTestUtils.clearHistory();
   let promiseVisit =
       promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
 
   // Start a download that is not allowed to finish yet.
   let beforeStartTimeMs = Date.now();
@@ -2174,17 +2219,17 @@ add_task(function test_history_tryToKeep
   continueResponses();
   yield promiseDownloadStopped(download);
 });
 
 /**
  * Tests that the temp download files are removed on exit and exiting private
  * mode after they have been launched.
  */
-add_task(function test_launchWhenSucceeded_deleteTempFileOnExit() {
+add_task(function* test_launchWhenSucceeded_deleteTempFileOnExit() {
   const kDeleteTempFileOnExit = "browser.helperApps.deleteTempFileOnExit";
 
   let customLauncherPath = getTempFile("app-launcher").path;
   let autoDeleteTargetPathOne = getTempFile(TEST_TARGET_FILE_NAME).path;
   let autoDeleteTargetPathTwo = getTempFile(TEST_TARGET_FILE_NAME).path;
   let noAutoDeleteTargetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
 
   let autoDeleteDownloadOne = yield Downloads.createDownload({
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -527,17 +527,17 @@ function promiseNewList(aIsPrivate)
  *        String containing the octets that are expected in the file.
  *
  * @return {Promise}
  * @resolves When the operation completes.
  * @rejects Never.
  */
 function promiseVerifyContents(aPath, aExpectedContents)
 {
-  return Task.spawn(function() {
+  return Task.spawn(function* () {
     let file = new FileUtils.File(aPath);
 
     if (!(yield OS.File.exists(aPath))) {
       do_throw("File does not exist: " + aPath);
     }
 
     if ((yield OS.File.stat(aPath)).size == 0) {
       do_throw("File is empty: " + aPath);
--- a/toolkit/components/jsdownloads/test/unit/tail.js
+++ b/toolkit/components/jsdownloads/test/unit/tail.js
@@ -7,17 +7,17 @@
  * Provides infrastructure for automated download components tests.
  */
 
 "use strict";
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Termination functions common to all tests
 
-add_task(function test_common_terminate()
+add_task(function* test_common_terminate()
 {
   // Ensure all the pending HTTP requests have a chance to finish.
   continueResponses();
 
   // Stop the HTTP server.  We must do this inside a task in "tail.js" until the
   // xpcshell testing framework supports asynchronous termination functions.
   let deferred = Promise.defer();
   gHttpServer.stop(deferred.resolve);
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadImport.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadImport.js
@@ -61,17 +61,17 @@ var gDownloadsRowNonImportable;
  * @param aSchemaVersion
  *        Number with the version of the database schema to set.
  *
  * @return {Promise}
  * @resolves The open connection to the database.
  * @rejects If an error occurred during the database creation.
  */
 function promiseEmptyDatabaseConnection({aPath, aSchemaVersion}) {
-  return Task.spawn(function () {
+  return Task.spawn(function* () {
     let connection = yield Sqlite.openConnection({ path: aPath });
 
     yield connection.execute("CREATE TABLE moz_downloads ("
                              + "id INTEGER PRIMARY KEY,"
                              + "name TEXT,"
                              + "source TEXT,"
                              + "target TEXT,"
                              + "tempPath TEXT,"
@@ -85,17 +85,17 @@ function promiseEmptyDatabaseConnection(
                              + "mimeType TEXT,"
                              + "preferredApplication TEXT,"
                              + "preferredAction INTEGER NOT NULL DEFAULT 0,"
                              + "autoResume INTEGER NOT NULL DEFAULT 0,"
                              + "guid TEXT)");
 
     yield connection.setSchemaVersion(aSchemaVersion);
 
-    throw new Task.Result(connection);
+    return connection;
   });
 }
 
 /**
  * Inserts a new entry in the database with the given columns' values.
  *
  * @param aConnection
  *        The database connection.
@@ -260,17 +260,17 @@ function getStartTime(aOffset) {
  *        with extra properties describing expected values that are not
  *        explictly part of the database.
  *
  * @return {Promise}
  * @resolves When the operation completes
  * @rejects Never
  */
 function checkDownload(aDownload, aDownloadRow) {
-  return Task.spawn(function() {
+  return Task.spawn(function*() {
     do_check_eq(aDownload.source.url, aDownloadRow.source);
     do_check_eq(aDownload.source.referrer, aDownloadRow.referrer);
 
     do_check_eq(aDownload.target.path,
                 NetUtil.newURI(aDownloadRow.target)
                        .QueryInterface(Ci.nsIFileURL).file.path);
 
     do_check_eq(aDownload.target.partFilePath, aDownloadRow.tempPath);
@@ -324,17 +324,17 @@ function checkDownload(aDownload, aDownl
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Preparation tasks
 
 /**
  * Prepares the list of downloads to be added to the database that should
  * be imported by the import procedure.
  */
-add_task(function prepareDownloadsToImport() {
+add_task(function* prepareDownloadsToImport() {
 
   let sourceUrl = httpUrl("source.txt");
   let sourceEntityId = yield promiseEntityID(sourceUrl);
 
   gDownloadsRowToImport = [
     // Paused download with autoResume and a partial file. By
     // setting the correct entityID the download can resume from
     // where it stopped, and to test that this works properly we
@@ -530,17 +530,17 @@ add_task(function prepareDownloadsToImpo
     },
   ];
 });
 
 /**
  * Prepares the list of downloads to be added to the database that should
  * *not* be imported by the import procedure.
  */
-add_task(function prepareNonImportableDownloads()
+add_task(function* prepareNonImportableDownloads()
 {
   gDownloadsRowNonImportable = [
     // Download with no source (should never happen in normal circumstances).
     {
       source: "",
       target: "nonimportable1.txt",
       tempPath: "",
       startTime: getStartTime(1),
@@ -655,17 +655,17 @@ add_task(function prepareNonImportableDo
 ////////////////////////////////////////////////////////////////////////////////
 //// Test
 
 /**
  * Creates a temporary Sqlite database with download data and perform an
  * import of that data to the new Downloads API to verify that the import
  * worked correctly.
  */
-add_task(function test_downloadImport()
+add_task(function* test_downloadImport()
 {
   let connection = null;
   let downloadsSqlite = getTempFile("downloads.sqlite").path;
 
   try {
     // Set up the database.
     connection = yield promiseEmptyDatabaseConnection({
       aPath: downloadsSqlite,
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
@@ -63,17 +63,17 @@ XPCOMUtils.defineLazyGetter(this, "gStri
   return Services.strings.
     createBundle("chrome://mozapps/locale/downloads/downloads.properties");
 });
 
 /**
  * Tests that the getSystemDownloadsDirectory returns a valid download
  * directory string path.
  */
-add_task(function test_getSystemDownloadsDirectory()
+add_task(function* test_getSystemDownloadsDirectory()
 {
   // Enable test mode for the getSystemDownloadsDirectory method to return
   // temp directory instead so we can check whether the desired directory
   // is created or not.
   DownloadIntegration.testMode = true;
   function cleanup() {
     DownloadIntegration.testMode = false;
   }
@@ -113,17 +113,17 @@ add_task(function test_getSystemDownload
   let downloadDirAfter = yield DownloadIntegration.getSystemDownloadsDirectory();
   do_check_neq(downloadDirBefore, downloadDirAfter);
 });
 
 /**
  * Tests that the getPreferredDownloadsDirectory returns a valid download
  * directory string path.
  */
-add_task(function test_getPreferredDownloadsDirectory()
+add_task(function* test_getPreferredDownloadsDirectory()
 {
   let folderListPrefName = "browser.download.folderList";
   let dirPrefName = "browser.download.dir";
   function cleanup() {
     Services.prefs.clearUserPref(folderListPrefName);
     Services.prefs.clearUserPref(dirPrefName);
   }
   do_register_cleanup(cleanup);
@@ -176,17 +176,17 @@ add_task(function test_getPreferredDownl
 
   cleanup();
 });
 
 /**
  * Tests that the getTemporaryDownloadsDirectory returns a valid download
  * directory string path.
  */
-add_task(function test_getTemporaryDownloadsDirectory()
+add_task(function* test_getTemporaryDownloadsDirectory()
 {
   let downloadDir = yield DownloadIntegration.getTemporaryDownloadsDirectory();
   do_check_neq(downloadDir, "");
 
   if ("nsILocalFileMac" in Ci) {
     let preferredDownloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
     do_check_eq(downloadDir, preferredDownloadDir);
   } else {
@@ -197,17 +197,17 @@ add_task(function test_getTemporaryDownl
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests DownloadObserver
 
 /**
  * Tests notifications prompts when observers are notified if there are public
  * and private active downloads.
  */
-add_task(function test_notifications()
+add_task(function* test_notifications()
 {
   enableObserversTestMode();
 
   for (let isPrivate of [false, true]) {
     mustInterruptResponses();
 
     let list = yield promiseNewList(isPrivate);
     let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
@@ -237,17 +237,17 @@ add_task(function test_notifications()
     yield list.remove(download3);
   }
 });
 
 /**
  * Tests that notifications prompts observers are not notified if there are no
  * public or private active downloads.
  */
-add_task(function test_no_notifications()
+add_task(function* test_no_notifications()
 {
   enableObserversTestMode();
 
   for (let isPrivate of [false, true]) {
     let list = yield promiseNewList(isPrivate);
     let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
     let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
     download1.start();
@@ -267,17 +267,17 @@ add_task(function test_no_notifications(
     yield list.remove(download2);
   }
 });
 
 /**
  * Tests notifications prompts when observers are notified if there are public
  * and private active downloads at the same time.
  */
-add_task(function test_mix_notifications()
+add_task(function* test_mix_notifications()
 {
   enableObserversTestMode();
   mustInterruptResponses();
 
   let publicList = yield promiseNewList();
   let privateList = yield Downloads.getList(Downloads.PRIVATE);
   let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
   let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
@@ -299,31 +299,31 @@ add_task(function test_mix_notifications
   yield publicList.remove(download1);
   yield privateList.remove(download2);
 });
 
 /**
  * Tests suspending and resuming as well as going offline and then online again.
  * The downloads should stop when suspending and start again when resuming.
  */
-add_task(function test_suspend_resume()
+add_task(function* test_suspend_resume()
 {
   enableObserversTestMode();
 
   // The default wake delay is 10 seconds, so set the wake delay to be much
   // faster for these tests.
   Services.prefs.setIntPref("browser.download.manager.resumeOnWakeDelay", 5);
 
   let addDownload = function(list)
   {
-    return Task.spawn(function () {
+    return Task.spawn(function* () {
       let download = yield promiseNewDownload(httpUrl("interruptible.txt"));
       download.start();
       list.add(download);
-      throw new Task.Result(download);
+      return download;
     });
   }
 
   let publicList = yield promiseNewList();
   let privateList = yield promiseNewList(true);
 
   let download1 = yield addDownload(publicList);
   let download2 = yield addDownload(publicList);
@@ -380,17 +380,17 @@ add_task(function test_suspend_resume()
 
   Services.prefs.clearUserPref("browser.download.manager.resumeOnWakeDelay");
 });
 
 /**
  * Tests both the downloads list and the in-progress downloads are clear when
  * private browsing observer is notified.
  */
-add_task(function test_exit_private_browsing()
+add_task(function* test_exit_private_browsing()
 {
   enableObserversTestMode();
   mustInterruptResponses();
 
   let privateList = yield promiseNewList(true);
   let download1 = yield promiseNewDownload(httpUrl("source.txt"));
   let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
   let promiseAttempt1 = download1.start();
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
@@ -66,32 +66,32 @@ function promiseExpirableDownloadVisit(a
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 /**
  * Checks the testing mechanism used to build different download lists.
  */
-add_task(function test_construction()
+add_task(function* test_construction()
 {
   let downloadListOne = yield promiseNewList();
   let downloadListTwo = yield promiseNewList();
   let privateDownloadListOne = yield promiseNewList(true);
   let privateDownloadListTwo = yield promiseNewList(true);
 
   do_check_neq(downloadListOne, downloadListTwo);
   do_check_neq(privateDownloadListOne, privateDownloadListTwo);
   do_check_neq(downloadListOne, privateDownloadListOne);
 });
 
 /**
  * Checks the methods to add and retrieve items from the list.
  */
-add_task(function test_add_getAll()
+add_task(function* test_add_getAll()
 {
   let list = yield promiseNewList();
 
   let downloadOne = yield promiseNewDownload();
   yield list.add(downloadOne);
 
   let itemsOne = yield list.getAll();
   do_check_eq(itemsOne.length, 1);
@@ -107,17 +107,17 @@ add_task(function test_add_getAll()
 
   // The first snapshot should not have been modified.
   do_check_eq(itemsOne.length, 1);
 });
 
 /**
  * Checks the method to remove items from the list.
  */
-add_task(function test_remove()
+add_task(function* test_remove()
 {
   let list = yield promiseNewList();
 
   yield list.add(yield promiseNewDownload());
   yield list.add(yield promiseNewDownload());
 
   let items = yield list.getAll();
   yield list.remove(items[0]);
@@ -129,17 +129,17 @@ add_task(function test_remove()
   do_check_eq(items.length, 1);
 });
 
 /**
  * Tests that the "add", "remove", and "getAll" methods on the global
  * DownloadCombinedList object combine the contents of the global DownloadList
  * objects for public and private downloads.
  */
-add_task(function test_DownloadCombinedList_add_remove_getAll()
+add_task(function* test_DownloadCombinedList_add_remove_getAll()
 {
   let publicList = yield promiseNewList();
   let privateList = yield Downloads.getList(Downloads.PRIVATE);
   let combinedList = yield Downloads.getList(Downloads.ALL);
 
   let publicDownload = yield promiseNewDownload();
   let privateDownload = yield Downloads.createDownload({
     source: { url: httpUrl("source.txt"), isPrivate: true },
@@ -169,17 +169,17 @@ add_task(function test_DownloadCombinedL
   do_check_eq((yield combinedList.getAll()).length, 0);
 });
 
 /**
  * Checks that views receive the download add and remove notifications, and that
  * adding and removing views works as expected, both for a normal and a combined
  * list.
  */
-add_task(function test_notifications_add_remove()
+add_task(function* test_notifications_add_remove()
 {
   for (let isCombined of [false, true]) {
     // Force creating a new list for both the public and combined cases.
     let list = yield promiseNewList();
     if (isCombined) {
       list = yield Downloads.getList(Downloads.ALL);
     }
 
@@ -234,17 +234,17 @@ add_task(function test_notifications_add
     do_check_eq(addNotifications, 3);
   }
 });
 
 /**
  * Checks that views receive the download change notifications, both for a
  * normal and a combined list.
  */
-add_task(function test_notifications_change()
+add_task(function* test_notifications_change()
 {
   for (let isCombined of [false, true]) {
     // Force creating a new list for both the public and combined cases.
     let list = yield promiseNewList();
     if (isCombined) {
       list = yield Downloads.getList(Downloads.ALL);
     }
 
@@ -273,17 +273,17 @@ add_task(function test_notifications_cha
     yield downloadTwo.start();
     do_check_false(receivedOnDownloadChanged);
   }
 });
 
 /**
  * Checks that the reference to "this" is correct in the view callbacks.
  */
-add_task(function test_notifications_this()
+add_task(function* test_notifications_this()
 {
   let list = yield promiseNewList();
 
   // Check that we receive change notifications.
   let receivedOnDownloadAdded = false;
   let receivedOnDownloadChanged = false;
   let receivedOnDownloadRemoved = false;
   let view = {
@@ -314,17 +314,17 @@ add_task(function test_notifications_thi
   do_check_true(receivedOnDownloadAdded);
   do_check_true(receivedOnDownloadChanged);
   do_check_true(receivedOnDownloadRemoved);
 });
 
 /**
  * Checks that download is removed on history expiration.
  */
-add_task(function test_history_expiration()
+add_task(function* test_history_expiration()
 {
   mustInterruptResponses();
 
   function cleanup() {
     Services.prefs.clearUserPref("places.history.expiration.max_pages");
   }
   do_register_cleanup(cleanup);
 
@@ -369,17 +369,17 @@ add_task(function test_history_expiratio
   yield deferred.promise;
 
   cleanup();
 });
 
 /**
  * Checks all downloads are removed after clearing history.
  */
-add_task(function test_history_clear()
+add_task(function* test_history_clear()
 {
   let list = yield promiseNewList();
   let downloadOne = yield promiseNewDownload();
   let downloadTwo = yield promiseNewDownload();
   yield list.add(downloadOne);
   yield list.add(downloadTwo);
 
   let deferred = Promise.defer();
@@ -401,17 +401,17 @@ add_task(function test_history_clear()
   // Wait for the removal notifications that may still be pending.
   yield deferred.promise;
 });
 
 /**
  * Tests the removeFinished method to ensure that it only removes
  * finished downloads.
  */
-add_task(function test_removeFinished()
+add_task(function* test_removeFinished()
 {
   let list = yield promiseNewList();
   let downloadOne = yield promiseNewDownload();
   let downloadTwo = yield promiseNewDownload();
   let downloadThree = yield promiseNewDownload();
   let downloadFour = yield promiseNewDownload();
   yield list.add(downloadOne);
   yield list.add(downloadTwo);
@@ -447,17 +447,17 @@ add_task(function test_removeFinished()
   let downloads = yield list.getAll()
   do_check_eq(downloads.length, 1);
 });
 
 /**
  * Tests the global DownloadSummary objects for the public, private, and
  * combined download lists.
  */
-add_task(function test_DownloadSummary()
+add_task(function* test_DownloadSummary()
 {
   mustInterruptResponses();
 
   let publicList = yield promiseNewList();
   let privateList = yield Downloads.getList(Downloads.PRIVATE);
 
   let publicSummary = yield Downloads.getSummary(Downloads.PUBLIC);
   let privateSummary = yield Downloads.getSummary(Downloads.PRIVATE);
@@ -541,17 +541,17 @@ add_task(function test_DownloadSummary()
   do_check_eq(combinedSummary.progressCurrentBytes, 0);
 });
 
 /**
  * Checks that views receive the summary change notification.  This is tested on
  * the combined summary when adding a public download, as we assume that if we
  * pass the test in this case we will also pass it in the others.
  */
-add_task(function test_DownloadSummary_notifications()
+add_task(function* test_DownloadSummary_notifications()
 {
   let list = yield promiseNewList();
   let summary = yield Downloads.getSummary(Downloads.ALL);
 
   let download = yield promiseNewDownload();
   yield list.add(download);
 
   // Check that we receive change notifications.
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js
@@ -39,17 +39,17 @@ function promiseNewListAndStore(aStorePa
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 /**
  * Saves downloads to a file, then reloads them.
  */
-add_task(function test_save_reload()
+add_task(function* test_save_reload()
 {
   let [listForSave, storeForSave] = yield promiseNewListAndStore();
   let [listForLoad, storeForLoad] = yield promiseNewListAndStore(
                                                  storeForSave.path);
 
   listForSave.add(yield promiseNewDownload(httpUrl("source.txt")));
   listForSave.add(yield Downloads.createDownload({
     source: { url: httpUrl("empty.txt"),
@@ -96,17 +96,17 @@ add_task(function test_save_reload()
     do_check_eq(itemsForSave[i].saver.toSerializable(),
                 itemsForLoad[i].saver.toSerializable());
   }
 });
 
 /**
  * Checks that saving an empty list deletes any existing file.
  */
-add_task(function test_save_empty()
+add_task(function* test_save_empty()
 {
   let [list, store] = yield promiseNewListAndStore();
 
   let createdFile = yield OS.File.open(store.path, { create: true });
   yield createdFile.close();
 
   yield store.save();
 
@@ -114,34 +114,34 @@ add_task(function test_save_empty()
 
   // If the file does not exist, saving should not generate exceptions.
   yield store.save();
 });
 
 /**
  * Checks that loading from a missing file results in an empty list.
  */
-add_task(function test_load_empty()
+add_task(function* test_load_empty()
 {
   let [list, store] = yield promiseNewListAndStore();
 
   do_check_false(yield OS.File.exists(store.path));
 
   yield store.load();
 
   let items = yield list.getAll();
   do_check_eq(items.length, 0);
 });
 
 /**
  * Loads downloads from a string in a predefined format.  The purpose of this
  * test is to verify that the JSON format used in previous versions can be
  * loaded, assuming the file is reloaded on the same platform.
  */
-add_task(function test_load_string_predefined()
+add_task(function* test_load_string_predefined()
 {
   let [list, store] = yield promiseNewListAndStore();
 
   // The platform-dependent file name should be generated dynamically.
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   let filePathLiteral = JSON.stringify(targetPath);
   let sourceUriLiteral = JSON.stringify(httpUrl("source.txt"));
   let emptyUriLiteral = JSON.stringify(httpUrl("empty.txt"));
@@ -169,17 +169,17 @@ add_task(function test_load_string_prede
   do_check_eq(items[1].source.url, httpUrl("empty.txt"));
   do_check_eq(items[1].source.referrer, TEST_REFERRER_URL);
   do_check_eq(items[1].target.path, targetPath);
 });
 
 /**
  * Loads downloads from a well-formed JSON string containing unrecognized data.
  */
-add_task(function test_load_string_unrecognized()
+add_task(function* test_load_string_unrecognized()
 {
   let [list, store] = yield promiseNewListAndStore();
 
   // The platform-dependent file name should be generated dynamically.
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   let filePathLiteral = JSON.stringify(targetPath);
   let sourceUriLiteral = JSON.stringify(httpUrl("source.txt"));
 
@@ -201,43 +201,46 @@ add_task(function test_load_string_unrec
 
   do_check_eq(items[0].source.url, httpUrl("source.txt"));
   do_check_eq(items[0].target.path, targetPath);
 });
 
 /**
  * Loads downloads from a malformed JSON string.
  */
-add_task(function test_load_string_malformed()
+add_task(function* test_load_string_malformed()
 {
   let [list, store] = yield promiseNewListAndStore();
 
   let string = "{\"list\":[{\"source\":null,\"target\":null}," +
                 "{\"source\":{\"url\":\"about:blank\"}}}";
 
   yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string),
                             { tmpPath: store.path + ".tmp" });
 
   try {
     yield store.load();
     do_throw("Exception expected when JSON data is malformed.");
-  } catch (ex if ex.name == "SyntaxError") {
+  } catch (ex) {
+    if (ex.name != "SyntaxError") {
+      throw ex;
+    }
     do_print("The expected SyntaxError exception was thrown.");
   }
 
   let items = yield list.getAll();
 
   do_check_eq(items.length, 0);
 });
 
 /**
  * Saves downloads with unknown properties to a file and then reloads
  * them to ensure that these properties are preserved.
  */
-add_task(function test_save_reload_unknownProperties()
+add_task(function* test_save_reload_unknownProperties()
 {
   let [listForSave, storeForSave] = yield promiseNewListAndStore();
   let [listForLoad, storeForLoad] = yield promiseNewListAndStore(
                                                  storeForSave.path);
 
   let download1 = yield promiseNewDownload(httpUrl("source.txt"));
   // startTime should be ignored as it is a known property, and error
   // is ignored by serialization
--- a/toolkit/components/jsdownloads/test/unit/test_Downloads.js
+++ b/toolkit/components/jsdownloads/test/unit/test_Downloads.js
@@ -11,43 +11,43 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 /**
  * Tests that the createDownload function exists and can be called.  More
  * detailed tests are implemented separately for the DownloadCore module.
  */
-add_task(function test_createDownload()
+add_task(function* test_createDownload()
 {
   // Creates a simple Download object without starting the download.
   yield Downloads.createDownload({
     source: { url: "about:blank" },
     target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
     saver: { type: "copy" },
   });
 });
 
 /**
  * Tests createDownload for private download.
  */
-add_task(function test_createDownload_private()
+add_task(function* test_createDownload_private()
 {
   let download = yield Downloads.createDownload({
     source: { url: "about:blank", isPrivate: true },
     target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
     saver: { type: "copy" }
   });
   do_check_true(download.source.isPrivate);
 });
 
 /**
  * Tests createDownload for normal (public) download.
  */
-add_task(function test_createDownload_public()
+add_task(function* test_createDownload_public()
 {
   let tempPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   let download = yield Downloads.createDownload({
     source: { url: "about:blank", isPrivate: false },
     target: { path: tempPath },
     saver: { type: "copy" }
   });
   do_check_false(download.source.isPrivate);
@@ -58,62 +58,66 @@ add_task(function test_createDownload_pu
     saver: { type: "copy" }
   });
   do_check_false(download.source.isPrivate);
 });
 
 /**
  * Tests createDownload for a pdf saver throws if only given a url.
  */
-add_task(function test_createDownload_pdf()
+add_task(function* test_createDownload_pdf()
 {
   let download = yield Downloads.createDownload({
     source: { url: "about:blank" },
     target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
     saver: { type: "pdf" },
   });
 
   try {
     yield download.start();
     do_throw("The download should have failed.");
-  } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) { }
+  } catch (ex) {
+    if (!(ex instanceof Downloads.Error) || !ex.becauseSourceFailed) {
+      throw ex;
+    }
+  }
 
   do_check_false(download.succeeded);
   do_check_true(download.stopped);
   do_check_false(download.canceled);
   do_check_true(download.error !== null);
   do_check_true(download.error.becauseSourceFailed);
   do_check_false(download.error.becauseTargetFailed);
   do_check_false(yield OS.File.exists(download.target.path));
 });
 
 /**
  * Tests "fetch" with nsIURI and nsIFile as arguments.
  */
-add_task(function test_fetch_uri_file_arguments()
+add_task(function* test_fetch_uri_file_arguments()
 {
   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
   yield Downloads.fetch(NetUtil.newURI(httpUrl("source.txt")), targetFile);
   yield promiseVerifyContents(targetFile.path, TEST_DATA_SHORT);
 });
 
 /**
  * Tests "fetch" with DownloadSource and DownloadTarget as arguments.
  */
-add_task(function test_fetch_object_arguments()
+add_task(function* test_fetch_object_arguments()
 {
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   yield Downloads.fetch({ url: httpUrl("source.txt") }, { path: targetPath });
   yield promiseVerifyContents(targetPath, TEST_DATA_SHORT);
 });
 
 /**
  * Tests "fetch" with string arguments.
  */
-add_task(function test_fetch_string_arguments()
+add_task(function* test_fetch_string_arguments()
 {
   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   yield Downloads.fetch(httpUrl("source.txt"), targetPath);
   yield promiseVerifyContents(targetPath, TEST_DATA_SHORT);
 
   targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   yield Downloads.fetch(new String(httpUrl("source.txt")),
                         new String(targetPath));
@@ -121,17 +125,17 @@ add_task(function test_fetch_string_argu
 });
 
 /**
  * Tests that the getList function returns the same list when called multiple
  * times with the same argument, but returns different lists when called with
  * different arguments.  More detailed tests are implemented separately for the
  * DownloadList module.
  */
-add_task(function test_getList()
+add_task(function* test_getList()
 {
   let publicListOne = yield Downloads.getList(Downloads.PUBLIC);
   let privateListOne = yield Downloads.getList(Downloads.PRIVATE);
 
   let publicListTwo = yield Downloads.getList(Downloads.PUBLIC);
   let privateListTwo = yield Downloads.getList(Downloads.PRIVATE);
 
   do_check_eq(publicListOne, publicListTwo);
@@ -141,17 +145,17 @@ add_task(function test_getList()
 });
 
 /**
  * Tests that the getSummary function returns the same summary when called
  * multiple times with the same argument, but returns different summaries when
  * called with different arguments.  More detailed tests are implemented
  * separately for the DownloadSummary object in the DownloadList module.
  */
-add_task(function test_getSummary()
+add_task(function* test_getSummary()
 {
   let publicSummaryOne = yield Downloads.getSummary(Downloads.PUBLIC);
   let privateSummaryOne = yield Downloads.getSummary(Downloads.PRIVATE);
 
   let publicSummaryTwo = yield Downloads.getSummary(Downloads.PUBLIC);
   let privateSummaryTwo = yield Downloads.getSummary(Downloads.PRIVATE);
 
   do_check_eq(publicSummaryOne, publicSummaryTwo);
@@ -159,37 +163,37 @@ add_task(function test_getSummary()
 
   do_check_neq(publicSummaryOne, privateSummaryOne);
 });
 
 /**
  * Tests that the getSystemDownloadsDirectory returns a non-empty download
  * directory string.
  */
-add_task(function test_getSystemDownloadsDirectory()
+add_task(function* test_getSystemDownloadsDirectory()
 {
   let downloadDir = yield Downloads.getSystemDownloadsDirectory();
   do_check_neq(downloadDir, "");
 });
 
 /**
  * Tests that the getPreferredDownloadsDirectory returns a non-empty download
  * directory string.
  */
-add_task(function test_getPreferredDownloadsDirectory()
+add_task(function* test_getPreferredDownloadsDirectory()
 {
   let downloadDir = yield Downloads.getPreferredDownloadsDirectory();
   do_check_neq(downloadDir, "");
 });
 
 /**
  * Tests that the getTemporaryDownloadsDirectory returns a non-empty download
  * directory string.
  */
-add_task(function test_getTemporaryDownloadsDirectory()
+add_task(function* test_getTemporaryDownloadsDirectory()
 {
   let downloadDir = yield Downloads.getTemporaryDownloadsDirectory();
   do_check_neq(downloadDir, "");
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Termination
 
--- a/toolkit/components/jsdownloads/test/unit/test_PrivateTemp.js
+++ b/toolkit/components/jsdownloads/test/unit/test_PrivateTemp.js
@@ -4,17 +4,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /*
  * The temporary directory downloads saves to, should be only readable
  * for the current user.
  */
-add_task(function test_private_temp() {
+add_task(function* test_private_temp() {
 
   let download = yield promiseStartExternalHelperAppServiceDownload(
                                                          httpUrl("empty.txt"));
 
   yield promiseDownloadStopped(download);
 
   var targetFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
   targetFile.initWithPath(download.target.path);
--- a/toolkit/components/satchel/FormHistory.jsm
+++ b/toolkit/components/satchel/FormHistory.jsm
@@ -86,32 +86,28 @@
 this.EXPORTED_SYMBOLS = ["FormHistory"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidService",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 const DB_SCHEMA_VERSION = 4;
 const DAY_IN_MS  = 86400000; // 1 day in milliseconds
 const MAX_SEARCH_TOKENS = 10;
 const NOOP = function noop() {};
 
-var supportsDeletedTable =
-#ifdef ANDROID
-  true;
-#else
-  false;
-#endif
+var supportsDeletedTable = AppConstants.platform == "android";
 
 var Prefs = {
   initialized: false,
 
   get debug() { this.ensureInitialized(); return this._debug; },
   get enabled() { this.ensureInitialized(); return this._enabled; },
   get expireDays() { this.ensureInitialized(); return this._expireDays; },
 
@@ -377,17 +373,19 @@ XPCOMUtils.defineLazyGetter(this, "dbCon
 
   try {
     dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
     dbFile.append("formhistory.sqlite");
     log("Opening database at " + dbFile.path);
 
     _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
     dbInit();
-  } catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+  } catch (e) {
+    if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
+      throw e;
     dbCleanup(dbFile);
     _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
     dbInit();
   }
 
   return _dbConnection;
 });
 
@@ -460,17 +458,17 @@ function dbInit() {
     dbMigrate(version);
   }
 }
 
 function dbCreate() {
   log("Creating DB -- tables");
   for (let name in dbSchema.tables) {
     let table = dbSchema.tables[name];
-    let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
+    let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
     log("Creating table " + name + " with " + tSQL);
     _dbConnection.createTable(name, tSQL);
   }
 
   log("Creating DB -- indices");
   for (let name in dbSchema.indices) {
     let index = dbSchema.indices[name];
     let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
@@ -527,33 +525,33 @@ function dbMigrate(oldVersion) {
 var Migrators = {
   /*
    * Updates the DB schema to v3 (bug 506402).
    * Adds deleted form history table.
    */
   dbMigrateToVersion4: function dbMigrateToVersion4() {
     if (!_dbConnection.tableExists("moz_deleted_formhistory")) {
       let table = dbSchema.tables["moz_deleted_formhistory"];
-      let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
+      let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
       _dbConnection.createTable("moz_deleted_formhistory", tSQL);
     }
   }
 };
 
 /**
  * dbAreExpectedColumnsPresent
  *
  * Sanity check to ensure that the columns this version of the code expects
  * are present in the DB we're using.
  */
 function dbAreExpectedColumnsPresent() {
   for (let name in dbSchema.tables) {
     let table = dbSchema.tables[name];
     let query = "SELECT " +
-                [col for (col in table)].join(", ") +
+                Object.keys(table).join(", ") +
                 " FROM " + name;
     try {
       let stmt = _dbConnection.createStatement(query);
       // (no need to execute statement, if it compiled we're good)
       stmt.finalize();
     } catch (e) {
       return false;
     }
--- a/toolkit/components/satchel/moz.build
+++ b/toolkit/components/satchel/moz.build
@@ -23,31 +23,25 @@ SOURCES += [
 
 LOCAL_INCLUDES += [
     '../build',
 ]
 
 EXTRA_COMPONENTS += [
     'FormHistoryStartup.js',
     'nsFormAutoComplete.js',
+    'nsFormHistory.js',
     'nsInputListAutoComplete.js',
     'satchel.manifest',
 ]
 
-EXTRA_PP_COMPONENTS += [
-    'nsFormHistory.js',
-]
-
 EXTRA_JS_MODULES += [
     'AutoCompleteE10S.jsm',
+    'FormHistory.jsm',
     'nsFormAutoCompleteResult.jsm',
 ]
 
-EXTRA_PP_JS_MODULES += [
-    'FormHistory.jsm',
-]
-
 FINAL_LIBRARY = 'xul'
 
 JAR_MANIFESTS += ['jar.mn']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/toolkit/components/satchel/nsFormAutoComplete.js
+++ b/toolkit/components/satchel/nsFormAutoComplete.js
@@ -512,17 +512,17 @@ FormAutoCompleteChild.prototype = {
         // was cancelled, while waiting for a result.
         if (search != this._pendingSearch) {
           return;
         }
         this._pendingSearch = null;
 
         let result = new FormAutoCompleteResult(
           null,
-          [for (res of message.data.results) {text: res}],
+          Array.from(message.data.results, res => ({ text: res })),
           null,
           null,
           mm
         );
         if (aListener) {
           aListener.onSearchCompletion(result);
         }
       }
--- a/toolkit/components/satchel/nsFormHistory.js
+++ b/toolkit/components/satchel/nsFormHistory.js
@@ -344,36 +344,36 @@ FormHistory.prototype = {
                 stmt.reset();
             }
         }
         if (!existingTransactionInProgress)
             this.dbConnection.commitTransaction();
     },
 
     moveToDeletedTable : function moveToDeletedTable(values, params) {
-#ifdef ANDROID
-        this.log("Moving entries to deleted table.");
+        if (AppConstants.platform == "android") {
+            this.log("Moving entries to deleted table.");
 
-        let stmt;
+            let stmt;
 
-        try {
-            // Move the entries to the deleted items table.
-            let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted) ";
-            if (values) query += values;
-            stmt = this.dbCreateStatement(query, params);
-            stmt.execute();
-        } catch (e) {
-            this.log("Moving deleted entries failed: " + e);
-            throw e;
-        } finally {
-            if (stmt) {
-                stmt.reset();
+            try {
+                // Move the entries to the deleted items table.
+                let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted) ";
+                if (values) query += values;
+                stmt = this.dbCreateStatement(query, params);
+                stmt.execute();
+            } catch (e) {
+                this.log("Moving deleted entries failed: " + e);
+                throw e;
+            } finally {
+                if (stmt) {
+                    stmt.reset();
+                }
             }
         }
-#endif
     },
 
     get dbConnection() {
         // Make sure dbConnection can't be called from now to prevent infinite loops.
         delete FormHistory.prototype.dbConnection;
 
         try {
             this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
@@ -619,17 +619,17 @@ FormHistory.prototype = {
                             "(" + index.columns.join(", ") + ")";
             this.dbConnection.executeSimpleSQL(statement);
         }
 
         this.dbConnection.schemaVersion = DB_VERSION;
     },
 
     dbCreateTable: function(name, table) {
-        let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
+        let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
         this.log("Creating table " + name + " with " + tSQL);
         this.dbConnection.createTable(name, tSQL);
     },
 
     dbMigrate : function (oldVersion) {
         this.log("Attempting to migrate from version " + oldVersion);
 
         if (oldVersion > DB_VERSION) {
@@ -797,17 +797,17 @@ FormHistory.prototype = {
      *
      * Sanity check to ensure that the columns this version of the code expects
      * are present in the DB we're using.
      */
     dbAreExpectedColumnsPresent : function () {
         for (let name in this.dbSchema.tables) {
             let table = this.dbSchema.tables[name];
             let query = "SELECT " +
-                        [col for (col in table)].join(", ") +
+                        Object.keys(table).join(", ") +
                         " FROM " + name;
             try {
                 let stmt = this.dbConnection.createStatement(query);
                 // (no need to execute statement, if it compiled we're good)
                 stmt.finalize();
             } catch (e) {
                 return false;
             }
--- a/toolkit/components/satchel/test/unit/head_satchel.js
+++ b/toolkit/components/satchel/test/unit/head_satchel.js
@@ -38,17 +38,17 @@ const isGUID = /[A-Za-z0-9\+\/]{16}/;
 
 // Find form history entries.
 function searchEntries(terms, params, iter) {
   let results = [];
   FormHistory.search(terms, params, { handleResult: result => results.push(result),
                                       handleError: function (error) {
                                         do_throw("Error occurred searching form history: " + error);
                                       },
-                                      handleCompletion: function (reason) { if (!reason) iter.send(results); }
+                                      handleCompletion: function (reason) { if (!reason) iter.next(results); }
                                     });
 }
 
 // Count the number of entries with the given name and value, and call then(number)
 // when done. If name or value is null, then the value of that field does not matter.
 function countEntries(name, value, then) {
   var obj = {};
   if (name !== null)
--- a/toolkit/components/satchel/test/unit/test_async_expire.js
+++ b/toolkit/components/satchel/test/unit/test_async_expire.js
@@ -43,17 +43,17 @@ function run_test()
   iter.next();
 }
 
 function next_test()
 {
   iter.next();
 }
 
-function tests()
+function* tests()
 {
   Services.obs.addObserver(TestObserver, "satchel-storage-changed", true);
 
   // ===== test init =====
   var testfile = do_get_file("asyncformhistory_expire.sqlite");
   var profileDir = do_get_profile();
 
   // Cleanup from any previous tests or failures.
--- a/toolkit/components/satchel/test/unit/test_db_update_v4.js
+++ b/toolkit/components/satchel/test/unit/test_db_update_v4.js
@@ -8,17 +8,17 @@ var iter;
 
 function run_test()
 {
   do_test_pending();
   iter = next_test();
   iter.next();
 }
 
-function next_test()
+function* next_test()
 {
   try {
 
   // ===== test init =====
   var testfile = do_get_file("formhistory_v3.sqlite");
   var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
 
   // Cleanup from any previous tests or failures.
--- a/toolkit/components/satchel/test/unit/test_db_update_v4b.js
+++ b/toolkit/components/satchel/test/unit/test_db_update_v4b.js
@@ -8,17 +8,17 @@ var iter;
 
 function run_test()
 {
   do_test_pending();
   iter = next_test();
   iter.next();
 }
 
-function next_test()
+function* next_test()
 {
   try {
 
   // ===== test init =====
   var testfile = do_get_file("formhistory_v3v4.sqlite");
   var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
 
   // Cleanup from any previous tests or failures.
--- a/toolkit/components/satchel/test/unit/test_db_update_v999a.js
+++ b/toolkit/components/satchel/test/unit/test_db_update_v999a.js
@@ -19,17 +19,17 @@ function run_test()
   iter.next();
 }
 
 function next_test()
 {
   iter.next();
 }
 
-function tests()
+function* tests()
 {
   try {
   var testnum = 0;
 
   // ===== test init =====
   var testfile = do_get_file("formhistory_v999a.sqlite");
   var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
 
--- a/toolkit/components/satchel/test/unit/test_db_update_v999b.js
+++ b/toolkit/components/satchel/test/unit/test_db_update_v999b.js
@@ -19,17 +19,17 @@ function run_test()
   iter.next();
 }
 
 function next_test()
 {
   iter.next();
 }
 
-function tests()
+function* tests()
 {
   try {
   var testnum = 0;
 
   // ===== test init =====
   var testfile = do_get_file("formhistory_v999b.sqlite");
   var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
 
--- a/toolkit/components/satchel/test/unit/test_history_api.js
+++ b/toolkit/components/satchel/test/unit/test_history_api.js
@@ -88,17 +88,17 @@ function promiseSearchEntries(terms, par
 
 function promiseCountEntries(name, value, checkFn)
 {
   let deferred = Promise.defer();
   countEntries(name, value, function (result) { checkFn(result); deferred.resolve(); } );
   return deferred.promise;
 }
 
-add_task(function ()
+add_task(function* ()
 {
   let oldSupportsDeletedTable = FormHistory._supportsDeletedTable;
   FormHistory._supportsDeletedTable = true;
 
   try {
 
   // ===== test init =====
   var testfile = do_get_file("formhistory_apitest.sqlite");
--- a/toolkit/components/satchel/test/unit/test_notify.js
+++ b/toolkit/components/satchel/test/unit/test_notify.js
@@ -41,17 +41,17 @@ function run_test() {
   testIterator.next();
 }
 
 function next_test()
 {
   testIterator.next();
 }
 
-function run_test_steps() {
+function* run_test_steps() {
 
 try {
 
 var testnum = 0;
 var testdesc = "Setup of test form history entries";
 
 var entry1 = ["entry1", "value1"];
 var entry2 = ["entry2", "value2"];
--- a/toolkit/components/telemetry/TelemetryArchive.jsm
+++ b/toolkit/components/telemetry/TelemetryArchive.jsm
@@ -94,21 +94,21 @@ var TelemetryArchiveImpl = {
         return Promise.reject(new Error("missing field " + field));
       }
     }
 
     return TelemetryStorage.saveArchivedPing(ping);
   },
 
   _buildArchivedPingList: function(archivedPingsMap) {
-    let list = [for (p of archivedPingsMap) {
+    let list = Array.from(archivedPingsMap, p => ({
       id: p[0],
       timestampCreated: p[1].timestampCreated,
       type: p[1].type,
-    }];
+    }));
 
     list.sort((a, b) => a.timestampCreated - b.timestampCreated);
 
     return list;
   },
 
   promiseArchivedPingList: function() {
     this._log.trace("promiseArchivedPingList");
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -448,17 +448,17 @@ var SendScheduler = {
       let sending = pending.slice(0, MAX_PING_SENDS_PER_MINUTE);
       pending = pending.slice(MAX_PING_SENDS_PER_MINUTE);
       this._log.trace("_doSendTask - triggering sending of " + sending.length + " pings now" +
                       ", " + pending.length + " pings waiting");
 
       this._sendsFailed = false;
       const sendStartTime = Policy.now();
       this._sendTaskState = "wait on ping sends";
-      yield TelemetrySendImpl.sendPings(current, [for (p of sending) p.id]);
+      yield TelemetrySendImpl.sendPings(current, sending.map(p => p.id));
       if (this._shutdown || (TelemetrySend.pendingPingCount == 0)) {
         this._log.trace("_doSendTask - bailing out after sending, shutdown: " + this._shutdown +
                         ", pendingPingCount: " + TelemetrySend.pendingPingCount);
         this._sendTaskState = "bail out - shutdown & pending check after send";
         return;
       }
 
       // Calculate the delay before sending the next batch of pings.
@@ -1062,19 +1062,19 @@ var TelemetrySendImpl = {
 
   /**
    * Return a promise that allows to wait on pending pings.
    * @return {Object<Promise>} A promise resolved when all the pending pings promises
    *         are resolved.
    */
   promisePendingPingActivity: function () {
     this._log.trace("promisePendingPingActivity - Waiting for ping task");
-    let p = [for (p of this._pendingPingActivity) p.catch(ex => {
+    let p = Array.from(this._pendingPingActivity, p => p.catch(ex => {
       this._log.error("promisePendingPingActivity - ping activity had an error", ex);
-    })];
+    }));
     p.push(SendScheduler.waitOnSendTask());
     return Promise.all(p);
   },
 
   _persistCurrentPings: Task.async(function*() {
     for (let [id, ping] of this._currentPings) {
       try {
         yield savePing(ping);
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1103,17 +1103,17 @@ var Impl = {
    * Return true if we're interested in having a STARTUP_* histogram for
    * the given histogram name.
    */
   isInterestingStartupHistogram: function isInterestingStartupHistogram(name) {
     return this._startupHistogramRegex.test(name);
   },
 
   getChildPayloads: function getChildPayloads() {
-    return [for (child of this._childTelemetry) child.payload];
+    return this._childTelemetry.map(child => child.payload);
   },
 
   /**
    * Make a copy of interesting histograms at startup.
    */
   gatherStartupHistograms: function gatherStartupHistograms() {
     this._log.trace("gatherStartupHistograms");
 
--- a/toolkit/components/telemetry/TelemetryStorage.jsm
+++ b/toolkit/components/telemetry/TelemetryStorage.jsm
@@ -111,17 +111,17 @@ var Policy = {
 };
 
 /**
  * Wait for all promises in iterable to resolve or reject. This function
  * always resolves its promise with undefined, and never rejects.
  */
 function waitForAll(it) {
   let dummy = () => {};
-  let promises = [for (p of it) p.catch(dummy)];
+  let promises = Array.from(it, p => p.catch(dummy));
   return Promise.all(promises);
 }
 
 this.TelemetryStorage = {
   get pingDirectoryPath() {
     return OS.Path.join(OS.Constants.Path.profileDir, "saved-telemetry-pings");
   },
 
@@ -687,17 +687,20 @@ var TelemetryStorageImpl = {
       }
     };
 
     try {
       // Try to load a compressed version of the archived ping first.
       this._log.trace("loadArchivedPing - loading ping from: " + pathCompressed);
       yield* checkSize(pathCompressed);
       return yield this.loadPingFile(pathCompressed, /*compressed*/ true);
-    } catch (ex if ex.becauseNoSuchFile) {
+    } catch (ex) {
+      if (!ex.becauseNoSuchFile) {
+        throw ex;
+      }
       // If that fails, look for the uncompressed version.
       this._log.trace("loadArchivedPing - compressed ping not found, loading: " + path);
       yield* checkSize(path);
       return yield this.loadPingFile(path, /*compressed*/ false);
     }
   }),
 
   /**
@@ -876,21 +879,21 @@ var TelemetryStorageImpl = {
    * Enforce a disk quota for the pings archive.
    * @return {Promise} Resolved when the quota check is complete.
    */
   _enforceArchiveQuota: Task.async(function*() {
     this._log.trace("_enforceArchiveQuota");
     let startTimeStamp = Policy.now().getTime();
 
     // Build an ordered list, from newer to older, of archived pings.
-    let pingList = [for (p of this._archivedPings) {
+    let pingList = Array.from(this._archivedPings, p => ({
       id: p[0],
       timestampCreated: p[1].timestampCreated,
       type: p[1].type,
-    }];
+    }));
 
     pingList.sort((a, b) => b.timestampCreated - a.timestampCreated);
 
     // If our archive is too big, we should reduce it to reach 90% of the quota.
     const SAFE_QUOTA = Policy.getArchiveQuota() * 0.9;
     // The index of the last ping to keep. Pings older than this one will be deleted if
     // the archive exceeds the quota.
     let lastPingIndexToKeep = null;
@@ -1019,20 +1022,20 @@ var TelemetryStorageImpl = {
    * Enforce a disk quota for the pending pings.
    * @return {Promise} Resolved when the quota check is complete.
    */
   _enforcePendingPingsQuota: Task.async(function*() {
     this._log.trace("_enforcePendingPingsQuota");
     let startTimeStamp = Policy.now().getTime();
 
     // Build an ordered list, from newer to older, of pending pings.
-    let pingList = [for (p of this._pendingPings) {
+    let pingList = Array.from(this._pendingPings, p => ({
       id: p[0],
       lastModificationDate: p[1].lastModificationDate,
-    }];
+    }));
 
     pingList.sort((a, b) => b.lastModificationDate - a.lastModificationDate);
 
     // If our pending pings directory is too big, we should reduce it to reach 90% of the quota.
     const SAFE_QUOTA = Policy.getPendingPingsQuota() * 0.9;
     // The index of the last ping to keep. Pings older than this one will be deleted if
     // the pending pings directory size exceeds the quota.
     let lastPingIndexToKeep = null;
@@ -1225,17 +1228,20 @@ var TelemetryStorageImpl = {
     try {
       this._log.trace("savePingToFile - path: " + filePath);
       let pingString = JSON.stringify(ping);
       let options = { tmpPath: filePath + ".tmp", noOverwrite: !overwrite };
       if (compress) {
         options.compression = "lz4";
       }
       yield OS.File.writeAtomic(filePath, pingString, options);
-    } catch(e if e.becauseExists) {
+    } catch(e) {
+      if (!e.becauseExists) {
+        throw e;
+      }
     }
   }),
 
   /**
    * Save a ping to its file.
    *
    * @param {object} ping The content of the ping to save.
    * @param {bool} overwrite If |true|, the file will be overwritten
@@ -1290,17 +1296,20 @@ var TelemetryStorageImpl = {
       this._log.trace("loadPendingPing - unknown id " + id);
       throw new Error("TelemetryStorage.loadPendingPing - no ping with id " + id);
     }
 
     // Try to get the dimension of the ping. If that fails, update the histograms.
     let fileSize = 0;
     try {
       fileSize = (yield OS.File.stat(info.path)).size;
-    } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
+    } catch (e) {
+      if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile) {
+        throw e;
+      }
       // Fall through and let |loadPingFile| report the error.
     }
 
     // Purge pings which are too big.
     if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
       yield this.removePendingPing(id);
       Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB")
                .add(Math.floor(fileSize / 1024 / 1024));
@@ -1511,20 +1520,20 @@ var TelemetryStorageImpl = {
       });
     }
 
     this._scannedPendingDirectory = true;
     return this._buildPingList();
   }),
 
   _buildPingList: function() {
-    const list = [for (p of this._pendingPings) {
+    const list = Array.from(this._pendingPings, p => ({
       id: p[0],
       lastModificationDate: p[1].lastModificationDate,
-    }];
+    }));
 
     list.sort((a, b) => b.lastModificationDate - a.lastModificationDate);
     return list;
   },
 
   get pendingPingCount() {
     return this._pendingPings.size;
   },
@@ -1632,33 +1641,37 @@ var TelemetryStorageImpl = {
     return this._abortedSessionSerializer.enqueueTask(() =>
       this.savePingToFile(ping, gAbortedSessionFilePath, true));
   }),
 
   loadAbortedSessionPing: Task.async(function*() {
     let ping = null;
     try {
       ping = yield this.loadPingFile(gAbortedSessionFilePath);
-    } catch (ex if ex.becauseNoSuchFile) {
-      this._log.trace("loadAbortedSessionPing - no such file");
     } catch (ex) {
-      this._log.error("loadAbortedSessionPing - error loading ping", ex)
+      if (ex.becauseNoSuchFile) {
+        this._log.trace("loadAbortedSessionPing - no such file");
+      } else {
+        this._log.error("loadAbortedSessionPing - error loading ping", ex)
+      }
     }
     return ping;
   }),
 
   removeAbortedSessionPing: function() {
     return this._abortedSessionSerializer.enqueueTask(Task.async(function*() {
       try {
         yield OS.File.remove(gAbortedSessionFilePath, { ignoreAbsent: false });
         this._log.trace("removeAbortedSessionPing - success");
-      } catch (ex if ex.becauseNoSuchFile) {
-        this._log.trace("removeAbortedSessionPing - no such file");
       } catch (ex) {
-        this._log.error("removeAbortedSessionPing - error removing ping", ex)
+        if (ex.becauseNoSuchFile) {
+          this._log.trace("removeAbortedSessionPing - no such file");
+        } else {
+          this._log.error("removeAbortedSessionPing - error removing ping", ex)
+        }
       }
     }.bind(this)));
   },
 
   /**
    * Save the deletion ping.
    * @param ping The deletion ping.
    * @return {Promise} Resolved when the ping is saved.
@@ -1677,20 +1690,22 @@ var TelemetryStorageImpl = {
    * Remove the deletion ping.
    * @return {Promise} Resolved when the ping is deleted from the disk.
    */
   removeDeletionPing: Task.async(function*() {
     return this._deletionPingSerializer.enqueueTask(Task.async(function*() {
       try {
         yield OS.File.remove(gDeletionPingFilePath, { ignoreAbsent: false });
         this._log.trace("removeDeletionPing - success");
-      } catch (ex if ex.becauseNoSuchFile) {
-        this._log.trace("removeDeletionPing - no such file");
       } catch (ex) {
-        this._log.error("removeDeletionPing - error removing ping", ex)
+        if (ex.becauseNoSuchFile) {
+          this._log.trace("removeDeletionPing - no such file");
+        } else {
+          this._log.error("removeDeletionPing - error removing ping", ex)
+        }
       }
     }.bind(this)));
   }),
 
   isDeletionPing: function(aPingId) {
     this._log.trace("isDeletionPing - id: " + aPingId);
     let pingInfo = this._pendingPings.get(aPingId);
     if (!pingInfo) {
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -93,17 +93,17 @@ const PingServer = {
       results.push(yield this.promiseNextRequest());
     }
 
     return results;
   }),
 
   promiseNextPings: function(count) {
     return this.promiseNextRequests(count).then(requests => {
-      return [for (req of requests) decodeRequestPayload(req)];
+      return Array.from(requests, decodeRequestPayload);
     });
   },
 };
 
 /**
  * Decode the payload of an HTTP request into a ping.
  * @param {Object} request The data representing an HTTP request (nsIHttpRequest).
  * @return {Object} The decoded ping payload.
@@ -143,17 +143,20 @@ function decodeRequestPayload(request) {
 
   return payload;
 }
 
 function wrapWithExceptionHandler(f) {
   function wrapper(...args) {
     try {
       f(...args);
-    } catch (ex if typeof(ex) == 'object') {
+    } catch (ex) {
+      if (typeof(ex) != 'object') {
+        throw ex;
+      }
       dump("Caught exception: " + ex.message + "\n");
       dump(ex.stack);
       do_test_finished();
     }
   }
   return wrapper;
 }
 
--- a/toolkit/components/telemetry/tests/unit/test_PingAPI.js
+++ b/toolkit/components/telemetry/tests/unit/test_PingAPI.js
@@ -101,17 +101,17 @@ add_task(function* test_archivedPings() 
       id: data.id,
       type: data.type,
       timestampCreated: data.dateCreated.getTime(),
     });
     Assert.deepEqual(list, expectedPingList, "Archived ping list should contain submitted pings");
   }
 
   // Check loading the archived pings.
-  let ids = [for (p of PINGS) p.id];
+  let ids = PINGS.map(p => p.id);
   let checkLoadingPings = Task.async(function*() {
     for (let data of PINGS) {
       let ping = yield TelemetryArchive.promiseArchivedPingById(data.id);
       Assert.equal(ping.id, data.id, "Archived ping should have matching id");
       Assert.equal(ping.type, data.type, "Archived ping should have matching type");
       Assert.equal(ping.creationDate, data.dateCreated.toISOString(),
                    "Archived ping should have matching creation date");
     }
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js
@@ -19,17 +19,17 @@ function check_event(event, id, data)
     do_check_eq(event.length, data.length + 2);
     for (var i = 0; i < data.length; ++i) {
       do_check_eq(typeof(event[i + 2]), "string");
       do_check_eq(event[i + 2], data[i]);
     }
   }
 }
 
-function run_test()
+function* run_test()
 {
   yield TelemetrySession.setup();
 
   TelemetryLog.log(TEST_PREFIX + "1", ["val", 123, undefined]);
   TelemetryLog.log(TEST_PREFIX + "2", []);
   TelemetryLog.log(TEST_PREFIX + "3");
 
   var log = TelemetrySession.getPayload().log.filter(function(e) {
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -554,17 +554,17 @@ add_task(function* test_saveLoadPing() {
   const requests = yield PingServer.promiseNextRequests(2);
 
   for (let req of requests) {
     Assert.equal(req.getHeader("content-type"), "application/json; charset=UTF-8",
                  "The request must have the correct content-type.");
   }
 
   // We decode both requests to check for the |reason|.
-  let pings = [for (req of requests) decodeRequestPayload(req)];
+  let pings = Array.from(requests, decodeRequestPayload);
 
   // Check we have the correct two requests. Ordering is not guaranteed. The ping type
   // is encoded in the URL.
   if (pings[0].type != PING_TYPE_MAIN) {
     pings.reverse();
   }
 
   checkPingFormat(pings[0], PING_TYPE_MAIN, true, true);
--- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
+++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
@@ -443,29 +443,29 @@ function numberRange(lower, upper)
     a.push(i);
   }
   return a;
 }
 
 function test_keyed_boolean_histogram()
 {
   const KEYED_ID = "test::keyed::boolean";
-  let KEYS = ["key"+(i+1) for (i of numberRange(0, 2))];
+  let KEYS = numberRange(0, 2).map(i => "key" + (i + 1));
   KEYS.push("漢語");
   let histogramBase = {
     "min": 1,
     "max": 2,
     "histogram_type": 2,
     "sum": 1,
     "sum_squares_lo": 1,
     "sum_squares_hi": 0,
     "ranges": [0, 1, 2],
     "counts": [0, 1, 0]
   };
-  let testHistograms = [JSON.parse(JSON.stringify(histogramBase)) for (i of numberRange(0, 3))];
+  let testHistograms = numberRange(0, 3).map(i => JSON.parse(JSON.stringify(histogramBase)));
   let testKeys = [];
   let testSnapShot = {};
 
   let h = Telemetry.newKeyedHistogram(KEYED_ID, "never", Telemetry.HISTOGRAM_BOOLEAN);
   for (let i=0; i<2; ++i) {
     let key = KEYS[i];
     h.add(key, true);
     testSnapShot[key] = testHistograms[i];
@@ -495,28 +495,28 @@ function test_keyed_boolean_histogram()
   h.clear();
   Assert.deepEqual(h.keys(), []);
   Assert.deepEqual(h.snapshot(), {});
 }
 
 function test_keyed_count_histogram()
 {
   const KEYED_ID = "test::keyed::count";
-  const KEYS = ["key"+(i+1) for (i of numberRange(0, 5))];
+  const KEYS = numberRange(0, 5).map(i => "key" + (i + 1));
   let histogramBase = {
     "min": 1,
     "max": 2,
     "histogram_type": 4,
     "sum": 0,
     "sum_squares_lo": 0,
     "sum_squares_hi": 0,
     "ranges": [0, 1, 2],
     "counts": [1, 0, 0]
   };
-  let testHistograms = [JSON.parse(JSON.stringify(histogramBase)) for (i of numberRange(0, 5))];
+  let testHistograms = numberRange(0, 5).map(i => JSON.parse(JSON.stringify(histogramBase)));
   let testKeys = [];
   let testSnapShot = {};
 
   let h = Telemetry.newKeyedHistogram(KEYED_ID, "never", Telemetry.HISTOGRAM_COUNT);
   for (let i=0; i<4; ++i) {
     let key = KEYS[i];
     let value = i*2 + 1;
 
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -252,17 +252,17 @@ this.PageThumbs = {
                                                            aBrowser.docShell));
     }
   },
 
   // The background thumbnail service captures to canvas but doesn't want to
   // participate in this service's telemetry, which is why this method exists.
   _captureToCanvas: function (aBrowser, aCanvas, aCallback) {
     if (aBrowser.isRemoteBrowser) {
-      Task.spawn(function () {
+      Task.spawn(function* () {
         let data =
           yield this._captureRemoteThumbnail(aBrowser, aCanvas);
         let canvas = data.thumbnail;
         let ctx = canvas.getContext("2d");
         let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
         aCanvas.getContext("2d").putImageData(imgData, 0, 0);
         if (aCallback) {
           aCallback(aCanvas);
@@ -356,17 +356,17 @@ this.PageThumbs = {
       originalURL = channel.originalURI.spec;
       // see if this was an error response.
       channelError = this._isChannelErrorResponse(channel);
     } else {
       // We need channel info (bug 1073957)
       originalURL = url;
     }
 
-    Task.spawn((function task() {
+    Task.spawn((function* task() {
       let isSuccess = true;
       try {
         let blob = yield this.captureToBlob(aBrowser);
         let buffer = yield TaskUtils.readBlob(blob);
         yield this._store(originalURL, url, buffer, channelError);
       } catch (ex) {
         Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
         isSuccess = false;
@@ -412,17 +412,17 @@ this.PageThumbs = {
    * @param aOriginalURL The URL with which the capture was initiated.
    * @param aFinalURL The URL to which aOriginalURL ultimately resolved.
    * @param aData An ArrayBuffer containing the image data.
    * @param aNoOverwrite If true and files for the URLs already exist, the files
    *                     will not be overwritten.
    * @return {Promise}
    */
   _store: function PageThumbs__store(aOriginalURL, aFinalURL, aData, aNoOverwrite) {
-    return Task.spawn(function () {
+    return Task.spawn(function* () {
       let telemetryStoreTime = new Date();
       yield PageThumbsStorage.writeData(aFinalURL, aData, aNoOverwrite);
       Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
         .add(new Date() - telemetryStoreTime);
 
       Services.obs.notifyObservers(null, "page-thumbnail:create", aFinalURL);
       // We've been redirected. Create a copy of the current thumbnail for
       // the redirect source. We need to do this because:
@@ -841,17 +841,17 @@ var PageThumbsExpiration = {
         filter(filterCallback)
       else
         filter.filterForThumbnailExpiration(filterCallback);
     }
   },
 
   expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
     let path = this.path;
-    let keep = [PageThumbsStorage.getLeafNameForURL(url) for (url of aURLsToKeep)];
+    let keep = aURLsToKeep.map(url => PageThumbsStorage.getLeafNameForURL(url));
     let msg = [
       PageThumbsStorage.path,
       keep,
       EXPIRATION_MIN_CHUNK_SIZE
     ];
 
     return PageThumbsWorker.post(
       "expireFilesInDirectory",
--- a/toolkit/components/thumbnails/PageThumbsWorker.js
+++ b/toolkit/components/thumbnails/PageThumbsWorker.js
@@ -36,17 +36,20 @@ var Agent = {
   // Checks if the specified file exists and has an age less than as
   // specifed (in seconds).
   isFileRecent: function Agent_isFileRecent(path, maxAge) {
     try {
       let stat = OS.File.stat(path);
       let maxDate = new Date();
       maxDate.setSeconds(maxDate.getSeconds() - maxAge);
       return stat.lastModificationDate > maxDate;
-    } catch (ex if ex instanceof OS.File.Error) {
+    } catch (ex) {
+      if (!(ex instanceof OS.File.Error)) {
+        throw ex;
+      }
       // file doesn't exist (or can't be stat'd) - must be stale.
       return false;
     }
   },
 
   remove: function Agent_removeFile(path) {
     try {
       OS.File.remove(path);
@@ -77,19 +80,23 @@ var Agent = {
   function Agent_getFileEntriesInDirectory(path, skipFiles) {
     let iter = new OS.File.DirectoryIterator(path);
     if (!iter.exists()) {
       return [];
     }
 
     let skip = new Set(skipFiles);
 
-    return [entry
-            for (entry in iter)
-            if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name))];
+    let entries = [];
+    for (let entry in iter) {
+      if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name)) {
+        entries.push(entry);
+      }
+    }
+    return entries;
   },
 
   moveOrDeleteAllThumbnails:
   function Agent_moveOrDeleteAllThumbnails(pathFrom, pathTo) {
     OS.File.makeDir(pathTo, {ignoreExisting: true});
     if (pathFrom == pathTo) {
       return true;
     }
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url = "invalid-protocol://ffggfsdfsdf/";
   ok(!thumbnailExists(url), "Thumbnail should not be cached already.");
   let numCalls = 0;
   BackgroundPageThumbs.capture(url, {
     onDone: function onDone(capturedURL) {
       is(capturedURL, url, "Captured URL should be URL passed to capture");
       is(numCalls++, 0, "onDone should be called only once");
       ok(!thumbnailExists(url),
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url = "http://www.example.com/";
   ok(!thumbnailExists(url), "Thumbnail should not be cached yet.");
 
   let capturedURL = yield bgCapture(url);
   is(capturedURL, url, "Captured URL should be URL passed to capture");
 
   ok(thumbnailExists(url), "Thumbnail should be cached after capture");
   removeThumbnail(url);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let numNotifications = 0;
   function observe(subject, topic, data) {
     is(topic, "page-thumbnail:create", "got expected topic");
     numNotifications += 1;
   }
 
   Services.obs.addObserver(observe, "page-thumbnail:create", false);
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let crashObserver = bgAddCrashObserver();
 
   // make a good capture first - this ensures we have the <browser>
   let goodUrl = bgTestPageURL();
   yield bgCapture(goodUrl);
   ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture");
   removeThumbnail(goodUrl);
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let crashObserver = bgAddCrashObserver();
 
   // make a good capture first - this ensures we have the <browser>
   let goodUrl = bgTestPageURL();
   yield bgCapture(goodUrl);
   ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture");
   removeThumbnail(goodUrl);
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url1 = "http://example.com/1";
   ok(!thumbnailExists(url1), "First file should not exist yet.");
 
   let url2 = "http://example.com/2";
   ok(!thumbnailExists(url2), "Second file should not exist yet.");
 
   let defaultTimeout = BackgroundPageThumbs._destroyBrowserTimeout;
   BackgroundPageThumbs._destroyBrowserTimeout = 1000;
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url = "data:text/html,<script>try { alert('yo!'); } catch (e) {}</script>";
   ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
 
   let capturedURL = yield bgCapture(url);
   is(capturedURL, url, "Captured URL should be URL passed to capture.");
   ok(thumbnailExists(url),
      "Thumbnail file should exist even though it alerted.");
   removeThumbnail(url);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js
@@ -4,17 +4,17 @@
 // the following tests attempt to display modal dialogs.  The test just
 // relies on the fact that if the dialog was displayed the test will hang
 // and timeout.  IOW - the tests would pass if the dialogs appear and are
 // manually closed by the user - so don't do that :)  (obviously there is
 // noone available to do that when run via tbpl etc, so this should be safe,
 // and it's tricky to use the window-watcher to check a window *does not*
 // appear - how long should the watcher be active before assuming it's not
 // going to appear?)
-function runTests() {
+function* runTests() {
   let url = "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=anyone";
   ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
 
   let capturedURL = yield bgCapture(url);
   is(capturedURL, url, "Captured URL should be URL passed to capture.");
   ok(thumbnailExists(url),
      "Thumbnail file should exist even though it requires auth.");
   removeThumbnail(url);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   // Visit the test page in the browser and tell it to set a cookie.
   let url = bgTestPageURL({ setGreenCookie: true });
   let tab = gBrowser.loadOneTab(url, { inBackground: false });
   let browser = tab.linkedBrowser;
   yield whenLoaded(browser);
 
   // The root element of the page shouldn't be green yet.
   let greenStr = "rgb(0, 255, 0)";
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // check that if a page captured in the background attempts to set a cookie,
 // that cookie is not saved for subsequent requests.
-function runTests() {
+function* runTests() {
   let url = bgTestPageURL({ setRedCookie: true });
   ok(!thumbnailExists(url), "Thumbnail file should not exist before capture.");
   yield bgCapture(url);
   ok(thumbnailExists(url), "Thumbnail file should exist after capture.");
   removeThumbnail(url);
   // now load it up in a browser - it should *not* be red, otherwise the
   // cookie above was saved.
   let tab = gBrowser.loadOneTab(url, { inBackground: false });
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url = "http://example.com/1";
   ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
   let numCallbacks = 0;
   let doneCallback = function(doneUrl) {
     is(doneUrl, url, "called back with correct url");
     numCallbacks += 1;
     // We will delete the file after the first callback, then check it
     // still doesn't exist on the second callback, which should give us
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let urls = [
     "http://www.example.com/0",
     "http://www.example.com/1",
     // an item that will timeout to ensure timeouts work and we resume.
     bgTestPageURL({ wait: 2002 }),
     "http://www.example.com/2",
   ];
   dontExpireThumbnailURLs(urls);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let finalURL = "http://example.com/redirected";
   let originalURL = bgTestPageURL({ redirect: finalURL });
 
   ok(!thumbnailExists(originalURL),
      "Thumbnail file for original URL should not exist yet.");
   ok(!thumbnailExists(finalURL),
      "Thumbnail file for final URL should not exist yet.");
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+function* runTests() {
   let url = bgTestPageURL({ wait: 30000 });
   ok(!thumbnailExists(url), "Thumbnail should not be cached already.");
   let numCalls = 0;
   BackgroundPageThumbs.capture(url, {
     timeout: 0,
     onDone: function onDone(capturedURL) {
       is(capturedURL, url, "Captured URL should be URL passed to capture");
       is(numCalls++, 0, "onDone should be called only once");
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * These tests ensure that capturing a sites's thumbnail, saving it and
  * retrieving it from the cache works.
  */
-function runTests() {
+function* runTests() {
   // Create a tab that shows an error page.
   let tab = gBrowser.addTab("http://127.0.0.1:1/");
   let browser = tab.linkedBrowser;
   yield browser.addEventListener("DOMContentLoaded", function onLoad() {
     browser.removeEventListener("DOMContentLoaded", onLoad, false);
     PageThumbs.shouldStoreThumbnail(browser, (aResult) => {
       ok(!aResult, "we're not going to capture an error page");
       executeSoon(next);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js
@@ -7,17 +7,17 @@ const URL = "http://mochi.test:8888/brow
 function isRedThumbnailFuzz(r, g, b, expectedR, expectedB, expectedG, aFuzz)
 {
   return (Math.abs(r - expectedR) <= aFuzz) &&
          (Math.abs(g - expectedG) <= aFuzz) &&
          (Math.abs(b - expectedB) <= aFuzz);
 }
 
 // Test for black borders caused by scrollbars.
-function runTests() {
+function* runTests() {
   // Create a tab with a page with a red background and scrollbars.
   yield addTab(URL);
   yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
 
   // Check the thumbnail color of the bottom right pixel.
   yield whenFileExists(URL);
   yield retrieveImageDataForURL(URL, function (aData) {
     let [r, g, b] = [].slice.call(aData, -4);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/" +
             "test/background_red.html?" + Date.now();
 
 // Test PageThumbs API function getThumbnailPath
-function runTests() {
+function* runTests() {
 
   let path = PageThumbs.getThumbnailPath(URL);
   yield testIfExists(path, false, "Thumbnail file does not exist");
 
   yield addVisitsAndRepopulateNewTabLinks(URL, next);
   yield createThumbnail(URL);
 
   path = PageThumbs.getThumbnailPath(URL);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_capture.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_capture.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * These tests ensure that capturing a sites's thumbnail, saving it and
  * retrieving it from the cache works.
  */
-function runTests() {
+function* runTests() {
   // Create a tab with a red background.
   yield addTab("data:text/html,<body bgcolor=ff0000></body>");
   yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
 
   // Load a page with a green background.
   yield navigateTo("data:text/html,<body bgcolor=00ff00></body>");
   yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js
@@ -9,17 +9,17 @@ const URL3 = URL + "#3";
 var tmp = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("resource://gre/modules/PageThumbs.jsm", tmp);
 
 const EXPIRATION_MIN_CHUNK_SIZE = 50;
 const {PageThumbsExpiration} = tmp;
 
-function runTests() {
+function* runTests() {
   // Create dummy URLs.
   let dummyURLs = [];
   for (let i = 0; i < EXPIRATION_MIN_CHUNK_SIZE + 10; i++) {
     dummyURLs.push(URL + "#dummy" + i);
   }
 
   // Make sure our thumbnails aren't expired too early.
   dontExpireThumbnailURLs([URL1, URL2, URL3].concat(dummyURLs));
@@ -53,22 +53,22 @@ function runTests() {
   for (let url of dummyURLs) {
     yield createDummyThumbnail(url);
   }
 
   ok(dummyURLs.every(thumbnailExists), "all dummy thumbnails created");
 
   // Expire thumbnails and expect 10 remaining.
   yield expireThumbnails([]);
-  let remainingURLs = [u for (u of dummyURLs) if (thumbnailExists(u))];
+  let remainingURLs = dummyURLs.filter(thumbnailExists);
   is(remainingURLs.length, 10, "10 dummy thumbnails remaining");
 
   // Expire thumbnails again. All should be gone by now.
   yield expireThumbnails([]);
-  remainingURLs = [u for (u of remainingURLs) if (thumbnailExists(u))];
+  remainingURLs = remainingURLs.filter(thumbnailExists);
   is(remainingURLs.length, 0, "no dummy thumbnails remaining");
 }
 
 function createDummyThumbnail(aURL) {
   info("Creating dummy thumbnail for " + aURL);
   let dummy = new Uint8Array(10);
   for (let i = 0; i < 10; ++i) {
     dummy[i] = i;
--- a/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PREF_DISK_CACHE_SSL = "browser.cache.disk_cache_ssl";
 const URL = "://example.com/browser/toolkit/components/thumbnails/" +
             "test/privacy_cache_control.sjs";
 
-function runTests() {
+function* runTests() {
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref(PREF_DISK_CACHE_SSL);
   });
 
   let positive = [
     // A normal HTTP page without any Cache-Control header.
     {scheme: "http", cacheControl: null, diskCacheSSL: false},
 
--- a/toolkit/components/thumbnails/test/browser_thumbnails_redirect.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_redirect.js
@@ -5,17 +5,17 @@ const URL = "http://mochi.test:8888/brow
             "test/background_red_redirect.sjs";
 // loading URL will redirect us to...
 const FINAL_URL = "http://mochi.test:8888/browser/toolkit/components/" +
                   "thumbnails/test/background_red.html";
 
 /**
  * These tests ensure that we save and provide thumbnails for redirecting sites.
  */
-function runTests() {
+function* runTests() {
   dontExpireThumbnailURLs([URL, FINAL_URL]);
 
   // Kick off history by loading a tab first or the test fails in single mode.
   yield addTab(URL);
   gBrowser.removeTab(gBrowser.selectedTab);
 
   // Create a tab, redirecting to a page with a red background.
   yield addTab(URL);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
@@ -12,17 +12,17 @@ XPCOMUtils.defineLazyGetter(this, "Sanit
   return tmp.Sanitizer;
 });
 
 /**
  * These tests ensure that the thumbnail storage is working as intended.
  * Newly captured thumbnails should be saved as files and they should as well
  * be removed when the user sanitizes their history.
  */
-function runTests() {
+function* runTests() {
   yield Task.spawn(function*() {
     dontExpireThumbnailURLs([URL, URL_COPY]);
 
     yield promiseClearHistory();
     yield promiseAddVisitsAndRepopulateNewTabLinks(URL);
     yield promiseCreateThumbnail();
 
     // Make sure Storage.copy() updates an existing file.
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
@@ -16,17 +16,17 @@ var {PageThumbsStorageMigrator} = tmp;
 XPCOMUtils.defineLazyServiceGetter(this, "gDirSvc",
   "@mozilla.org/file/directory_service;1", "nsIProperties");
 
 /**
  * This test makes sure we correctly migrate to thumbnail storage version 3.
  * This means copying existing thumbnails from the roaming to the local profile
  * directory and should just apply to Linux.
  */
-function runTests() {
+function* runTests() {
   let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
                  .getService(Ci.nsIProperties);
 
   // Prepare a local profile directory.
   let localProfile = FileUtils.getDir("ProfD", ["local-test"], true);
   changeLocation("ProfLD", localProfile);
 
   let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY], true);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_update.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_update.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * These tests check the auto-update facility of the thumbnail service.
  */
 
-function runTests() {
+function* runTests() {
   // A "trampoline" - a generator that iterates over sub-iterators
   let tests = [
     simpleCaptureTest,
     capIfStaleErrorResponseUpdateTest,
     capIfStaleGoodResponseUpdateTest,
     regularCapErrorResponseUpdateTest,
     regularCapGoodResponseUpdateTest
   ];
@@ -36,17 +36,17 @@ function getThumbnailModifiedTime(url) {
   let fname = PageThumbsStorage.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(fname);
   return file.lastModifiedTime;
 }
 
 // The tests!
 /* Check functionality of a normal captureAndStoreIfStale request */
-function simpleCaptureTest() {
+function* simpleCaptureTest() {
   let numNotifications = 0;
   const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?simple";
 
   function observe(subject, topic, data) {
     is(topic, "page-thumbnail:create", "got expected topic");
     is(data, URL, "data is our test URL");
     if (++numNotifications == 2) {
       // This is the final notification and signals test success...
@@ -78,17 +78,17 @@ function simpleCaptureTest() {
     });
   });
   yield undefined // wait for callbacks to call 'next'...
 }
 
 /* Check functionality of captureAndStoreIfStale when there is an error response
    from the server.
  */
-function capIfStaleErrorResponseUpdateTest() {
+function* capIfStaleErrorResponseUpdateTest() {
   const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail";
   yield addTab(URL);
 
   yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
   // update the thumbnail to be stale, then re-request it.  The server will
   // return a 400 response and a red thumbnail.
   // The service should not save the thumbnail - so we (a) check the thumbnail
   // remains green and (b) check the mtime of the file is < now.
@@ -108,17 +108,17 @@ function capIfStaleErrorResponseUpdateTe
   });
   yield undefined; // wait for callback to call 'next'...
 }
 
 /* Check functionality of captureAndStoreIfStale when there is a non-error
    response from the server.  This test is somewhat redundant - although it is
    using a http:// URL instead of a data: url like most others.
  */
-function capIfStaleGoodResponseUpdateTest() {
+function* capIfStaleGoodResponseUpdateTest() {
   const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok";
   yield addTab(URL);
   let browser = gBrowser.selectedBrowser;
 
   yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
   // update the thumbnail to be stale, then re-request it.  The server will
   // return a 200 response and a red thumbnail - so that new thumbnail should
   // end up captured.
@@ -138,31 +138,31 @@ function capIfStaleGoodResponseUpdateTes
     });
   });
   yield undefined; // wait for callback to call 'next'...
 }
 
 /* Check functionality of captureAndStore when there is an error response
    from the server.
  */
-function regularCapErrorResponseUpdateTest() {
+function* regularCapErrorResponseUpdateTest() {
   const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail";
   yield addTab(URL);
   yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
   gBrowser.removeTab(gBrowser.selectedTab);
   // do it again - the server will return a 400, so the foreground service
   // should not update it.
   yield addTab(URL);
   yield captureAndCheckColor(0, 255, 0, "we still have a green thumbnail");
 }
 
 /* Check functionality of captureAndStore when there is an OK response
    from the server.
  */
-function regularCapGoodResponseUpdateTest() {
+function* regularCapGoodResponseUpdateTest() {
   const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok";
   yield addTab(URL);
   yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
   gBrowser.removeTab(gBrowser.selectedTab);
   // do it again - the server will return a 200, so the foreground service
   // should  update it.
   yield addTab(URL);
   yield captureAndCheckColor(255, 0, 0, "we now  have a red thumbnail");
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -50,27 +50,28 @@ var TestRunner = {
   },
 
   /**
    * Runs the next available test or finishes if there's no test left.
    * @param aValue This value will be passed to the yielder via the runner's
    *               iterator.
    */
   next: function (aValue) {
-    try {
-      let value = TestRunner._iter.send(aValue);
-      if (value && typeof value.then == "function") {
-        value.then(result => {
-          next(result);
-        }, error => {
-          ok(false, error + "\n" + error.stack);
-        });
-      }
-    } catch (e if e instanceof StopIteration) {
+    let { done, value } = TestRunner._iter.next(aValue);
+    if (done) {
       finish();
+      return;
+    }
+
+    if (value && typeof value.then == "function") {
+      value.then(result => {
+        next(result);
+      }, error => {
+        ok(false, error + "\n" + error.stack);
+      });
     }
   }
 };
 
 /**
  * Continues the current test execution.
  * @param aValue This value will be passed to the yielder via the runner's
  *               iterator.
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -47,18 +47,20 @@ var snapshotFormatters = {
       $("updatechannel-box").textContent = data.updateChannel;
 
     $("multiprocess-box").textContent = stringBundle().formatStringFromName("multiProcessStatus",
       [data.numRemoteWindows, data.numTotalWindows, data.remoteAutoStart], 3);
 
     $("safemode-box").textContent = data.safeMode;
   },
 
-#ifdef MOZ_CRASHREPORTER
   crashes: function crashes(data) {
+    if (!AppConstants.MOZ_CRASHREPORTER)
+      return;
+
     let strings = stringBundle();
     let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
     $("crashes-title").textContent =
       PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
                 .replace("#1", daysRange);
     let reportURL;
     try {
       reportURL = Services.prefs.getCharPref("breakpad.reportURL");
@@ -112,17 +114,16 @@ var snapshotFormatters = {
       return $.new("tr", [
         $.new("td", [
           $.new("a", crash.id, null, {href : reportURL + crash.id})
         ]),
         $.new("td", formattedDate)
       ]);
     }));
   },
-#endif
 
   extensions: function extensions(data) {
     $.append($("extensions-tbody"), data.map(function (extension) {
       return $.new("tr", [
         $.new("td", extension.name),
         $.new("td", extension.version),
         $.new("td", extension.isActive),
         $.new("td", extension.id),
@@ -368,33 +369,34 @@ var snapshotFormatters = {
     let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
     userJSFile.append("user.js");
     $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
     $("prefs-user-js-section").style.display = "";
     // Clear the no-copy class
     $("prefs-user-js-section").className = "";
   },
 
-#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
   sandbox: function sandbox(data) {
+    if (AppConstants.platform != "linux" || !AppConstants.MOZ_SANDBOX)
+      return;
+
     let strings = stringBundle();
     let tbody = $("sandbox-tbody");
     for (let key in data) {
       // Simplify the display a little in the common case.
       if (key === "hasPrivilegedUserNamespaces" &&
           data[key] === data["hasUserNamespaces"]) {
         continue;
       }
       tbody.appendChild($.new("tr", [
         $.new("th", strings.GetStringFromName(key), "column"),
         $.new("td", data[key])
       ]));
     }
   },
-#endif
 };
 
 var $ = document.getElementById.bind(document);
 
 $.new = function $_new(tag, textContentOrChildren, className, attributes) {
   let elt = document.createElement(tag);
   if (className)
     elt.className = className;
@@ -465,25 +467,25 @@ function copyRawDataToClipboard(button) 
       let transferable = Cc["@mozilla.org/widget/transferable;1"].
                          createInstance(Ci.nsITransferable);
       transferable.init(getLoadContext());
       transferable.addDataFlavor("text/unicode");
       transferable.setTransferData("text/unicode", str, str.data.length * 2);
       Cc["@mozilla.org/widget/clipboard;1"].
         getService(Ci.nsIClipboard).
         setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
-#ifdef ANDROID
-      // Present a toast notification.
-      let message = {
-        type: "Toast:Show",
-        message: stringBundle().GetStringFromName("rawDataCopied"),
-        duration: "short"
-      };
-      Services.androidBridge.handleGeckoMessage(message);
-#endif
+      if (AppConstants.platform == "android") {
+        // Present a toast notification.
+        let message = {
+          type: "Toast:Show",
+          message: stringBundle().GetStringFromName("rawDataCopied"),
+          duration: "short"
+        };
+        Services.androidBridge.handleGeckoMessage(message);
+      }
     });
   }
   catch (err) {
     if (button)
       button.disabled = false;
     throw err;
   }
 }
@@ -519,37 +521,37 @@ function copyContentsToClipboard() {
   ssText.data = dataText;
   transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
 
   // Store the data into the clipboard.
   let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
                     .getService(Ci.nsIClipboard);
   clipboard.setData(transferable, null, clipboard.kGlobalClipboard);
 
-#ifdef ANDROID
-  // Present a toast notification.
-  let message = {
-    type: "Toast:Show",
-    message: stringBundle().GetStringFromName("textCopied"),
-    duration: "short"
-  };
-  Services.androidBridge.handleGeckoMessage(message);
-#endif
+  if (AppConstants.platform == "android") {
+    // Present a toast notification.
+    let message = {
+      type: "Toast:Show",
+      message: stringBundle().GetStringFromName("textCopied"),
+      duration: "short"
+    };
+    Services.androidBridge.handleGeckoMessage(message);
+  }
 }
 
 // Return the plain text representation of an element.  Do a little bit
 // of pretty-printing to make it human-readable.
 function createTextForElement(elem) {
   let serializer = new Serializer();
   let text = serializer.serialize(elem);
 
   // Actual CR/LF pairs are needed for some Windows tex