Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 01 Apr 2015 12:44:33 -0400
changeset 237040 e044f4d172e2146d90961ec5314d641dac8ec38b
parent 237039 2af3eafd07f371b39b2ab4465440802184da68ff (current diff)
parent 237023 e5b72a8edb82b506112f06dce71c55893ae9384d (diff)
child 237102 37ddc5e2eb729fa1f07531b92c84dbfcefe0fe71
child 237156 45af6b8fe218a27d74f207e7cd902404f0230c4f
push id28522
push userkwierso@gmail.com
push dateWed, 01 Apr 2015 20:46:02 +0000
treeherdermozilla-central@e044f4d172e2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team. a=merge
dom/indexedDB/TransactionThreadPool.cpp
dom/indexedDB/TransactionThreadPool.h
dom/plugins/ipc/COMMessageFilter.cpp
dom/plugins/ipc/COMMessageFilter.h
js/src/gdb/lib-for-tests/prolog.py
js/src/jit-test/lib/prolog.js
security/manager/ssl/public/nsISSLErrorListener.idl
--- a/accessible/base/nsAccessiblePivot.cpp
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -19,17 +19,17 @@ using namespace mozilla::a11y;
  */
 class RuleCache
 {
 public:
   explicit RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
                                                           mAcceptRoles(nullptr) { }
   ~RuleCache () {
     if (mAcceptRoles)
-      nsMemory::Free(mAcceptRoles);
+      free(mAcceptRoles);
   }
 
   nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
 
 private:
   nsCOMPtr<nsIAccessibleTraversalRule> mRule;
   uint32_t* mAcceptRoles;
   uint32_t mAcceptRolesLength;
--- 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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3b3407510d6e2c60242e8b9b5f2bc1783ca0a0e4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
--- 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="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3b3407510d6e2c60242e8b9b5f2bc1783ca0a0e4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/config.json
+++ b/b2g/config/flame-kk/config.json
@@ -25,17 +25,17 @@
         "{workdir}/.config",
         "{workdir}/sources.xml",
         "{workdir}/profile.sh",
         ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
         ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
         ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
-        "VARIANT": "user",
+        "VARIANT": "userdebug",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly",
         "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
     },
     "b2g_manifest": "flame-kk.xml",
     "b2g_manifest_intree": true,
     "additional_source_tarballs": ["backup-flame.tar.xz"],
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "03164bd160809747e6a198e0dba1b7c3ee7789f5", 
+        "git_revision": "4bb3a933bd805e8df1e11827cb247754c3565b0b", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "696e7b30b79f77008c68c3df0a5d501bb8718c0c", 
+    "revision": "00266dd4a8170bfe9208338a1ecafbef0bb53306", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03164bd160809747e6a198e0dba1b7c3ee7789f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb3a933bd805e8df1e11827cb247754c3565b0b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d99ab92d0b829a6c78b5284481d5b236d3901f11"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -425,16 +425,19 @@
 @RESPATH@/components/nsSidebar.manifest
 @RESPATH@/components/nsSidebar.js
 @RESPATH@/components/nsAsyncShutdown.manifest
 @RESPATH@/components/nsAsyncShutdown.js
 @RESPATH@/components/htmlMenuBuilder.js
 @RESPATH@/components/htmlMenuBuilder.manifest
 @RESPATH@/components/PresentationDeviceInfoManager.manifest
 @RESPATH@/components/PresentationDeviceInfoManager.js
+@RESPATH@/components/BuiltinProviders.manifest
+@RESPATH@/components/TCPPresentationServer.js
+
 #ifdef MOZ_SECUREELEMENT
 @RESPATH@/components/SecureElement.js
 @RESPATH@/components/SecureElement.manifest
 @RESPATH@/components/UiccConnector.js
 @RESPATH@/components/UiccConnector.manifest
 #endif
 
 ; WiFi, NetworkManager, NetworkStats
--- a/browser/installer/Makefile.in
+++ b/browser/installer/Makefile.in
@@ -89,16 +89,20 @@ endif
 ifdef NECKO_WIFI
 DEFINES += -DNECKO_WIFI
 endif
 
 ifdef GKMEDIAS_SHARED_LIBRARY
 DEFINES += -DGKMEDIAS_SHARED_LIBRARY
 endif
 
+ifdef MAKENSISU
+DEFINES += -DHAVE_MAKENSISU=1
+endif
+
 ifdef MOZ_PKG_MANIFEST_P
 MOZ_PKG_MANIFEST = package-manifest
 
 $(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) $(GLOBAL_DEPS)
 	$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) $(MOZ_PKG_MANIFEST_P) -o $@)
 
 GARBAGE += $(MOZ_PKG_MANIFEST)
 endif
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -49,17 +49,17 @@
 @RESPATH@/browser/defaults/profile/bookmarks.html
 @RESPATH@/browser/defaults/profile/chrome/*
 @RESPATH@/browser/defaults/profile/localstore.rdf
 @RESPATH@/browser/defaults/profile/mimeTypes.rdf
 @RESPATH@/dictionaries/*
 @RESPATH@/hyphenation/*
 @RESPATH@/browser/@PREF_DIR@/firefox-l10n.js
 @RESPATH@/browser/searchplugins/*
-#ifdef XP_WIN32
+#ifdef HAVE_MAKENSISU
 @BINPATH@/uninstall/helper.exe
 #endif
 #ifdef MOZ_UPDATER
 @RESPATH@/update.locale
 @RESPATH@/updater.ini
 #endif
 
 [xpcom]
@@ -604,16 +604,18 @@
 @RESPATH@/components/dom_webspeechsynth.xpt
 #endif
 
 @RESPATH@/components/nsAsyncShutdown.manifest
 @RESPATH@/components/nsAsyncShutdown.js
 
 @RESPATH@/components/PresentationDeviceInfoManager.manifest
 @RESPATH@/components/PresentationDeviceInfoManager.js
+@RESPATH@/components/BuiltinProviders.manifest
+@RESPATH@/components/TCPPresentationServer.js
 
 ; InputMethod API
 @RESPATH@/components/MozKeyboard.js
 @RESPATH@/components/InputMethod.manifest
 
 #ifdef MOZ_DEBUG
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -74,33 +74,37 @@ if test -n "$USE_ICU"; then
     fi
 
     version=`sed -n 's/^[[:space:]]*#[[:space:]]*define[[:space:]][[:space:]]*U_ICU_VERSION_MAJOR_NUM[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' "$icudir/common/unicode/uvernum.h"`
     if test x"$version" = x; then
        AC_MSG_ERROR([cannot determine icu version number from uvernum.h header file $lineno])
     fi
     MOZ_ICU_VERSION="$version"
 
+    if test "$OS_TARGET" = WINNT; then
+        MOZ_SHARED_ICU=1
+    fi
+
     if test -z "${JS_STANDALONE}" -a -n "${JS_SHARED_LIBRARY}${MOZ_NATIVE_ICU}"; then
         MOZ_SHARED_ICU=1
     fi
 
     AC_SUBST(MOZ_ICU_VERSION)
     AC_SUBST(MOZ_SHARED_ICU)
 
     if test -z "$MOZ_NATIVE_ICU"; then
         case "$OS_TARGET" in
             WINNT)
                 ICU_LIB_NAMES="icuin icuuc icudt"
                 MOZ_ICU_DBG_SUFFIX=
                 if test -n "$MOZ_DEBUG" -a -z "$MOZ_NO_DEBUG_RTL"; then
                     MOZ_ICU_DBG_SUFFIX=d
                 fi
                 ;;
-            Darwin|Linux|DragonFly|FreeBSD|NetBSD|OpenBSD|GNU/kFreeBSD)
+            Darwin|Linux|DragonFly|FreeBSD|NetBSD|OpenBSD|GNU/kFreeBSD|SunOS)
                 ICU_LIB_NAMES="icui18n icuuc icudata"
                 ;;
             *)
                 AC_MSG_ERROR([ECMAScript Internationalization API is not yet supported on this platform])
         esac
     fi
 fi
 
--- a/caps/nsNullPrincipal.cpp
+++ b/caps/nsNullPrincipal.cpp
@@ -10,18 +10,16 @@
  * same-origin with anything but themselves.
  */
 
 #include "mozilla/ArrayUtils.h"
 
 #include "nsNullPrincipal.h"
 #include "nsNullPrincipalURI.h"
 #include "nsMemory.h"
-#include "nsIUUIDGenerator.h"
-#include "nsID.h"
 #include "nsNetUtil.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsNetCID.h"
 #include "nsError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPrincipal.h"
@@ -73,74 +71,45 @@ nsNullPrincipal::~nsNullPrincipal()
 nsNullPrincipal::CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom)
 {
   nsRefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal();
   nsresult rv = nullPrin->Init(aInheritFrom->GetAppId(),
                                aInheritFrom->GetIsInBrowserElement());
   return NS_SUCCEEDED(rv) ? nullPrin.forget() : nullptr;
 }
 
-#define NS_NULLPRINCIPAL_PREFIX NS_NULLPRINCIPAL_SCHEME ":"
+/* static */ already_AddRefed<nsNullPrincipal>
+nsNullPrincipal::Create(uint32_t aAppId, bool aInMozBrowser)
+{
+  nsRefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal();
+  nsresult rv = nullPrin->Init(aAppId, aInMozBrowser);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  return nullPrin.forget();
+}
 
 nsresult
 nsNullPrincipal::Init(uint32_t aAppId, bool aInMozBrowser)
 {
   MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
   mAppId = aAppId;
   mInMozBrowser = aInMozBrowser;
 
-  nsCString str;
-  nsresult rv = GenerateNullPrincipalURI(str);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mURI = new nsNullPrincipalURI(str);
+  mURI = nsNullPrincipalURI::Create();
+  NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_AVAILABLE);
 
   return NS_OK;
 }
 
 void
 nsNullPrincipal::GetScriptLocation(nsACString &aStr)
 {
   mURI->GetSpec(aStr);
 }
 
-nsresult
-nsNullPrincipal::GenerateNullPrincipalURI(nsACString &aStr)
-{
-  // FIXME: bug 327161 -- make sure the uuid generator is reseeding-resistant.
-  nsresult rv;
-  nsCOMPtr<nsIUUIDGenerator> uuidgen =
-    do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsID id;
-  rv = uuidgen->GenerateUUIDInPlace(&id);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  char chars[NSID_LENGTH];
-  id.ToProvidedString(chars);
-
-  uint32_t suffixLen = NSID_LENGTH - 1;
-  uint32_t prefixLen = ArrayLength(NS_NULLPRINCIPAL_PREFIX) - 1;
-
-  // Use an nsCString so we only do the allocation once here and then share
-  // with nsJSPrincipals
-  aStr.SetCapacity(prefixLen + suffixLen);
-
-  aStr.Append(NS_NULLPRINCIPAL_PREFIX);
-  aStr.Append(chars);
-
-  if (aStr.Length() != prefixLen + suffixLen) {
-    NS_WARNING("Out of memory allocating null-principal URI");
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return NS_OK;
-}
-
 #ifdef DEBUG
 void nsNullPrincipal::dumpImpl()
 {
   nsAutoCString str;
   mURI->GetSpec(str);
   fprintf(stderr, "nsNullPrincipal (%p) = %s\n", this, str.get());
 }
 #endif 
--- a/caps/nsNullPrincipal.h
+++ b/caps/nsNullPrincipal.h
@@ -25,37 +25,44 @@ class nsIURI;
   { 0x93, 0x42, 0x90, 0xbf, 0xc9, 0xb7, 0xa1, 0xab } }
 #define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1"
 
 #define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal"
 
 class nsNullPrincipal final : public nsJSPrincipals
 {
 public:
+  // This should only be used by deserialization, and the factory constructor.
+  // Other consumers should use the Create and CreateWithInheritedAttributes
+  // methods.
   nsNullPrincipal();
 
   // Our refcount is managed by nsJSPrincipals.  Use this macro to avoid an
   // extra refcount member.
 
   // FIXME: bug 327245 -- I sorta wish there were a clean way to share the
   // nsJSPrincipals munging code between the various principal classes without
   // giving up the NS_DECL_NSIPRINCIPAL goodness.
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRINCIPAL
   NS_DECL_NSISERIALIZABLE
 
+  // Returns null on failure.
   static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIPrincipal *aInheritFrom);
 
+  // Returns null on failure.
+  static already_AddRefed<nsNullPrincipal>
+    Create(uint32_t aAppId = nsIScriptSecurityManager::NO_APP_ID,
+           bool aInMozBrowser = false);
+
   nsresult Init(uint32_t aAppId = nsIScriptSecurityManager::NO_APP_ID,
                 bool aInMozBrowser = false);
 
   virtual void GetScriptLocation(nsACString &aStr) override;
 
-  static nsresult GenerateNullPrincipalURI(nsACString &aStr);
-
 #ifdef DEBUG
   virtual void dumpImpl() override;
 #endif 
 
  protected:
   virtual ~nsNullPrincipal();
 
   nsCOMPtr<nsIURI> mURI;
--- a/caps/nsNullPrincipalURI.cpp
+++ b/caps/nsNullPrincipalURI.cpp
@@ -9,39 +9,61 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "mozilla/ipc/URIParams.h"
 
 #include "nsNetUtil.h"
 #include "nsEscape.h"
 #include "nsCRT.h"
+#include "nsIUUIDGenerator.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsNullPrincipalURI
 
-nsNullPrincipalURI::nsNullPrincipalURI(const nsCString &aSpec)
+nsNullPrincipalURI::nsNullPrincipalURI()
+  : mPath(mPathBytes, ArrayLength(mPathBytes), ArrayLength(mPathBytes) - 1)
 {
-  InitializeFromSpec(aSpec);
+}
+
+nsNullPrincipalURI::nsNullPrincipalURI(const nsNullPrincipalURI& aOther)
+  : mPath(mPathBytes, ArrayLength(mPathBytes), ArrayLength(mPathBytes) - 1)
+{
+  mPath.Assign(aOther.mPath);
 }
 
-void
-nsNullPrincipalURI::InitializeFromSpec(const nsCString &aSpec)
+nsresult
+nsNullPrincipalURI::Init()
 {
-  int32_t dividerPosition = aSpec.FindChar(':');
-  NS_ASSERTION(dividerPosition != -1, "Malformed URI!");
+  // FIXME: bug 327161 -- make sure the uuid generator is reseeding-resistant.
+  nsCOMPtr<nsIUUIDGenerator> uuidgen = services::GetUUIDGenerator();
+  NS_ENSURE_TRUE(uuidgen, NS_ERROR_NOT_AVAILABLE);
+
+  nsID id;
+  nsresult rv = uuidgen->GenerateUUIDInPlace(&id);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  MOZ_ASSERT(mPathBytes == mPath.BeginWriting());
+
+  id.ToProvidedString(mPathBytes);
 
-  mozilla::DebugOnly<int32_t> n = aSpec.Left(mScheme, dividerPosition);
-  NS_ASSERTION(n == dividerPosition, "Storing the scheme failed!");
+  MOZ_ASSERT(mPath.Length() == NSID_LENGTH - 1);
+  MOZ_ASSERT(strlen(mPath.get()) == NSID_LENGTH - 1);
+
+  return NS_OK;
+}
 
-  int32_t count = aSpec.Length() - dividerPosition - 1;
-  n = aSpec.Mid(mPath, dividerPosition + 1, count);
-  NS_ASSERTION(n == count, "Storing the path failed!");
-
-  ToLowerCase(mScheme);
+/* static */
+already_AddRefed<nsNullPrincipalURI>
+nsNullPrincipalURI::Create()
+{
+  nsRefPtr<nsNullPrincipalURI> uri = new nsNullPrincipalURI();
+  nsresult rv = uri->Init();
+  NS_ENSURE_SUCCESS(rv, nullptr);
+  return uri.forget();
 }
 
 static NS_DEFINE_CID(kNullPrincipalURIImplementationCID,
                      NS_NULLPRINCIPALURI_IMPLEMENTATION_CID);
 
 NS_IMPL_ADDREF(nsNullPrincipalURI)
 NS_IMPL_RELEASE(nsNullPrincipalURI)
 
@@ -142,17 +164,17 @@ NS_IMETHODIMP
 nsNullPrincipalURI::SetRef(const nsACString &aRef)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::GetPrePath(nsACString &_prePath)
 {
-  _prePath = mScheme + NS_LITERAL_CSTRING(":");
+  _prePath = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::GetPort(int32_t *_port)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
@@ -161,30 +183,30 @@ NS_IMETHODIMP
 nsNullPrincipalURI::SetPort(int32_t aPort)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::GetScheme(nsACString &_scheme)
 {
-  _scheme = mScheme;
+  _scheme = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::SetScheme(const nsACString &aScheme)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::GetSpec(nsACString &_spec)
 {
-  _spec = mScheme + NS_LITERAL_CSTRING(":") + mPath;
+  _spec = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":") + mPath;
   return NS_OK;
 }
 
 // result may contain unescaped UTF-8 characters
 NS_IMETHODIMP
 nsNullPrincipalURI::GetSpecIgnoringRef(nsACString &result)
 {
   return GetSpec(result);
@@ -225,18 +247,17 @@ NS_IMETHODIMP
 nsNullPrincipalURI::SetUserPass(const nsACString &aUserPass)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::Clone(nsIURI **_newURI)
 {
-  nsCOMPtr<nsIURI> uri =
-    new nsNullPrincipalURI(mScheme + NS_LITERAL_CSTRING(":") + mPath);
+  nsCOMPtr<nsIURI> uri = new nsNullPrincipalURI(*this);
   uri.forget(_newURI);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::CloneIgnoringRef(nsIURI **_newURI)
 {
   // GetRef/SetRef not supported by nsNullPrincipalURI, so
@@ -247,17 +268,17 @@ nsNullPrincipalURI::CloneIgnoringRef(nsI
 NS_IMETHODIMP
 nsNullPrincipalURI::Equals(nsIURI *aOther, bool *_equals)
 {
   *_equals = false;
   nsNullPrincipalURI *otherURI;
   nsresult rv = aOther->QueryInterface(kNullPrincipalURIImplementationCID,
                                        (void **)&otherURI);
   if (NS_SUCCEEDED(rv)) {
-    *_equals = (mScheme == otherURI->mScheme && mPath == otherURI->mPath);
+    *_equals = mPath == otherURI->mPath;
     NS_RELEASE(otherURI);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::EqualsExceptRef(nsIURI *aOther, bool *_equals)
 {
@@ -272,17 +293,17 @@ nsNullPrincipalURI::Resolve(const nsACSt
 {
   _resolvedURI = aRelativePath;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::SchemeIs(const char *aScheme, bool *_schemeIs)
 {
-  *_schemeIs = (0 == nsCRT::strcasecmp(mScheme.get(), aScheme));
+  *_schemeIs = (0 == nsCRT::strcasecmp(NS_NULLPRINCIPAL_SCHEME, aScheme));
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIIPCSerializableURI
 
 void
 nsNullPrincipalURI::Serialize(mozilla::ipc::URIParams &aParams)
@@ -293,31 +314,28 @@ nsNullPrincipalURI::Serialize(mozilla::i
 bool
 nsNullPrincipalURI::Deserialize(const mozilla::ipc::URIParams &aParams)
 {
   if (aParams.type() != mozilla::ipc::URIParams::TNullPrincipalURIParams) {
     MOZ_ASSERT_UNREACHABLE("unexpected URIParams type");
     return false;
   }
 
-  nsCString str;
-  nsresult rv = nsNullPrincipal::GenerateNullPrincipalURI(str);
+  nsresult rv = Init();
   NS_ENSURE_SUCCESS(rv, false);
 
-  InitializeFromSpec(str);
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsISizeOf
 
 size_t
 nsNullPrincipalURI::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
-  return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
-         mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
 }
 
 size_t
 nsNullPrincipalURI::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
 }
 
--- a/caps/nsNullPrincipalURI.h
+++ b/caps/nsNullPrincipalURI.h
@@ -13,16 +13,18 @@
 
 #include "nsIURI.h"
 #include "nsISizeOf.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "mozilla/Attributes.h"
 #include "nsIIPCSerializableURI.h"
 #include "mozilla/MemoryReporting.h"
+#include "nsNullPrincipal.h"
+#include "nsID.h"
 
 // {51fcd543-3b52-41f7-b91b-6b54102236e6}
 #define NS_NULLPRINCIPALURI_IMPLEMENTATION_CID \
   {0x51fcd543, 0x3b52, 0x41f7, \
     {0xb9, 0x1b, 0x6b, 0x54, 0x10, 0x22, 0x36, 0xe6} }
 
 class nsNullPrincipalURI final : public nsIURI
                                , public nsISizeOf
@@ -32,23 +34,27 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURI
   NS_DECL_NSIIPCSERIALIZABLEURI
 
   // nsISizeOf
   virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
-  explicit nsNullPrincipalURI(const nsCString &aSpec);
+  // NB: This constructor exists only for deserialization.  Everyone
+  // else should call Create.
+  nsNullPrincipalURI();
 
-  // NB: This constructor exists only for deserialization.
-  nsNullPrincipalURI() { }
+  // Returns null on failure.
+  static already_AddRefed<nsNullPrincipalURI> Create();
 
 private:
+  nsNullPrincipalURI(const nsNullPrincipalURI& aOther);
+
   ~nsNullPrincipalURI() {}
 
-  void InitializeFromSpec(const nsCString &aSpec);
+  nsresult Init();
 
-  nsCString mScheme;
-  nsCString mPath;
+  char mPathBytes[NSID_LENGTH];
+  nsFixedCString mPath;
 };
 
 #endif // __nsNullPrincipalURI_h__
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -168,17 +168,17 @@ public:
           mDidGetFlags(false),
           mMustFreeName(false)
     {
     }
 
     ~ClassInfoData()
     {
         if (mMustFreeName)
-            nsMemory::Free(mName);
+            free(mName);
     }
 
     uint32_t GetFlags()
     {
         if (!mDidGetFlags) {
             if (mClassInfo) {
                 nsresult rv = mClassInfo->GetFlags(&mFlags);
                 if (NS_FAILED(rv)) {
@@ -988,17 +988,18 @@ nsScriptSecurityManager::CreateCodebaseP
     // At least all the callers would do the right thing in those cases, as far
     // as I can tell.  --bz
 
     nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI);
     if (uriPrinc) {
         nsCOMPtr<nsIPrincipal> principal;
         uriPrinc->GetPrincipal(getter_AddRefs(principal));
         if (!principal) {
-            return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result);
+            principal = nsNullPrincipal::Create();
+            NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
         }
 
         principal.forget(result);
 
         return NS_OK;
     }
 
     nsRefPtr<nsPrincipal> codebase = new nsPrincipal();
@@ -1083,25 +1084,26 @@ nsScriptSecurityManager::GetCodebasePrin
 {
     NS_ENSURE_ARG(aURI);
 
     bool inheritsPrincipal;
     nsresult rv =
         NS_URIChainHasFlags(aURI,
                             nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                             &inheritsPrincipal);
+    nsCOMPtr<nsIPrincipal> principal;
     if (NS_FAILED(rv) || inheritsPrincipal) {
-        return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result);
+        principal = nsNullPrincipal::Create();
+        NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+    } else {
+        rv = CreateCodebasePrincipal(aURI, aAppId, aInMozBrowser,
+                                     getter_AddRefs(principal));
+        NS_ENSURE_SUCCESS(rv, rv);
     }
-
-    nsCOMPtr<nsIPrincipal> principal;
-    rv = CreateCodebasePrincipal(aURI, aAppId, aInMozBrowser,
-                                 getter_AddRefs(principal));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_IF_ADDREF(*result = principal);
+    principal.forget(result);
 
     return NS_OK;
 }
 
 // static
 nsIPrincipal*
 nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj)
 {
--- a/configure.in
+++ b/configure.in
@@ -8999,20 +8999,16 @@ if test "$MOZ_WIDGET_TOOLKIT" = "android
 else
     _INTL_API=yes
 fi
 
 if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
     USE_ICU=1
 fi
 
-if test "$OS_TARGET" = WINNT; then
-    MOZ_SHARED_ICU=1
-fi
-
 MOZ_CONFIG_ICU()
 
 if test -z "$JS_SHARED_LIBRARY"; then
   AC_DEFINE(MOZ_STATIC_JS)
 fi
 AC_SUBST(JS_SHARED_LIBRARY)
 
 MOZ_CREATE_CONFIG_STATUS()
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11042,17 +11042,17 @@ nsDocShell::ScrollToAnchor(nsACString& a
     // In that case, we should just fall through to using the
     // page's charset.
     nsresult rv = NS_ERROR_FAILURE;
     NS_ConvertUTF8toUTF16 uStr(str);
     if (!uStr.IsEmpty()) {
       rv = shell->GoToAnchor(NS_ConvertUTF8toUTF16(str), scroll,
                              nsIPresShell::SCROLL_SMOOTH_AUTO);
     }
-    nsMemory::Free(str);
+    free(str);
 
     // Above will fail if the anchor name is not UTF-8.  Need to
     // convert from document charset to unicode.
     if (NS_FAILED(rv)) {
       // Get a document charset
       NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
       nsIDocument* doc = mContentViewer->GetDocument();
       NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -267,17 +267,21 @@ Animation::IsInEffect() const
 }
 
 const AnimationProperty*
 Animation::GetAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
-      return &mProperties[propIdx];
+      const AnimationProperty* result = &mProperties[propIdx];
+      if (!result->mWinsInCascade) {
+        result = nullptr;
+      }
+      return result;
     }
   }
   return nullptr;
 }
 
 void
 Animation::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
                         nsCSSPropertySet& aSetProperties)
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -111,18 +111,19 @@ public:
   const nsSMILKeySpline* GetFunction() const {
     NS_ASSERTION(mType == nsTimingFunction::Function, "Type mismatch");
     return &mTimingFunction;
   }
   Type GetType() const { return mType; }
   uint32_t GetSteps() const { return mSteps; }
   bool operator==(const ComputedTimingFunction& aOther) const {
     return mType == aOther.mType &&
-           mTimingFunction == aOther.mTimingFunction &&
-           mSteps == aOther.mSteps;
+           (mType == nsTimingFunction::Function ?
+	    mTimingFunction == aOther.mTimingFunction :
+	    mSteps == aOther.mSteps);
   }
   bool operator!=(const ComputedTimingFunction& aOther) const {
     return !(*this == aOther);
   }
 
 private:
   Type mType;
   nsSMILKeySpline mTimingFunction;
@@ -154,19 +155,22 @@ struct AnimationProperty
   // Does this property win in the CSS Cascade?
   //
   // For CSS transitions, this is true as long as a CSS animation on the
   // same property and element is not running, in which case we set this
   // to false so that the animation (lower in the cascade) can win.  We
   // then use this to decide whether to apply the style both in the CSS
   // cascade and for OMTA.
   //
-  // FIXME (bug 847287): For CSS Animations, which are overridden by
-  // !important rules in the cascade, we actually determine this from
-  // the CSS cascade computations, and then use it for OMTA.
+  // For CSS Animations, which are overridden by !important rules in the
+  // cascade, we actually determine this from the CSS cascade
+  // computations, and then use it for OMTA.
+  // **NOTE**: For CSS animations, we only bother setting mWinsInCascade
+  // accurately for properties that we can animate on the compositor.
+  // For other properties, we make it always be true.
   bool mWinsInCascade;
 
   InfallibleTArray<AnimationPropertySegment> mSegments;
 
   bool operator==(const AnimationProperty& aOther) const {
     return mProperty == aOther.mProperty &&
            mWinsInCascade == aOther.mWinsInCascade &&
            mSegments == aOther.mSegments;
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -1,16 +1,17 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationPlayer.h"
 #include "AnimationUtils.h"
 #include "mozilla/dom/AnimationPlayerBinding.h"
+#include "mozilla/AutoRestore.h"
 #include "AnimationCommon.h" // For AnimationPlayerCollection,
                              // CommonAnimationManager
 #include "nsIDocument.h" // For nsIDocument
 #include "nsIPresShell.h" // For nsIPresShell
 #include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336)
 #include "PendingPlayerTracker.h" // For PendingPlayerTracker
 
 namespace mozilla {
@@ -101,17 +102,17 @@ AnimationPlayer::SilentlySetCurrentTime(
       mPlaybackRate == 0.0
       /*or, once supported, if we have a pending pause task*/) {
     mHoldTime.SetValue(aSeekTime);
     if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
       mStartTime.SetNull();
     }
   } else {
     mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
-                          (aSeekTime / mPlaybackRate));
+                          (aSeekTime.MultDouble(1 / mPlaybackRate)));
   }
 
   mPreviousCurrentTime.SetNull();
 }
 
 // Implements http://w3c.github.io/web-animations/#set-the-current-time
 void
 AnimationPlayer::SetCurrentTime(const TimeDuration& aSeekTime)
@@ -282,24 +283,24 @@ AnimationPlayer::Tick()
 {
   // Since we are not guaranteed to get only one call per refresh driver tick,
   // it's possible that mPendingReadyTime is set to a time in the future.
   // In that case, we should wait until the next refresh driver tick before
   // resuming.
   if (mPendingState != PendingState::NotPending &&
       !mPendingReadyTime.IsNull() &&
       mPendingReadyTime.Value() <= mTimeline->GetCurrentTime().Value()) {
-    ResumeAt(mPendingReadyTime.Value());
+    FinishPendingAt(mPendingReadyTime.Value());
     mPendingReadyTime.SetNull();
   }
 
   if (IsPossiblyOrphanedPendingPlayer()) {
     MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
                "Orphaned pending players should have an active timeline");
-    ResumeAt(mTimeline->GetCurrentTime().Value());
+    FinishPendingAt(mTimeline->GetCurrentTime().Value());
   }
 
   UpdateTiming();
 }
 
 void
 AnimationPlayer::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime)
 {
@@ -318,17 +319,17 @@ AnimationPlayer::TriggerOnNextTick(const
 void
 AnimationPlayer::TriggerNow()
 {
   MOZ_ASSERT(PlayState() == AnimationPlayState::Pending,
              "Expected to start a pending player");
   MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
              "Expected an active timeline");
 
-  ResumeAt(mTimeline->GetCurrentTime().Value());
+  FinishPendingAt(mTimeline->GetCurrentTime().Value());
 }
 
 Nullable<TimeDuration>
 AnimationPlayer::GetCurrentOrPendingStartTime() const
 {
   Nullable<TimeDuration> result;
 
   if (!mStartTime.IsNull()) {
@@ -418,24 +419,85 @@ AnimationPlayer::ComposeStyle(nsRefPtr<c
   }
 
   AnimationPlayState playState = PlayState();
   if (playState == AnimationPlayState::Running ||
       playState == AnimationPlayState::Pending) {
     aNeedsRefreshes = true;
   }
 
-  mSource->ComposeStyle(aStyleRule, aSetProperties);
+  // In order to prevent flicker, there are a few cases where we want to use
+  // a different time for rendering that would otherwise be returned by
+  // GetCurrentTime. These are:
+  //
+  // (a) For animations that are pausing but which are still running on the
+  //     compositor. In this case we send a layer transaction that removes the
+  //     animation but which also contains the animation values calculated on
+  //     the main thread. To prevent flicker when this occurs we want to ensure
+  //     the timeline time used to calculate the main thread animation values
+  //     does not lag far behind the time used on the compositor. Ideally we
+  //     would like to use the "animation ready time" calculated at the end of
+  //     the layer transaction as the timeline time but it will be too late to
+  //     update the style rule at that point so instead we just use the current
+  //     wallclock time.
+  //
+  // (b) For animations that are pausing that we have already taken off the
+  //     compositor. In this case we record a pending ready time but we don't
+  //     apply it until the next tick. However, while waiting for the next tick,
+  //     we should still use the pending ready time as the timeline time. If we
+  //     use the regular timeline time the animation may appear jump backwards
+  //     if the main thread's timeline time lags behind the compositor.
+  //
+  // (c) For animations that are play-pending due to an aborted pause operation
+  //     (i.e. a pause operation that was interrupted before we entered the
+  //     paused state). When we cancel a pending pause we might momentarily take
+  //     the animation off the compositor, only to re-add it moments later. In
+  //     that case the compositor might have been ahead of the main thread so we
+  //     should use the current wallclock time to ensure the animation doesn't
+  //     temporarily jump backwards.
+  //
+  // To address each of these cases we temporarily tweak the hold time
+  // immediately before updating the style rule and then restore it immediately
+  // afterwards. This is purely to prevent visual flicker. Other behavior
+  // such as dispatching events continues to rely on the regular timeline time.
+  {
+    AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
+    bool updatedHoldTime = false;
 
-  //XXXjwatt mIsPreviousStateFinished = (playState == AnimationPlayState::Finished);
+    if (PlayState() == AnimationPlayState::Pending &&
+        mHoldTime.IsNull() &&
+        !mStartTime.IsNull()) {
+      Nullable<TimeDuration> timeToUse = mPendingReadyTime;
+      if (timeToUse.IsNull() &&
+          mTimeline &&
+          !mTimeline->IsUnderTestControl()) {
+        timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
+      }
+      if (!timeToUse.IsNull()) {
+        mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
+                            .MultDouble(mPlaybackRate));
+        // Push the change down to the source content
+        UpdateSourceContent();
+        updatedHoldTime = true;
+      }
+    }
+
+    mSource->ComposeStyle(aStyleRule, aSetProperties);
+
+    if (updatedHoldTime) {
+      UpdateTiming();
+    }
+  }
 }
 
 void
 AnimationPlayer::DoPlay(LimitBehavior aLimitBehavior)
 {
+  bool abortedPause = mPendingState == PendingState::PausePending;
+
   bool reuseReadyPromise = false;
   if (mPendingState != PendingState::NotPending) {
     CancelPendingTasks();
     reuseReadyPromise = true;
   }
 
   Nullable<TimeDuration> currentTime = GetCurrentTime();
   if (mPlaybackRate > 0.0 &&
@@ -449,22 +511,30 @@ AnimationPlayer::DoPlay(LimitBehavior aL
               (aLimitBehavior == LimitBehavior::AutoRewind &&
                (currentTime.Value().ToMilliseconds() <= 0.0 ||
                 currentTime.Value() > SourceContentEnd())))) {
     mHoldTime.SetValue(TimeDuration(SourceContentEnd()));
   } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
     mHoldTime.SetValue(TimeDuration(0));
   }
 
-  if (mHoldTime.IsNull()) {
+  // If the hold time is null then we're either already playing normally (and
+  // we can ignore this call) or we aborted a pending pause operation (in which
+  // case, for consistency, we need to go through the motions of doing an
+  // asynchronous start even though we already have a resolved start time).
+  if (mHoldTime.IsNull() && !abortedPause) {
     return;
   }
 
-  // Clear the start time until we resolve a new one
-  mStartTime.SetNull();
+  // Clear the start time until we resolve a new one (unless we are aborting
+  // a pending pause operation, in which case we keep the old start time so
+  // that the animation continues moving uninterrupted by the aborted pause).
+  if (!abortedPause) {
+    mStartTime.SetNull();
+  }
 
   if (!reuseReadyPromise) {
     // Clear ready promise. We'll create a new one lazily.
     mReady = nullptr;
   }
 
   mPendingState = PendingState::PlayPending;
 
@@ -479,57 +549,93 @@ AnimationPlayer::DoPlay(LimitBehavior aL
 
   // We may have updated the current time when we set the hold time above.
   UpdateTiming();
 }
 
 void
 AnimationPlayer::DoPause()
 {
+  if (mPendingState == PendingState::PausePending) {
+    return;
+  }
+
+  bool reuseReadyPromise = false;
   if (mPendingState == PendingState::PlayPending) {
     CancelPendingTasks();
-    // Resolve the ready promise since we currently only use it for
-    // players that are waiting to play. Later (in bug 1109390), we will
-    // use this for players waiting to pause as well and then we won't
-    // want to resolve it just yet.
-    if (mReady) {
-      mReady->MaybeResolve(this);
-    }
+    reuseReadyPromise = true;
   }
 
   // Mark this as no longer running on the compositor so that next time
   // we update animations we won't throttle them and will have a chance
   // to remove the animation from any layer it might be on.
   mIsRunningOnCompositor = false;
 
-  // Bug 1109390 - check for null result here and go to pending state
-  mHoldTime = GetCurrentTime();
-  mStartTime.SetNull();
+  if (!reuseReadyPromise) {
+    // Clear ready promise. We'll create a new one lazily.
+    mReady = nullptr;
+  }
+
+  mPendingState = PendingState::PausePending;
+
+  nsIDocument* doc = GetRenderedDocument();
+  if (!doc) {
+    TriggerOnNextTick(Nullable<TimeDuration>());
+    return;
+  }
+
+  PendingPlayerTracker* tracker = doc->GetOrCreatePendingPlayerTracker();
+  tracker->AddPausePending(*this);
 
   UpdateFinishedState();
 }
 
 void
-AnimationPlayer::ResumeAt(const TimeDuration& aResumeTime)
+AnimationPlayer::ResumeAt(const TimeDuration& aReadyTime)
 {
   // This method is only expected to be called for a player that is
   // waiting to play. We can easily adapt it to handle other states
   // but it's currently not necessary.
   MOZ_ASSERT(mPendingState == PendingState::PlayPending,
              "Expected to resume a play-pending player");
-  MOZ_ASSERT(!mHoldTime.IsNull(),
-             "A player in the play-pending state should have a resolved"
-             " hold time");
+  MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
+             "A player in the play-pending state should have either a"
+             " resolved hold time or resolved start time (but not both)");
+
+  // If we aborted a pending pause operation we will already have a start time
+  // we should use. In all other cases, we resolve it from the ready time.
+  if (mStartTime.IsNull()) {
+    if (mPlaybackRate != 0) {
+      mStartTime.SetValue(aReadyTime -
+                          (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
+      mHoldTime.SetNull();
+    } else {
+      mStartTime.SetValue(aReadyTime);
+    }
+  }
+  mPendingState = PendingState::NotPending;
 
-  if (mPlaybackRate != 0) {
-    mStartTime.SetValue(aResumeTime - (mHoldTime.Value() / mPlaybackRate));
-    mHoldTime.SetNull();
-  } else {
-    mStartTime.SetValue(aResumeTime);
+  UpdateTiming();
+
+  if (mReady) {
+    mReady->MaybeResolve(this);
   }
+}
+
+void
+AnimationPlayer::PauseAt(const TimeDuration& aReadyTime)
+{
+  MOZ_ASSERT(mPendingState == PendingState::PausePending,
+             "Expected to pause a pause-pending player");
+
+  if (!mStartTime.IsNull()) {
+    mHoldTime.SetValue((aReadyTime - mStartTime.Value())
+                        .MultDouble(mPlaybackRate));
+  }
+  mStartTime.SetNull();
   mPendingState = PendingState::NotPending;
 
   UpdateTiming();
 
   if (mReady) {
     mReady->MaybeResolve(this);
   }
 }
@@ -569,33 +675,35 @@ AnimationPlayer::UpdateFinishedState(boo
         mHoldTime = currentTime;
       } else {
         mHoldTime.SetValue(0);
       }
     } else if (mPlaybackRate != 0.0 &&
                !currentTime.IsNull()) {
       if (aSeekFlag && !mHoldTime.IsNull()) {
         mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
-                              (mHoldTime.Value() / mPlaybackRate));
+                              (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
       }
       mHoldTime.SetNull();
     }
   }
 
   bool currentFinishedState = IsFinished();
   if (currentFinishedState && !mIsPreviousStateFinished) {
     if (mFinished) {
       mFinished->MaybeResolve(this);
     }
   } else if (!currentFinishedState && mIsPreviousStateFinished) {
     // Clear finished promise. We'll create a new one lazily.
     mFinished = nullptr;
   }
   mIsPreviousStateFinished = currentFinishedState;
-  mPreviousCurrentTime = currentTime;
+  // We must recalculate the current time to take account of any mHoldTime
+  // changes the code above made.
+  mPreviousCurrentTime = GetCurrentTime();
 }
 
 void
 AnimationPlayer::UpdateSourceContent()
 {
   if (mSource) {
     mSource->SetParentTime(GetCurrentTime());
     UpdateRelevance();
--- a/dom/animation/AnimationPlayer.h
+++ b/dom/animation/AnimationPlayer.h
@@ -257,17 +257,28 @@ public:
   // is running and has source content to sample).
   void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties,
                     bool& aNeedsRefreshes);
 
 protected:
   void DoPlay(LimitBehavior aLimitBehavior);
   void DoPause();
-  void ResumeAt(const TimeDuration& aResumeTime);
+  void ResumeAt(const TimeDuration& aReadyTime);
+  void PauseAt(const TimeDuration& aReadyTime);
+  void FinishPendingAt(const TimeDuration& aReadyTime)
+  {
+    if (mPendingState == PendingState::PlayPending) {
+      ResumeAt(aReadyTime);
+    } else if (mPendingState == PendingState::PausePending) {
+      PauseAt(aReadyTime);
+    } else {
+      NS_NOTREACHED("Can't finish pending if we're not in a pending state");
+    }
+  }
 
   void UpdateTiming();
   void UpdateFinishedState(bool aSeekFlag = false);
   void UpdateSourceContent();
   void FlushStyle() const;
   void PostUpdate();
   /**
    * Remove this player from the pending player tracker and reset
--- a/dom/animation/test/chrome/test_animation_observers.html
+++ b/dom/animation/test/chrome/test_animation_observers.html
@@ -359,27 +359,40 @@ function assert_records(expected, desc) 
     e.style = "transition-duration: 100s; " +
               "transition-property: color, background-color, line-height; " +
               "color: blue; background-color: lime; line-height: 24px;";
 
     // The transitions should cause the creation of three Animations.
     var animations = e.getAnimations();
     is(animations.length, 3, "getAnimations().length after transition starts");
 
+    info("animation states: " + animations.map(p => p.playState));
+    info("animation times:  " + animations.map(p => p.currentTime));
+
     // Wait for the single MutationRecord for the Animation additions to
     // be delivered.
     yield await_frame();
     assert_records([{ added: animations, changed: [], removed: [] }],
                    "records after transition starts");
 
+    info("animation states: " + animations.map(p => p.playState));
+    info("animation times:  " + animations.map(p => p.currentTime));
+
     // Wait for the Animations to get going, then seek well into
     // the transitions.
     yield await_frame();
+
+    info("animation states: " + animations.map(p => p.playState));
+    info("animation times:  " + animations.map(p => p.currentTime));
+
     animations.forEach(p => p.currentTime = 50000);
 
+    info("animation states: " + animations.map(p => p.playState));
+    info("animation times:  " + animations.map(p => p.currentTime));
+
     is(animations.filter(p => p.playState == "running").length, 3, "number of running Animations");
 
     // Cancel one of the transitions by setting transition-property.
     e.style.transitionProperty = "background-color, line-height";
 
     var colorAnimation  = animations.filter(p => p.playState != "running");
     var otherAnimations = animations.filter(p => p.playState == "running");
 
--- a/dom/animation/test/css-animations/test_animation-pausing.html
+++ b/dom/animation/test/css-animations/test_animation-pausing.html
@@ -171,9 +171,32 @@ async_test(function(t) {
                   'animation-play-state is running');
     assert_equals(getMarginLeft(cs), previousAnimVal,
                   'Paused value of margin-left does not change');
     t.done();
   }));
 }, 'pause() applies pending changes to animation-play-state first');
 // (Note that we can't actually test for this; see comment above, in test-body.)
 
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 1000s' });
+  var animation = div.getAnimations()[0];
+
+  var readyPromiseRun = false;
+
+  animation.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+    assert_equals(animation.playState, 'pending', 'Animation is pause pending');
+
+    // Set current time
+    animation.currentTime = 5000;
+    assert_equals(animation.playState, 'running',
+                  'Animation is running immediately after setting currentTime');
+
+    // The ready promise should now be resolved. If it's not then test will
+    // probably time out before anything else happens that causes it to resolve.
+    return animation.ready;
+  })).then(t.step_func(function() {
+    t.done();
+  }));
+}, 'Setting the current time cancels a pending pause');
+
 </script>
--- a/dom/animation/test/css-animations/test_animation-player-currenttime.html
+++ b/dom/animation/test/css-animations/test_animation-player-currenttime.html
@@ -457,48 +457,48 @@ async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
   animation.currentTime = currentTimeForBeforePhase(animation.timeline);
 
-  waitForTwoAnimationFrames().then(function() {
+  waitForAnimationFrames(2).then(function() {
     eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> active, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
   animation.currentTime = currentTimeForBeforePhase(animation.timeline);
 
-  waitForTwoAnimationFrames().then(function() {
+  waitForAnimationFrames(2).then(function() {
     eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvent('animationstart').then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> before, then back');
 
@@ -507,17 +507,17 @@ async_test(function(t) {
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvent('animationstart').then(function() {
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> after, then back');
 
@@ -527,17 +527,17 @@ async_test(function(t) {
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvents(['animationstart',
                               'animationend']).then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> before, then back');
 
@@ -547,17 +547,17 @@ async_test(function(t) {
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvents(['animationstart',
                               'animationend']).then(function() {
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> active, then back');
 
@@ -606,11 +606,52 @@ async_test(function(t) {
       'Animation.currentTime is unchanged after pausing');
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Animation.currentTime after pausing');
 
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  div.style.animation = ANIM_PROPERTY_VAL;
+
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(function() {
+    // just before animation ends:
+    animation.currentTime = ANIM_DELAY_MS + ANIM_DUR_MS - 1;
+
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(function() {
+    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
+      'Animation.currentTime should not continue to increase after the ' +
+      'animation has finished');
+    t.done();
+  }));
+}, 'Test Animation.currentTime clamping');
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  div.style.animation = ANIM_PROPERTY_VAL;
+
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(function() {
+    // play backwards:
+    animation.playbackRate = -1;
+
+    // just before animation ends (at the "start"):
+    animation.currentTime = 1;
+
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(function() {
+    assert_equals(animation.currentTime, 0,
+      'Animation.currentTime should not continue to decrease after an ' +
+      'animation running in reverse has finished and currentTime is zero');
+    t.done();
+  }));
+}, 'Test Animation.currentTime clamping for reversed animation');
+
     </script>
   </body>
 </html>
--- a/dom/animation/test/css-animations/test_animation-player-finished.html
+++ b/dom/animation/test/css-animations/test_animation-player-finished.html
@@ -234,17 +234,17 @@ async_test(function(t) {
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = ANIM_PROP_VAL;
   var animation = div.getAnimations()[0];
 
   animation.ready.then(function() {
     animation.playbackRate = 0;
     animation.currentTime = ANIM_DURATION + 1000;
-    return waitForTwoAnimationFrames();
+    return waitForAnimationFrames(2);
   }).then(t.step_func(function() {
     t.done();
   }));
 
   animation.finished.then(t.step_func(function() {
     assert_unreached('finished promise should not resolve when playbackRate ' +
                      'is zero');
   }));
@@ -269,17 +269,17 @@ async_test(function(t) {
   var animation = div.getAnimations()[0];
 
   var previousFinishedPromise = animation.finished;
 
   animation.currentTime = ANIM_DURATION;
 
   animation.finished.then(function() {
     div.style.animationPlayState = 'running';
-    return waitForTwoAnimationFrames();
+    return waitForAnimationFrames(2);
   }).then(t.step_func(function() {
     assert_equals(animation.finished, previousFinishedPromise,
                   'Should not replay when animation-play-state changes to ' +
                   '"running" on finished animation');
     assert_equals(animation.currentTime, ANIM_DURATION,
                   'currentTime should not change when animation-play-state ' +
                   'changes to "running" on finished animation');
     t.done();
--- a/dom/animation/test/css-animations/test_animation-player-playstate.html
+++ b/dom/animation/test/css-animations/test_animation-player-playstate.html
@@ -25,36 +25,32 @@ async_test(function(t) {
 }, 'Animation returns correct playState when running');
 
 async_test(function(t) {
   var div = addDiv(t);
   var cs = window.getComputedStyle(div);
   div.style.animation = 'anim 1000s paused';
 
   var animation = div.getAnimations()[0];
-  // FIXME: When we turn on async pausing later in this patch series, enable
-  // this
-  //   assert_equals(animation.playState, 'pending');
+  assert_equals(animation.playState, 'pending');
 
   animation.ready.then(t.step_func(function() {
     assert_equals(animation.playState, 'paused');
     t.done();
   }));
 }, 'Animation returns correct playState when paused');
 
 async_test(function(t) {
   var div = addDiv(t);
   var cs = window.getComputedStyle(div);
   div.style.animation = 'anim 1000s';
 
   var animation = div.getAnimations()[0];
   animation.pause();
-  // FIXME: When we turn on async pausing later in this patch series, enable
-  // this
-  //   assert_equals(animation.playState, 'pending');
+  assert_equals(animation.playState, 'pending');
 
   animation.ready.then(t.step_func(function() {
     assert_equals(animation.playState, 'paused');
     t.done();
   }));
 }, 'Animation.playState updates when paused by script');
 
 test(function(t) {
--- a/dom/animation/test/css-animations/test_animation-player-ready.html
+++ b/dom/animation/test/css-animations/test_animation-player-ready.html
@@ -13,43 +13,49 @@
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
   var originalReadyPromise = animation.ready;
-  animation.ready.then(function() {
+  var pauseReadyPromise;
+
+  animation.ready.then(t.step_func(function() {
     assert_equals(animation.ready, originalReadyPromise,
                   'Ready promise is the same object when playing completes');
     animation.pause();
-    // TODO: When we implement deferred pausing (bug 1109390), change this to
-    // assert_not_equals and wait on the new promise before continuing.
-    assert_equals(animation.ready, originalReadyPromise,
-                  'Ready promise does not change when pausing (for now)');
+    assert_not_equals(animation.ready, originalReadyPromise,
+                      'A new ready promise is created when pausing');
+    pauseReadyPromise = animation.ready;
+    // Wait for the promise to fulfill since if we abort the pause the ready
+    // promise object is reused.
+    return animation.ready;
+  })).then(t.step_func(function() {
     animation.play();
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'Ready promise object identity differs after calling'
-                      + ' play()');
+    assert_not_equals(animation.ready, pauseReadyPromise,
+                      'A new ready promise is created when playing');
     t.done();
-  });
-}, 'A new ready promise is created each time play() is called'
-   + ' the animation property');
+  }));
+}, 'A new ready promise is created when play()/pause() is called');
 
-test(function(t) {
+async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s paused';
   var animation = div.getAnimations()[0];
 
   var originalReadyPromise = animation.ready;
-  div.style.animationPlayState = 'running';
-  assert_not_equals(animation.ready, originalReadyPromise,
-                    'After updating animation-play-state a new ready promise'
-                    + ' object is created');
+  animation.ready.then(function() {
+    div.style.animationPlayState = 'running';
+    assert_not_equals(animation.ready, originalReadyPromise,
+                      'After updating animation-play-state a new ready promise'
+                      + ' object is created');
+    t.done();
+  });
 }, 'A new ready promise is created when setting animation-play-state: running');
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
   animation.ready.then(function() {
@@ -130,9 +136,79 @@ async_test(function(t) {
 
   // Now update the animation and flush styles
   div.style.animation = 'def 100s';
   window.getComputedStyle(div).animation;
 
 }, 'ready promise is rejected when an animation is cancelled by updating'
    + ' the animation property');
 
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: abc 100s' });
+  var animation = div.getAnimations()[0];
+
+  var originalReadyPromise = animation.ready;
+  animation.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+    assert_not_equals(animation.ready, originalReadyPromise,
+                      'A new Promise object is generated when setting'
+                      + ' animation-play-state: paused');
+    t.done();
+  }));
+}, 'A new ready promise is created when setting animation-play-state: paused');
+
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: abc 100s' });
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+    var firstReadyPromise = animation.ready;
+    animation.pause();
+    assert_equals(animation.ready, firstReadyPromise,
+                  'Ready promise objects are identical after redundant pause');
+    t.done();
+  }));
+}, 'Pausing twice re-uses the same Promise');
+
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: abc 100s' });
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+
+    // Flush style and verify we're pending at the same time
+    assert_equals(animation.playState, 'pending', 'Animation is pending');
+    var pauseReadyPromise = animation.ready;
+
+    // Now play again immediately
+    div.style.animationPlayState = 'running';
+    assert_equals(animation.playState, 'pending', 'Animation is still pending');
+    assert_equals(animation.ready, pauseReadyPromise,
+                  'The pause Promise is re-used when playing while waiting'
+                  + ' to pause');
+
+    return animation.ready;
+  })).then(t.step_func(function() {
+    assert_equals(animation.playState, 'running',
+                  'Animation is running after aborting a pause');
+    t.done();
+  }));
+}, 'If a pause operation is interrupted, the ready promise is reused');
+
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: abc 100s' });
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+    return animation.ready;
+  })).then(t.step_func(function(resolvedAnimation) {
+    assert_equals(resolvedAnimation, animation,
+                  'Promise received when ready Promise for a pause operation'
+                  + ' is completed is the animation on which the pause was'
+                  + ' performed');
+    t.done();
+  }));
+}, 'When a pause is complete the Promise callback gets the correct animation');
+
 </script>
--- a/dom/animation/test/css-animations/test_animation-player-starttime.html
+++ b/dom/animation/test/css-animations/test_animation-player-starttime.html
@@ -311,25 +311,23 @@ async_test(function(t)
 
 async_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   animation.ready.then(t.step_func(function() {
     div.style.animationPlayState = 'paused';
     getComputedStyle(div).animationPlayState;
-    /* FIXME: Switch this on once deferred pausing is enabled
     assert_not_equals(animation.startTime, null,
                       'startTime is resolved when pause-pending');
-    */
 
     div.style.animationPlayState = 'running';
     getComputedStyle(div).animationPlayState;
-    assert_equals(animation.startTime, null,
-                  'startTime is unresolved when play-pending');
+    assert_not_equals(animation.startTime, null,
+                      'startTime is preserved when a pause is aborted');
     t.done();
   }));
 }, 'startTime while pause-pending and play-pending');
 
 async_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
@@ -342,16 +340,45 @@ async_test(function(t)
     // FIXME: Enable this once we implement finishing behavior (bug 1074630)
     /*
     assert_equals(animation.startTime, null, 'startTime is unresolved');
     */
     t.done();
   }));
 }, 'startTime while play-pending from finished state');
 
+async_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 1000s' });
+  var animation = div.getAnimations()[0];
+
+  assert_equals(animation.startTime, null, 'The initial startTime is null');
+  var initialTimelineTime = document.timeline.currentTime;
+
+  animation.ready.then(t.step_func(function() {
+    assert_true(animation.startTime > initialTimelineTime,
+                'After the animation has started, startTime is greater than ' +
+                'the time when it was started');
+    var startTimeBeforePausing = animation.startTime;
+
+    div.style.animationPlayState = 'paused';
+    // Flush styles just in case querying animation.startTime doesn't flush
+    // styles (which would be a bug in of itself and could mask a further bug
+    // by causing startTime to appear to not change).
+    getComputedStyle(div).animationPlayState;
+
+    assert_equals(animation.startTime, startTimeBeforePausing,
+                  'The startTime does not change when pausing-pending');
+    return animation.ready;
+  })).then(t.step_func(function() {
+    assert_equals(animation.startTime, null,
+                  'After actually pausing, the startTime of an animation ' +
+                  'is null');
+    t.done();
+  }));
+}, 'Pausing should make the startTime become null');
 
 test(function(t)
 {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
   var currentTime = animation.timeline.currentTime;
@@ -478,48 +505,48 @@ async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.startTime = startTimeForActivePhase(animation.timeline);
   animation.startTime = startTimeForBeforePhase(animation.timeline);
 
-  waitForTwoAnimationFrames().then(function() {
+  waitForAnimationFrames(2).then(function() {
     eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> active, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.startTime = startTimeForAfterPhase(animation.timeline);
   animation.startTime = startTimeForBeforePhase(animation.timeline);
 
-  waitForTwoAnimationFrames().then(function() {
+  waitForAnimationFrames(2).then(function() {
     eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvent('animationstart').then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> before, then back');
 
@@ -528,17 +555,17 @@ async_test(function(t) {
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvent('animationstart').then(function() {
     animation.startTime = startTimeForAfterPhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> after, then back');
 
@@ -548,17 +575,17 @@ async_test(function(t) {
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvents(['animationstart',
                               'animationend']).then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> before, then back');
 
@@ -568,17 +595,17 @@ async_test(function(t) {
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   eventWatcher.waitForEvents(['animationstart',
                               'animationend']).then(function() {
     animation.startTime = startTimeForActivePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
-    waitForTwoAnimationFrames().then(function() {
+    waitForAnimationFrames(2).then(function() {
       eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> active, then back');
 
--- a/dom/animation/test/css-animations/test_element-get-animation-players.html
+++ b/dom/animation/test/css-animations/test_element-get-animation-players.html
@@ -228,20 +228,20 @@ test(function(t) {
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim1 100s';
   var originalAnimation = div.getAnimations()[0];
 
   // Update pause state (an Animation change)
   div.style.animationPlayState = 'paused';
-  var pausedAnimation = div.getAnimations()[0];
-  assert_equals(pausedAnimation.playState, 'paused',
-                'animation\'s paused state is updated');
-  assert_equals(originalAnimation, pausedAnimation,
+  var pendingAnimation = div.getAnimations()[0];
+  assert_equals(pendingAnimation.playState, 'pending',
+                'animation\'s play state is updated');
+  assert_equals(originalAnimation, pendingAnimation,
                 'getAnimations returns the same objects even when their'
                 + ' play state changes');
 
   // Update duration (an Animation change)
   div.style.animationDuration = '200s';
   var extendedAnimation = div.getAnimations()[0];
   // FIXME: Check extendedAnimation.source.timing.duration has changed once the
   // API is available
--- a/dom/animation/test/css-transitions/test_animation-pausing.html
+++ b/dom/animation/test/css-transitions/test_animation-pausing.html
@@ -24,18 +24,20 @@ async_test(function(t) {
   var animation = div.getAnimations()[0];
   assert_equals(getMarginLeft(cs), 0,
                 'Initial value of margin-left is zero');
   var previousAnimVal = getMarginLeft(cs);
 
   animation.ready.then(waitForFrame).then(t.step_func(function() {
     assert_true(getMarginLeft(cs) > previousAnimVal,
                 'margin-left is initially increasing');
+    animation.pause();
+    return animation.ready;
+  })).then(t.step_func(function() {
     previousAnimVal = getMarginLeft(cs);
-    animation.pause();
     return waitForFrame();
   })).then(t.step_func(function() {
     assert_equals(getMarginLeft(cs), previousAnimVal,
                   'margin-left does not increase after calling pause()');
     previousAnimVal = getMarginLeft(cs);
     animation.play();
     return animation.ready.then(waitForFrame);
   })).then(t.step_func(function() {
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-transitions/test_animation-player-currenttime.html
@@ -0,0 +1,423 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Tests for the effect of setting a CSS transition's
+           Animation.currentTime</title>
+    <style>
+
+.animated-div {
+  margin-left: 100px;
+  transition: margin-left 1000s linear 1000s;
+}
+
+    </style>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../testcommon.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script type="text/javascript">
+
+'use strict';
+
+// TODO: add equivalent tests without an animation-delay, but first we need to
+// change the timing of animationstart dispatch. (Right now the animationstart
+// event will fire before the ready Promise is resolved if there is no
+// animation-delay.)
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
+
+// TODO: Once the computedTiming property is implemented, add checks to the
+// checker helpers to ensure that computedTiming's properties are updated as
+// expected.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
+
+
+const ANIM_DELAY_MS = 1000000; // 1000s
+const ANIM_DUR_MS = 1000000; // 1000s
+
+/**
+ * These helpers get the value that the currentTime needs to be set to, to put
+ * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
+ * the middle of various phases or points through the active duration.
+ */
+function currentTimeForBeforePhase() {
+  return ANIM_DELAY_MS / 2;
+}
+function currentTimeForActivePhase() {
+  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
+}
+function currentTimeForAfterPhase() {
+  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
+}
+function currentTimeForStartOfActiveInterval() {
+  return ANIM_DELAY_MS;
+}
+function currentTimeForFiftyPercentThroughActiveInterval() {
+  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
+}
+function currentTimeForEndOfActiveInterval() {
+  return ANIM_DELAY_MS + ANIM_DUR_MS;
+}
+
+
+// Expected computed 'margin-left' values at points during the active interval:
+// When we assert_between_inclusive using these values we could in theory cause
+// intermittent failure due to very long delays between paints, but since the
+// active duration is 1000s long, a delay would need to be around 100s to cause
+// that. If that's happening then there are likely other issues that should be
+// fixed, so a failure to make us look into that seems like a good thing.
+const INITIAL_POSITION = 100;
+const TEN_PCT_POSITION = 110;
+const FIFTY_PCT_POSITION = 150;
+const END_POSITION = 200;
+
+/**
+ * CSS animation events fire asynchronously after we set 'startTime'. This
+ * helper class allows us to handle such events using Promises.
+ *
+ * To use this class:
+ *
+ *   var eventWatcher = new EventWatcher(watchedNode, eventTypes);
+ *   eventWatcher.waitForEvent(eventType).then(function() {
+ *     // Promise fulfilled
+ *     checkStuff();
+ *     makeSomeChanges();
+ *     return eventWatcher.waitForEvent(nextEventType);
+ *   }).then(function() {
+ *     // Promise fulfilled
+ *     checkMoreStuff();
+ *     eventWatcher.stopWatching(); // all done - stop listening for events
+ *   });
+ *
+ * This class will assert_unreached() if an event occurs when there is no
+ * Promise created by a waitForEvent() call waiting to be fulfilled, or if the
+ * event is of a different type to the type passed to waitForEvent. This helps
+ * provide test coverage to ensure that only events that are expected occur, in
+ * the correct order and with the correct timing. It also helps vastly simplify
+ * the already complex code below by avoiding lots of gnarly error handling
+ * code.
+ */
+function EventWatcher(watchedNode, eventTypes)
+{
+  if (typeof eventTypes == 'string') {
+    eventTypes = [eventTypes];
+  }
+
+  var waitingFor = null;
+
+  function eventHandler(evt) {
+    if (!waitingFor) {
+      assert_unreached('Not expecting event, but got: ' + evt.type +
+        ' targeting element #' + evt.target.getAttribute('id'));
+      return;
+    }
+    if (evt.type != waitingFor.types[0]) {
+      assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
+                        evt.type + ' event');
+      return;
+    }
+    if (waitingFor.types.length > 1) {
+      // Pop first event from array
+      waitingFor.types.shift();
+      return;
+    }
+    // We need to null out waitingFor before calling the resolve function since
+    // the Promise's resolve handlers may call waitForEvent() which will need
+    // to set waitingFor.
+    var resolveFunc = waitingFor.resolve;
+    waitingFor = null;
+    resolveFunc(evt);
+  }
+
+  for (var i = 0; i < eventTypes.length; i++) {
+    watchedNode.addEventListener(eventTypes[i], eventHandler);
+  }
+
+  this.waitForEvent = function(type) {
+    if (typeof type != 'string') {
+      return Promise.reject('Event type not a string');
+    }
+    return this.waitForEvents([type]);
+  };
+
+  /**
+   * This is useful when two events are expected to fire one immediately after
+   * the other. This happens when we skip over the entire active interval for
+   * instance. In this case an 'animationstart' and an 'animationend' are fired
+   * and due to the asynchronous nature of Promise callbacks this won't work:
+   *
+   *   eventWatcher.waitForEvent('animationstart').then(function() {
+   *     return waitForEvent('animationend');
+   *   }).then(...);
+   *
+   * It doesn't work because the 'animationend' listener is added too late,
+   * because the resolve handler for the first Promise is called asynchronously
+   * some time after the 'animationstart' event is called, rather than at the
+   * time the event reaches the watched element.
+   */
+  this.waitForEvents = function(types) {
+    if (waitingFor) {
+      return Promise.reject('Already waiting for an event');
+    }
+    return new Promise(function(resolve, reject) {
+      waitingFor = {
+        types: types,
+        resolve: resolve,
+        reject: reject
+      };
+    });
+  };
+
+  this.stopWatching = function() {
+    for (var i = 0; i < eventTypes.length; i++) {
+      watchedNode.removeEventListener(eventTypes[i], eventHandler);
+    }
+  };
+
+  return this;
+}
+
+// The terms used for the naming of the following helper functions refer to
+// terms used in the Web Animations specification for specific phases of an
+// animation. The terms can be found here:
+//
+//   http://w3c.github.io/web-animations/#animation-effect-phases-and-states
+
+// Called when currentTime is set to zero (the beginning of the start delay).
+function checkStateOnSettingCurrentTimeToZero(animation)
+{
+  // We don't test animation.currentTime since our caller just set it.
+
+  assert_equals(animation.playState, 'running',
+    'Animation.playState should be "running" at the start of ' +
+    'the start delay');
+
+  assert_equals(animation.source.target.style.animationPlayState, 'running',
+    'Animation.source.target.style.animationPlayState should be ' +
+    '"running" at the start of the start delay');
+
+  var div = animation.source.target;
+  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_equals(marginLeft, UNANIMATED_POSITION,
+                'the computed value of margin-left should be unaffected ' +
+                'at the beginning of the start delay');
+}
+
+// Called when the ready Promise's callbacks should happen
+function checkStateOnReadyPromiseResolved(animation)
+{
+  // the 0.0001 here is for rounding error
+  assert_less_than_equal(animation.currentTime,
+    animation.timeline.currentTime - animation.startTime + 0.0001,
+    'Animation.currentTime should be less than the local time ' +
+    'equivalent of the timeline\'s currentTime on the first paint tick ' +
+    'after animation creation');
+
+  assert_equals(animation.playState, 'running',
+    'Animation.playState should be "running" on the first paint ' +
+    'tick after animation creation');
+
+  var div = animation.source.target;
+  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_equals(marginLeft, INITIAL_POSITION,
+                'the computed value of margin-left should be unaffected ' +
+                'by an animation with a delay on ready Promise resolve');
+}
+
+// Called when currentTime is set to the time the active interval starts.
+function checkStateAtActiveIntervalStartTime(animation)
+{
+  // We don't test animation.currentTime since our caller just set it.
+
+  assert_equals(animation.playState, 'running',
+    'Animation.playState should be "running" at the start of ' +
+    'the active interval');
+
+  var div = animation.source.target;
+  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
+    'the computed value of margin-left should be close to the value at the ' +
+    'beginning of the animation');
+}
+
+function checkStateAtFiftyPctOfActiveInterval(animation)
+{
+  // We don't test animation.currentTime since our caller just set it.
+
+  var div = animation.source.target;
+  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_equals(marginLeft, FIFTY_PCT_POSITION,
+    'the computed value of margin-left should be half way through the ' +
+    'animation at the midpoint of the active interval');
+}
+
+// Called when currentTime is set to the time the active interval ends.
+function checkStateAtActiveIntervalEndTime(animation)
+{
+  // We don't test animation.currentTime since our caller just set it.
+
+  assert_equals(animation.playState, 'finished',
+    'Animation.playState should be "finished" at the end of ' +
+    'the active interval');
+
+  var div = animation.source.target;
+  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_equals(marginLeft, END_POSITION,
+    'the computed value of margin-left should be the final transitioned-to ' +
+    'value at the end of the active duration');
+}
+
+test(function(t)
+{
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+  assert_equals(animation.currentTime, 0, 'currentTime should be zero');
+}, 'currentTime of a newly created transition is zero');
+
+
+test(function(t)
+{
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  // So that animation is running instead of paused when we set currentTime:
+  animation.startTime = animation.timeline.currentTime;
+
+  animation.currentTime = 10;
+  assert_equals(animation.currentTime, 10,
+    'Check setting of currentTime actually works');
+}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
+   'currentTime');
+
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  var eventWatcher = new EventWatcher(div, 'transitionend');
+
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    checkStateOnReadyPromiseResolved(animation);
+
+    animation.currentTime = currentTimeForStartOfActiveInterval();
+    checkStateAtActiveIntervalStartTime(animation);
+
+    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
+    checkStateAtFiftyPctOfActiveInterval(animation);
+
+    animation.currentTime = currentTimeForEndOfActiveInterval();
+    return eventWatcher.waitForEvent('transitionend');
+  })).then(t.step_func(function() {
+    checkStateAtActiveIntervalEndTime(animation);
+
+    eventWatcher.stopWatching();
+  })).catch(t.step_func(function(reason) {
+    assert_unreached(reason);
+  })).then(function() {
+    t.done();
+  });
+}, 'Skipping forward through transition');
+
+
+test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  var eventWatcher = new EventWatcher(div, 'transitionend');
+
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  // Unlike in the case of CSS animations, we cannot skip to the end and skip
+  // backwards since when we reach the end the transition effect is removed and
+  // changes to the Animation object no longer affect the element. For
+  // this reason we only skip forwards as far as the 50% through point.
+
+  animation.ready.then(t.step_func(function() {
+    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
+    checkStateAtFiftyPctOfActiveInterval(animation);
+
+    animation.currentTime = currentTimeForStartOfActiveInterval();
+
+    // Despite going backwards from being in the active interval to being
+    // before it, we now expect a 'transitionend' event because the transition
+    // should go from being active to inactive.
+    //
+    // Calling checkStateAtActiveIntervalStartTime will check computed style,
+    // causing computed style to be updated and the 'transitionend' event to
+    // be dispatched synchronously. We need to call waitForEvent first
+    // otherwise eventWatcher will assert that the event was unexpected.
+    eventWatcher.waitForEvent('transitionend').then(function() {
+      eventWatcher.stopWatching();
+      t.done();
+    });
+    checkStateAtActiveIntervalStartTime(animation);
+  }));
+}, 'Skipping backwards through transition');
+
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    var exception;
+    try {
+      animation.currentTime = null;
+    } catch (e) {
+      exception = e;
+    }
+    assert_equals(exception.name, 'TypeError',
+      'Expect TypeError exception on trying to set ' +
+      'Animation.currentTime to null');
+  })).catch(t.step_func(function(reason) {
+    assert_unreached(reason);
+  })).then(function() {
+    t.done();
+  });
+}, 'Setting currentTime to null');
+
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+  var pauseTime;
+
+  animation.ready.then(t.step_func(function() {
+    assert_not_equals(animation.currentTime, null,
+      'Animation.currentTime not null on ready Promise resolve');
+    animation.pause();
+    return animation.ready;
+  })).then(t.step_func(function() {
+    pauseTime = animation.currentTime;
+    return waitForFrame();
+  })).then(t.step_func(function() {
+    assert_equals(animation.currentTime, pauseTime,
+      'Animation.currentTime is unchanged after pausing');
+  })).catch(t.step_func(function(reason) {
+    assert_unreached(reason);
+  })).then(function() {
+    t.done();
+  });
+}, 'Animation.currentTime after pausing');
+
+    </script>
+  </body>
+</html>
--- a/dom/animation/test/css-transitions/test_animation-player-ready.html
+++ b/dom/animation/test/css-transitions/test_animation-player-ready.html
@@ -17,24 +17,18 @@ async_test(function(t) {
 
   var animation = div.getAnimations()[0];
   var originalReadyPromise = animation.ready;
 
   animation.ready.then(t.step_func(function() {
     assert_equals(animation.ready, originalReadyPromise,
                   'Ready promise is the same object when playing completes');
     animation.pause();
-    // TODO: When we implement deferred pausing, change this to
-    // assert_not_equals and wait on the new promise before continuing.
-    assert_equals(animation.ready, originalReadyPromise,
-                  'Ready promise does not change when pausing (for now)');
-    animation.play();
     assert_not_equals(animation.ready, originalReadyPromise,
-                      'Ready promise object identity differs after calling'
-                      + ' play()');
+                      'Ready promise object identity differs when pausing');
     t.done();
   }));
 }, 'A new ready promise is created each time play() is called'
    + ' the animation property');
 
 async_test(function(t) {
   var div = addDiv(t);
 
--- a/dom/animation/test/css-transitions/test_animation-player-starttime.html
+++ b/dom/animation/test/css-transitions/test_animation-player-starttime.html
@@ -31,17 +31,16 @@
 // TODO: Once the computedTiming property is implemented, add checks to the
 // checker helpers to ensure that computedTiming's properties are updated as
 // expected.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
 
 
 const ANIM_DELAY_MS = 1000000; // 1000s
 const ANIM_DUR_MS = 1000000; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
 
 /**
  * These helpers get the value that the startTime needs to be set to, to put an
  * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
  * middle of various phases or points through the active duration.
  */
 function startTimeForBeforePhase(timeline) {
   return timeline.currentTime - ANIM_DELAY_MS / 2;
@@ -179,17 +178,17 @@ function EventWatcher(watchedNode, event
 
   return this;
 }
 
 // The terms used for the naming of the following helper functions refer to
 // terms used in the Web Animations specification for specific phases of an
 // animation. The terms can be found here:
 //
-//   http://w3c.github.io/web-animations/#animation-node-phases-and-states
+//   http://w3c.github.io/web-animations/#animation-effect-phases-and-states
 //
 // Note the distinction between "player start time" and "animation start time".
 // The former is the start of the start delay. The latter is the start of the
 // active interval. (If there is no delay, they are the same.)
 
 // Called when the ready Promise's callbacks should happen
 function checkStateOnReadyPromiseResolved(animation)
 {
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -13,14 +13,15 @@ skip-if = buildapp == 'mulet'
 [css-animations/test_animation-player-playstate.html]
 [css-animations/test_animation-player-ready.html]
 [css-animations/test_animation-player-starttime.html]
 [css-animations/test_animation-target.html]
 [css-animations/test_element-get-animation-players.html]
 skip-if = buildapp == 'mulet'
 [css-transitions/test_animation-effect-name.html]
 [css-transitions/test_animation-pausing.html]
+[css-transitions/test_animation-player-currenttime.html]
 [css-transitions/test_animation-player-ready.html]
 [css-transitions/test_animation-target.html]
 [css-transitions/test_animation-player-starttime.html]
 [css-transitions/test_element-get-animation-players.html]
 skip-if = buildapp == 'mulet'
 [mozilla/test_deferred_start.html]
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -34,42 +34,44 @@ function addDiv(t, attrs) {
  */
 function waitForFrame() {
   return new Promise(function(resolve, reject) {
     window.requestAnimationFrame(resolve);
   });
 }
 
 /**
+ * Returns a Promise that is resolved after the given number of consecutive
+ * animation frames have occured (using requestAnimationFrame callbacks).
+ */
+function waitForAnimationFrames(frameCount) {
+  return new Promise(function(resolve, reject) {
+    function handleFrame() {
+      if (--frameCount <= 0) {
+        resolve();
+      } else {
+        window.requestAnimationFrame(handleFrame); // wait another frame
+      }
+    }
+    window.requestAnimationFrame(handleFrame);
+  });
+}
+
+/**
  * Wrapper that takes a sequence of N animations and returns:
  *
  *   Promise.all([animations[0].ready, animations[1].ready, ... animations[N-1].ready]);
  */
 function waitForAllAnimations(animations) {
   return Promise.all(animations.map(function(animation) {
     return animation.ready;
   }));
 }
 
 /**
- * Returns a Promise that is resolved after the next two animation frames have
- * occured (that is, after two consecutive requestAnimationFrame callbacks
- * have been called).
- */
-function waitForTwoAnimationFrames() {
-   return new Promise(function(resolve, reject) {
-     window.requestAnimationFrame(function() {
-       window.requestAnimationFrame(function() {
-         resolve();
-       });
-     });
-   });
-}
-
-/**
  * Flush the computed style for the given element. This is useful, for example,
  * when we are testing a transition and need the initial value of a property
  * to be computed so that when we synchronouslyet set it to a different value
  * we actually get a transition instead of that being the initial value.
  */
 function flushComputedStyle(elem) {
   var cs = window.getComputedStyle(elem);
   cs.marginLeft;
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -828,23 +828,25 @@ MainProcessRunnable::OpenCacheFileForWri
 
   if (mEnforcingQuota) {
     // Create the QuotaObject before all file IO and keep it alive until caching
     // completes to get maximum assertion coverage in QuotaManager against
     // concurrent removal, etc.
     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
     NS_ENSURE_STATE(mQuotaObject);
 
-    if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
+    if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
+                                       /* aTruncate */ false)) {
       // If the request fails, it might be because mOrigin is using too much
-      // space (MaybeAllocateMoreSpace will not evict our own origin since it is
+      // space (MaybeUpdateSize will not evict our own origin since it is
       // active). Try to make some space by evicting LRU entries until there is
       // enough space.
       EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
-      if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
+      if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
+                                         /* aTruncate */ false)) {
         mResult = JS::AsmJSCache_QuotaExceeded;
         return NS_ERROR_FAILURE;
       }
     }
   }
 
   int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -47,16 +47,17 @@ ThirdPartyUtil::IsThirdPartyInternal(con
                                      nsIURI* aSecondURI,
                                      bool* aResult)
 {
   NS_ENSURE_ARG(aSecondURI);
 
   // Get the base domain for aSecondURI.
   nsCString secondDomain;
   nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
+  LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(), secondDomain.get()));
   if (NS_FAILED(rv))
     return rv;
 
   // Check strict equality.
   *aResult = aFirstDomain != secondDomain;
   return NS_OK;
 }
 
@@ -175,16 +176,17 @@ ThirdPartyUtil::IsThirdPartyWindow(nsIDO
 // Determine if the URI associated with aChannel or any URI of the window
 // hierarchy associated with the channel is foreign with respect to aSecondURI.
 // See docs for mozIThirdPartyUtil.
 NS_IMETHODIMP 
 ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel,
                                     nsIURI* aURI,
                                     bool* aResult)
 {
+  LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel));
   NS_ENSURE_ARG(aChannel);
   NS_ASSERTION(aResult, "null outparam pointer");
 
   nsresult rv;
   bool doForce = false;
   bool checkWindowChain = true;
   bool parentIsThird = false;
   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -454,17 +454,22 @@ nsContentUtils::Init()
 
   sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
   if(!sSecurityManager)
     return NS_ERROR_FAILURE;
   NS_ADDREF(sSecurityManager);
 
   sSecurityManager->GetSystemPrincipal(&sSystemPrincipal);
   MOZ_ASSERT(sSystemPrincipal);
-  NS_ADDREF(sNullSubjectPrincipal = new nsNullPrincipal());
+
+  // We use the constructor here because we want infallible initialization; we
+  // apparently don't care whether sNullSubjectPrincipal has a sane URI or not.
+  nsRefPtr<nsNullPrincipal> nullPrincipal = new nsNullPrincipal();
+  nullPrincipal->Init();
+  nullPrincipal.forget(&sNullSubjectPrincipal);
 
   nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
   if (NS_FAILED(rv)) {
     // This makes life easier, but we can live without it.
 
     sIOService = nullptr;
   }
 
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -512,17 +512,17 @@ nsDOMClassInfo::RegisterExternalClasses(
     nsCID *cid;
     rv = registrar->ContractIDToCID(contractId, &cid);
     if (NS_FAILED(rv)) {
       NS_WARNING("Bad contract id registered with the script namespace manager");
       continue;
     }
 
     rv = nameSpaceManager->RegisterExternalClassName(categoryEntry.get(), *cid);
-    nsMemory::Free(cid);
+    free(cid);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return nameSpaceManager->RegisterExternalInterfaces(true);
 }
 
 #define _DOM_CLASSINFO_MAP_BEGIN(_class, _ifptr, _has_class_if)               \
   {                                                                           \
@@ -803,17 +803,17 @@ nsDOMClassInfo::GetInterfaces(uint32_t *
   *aCount = count;
 
   if (!count) {
     *aArray = nullptr;
 
     return NS_OK;
   }
 
-  *aArray = static_cast<nsIID **>(nsMemory::Alloc(count * sizeof(nsIID *)));
+  *aArray = static_cast<nsIID **>(moz_xmalloc(count * sizeof(nsIID *)));
   NS_ENSURE_TRUE(*aArray, NS_ERROR_OUT_OF_MEMORY);
 
   uint32_t i;
   for (i = 0; i < count; i++) {
     nsIID *iid = static_cast<nsIID *>(nsMemory::Clone(mData->mInterfaces[i],
                                                          sizeof(nsIID)));
 
     if (!iid) {
@@ -893,17 +893,17 @@ nsDOMClassInfo::PreCreate(nsISupports *n
                           JSObject *globalObj, JSObject **parentObj)
 {
   *parentObj = globalObj;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMClassInfo::AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
-                            JSObject *obj, jsid id, jsval *vp,
+                            JSObject *obj, jsid id, JS::Handle<JS::Value> val,
                             bool *_retval)
 {
   NS_WARNING("nsDOMClassInfo::AddProperty Don't call me!");
 
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
@@ -2401,17 +2401,18 @@ nsEventTargetSH::PreCreate(nsISupports *
 
   *parentObj = native_parent ? native_parent->GetGlobalJSObject() : globalObj;
 
   return *parentObj ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsEventTargetSH::AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
-                             JSObject *obj, jsid id, jsval *vp, bool *_retval)
+                             JSObject *obj, jsid id, JS::Handle<JS::Value> val,
+                             bool *_retval)
 {
   nsEventTargetSH::PreserveWrapper(GetNative(wrapper, obj));
 
   return NS_OK;
 }
 
 void
 nsEventTargetSH::PreserveWrapper(nsISupports *aNative)
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -203,17 +203,18 @@ protected:
 
   virtual ~nsEventTargetSH()
   {
   }
 public:
   NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,
                        JSObject *globalObj, JSObject **parentObj) override;
   NS_IMETHOD AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
-                         JSObject *obj, jsid id, JS::Value *vp, bool *_retval) override;
+                         JSObject *obj, jsid id, JS::Handle<JS::Value> val,
+                         bool *_retval) override;
 
   virtual void PreserveWrapper(nsISupports *aNative) override;
 
   static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
   {
     return new nsEventTargetSH(aData);
   }
 };
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2522,17 +2522,17 @@ nsDOMWindowUtils::StopFrameTimeRecording
   LayerManager *mgr = widget->GetLayerManager();
   if (!mgr)
     return NS_ERROR_FAILURE;
 
   nsTArray<float> tmpFrameIntervals;
   mgr->StopFrameTimeRecording(startIndex, tmpFrameIntervals);
   *frameCount = tmpFrameIntervals.Length();
 
-  *frameIntervals = (float*)nsMemory::Alloc(*frameCount * sizeof(float));
+  *frameIntervals = (float*)moz_xmalloc(*frameCount * sizeof(float));
 
   /* copy over the frame intervals and paint times into the arrays we just allocated */
   for (uint32_t i = 0; i < *frameCount; i++) {
     (*frameIntervals)[i] = tmpFrameIntervals[i];
   }
 
   return NS_OK;
 }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -10082,17 +10082,17 @@ nsDocument::ScrollToRef()
   char* tmpstr = ToNewCString(mScrollToRef);
   if (!tmpstr) {
     return;
   }
 
   nsUnescape(tmpstr);
   nsAutoCString unescapedRef;
   unescapedRef.Assign(tmpstr);
-  nsMemory::Free(tmpstr);
+  free(tmpstr);
 
   nsresult rv = NS_ERROR_FAILURE;
   // We assume that the bytes are in UTF-8, as it says in the spec:
   // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
   NS_ConvertUTF8toUTF16 ref(unescapedRef);
 
   nsCOMPtr<nsIPresShell> shell = GetShell();
   if (shell) {
--- a/dom/base/nsFormData.cpp
+++ b/dom/base/nsFormData.cpp
@@ -235,17 +235,17 @@ nsFormData::Append(const nsAString& aNam
 
   if (dataType == nsIDataType::VTYPE_INTERFACE ||
       dataType == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> supports;
     nsID *iid;
     rv = aValue->GetAsInterface(&iid, getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsMemory::Free(iid);
+    free(iid);
 
     nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports);
     nsRefPtr<File> blob = static_cast<File*>(domBlob.get());
     if (domBlob) {
       Optional<nsAString> temp;
       Append(aName, *blob, temp);
       return NS_OK;
     }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6086,17 +6086,17 @@ nsGlobalWindow::Dump(const nsAString& aS
     PrintToDebugger(cstr);
 #endif
 #ifdef ANDROID
     __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
 #endif
     FILE *fp = gDumpFile ? gDumpFile : stdout;
     fputs(cstr, fp);
     fflush(fp);
-    nsMemory::Free(cstr);
+    free(cstr);
   }
 
   return NS_OK;
 }
 
 void
 nsGlobalWindow::EnsureReflowFlushAndPaint()
 {
@@ -12937,18 +12937,18 @@ nsGlobalWindow::SuspendTimeouts(uint32_t
   if (!suspended) {
     nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
     if (ac) {
       for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
         ac->RemoveWindowListener(mEnabledSensors[i], this);
     }
     DisableGamepadUpdates();
 
-    // Suspend all of the workers for this window.
-    mozilla::dom::workers::SuspendWorkersForWindow(this);
+    // Freeze all of the workers for this window.
+    mozilla::dom::workers::FreezeWorkersForWindow(this);
 
     TimeStamp now = TimeStamp::Now();
     for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
       // Set mTimeRemaining to be the time remaining for this timer.
       if (t->mWhen > now)
         t->mTimeRemaining = t->mWhen - now;
       else
         t->mTimeRemaining = TimeDuration(0);
@@ -13026,18 +13026,18 @@ nsGlobalWindow::ResumeTimeouts(bool aTha
     }
     EnableGamepadUpdates();
 
     // Resume all of the AudioContexts for this window
     for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
       mAudioContexts[i]->Resume();
     }
 
-    // Resume all of the workers for this window.
-    mozilla::dom::workers::ResumeWorkersForWindow(this);
+    // Thaw all of the workers for this window.
+    mozilla::dom::workers::ThawWorkersForWindow(this);
 
     // Restore all of the timeouts, using the stored time remaining
     // (stored in timeout->mTimeRemaining).
 
     TimeStamp now = TimeStamp::Now();
 
 #ifdef DEBUG
     bool _seenDummyTimeout = false;
--- a/dom/base/nsHTMLContentSerializer.cpp
+++ b/dom/base/nsHTMLContentSerializer.cpp
@@ -582,17 +582,17 @@ nsHTMLContentSerializer::AppendAndTransl
       }
       else if (fullConstEntityText) {
         NS_ENSURE_TRUE(aOutputStr.AppendASCII(fullConstEntityText, mozilla::fallible), false);
         ++advanceLength;
       }
       // if it comes from nsIEntityConverter, it already has '&' and ';'
       else if (fullEntityText) {
         bool ok = AppendASCIItoUTF16(fullEntityText, aOutputStr, mozilla::fallible);
-        nsMemory::Free(fullEntityText);
+        free(fullEntityText);
         advanceLength += lengthReplaced;
         NS_ENSURE_TRUE(ok, false);
       }
       NS_ENSURE_TRUE(result, false);
     }
   } else {
     NS_ENSURE_TRUE(nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr), false);
   }
--- a/dom/base/nsHostObjectURI.cpp
+++ b/dom/base/nsHostObjectURI.cpp
@@ -223,17 +223,17 @@ nsHostObjectURI::GetClassDescription(cha
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsHostObjectURI::GetClassID(nsCID * *aClassID)
 {
   // Make sure to modify any subclasses as needed if this ever
   // changes to not call the virtual GetClassIDNoAlloc.
-  *aClassID = (nsCID*) nsMemory::Alloc(sizeof(nsCID));
+  *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
   NS_ENSURE_TRUE(*aClassID, NS_ERROR_OUT_OF_MEMORY);
 
   return GetClassIDNoAlloc(*aClassID);
 }
 
 NS_IMETHODIMP 
 nsHostObjectURI::GetFlags(uint32_t *aFlags)
 {
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -312,17 +312,17 @@ nsJSEnvironmentObserver::Observe(nsISupp
  ****************************************************************/
 
 class AutoFree {
 public:
   explicit AutoFree(void* aPtr) : mPtr(aPtr) {
   }
   ~AutoFree() {
     if (mPtr)
-      nsMemory::Free(mPtr);
+      free(mPtr);
   }
   void Invalidate() {
     mPtr = 0;
   }
 private:
   void *mPtr;
 };
 
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -1705,17 +1705,17 @@ nsPlainTextSerializer::Write(const nsASt
     // Find a place where we may have to do whitespace compression
     nextpos = str.FindCharInSet(" \t\n\r", bol);
 #ifdef DEBUG_wrapping
     nsAutoString remaining;
     str.Right(remaining, totLen - bol);
     foo = ToNewCString(remaining);
     //    printf("Next line: bol = %d, newlinepos = %d, totLen = %d, string = '%s'\n",
     //           bol, nextpos, totLen, foo);
-    nsMemory::Free(foo);
+    free(foo);
 #endif
 
     if (nextpos == kNotFound) {
       // The rest of the string
       offsetIntoBuffer = str.get() + bol;
       AddToLine(offsetIntoBuffer, totLen-bol);
       bol=totLen;
       mInWhitespace=false;
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -629,17 +629,17 @@ nsScriptNameSpaceManager::OperateCategor
   if (NS_FAILED(rv)) {
     NS_WARNING("Bad contract id registed with the script namespace manager");
     return NS_OK;
   }
 
   // Copy CID onto the stack, so we can free it right away and avoid having
   // to add cleanup code at every exit point from this function.
   nsCID cid = *cidPtr;
-  nsMemory::Free(cidPtr);
+  free(cidPtr);
 
   if (type == nsGlobalNameStruct::eTypeExternalConstructor) {
     nsXPIDLCString constructorProto;
     rv = aCategoryManager->GetCategoryEntry(JAVASCRIPT_GLOBAL_CONSTRUCTOR_PROTO_ALIAS_CATEGORY,
                                             categoryEntry.get(),
                                             getter_Copies(constructorProto));
     if (NS_SUCCEEDED(rv)) {
       nsGlobalNameStruct *s = AddToHash(&mGlobalNames, categoryEntry.get());
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -2461,17 +2461,17 @@ GetRequestBody(nsIVariant* aBody, nsIInp
 
   if (dataType == nsIDataType::VTYPE_INTERFACE ||
       dataType == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> supports;
     nsID *iid;
     rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsMemory::Free(iid);
+    free(iid);
 
     // document?
     nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(supports);
     if (doc) {
       return GetRequestBody(doc, aResult, aContentLength, aContentType, aCharset);
     }
 
     // nsISupportsString?
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -129,16 +129,19 @@ def dedent(s):
     return textwrap.dedent(s)
 
 
 # This works by transforming the fill()-template to an equivalent
 # string.Template.
 fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
 
 
+find_substitutions = re.compile(r"\${")
+
+
 @memoize
 def compile_fill_template(template):
     """
     Helper function for fill().  Given the template string passed to fill(),
     do the reusable part of template processing and return a pair (t,
     argModList) that can be used every time fill() is called with that
     template argument.
 
@@ -168,16 +171,18 @@ def compile_fill_template(template):
             raise ValueError("Invalid fill() template: $*{%s} must appear by itself on a line" % name)
 
         # Now replace this whole line of template with the indented equivalent.
         modified_name = name + "_" + str(depth)
         argModList.append((name, modified_name, depth))
         return "${" + modified_name + "}"
 
     t = re.sub(fill_multiline_substitution_re, replace, t)
+    if not re.search(find_substitutions, t):
+        raise TypeError("Using fill() when dedent() would do.")
     return (string.Template(t), argModList)
 
 
 def fill(template, **args):
     """
     Convenience function for filling in a multiline template.
 
     `fill(template, name1=v1, name2=v2)` is a lot like
@@ -1498,17 +1503,17 @@ class CGGetJSClassMethod(CGAbstractMetho
 class CGAddPropertyHook(CGAbstractClassHook):
     """
     A hook for addProperty, used to preserve our wrapper from GC.
     """
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('JS::Handle<jsid>', 'id'),
-                Argument('JS::MutableHandle<JS::Value>', 'vp')]
+                Argument('JS::Handle<JS::Value>', 'val')]
         CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
                                      'bool', args)
 
     def generate_code(self):
         assert self.descriptor.wrapperCache
         return dedent("""
             // We don't want to preserve if we don't have a wrapper, and we
             // obviously can't preserve if we're not initialized.
@@ -2761,24 +2766,23 @@ class CGCreateInterfaceObjectsMethod(CGA
             namedConstructors=namedConstructors,
             interfaceCache=interfaceCache,
             properties=properties,
             chromeProperties=chromeProperties,
             name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr")
 
         if self.descriptor.hasUnforgeableMembers:
             assert needInterfacePrototypeObject
-            setUnforgeableHolder = CGGeneric(fill(
+            setUnforgeableHolder = CGGeneric(dedent(
                 """
                 if (*protoCache) {
                   js::SetReservedSlot(*protoCache, DOM_INTERFACE_PROTO_SLOTS_BASE,
                                       JS::ObjectValue(*unforgeableHolder));
                 }
-                """,
-                name=self.descriptor.name))
+                """))
         else:
             setUnforgeableHolder = None
 
         aliasedMembers = [m for m in self.descriptor.interface.members if m.isMethod() and m.aliases]
         if aliasedMembers:
             assert needInterfacePrototypeObject
 
             def defineAlias(alias):
@@ -11509,17 +11513,17 @@ class CGDictionary(CGThing):
                 if (!${dictName}::Init(cx, val)) {
                   return false;
                 }
                 MOZ_ASSERT(IsConvertibleToDictionary(cx, val));
 
                 """,
                 dictName=self.makeClassName(self.dictionary.parent))
         else:
-            body += fill(
+            body += dedent(
                 """
                 if (!IsConvertibleToDictionary(cx, val)) {
                   return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription);
                 }
 
                 """)
 
         memberInits = [self.getMemberConversion(m).define()
@@ -11595,17 +11599,17 @@ class CGDictionary(CGThing):
                 if (!${dictName}::ToObjectInternal(cx, rval)) {
                   return false;
                 }
                 JS::Rooted<JSObject*> obj(cx, &rval.toObject());
 
                 """,
                 dictName=self.makeClassName(self.dictionary.parent))
         else:
-            body += fill(
+            body += dedent(
                 """
                 JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
                 if (!obj) {
                   return false;
                 }
                 rval.set(JS::ObjectValue(*obj));
 
                 """)
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -121,21 +121,23 @@ GetPrincipalFromWorkerPrivate(WorkerPriv
 
   return wp->GetPrincipal();
 }
 
 class InitializeRunnable final : public WorkerMainThreadRunnable
 {
 public:
   InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsAString& aOrigin,
-                     PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
+                     PrincipalInfo& aPrincipalInfo, bool& aPrivateBrowsing,
+                     ErrorResult& aRv)
     : WorkerMainThreadRunnable(aWorkerPrivate)
     , mWorkerPrivate(GetCurrentThreadWorkerPrivate())
     , mOrigin(aOrigin)
     , mPrincipalInfo(aPrincipalInfo)
+    , mPrivateBrowsing(aPrivateBrowsing)
     , mRv(aRv)
   {
     MOZ_ASSERT(mWorkerPrivate);
   }
 
   bool MainThreadRun() override
   {
     MOZ_ASSERT(NS_IsMainThread());
@@ -175,28 +177,31 @@ public:
 
     // Window doesn't exist for some kind of workers (eg: SharedWorkers)
     nsPIDOMWindow* window = wp->GetWindow();
     if (!window) {
       return true;
     }
 
     nsIDocument* doc = window->GetExtantDoc();
-    // No bfcache when BroadcastChannel is used.
     if (doc) {
+      mPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
+
+      // No bfcache when BroadcastChannel is used.
       doc->DisallowBFCaching();
     }
 
     return true;
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
   nsAString& mOrigin;
   PrincipalInfo& mPrincipalInfo;
+  bool& mPrivateBrowsing;
   ErrorResult& mRv;
 };
 
 class BCPostMessageRunnable final : public nsICancelableRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -391,22 +396,24 @@ BroadcastChannel::IsEnabled(JSContext* a
   runnable->Dispatch(workerPrivate->GetJSContext());
 
   return runnable->IsEnabled();
 }
 
 BroadcastChannel::BroadcastChannel(nsPIDOMWindow* aWindow,
                                    const PrincipalInfo& aPrincipalInfo,
                                    const nsAString& aOrigin,
-                                   const nsAString& aChannel)
+                                   const nsAString& aChannel,
+                                   bool aPrivateBrowsing)
   : DOMEventTargetHelper(aWindow)
   , mWorkerFeature(nullptr)
   , mPrincipalInfo(new PrincipalInfo(aPrincipalInfo))
   , mOrigin(aOrigin)
   , mChannel(aChannel)
+  , mPrivateBrowsing(aPrivateBrowsing)
   , mIsKeptAlive(false)
   , mInnerID(0)
   , mState(StateActive)
 {
   // Window can be null in workers
 }
 
 BroadcastChannel::~BroadcastChannel()
@@ -426,16 +433,17 @@ BroadcastChannel::Constructor(const Glob
                               const nsAString& aChannel,
                               ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   // Window is null in workers.
 
   nsAutoString origin;
   PrincipalInfo principalInfo;
+  bool privateBrowsing = false;
   WorkerPrivate* workerPrivate = nullptr;
 
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
 
     if (!incumbent) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
@@ -464,36 +472,40 @@ BroadcastChannel::Constructor(const Glob
     }
 
     aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     nsIDocument* doc = window->GetExtantDoc();
-    // No bfcache when BroadcastChannel is used.
     if (doc) {
+      privateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
+
+      // No bfcache when BroadcastChannel is used.
       doc->DisallowBFCaching();
     }
   } else {
     JSContext* cx = aGlobal.Context();
     workerPrivate = GetWorkerPrivateFromContext(cx);
     MOZ_ASSERT(workerPrivate);
 
     nsRefPtr<InitializeRunnable> runnable =
-      new InitializeRunnable(workerPrivate, origin, principalInfo, aRv);
+      new InitializeRunnable(workerPrivate, origin, principalInfo,
+                             privateBrowsing, aRv);
     runnable->Dispatch(cx);
   }
 
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsRefPtr<BroadcastChannel> bc =
-    new BroadcastChannel(window, principalInfo, origin, aChannel);
+    new BroadcastChannel(window, principalInfo, origin, aChannel,
+                         privateBrowsing);
 
   // Register this component to PBackground.
   PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
   if (actor) {
     bc->ActorCreated(actor);
   } else {
     BackgroundChild::GetOrCreateForCurrentThread(bc);
   }
@@ -609,17 +621,18 @@ BroadcastChannel::ActorCreated(PBackgrou
 {
   MOZ_ASSERT(aActor);
 
   if (mState == StateClosed) {
     return;
   }
 
   PBroadcastChannelChild* actor =
-    aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel);
+    aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel,
+                                             mPrivateBrowsing);
 
   mActor = static_cast<BroadcastChannelChild*>(actor);
   MOZ_ASSERT(mActor);
 
   mActor->SetParent(this);
 
   // Flush pending messages.
   for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) {
--- a/dom/broadcastchannel/BroadcastChannel.h
+++ b/dom/broadcastchannel/BroadcastChannel.h
@@ -87,17 +87,18 @@ public:
   {
     return mState != StateActive;
   }
 
 private:
   BroadcastChannel(nsPIDOMWindow* aWindow,
                    const PrincipalInfo& aPrincipalInfo,
                    const nsAString& aOrigin,
-                   const nsAString& aChannel);
+                   const nsAString& aChannel,
+                   bool aPrivateBrowsing);
 
   ~BroadcastChannel();
 
   void PostMessageData(BroadcastChannelMessage* aData);
 
   void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                            ErrorResult& aRv);
 
@@ -107,16 +108,17 @@ private:
   nsTArray<nsRefPtr<BroadcastChannelMessage>> mPendingMessages;
 
   nsAutoPtr<workers::WorkerFeature> mWorkerFeature;
 
   nsAutoPtr<PrincipalInfo> mPrincipalInfo;
 
   nsString mOrigin;
   nsString mChannel;
+  bool mPrivateBrowsing;
 
   bool mIsKeptAlive;
 
   uint64_t mInnerID;
 
   enum {
     StateActive,
     StateClosing,
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -20,20 +20,18 @@
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
 using namespace workers;
 
-BroadcastChannelChild::BroadcastChannelChild(const nsAString& aOrigin,
-                                             const nsAString& aChannel)
+BroadcastChannelChild::BroadcastChannelChild(const nsAString& aOrigin)
   : mOrigin(aOrigin)
-  , mChannel(aChannel)
   , mActorDestroyed(false)
 {
 }
 
 BroadcastChannelChild::~BroadcastChannelChild()
 {
   MOZ_ASSERT(!mBC);
 }
--- a/dom/broadcastchannel/BroadcastChannelChild.h
+++ b/dom/broadcastchannel/BroadcastChannelChild.h
@@ -32,29 +32,26 @@ public:
   virtual bool RecvNotify(const ClonedMessageData& aData) override;
 
   bool IsActorDestroyed() const
   {
     return mActorDestroyed;
   }
 
 private:
-  BroadcastChannelChild(const nsAString& aOrigin,
-                        const nsAString& aChannel);
-
+  explicit BroadcastChannelChild(const nsAString& aOrigin);
   ~BroadcastChannelChild();
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   // This raw pointer is actually the parent object.
   // It's set to null when the parent object is deleted.
   BroadcastChannel* mBC;
 
   nsString mOrigin;
-  nsString mChannel;
 
   bool mActorDestroyed;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_BroadcastChannelChild_h
--- a/dom/broadcastchannel/BroadcastChannelParent.cpp
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -13,20 +13,22 @@
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
 BroadcastChannelParent::BroadcastChannelParent(
                                             const nsAString& aOrigin,
-                                            const nsAString& aChannel)
+                                            const nsAString& aChannel,
+                                            bool aPrivateBrowsing)
   : mService(BroadcastChannelService::GetOrCreate())
   , mOrigin(aOrigin)
   , mChannel(aChannel)
+  , mPrivateBrowsing(aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
   mService->RegisterActor(this);
 }
 
 BroadcastChannelParent::~BroadcastChannelParent()
 {
   AssertIsOnBackgroundThread();
@@ -36,17 +38,17 @@ bool
 BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData)
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(!mService)) {
     return false;
   }
 
-  mService->PostMessage(this, aData, mOrigin, mChannel);
+  mService->PostMessage(this, aData, mOrigin, mChannel, mPrivateBrowsing);
   return true;
 }
 
 bool
 BroadcastChannelParent::RecvClose()
 {
   AssertIsOnBackgroundThread();
 
@@ -72,21 +74,24 @@ BroadcastChannelParent::ActorDestroy(Act
     // released too.
     mService->UnregisterActor(this);
   }
 }
 
 void
 BroadcastChannelParent::CheckAndDeliver(const ClonedMessageData& aData,
                                         const nsString& aOrigin,
-                                        const nsString& aChannel)
+                                        const nsString& aChannel,
+                                        bool aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
 
-  if (aOrigin == mOrigin && aChannel == mChannel) {
+  if (aOrigin == mOrigin &&
+      aChannel == mChannel &&
+      aPrivateBrowsing == mPrivateBrowsing) {
     // We need to duplicate data only if we have blobs or if the manager of
     // them is different than the manager of this parent actor.
     if (aData.blobsParent().IsEmpty() ||
         static_cast<BlobParent*>(aData.blobsParent()[0])->GetBackgroundManager() == Manager()) {
       unused << SendNotify(aData);
       return;
     }
 
--- a/dom/broadcastchannel/BroadcastChannelParent.h
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -19,31 +19,34 @@ class BroadcastChannelService;
 
 class BroadcastChannelParent final : public PBroadcastChannelParent
 {
   friend class mozilla::ipc::BackgroundParentImpl;
 
 public:
   void CheckAndDeliver(const ClonedMessageData& aData,
                        const nsString& aOrigin,
-                       const nsString& aChannel);
+                       const nsString& aChannel,
+                       bool aPrivateBrowsing);
 
 private:
   BroadcastChannelParent(const nsAString& aOrigin,
-                         const nsAString& aChannel);
+                         const nsAString& aChannel,
+                         bool aPrivateBrowsing);
   ~BroadcastChannelParent();
 
   virtual bool
   RecvPostMessage(const ClonedMessageData& aData) override;
 
   virtual bool RecvClose() override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   nsRefPtr<BroadcastChannelService> mService;
   nsString mOrigin;
   nsString mChannel;
+  bool mPrivateBrowsing;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_BroadcastChannelParent_h
--- a/dom/broadcastchannel/BroadcastChannelService.cpp
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -78,21 +78,23 @@ BroadcastChannelService::UnregisterActor
 
 namespace {
 
 struct MOZ_STACK_CLASS PostMessageData final
 {
   PostMessageData(BroadcastChannelParent* aParent,
                   const ClonedMessageData& aData,
                   const nsAString& aOrigin,
-                  const nsAString& aChannel)
+                  const nsAString& aChannel,
+                  bool aPrivateBrowsing)
     : mParent(aParent)
     , mData(aData)
     , mOrigin(aOrigin)
     , mChannel(aChannel)
+    , mPrivateBrowsing(aPrivateBrowsing)
   {
     MOZ_ASSERT(aParent);
     MOZ_COUNT_CTOR(PostMessageData);
 
     // We need to keep the array alive for the life-time of this
     // PostMessageData.
     if (!aData.blobsParent().IsEmpty()) {
       mFiles.SetCapacity(aData.blobsParent().Length());
@@ -111,44 +113,47 @@ struct MOZ_STACK_CLASS PostMessageData f
     MOZ_COUNT_DTOR(PostMessageData);
   }
 
   BroadcastChannelParent* mParent;
   const ClonedMessageData& mData;
   nsTArray<nsRefPtr<FileImpl>> mFiles;
   const nsString mOrigin;
   const nsString mChannel;
+  bool mPrivateBrowsing;
 };
 
 PLDHashOperator
 PostMessageEnumerator(nsPtrHashKey<BroadcastChannelParent>* aKey, void* aPtr)
 {
   AssertIsOnBackgroundThread();
 
   auto* data = static_cast<PostMessageData*>(aPtr);
   BroadcastChannelParent* parent = aKey->GetKey();
   MOZ_ASSERT(parent);
 
   if (parent != data->mParent) {
-    parent->CheckAndDeliver(data->mData, data->mOrigin, data->mChannel);
+    parent->CheckAndDeliver(data->mData, data->mOrigin, data->mChannel,
+                            data->mPrivateBrowsing);
   }
 
   return PL_DHASH_NEXT;
 }
 
 } // anonymous namespace
 
 void
 BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
                                      const ClonedMessageData& aData,
                                      const nsAString& aOrigin,
-                                     const nsAString& aChannel)
+                                     const nsAString& aChannel,
+                                     bool aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParent);
   MOZ_ASSERT(mAgents.Contains(aParent));
 
-  PostMessageData data(aParent, aData, aOrigin, aChannel);
+  PostMessageData data(aParent, aData, aOrigin, aChannel, aPrivateBrowsing);
   mAgents.EnumerateEntries(PostMessageEnumerator, &data);
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/broadcastchannel/BroadcastChannelService.h
+++ b/dom/broadcastchannel/BroadcastChannelService.h
@@ -27,17 +27,18 @@ public:
   static already_AddRefed<BroadcastChannelService> GetOrCreate();
 
   void RegisterActor(BroadcastChannelParent* aParent);
   void UnregisterActor(BroadcastChannelParent* aParent);
 
   void PostMessage(BroadcastChannelParent* aParent,
                    const ClonedMessageData& aData,
                    const nsAString& aOrigin,
-                   const nsAString& aChannel);
+                   const nsAString& aChannel,
+                   bool aPrivateBrowsing);
 
 private:
   BroadcastChannelService();
   ~BroadcastChannelService();
 
   nsTHashtable<nsPtrHashKey<BroadcastChannelParent>> mAgents;
 };
 
--- a/dom/broadcastchannel/moz.build
+++ b/dom/broadcastchannel/moz.build
@@ -19,13 +19,14 @@ IPDL_SOURCES += [
     'PBroadcastChannel.ipdl',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
 ]
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html><body></body></html>
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+skip-if = buildapp == 'b2g'
+support-files =
+  blank.html
+
+[test_broadcastchannel_private_browsing.html]
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_private_browsing.html
@@ -0,0 +1,118 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Test for BroadcastChannel - Private Browsing</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+
+<script type="application/javascript">
+
+const Ci = Components.interfaces;
+var mainWindow;
+
+var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
+                           .getService(Components.interfaces.nsIPrefBranch);
+prefBranch.setIntPref("browser.startup.page", 0);
+prefBranch.setCharPref("browser.startup.homepage_override.mstone", "ignore");
+
+var contentPage = "http://mochi.test:8888/chrome/dom/broadcastchannel/tests/blank.html";
+
+function testOnWindow(aIsPrivate, aCallback) {
+  var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad, false);
+    win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+      if (win.content.location.href != contentPage) {
+        win.gBrowser.loadURI(contentPage);
+        return;
+      }
+
+      win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+      SimpleTest.executeSoon(function() { aCallback(win); });
+    }, true);
+
+    if (!aIsPrivate) {
+      win.gBrowser.loadURI(contentPage);
+    }
+  }, true);
+}
+
+function setupWindow() {
+  mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIWebNavigation)
+                     .QueryInterface(Ci.nsIDocShellTreeItem)
+                     .rootTreeItem
+                     .QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindow);
+  runTest();
+}
+
+var gCounter = 0;
+
+function check(msg, private) {
+  is(msg, private ? "private" : "public", "Correct context!");
+  gCounter++;
+
+  if (gCounter > 1) {
+    runTest();
+  }
+}
+
+var wN;
+var wP;
+
+function doTests() {
+  testOnWindow(false, function(aWin) {
+    wN = aWin;
+
+    testOnWindow(true, function(aWin) {
+      wP = aWin;
+
+      var bcP = new wP.content.BroadcastChannel('foobar');
+      bcP.onmessage = function(e) { ok(false, "This should not be called!"); }
+
+      var bc = new wP.content.BroadcastChannel('foobar');
+      bc.onmessage = function(e) { check(e.data, true); }
+
+      var bcN = new wN.content.BroadcastChannel('foobar');
+      bcN.onmessage = function(e) { ok(false, "This should not be called!"); }
+
+      var bc = new wN.content.BroadcastChannel('foobar');
+      bc.onmessage = function(e) { check(e.data, false); }
+
+      bcP.postMessage('private');
+      bcN.postMessage('public');
+    });
+  });
+}
+
+var steps = [
+  setupWindow,
+  doTests
+];
+
+function runTest() {
+  if (!steps.length) {
+    wN.close();
+    wP.close();
+
+    prefBranch.clearUserPref("browser.startup.page")
+    prefBranch.clearUserPref("browser.startup.homepage_override.mstone");
+
+    SimpleTest.finish();
+    return;
+  }
+
+  var step = steps.shift();
+  step();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
+
+</script>
+</body>
+</html>
+
+
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -201,17 +201,17 @@ WebGL2Context::TransformFeedbackVaryings
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("transformFeedbackVaryings: program", program))
         return;
 
     GLsizei count = varyings.Length();
-    GLchar** tmpVaryings = (GLchar**) nsMemory::Alloc(count * sizeof(GLchar*));
+    GLchar** tmpVaryings = (GLchar**) moz_xmalloc(count * sizeof(GLchar*));
 
     for (GLsizei n = 0; n < count; n++) {
         tmpVaryings[n] = (GLchar*) ToNewCString(varyings[n]);
     }
 
     GLuint progname = program->mGLName;
     MakeContextCurrent();
     gl->fTransformFeedbackVaryings(progname, count, tmpVaryings, bufferMode);
--- a/dom/datastore/DataStoreCursorImpl.jsm
+++ b/dom/datastore/DataStoreCursorImpl.jsm
@@ -68,17 +68,17 @@ Cu.import('resource://gre/modules/XPCOMU
  *   YES L[0] == 'void': R=L[0]; state->init; loop
  *   NO: state->revisionCheck; loop
  *
  * State: done: send a 'done' with R
  */
 
 /* Helper functions */
 function createDOMError(aWindow, aEvent) {
-  return new aWindow.DOMError(aEvent.target.error.name);
+  return new aWindow.DOMError(aEvent);
 }
 
 /* DataStoreCursor object */
 this.DataStoreCursor = function(aWindow, aDataStore, aRevisionId) {
   debug("DataStoreCursor created");
   this.init(aWindow, aDataStore, aRevisionId);
 }
 
--- a/dom/datastore/DataStoreDB.cpp
+++ b/dom/datastore/DataStoreDB.cpp
@@ -310,19 +310,23 @@ DataStoreDB::DatabaseOpened()
   nsRefPtr<VersionChangeListener> listener =
     new VersionChangeListener(mDatabase);
   rv = mDatabase->EventTarget::AddEventListener(
     NS_LITERAL_STRING("versionchange"), listener, false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsRefPtr<IDBTransaction> txn = mDatabase->Transaction(mObjectStores,
-                                                        mTransactionMode,
-                                                        error);
+  StringOrStringSequence objectStores;
+  objectStores.RawSetAsStringSequence().AppendElements(mObjectStores);
+
+  nsRefPtr<IDBTransaction> txn;
+  error = mDatabase->Transaction(objectStores,
+                                 mTransactionMode,
+                                 getter_AddRefs(txn));
   if (NS_WARN_IF(error.Failed())) {
     return error.ErrorCode();
   }
 
   mTransaction = txn.forget();
   return NS_OK;
 }
 
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -853,32 +853,32 @@ Event::GetEventPopupControlState(WidgetE
   return abuse;
 }
 
 // static
 void
 Event::PopupAllowedEventsChanged()
 {
   if (sPopupAllowedEvents) {
-    nsMemory::Free(sPopupAllowedEvents);
+    free(sPopupAllowedEvents);
   }
 
   nsAdoptingCString str = Preferences::GetCString("dom.popup_allowed_events");
 
   // We'll want to do this even if str is empty to avoid looking up
   // this pref all the time if it's not set.
   sPopupAllowedEvents = ToNewCString(str);
 }
 
 // static
 void
 Event::Shutdown()
 {
   if (sPopupAllowedEvents) {
-    nsMemory::Free(sPopupAllowedEvents);
+    free(sPopupAllowedEvents);
   }
 }
 
 LayoutDeviceIntPoint
 Event::GetScreenCoords(nsPresContext* aPresContext,
                        WidgetEvent* aEvent,
                        LayoutDeviceIntPoint aPoint)
 {
--- a/dom/events/EventListenerService.cpp
+++ b/dom/events/EventListenerService.cpp
@@ -147,17 +147,17 @@ EventListenerService::GetListenerInfoFor
 
   int32_t count = listenerInfos.Count();
   if (count == 0) {
     return NS_OK;
   }
 
   *aOutArray =
     static_cast<nsIEventListenerInfo**>(
-      nsMemory::Alloc(sizeof(nsIEventListenerInfo*) * count));
+      moz_xmalloc(sizeof(nsIEventListenerInfo*) * count));
   NS_ENSURE_TRUE(*aOutArray, NS_ERROR_OUT_OF_MEMORY);
 
   for (int32_t i = 0; i < count; ++i) {
     NS_ADDREF((*aOutArray)[i] = listenerInfos[i]);
   }
   *aCount = count;
   return NS_OK;
 }
@@ -177,17 +177,17 @@ EventListenerService::GetEventTargetChai
   NS_ENSURE_SUCCESS(rv, rv);
   int32_t count = targets.Length();
   if (count == 0) {
     return NS_OK;
   }
 
   *aOutArray =
     static_cast<nsIDOMEventTarget**>(
-      nsMemory::Alloc(sizeof(nsIDOMEventTarget*) * count));
+      moz_xmalloc(sizeof(nsIDOMEventTarget*) * count));
   NS_ENSURE_TRUE(*aOutArray, NS_ERROR_OUT_OF_MEMORY);
 
   for (int32_t i = 0; i < count; ++i) {
     NS_ADDREF((*aOutArray)[i] = targets[i]);
   }
   *aCount = count;
 
   return NS_OK;
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1174,17 +1174,17 @@ HTMLInputElement::~HTMLInputElement()
   DestroyImageLoadingContent();
   FreeData();
 }
 
 void
 HTMLInputElement::FreeData()
 {
   if (!IsSingleLineTextControl(false)) {
-    nsMemory::Free(mInputData.mValue);
+    free(mInputData.mValue);
     mInputData.mValue = nullptr;
   } else {
     UnbindFromFrame(nullptr);
     delete mInputData.mState;
     mInputData.mState = nullptr;
   }
 }
 
@@ -2890,17 +2890,17 @@ HTMLInputElement::SetValueInternal(const
       if (IsSingleLineTextControl(false)) {
         if (!mInputData.mState->SetValue(value, aUserInput, aSetValueChanged)) {
           return NS_ERROR_OUT_OF_MEMORY;
         }
         if (mType == NS_FORM_INPUT_EMAIL) {
           UpdateAllValidityStates(mParserCreating);
         }
       } else {
-        nsMemory::Free(mInputData.mValue);
+        free(mInputData.mValue);
         mInputData.mValue = ToNewUnicode(value);
         if (aSetValueChanged) {
           SetValueChanged(true);
         }
         if (mType == NS_FORM_INPUT_NUMBER) {
           // This has to happen before OnValueChanged is called because that
           // method needs the new value of our frame's anon text control.
           nsNumberControlFrame* numberControlFrame =
--- a/dom/html/nsFormSubmission.cpp
+++ b/dom/html/nsFormSubmission.cpp
@@ -355,17 +355,17 @@ nsFSURLEncoded::URLEncode(const nsAStrin
   char16_t* convertedBuf =
     nsLinebreakConverter::ConvertUnicharLineBreaks(PromiseFlatString(aStr).get(),
                                                    nsLinebreakConverter::eLinebreakAny,
                                                    nsLinebreakConverter::eLinebreakNet);
   NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
 
   nsAutoCString encodedBuf;
   nsresult rv = EncodeVal(nsDependentString(convertedBuf), encodedBuf, false);
-  nsMemory::Free(convertedBuf);
+  free(convertedBuf);
   NS_ENSURE_SUCCESS(rv, rv);
 
   char* escapedBuf = nsEscape(encodedBuf.get(), url_XPAlphas);
   NS_ENSURE_TRUE(escapedBuf, NS_ERROR_OUT_OF_MEMORY);
   aEncoded.Adopt(escapedBuf);
 
   return NS_OK;
 }
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -3464,17 +3464,17 @@ nsHTMLDocument::QueryCommandState(const 
   if (cmdToDispatch.EqualsLiteral("cmd_align")) {
     char * actualAlignmentType = nullptr;
     rv = cmdParams->GetCStringValue("state_attribute", &actualAlignmentType);
     bool retval = false;
     if (!rv.Failed() && actualAlignmentType && actualAlignmentType[0]) {
       retval = paramToCheck.Equals(actualAlignmentType);
     }
     if (actualAlignmentType) {
-      nsMemory::Free(actualAlignmentType);
+      free(actualAlignmentType);
     }
     return retval;
   }
 
   // If command does not have a state_all value, this call fails and sets
   // retval to false.  This is fine -- we want to return false in that case
   // anyway (bug 738385), so we just succeed and return false regardless.
   bool retval = false;
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -46,16 +46,17 @@
 #include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/InputStreamParams.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/PBackground.h"
+#include "mozilla/storage/Variant.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsDataHashtable.h"
 #include "nsEscape.h"
 #include "nsHashKeys.h"
 #include "nsNetUtil.h"
 #include "nsIAppsService.h"
@@ -69,68 +70,80 @@
 #include "nsIObserverService.h"
 #include "nsIOfflineStorage.h"
 #include "nsIOutputStream.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsISupportsPriority.h"
+#include "nsIThread.h"
+#include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
 #include "snappy/snappy.h"
-#include "TransactionThreadPool.h"
+
+#define DISABLE_ASSERTS_FOR_FUZZING 0
+
+#if DISABLE_ASSERTS_FOR_FUZZING
+#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
+#else
+#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
+#endif
+
+#define IDB_DEBUG_LOG(_args)                                                   \
+  PR_LOG(IndexedDatabaseManager::GetLoggingModule(),                           \
+         PR_LOG_DEBUG,                                                         \
+         _args )
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#define IDB_MOBILE
+#endif
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 using namespace mozilla::dom::quota;
 using namespace mozilla::ipc;
 
-#define DISABLE_ASSERTS_FOR_FUZZING 0
-
-#if DISABLE_ASSERTS_FOR_FUZZING
-#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
-#else
-#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
-#endif
-
 namespace {
 
+class ConnectionPool;
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseLoggingInfo;
 class DatabaseFile;
 class DatabaseOfflineStorage;
 class Factory;
 class OpenDatabaseOp;
 class TransactionBase;
+class TransactionDatabaseOperationBase;
 class VersionChangeTransaction;
 
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 static_assert(JS_STRUCTURED_CLONE_VERSION == 5,
               "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 17;
+const uint32_t kMajorSchemaVersion = 18;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -139,23 +152,78 @@ static_assert(kMajorSchemaVersion <= 0xF
 static_assert(kMinorSchemaVersion <= 0xF,
               "Minor version needs to fit in 4 bits.");
 
 const int32_t kSQLiteSchemaVersion =
   int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
 
 const int32_t kStorageProgressGranularity = 1000;
 
+// Changing the value here will override the page size of new databases only.
+// A journal mode change and VACUUM are needed to change existing databases, so
+// the best way to do that is to use the schema version upgrade mechanism.
+const uint32_t kSQLitePageSizeOverride =
+#ifdef IDB_MOBILE
+  2048;
+#else
+  4096;
+#endif
+
+static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
+              (kSQLitePageSizeOverride % 2 == 0 &&
+               kSQLitePageSizeOverride >= 512  &&
+               kSQLitePageSizeOverride <= 65536),
+              "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
+
+// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
+// enforce a custom limit.
+const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
+
+// Set to some multiple of the page size to grow the database in larger chunks.
+const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
+
+static_assert(kSQLiteGrowthIncrement >= 0 &&
+              kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
+              kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
+              "Must be 0 (disabled) or a positive multiple of the page size!");
+
+// The maximum number of threads that can be used for database activity at a
+// single time.
+const uint32_t kMaxConnectionThreadCount = 20;
+
+static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
+
+// The maximum number of threads to keep when idle. Threads that become idle in
+// excess of this number will be shut down immediately.
+const uint32_t kMaxIdleConnectionThreadCount = 2;
+
+static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
+              "Idle thread limit must be less than total thread limit!");
+
+// The length of time that database connections will be held open after all
+// transactions have completed.
+const uint32_t kConnectionIdleCheckpointsMS = 2 * 1000; // 2 seconds
+
+// The length of time that database connections will be held open after all
+// transactions and checkpointing  have completed.
+const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
+
+// The length of time that idle threads will stay alive before being shut down.
+const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
+
 const char kSavepointClause[] = "SAVEPOINT sp;";
 
 const uint32_t kFileCopyBufferSize = 32768;
 
 const char kJournalDirectoryName[] = "journals";
 
 const char kFileManagerDirectoryNameSuffix[] = ".files";
+const char kSQLiteJournalSuffix[] = ".sqlite-journal";
+const char kSQLiteSHMSuffix[] = ".sqlite-shm";
+const char kSQLiteWALSuffix[] = ".sqlite-wal";
 
 const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
 
 #define IDB_PREFIX "indexedDB"
 
 #ifdef MOZ_CHILD_PERMISSIONS
 const char kPermissionString[] = IDB_PREFIX;
 #endif // MOZ_CHILD_PERMISSIONS
@@ -171,17 +239,43 @@ enum AppId {
   kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID
 };
 
 #ifdef DEBUG
 
 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGThreadSleepMS = 0;
 
-#endif
+const int32_t kDEBUGTransactionThreadPriority =
+  nsISupportsPriority::PRIORITY_NORMAL;
+const uint32_t kDEBUGTransactionThreadSleepMS = 0;
+
+#endif
+
+struct FreeDeleter
+{
+  void
+  operator()(void* aPtr) const
+  {
+    free(aPtr);
+  }
+};
+
+template <typename T>
+using UniqueFreePtr = UniquePtr<T, FreeDeleter>;
+
+template <size_t N>
+MOZ_CONSTEXPR size_t
+LiteralStringLength(const char (&aArr)[N])
+{
+  static_assert(N, "Zero-length string literal?!");
+
+  // Don't include the null terminator.
+  return N - 1;
+}
 
 /*******************************************************************************
  * Metadata classes
  ******************************************************************************/
 
 struct FullIndexMetadata
 {
   IndexMetadata mCommonMetadata;
@@ -227,16 +321,19 @@ public:
   {
     // This can happen either on the QuotaManager IO thread or on a
     // versionchange transaction thread. These threads can never race so this is
     // totally safe.
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
 
+  bool
+  HasLiveIndexes() const;
+
 private:
   ~FullObjectStoreMetadata()
   { }
 };
 
 typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
   ObjectStoreTable;
 
@@ -343,16 +440,73 @@ private:
       closure->mMetadata = aValue;
       return PL_DHASH_STOP;
     }
 
     return PL_DHASH_NEXT;
   }
 };
 
+struct IndexDataValue final
+{
+  int64_t mIndexId;
+  Key mKey;
+  bool mUnique;
+
+  IndexDataValue()
+    : mIndexId(0)
+    , mUnique(false)
+  {
+    MOZ_COUNT_CTOR(IndexDataValue);
+  }
+
+  explicit
+  IndexDataValue(const IndexDataValue& aOther)
+    : mIndexId(aOther.mIndexId)
+    , mKey(aOther.mKey)
+    , mUnique(aOther.mUnique)
+  {
+    MOZ_ASSERT(!aOther.mKey.IsUnset());
+
+    MOZ_COUNT_CTOR(IndexDataValue);
+  }
+
+  IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
+    : mIndexId(aIndexId)
+    , mKey(aKey)
+    , mUnique(aUnique)
+  {
+    MOZ_ASSERT(!aKey.IsUnset());
+
+    MOZ_COUNT_CTOR(IndexDataValue);
+  }
+
+  ~IndexDataValue()
+  {
+    MOZ_COUNT_DTOR(IndexDataValue);
+  }
+
+  bool
+  operator==(const IndexDataValue& aOther) const
+  {
+    return mIndexId == aOther.mIndexId &&
+           mKey == aOther.mKey;
+  }
+
+  bool
+  operator<(const IndexDataValue& aOther) const
+  {
+    if (mIndexId == aOther.mIndexId) {
+      return mKey < aOther.mKey;
+    }
+
+    return mIndexId < aOther.mIndexId;
+  }
+};
+
 /*******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t
 MakeSchemaVersion(uint32_t aMajorSchemaVersion,
                   uint32_t aMinorSchemaVersion)
 {
@@ -436,16 +590,345 @@ GetDatabaseFilename(const nsAString& aNa
     } else {
       substring.Append(*forwardIter++);
     }
   }
 
   aDatabaseFilename.AppendASCII(substring.get(), substring.Length());
 }
 
+uint32_t
+CompressedByteCountForNumber(uint64_t aNumber)
+{
+  MOZ_ASSERT(aNumber);
+
+  // All bytes have 7 bits available.
+  uint32_t count = 1;
+  while ((aNumber >>= 7)) {
+    count++;
+  }
+
+  return count;
+}
+
+uint32_t
+CompressedByteCountForIndexId(int64_t aIndexId)
+{
+  MOZ_ASSERT(aIndexId);
+  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+              "Overflow!");
+
+  return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
+}
+
+void
+WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator)
+{
+  MOZ_ASSERT(aIterator);
+  MOZ_ASSERT(*aIterator);
+
+  uint8_t*& buffer = *aIterator;
+
+#ifdef DEBUG
+  const uint8_t* bufferStart = buffer;
+  const uint64_t originalNumber = aNumber;
+#endif
+
+  while (true) {
+    uint64_t shiftedNumber = aNumber >> 7;
+    if (shiftedNumber) {
+      *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
+      aNumber = shiftedNumber;
+    } else {
+      *buffer++ = uint8_t(aNumber);
+      break;
+    }
+  }
+
+  MOZ_ASSERT(buffer > bufferStart);
+  MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
+               CompressedByteCountForNumber(originalNumber));
+}
+
+uint64_t
+ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
+{
+  MOZ_ASSERT(aIterator);
+  MOZ_ASSERT(*aIterator);
+  MOZ_ASSERT(aEnd);
+  MOZ_ASSERT(*aIterator < aEnd);
+
+  const uint8_t*& buffer = *aIterator;
+
+  uint8_t shiftCounter = 0;
+  uint64_t result = 0;
+
+  while (true) {
+    MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
+
+    result += (uint64_t(*buffer & 0x7f) << shiftCounter);
+    shiftCounter += 7;
+
+    if (!(*buffer++ & 0x80)) {
+      break;
+    }
+
+    if (NS_WARN_IF(buffer == aEnd)) {
+      MOZ_ASSERT(false);
+      break;
+    }
+  }
+
+  return result;
+}
+
+void
+WriteCompressedIndexId(int64_t aIndexId, bool aUnique, uint8_t** aIterator)
+{
+  MOZ_ASSERT(aIndexId);
+  MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+             "Overflow!");
+  MOZ_ASSERT(aIterator);
+  MOZ_ASSERT(*aIterator);
+
+  const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
+  WriteCompressedNumber(indexId, aIterator);
+}
+
+void
+ReadCompressedIndexId(const uint8_t** aIterator,
+                      const uint8_t* aEnd,
+                      int64_t* aIndexId,
+                      bool* aUnique)
+{
+  MOZ_ASSERT(aIterator);
+  MOZ_ASSERT(*aIterator);
+  MOZ_ASSERT(aIndexId);
+  MOZ_ASSERT(aUnique);
+
+  uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
+
+  if (indexId % 2) {
+    *aUnique = true;
+    indexId--;
+  } else {
+    *aUnique = false;
+  }
+
+  MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
+
+  *aIndexId = int64_t(indexId / 2);
+}
+
+// static
+nsresult
+MakeCompressedIndexDataValues(
+                             const FallibleTArray<IndexDataValue>& aIndexValues,
+                             UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
+                             uint32_t* aCompressedIndexDataValuesLength)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(!aCompressedIndexDataValues);
+  MOZ_ASSERT(aCompressedIndexDataValuesLength);
+
+  PROFILER_LABEL("IndexedDB",
+                 "MakeCompressedIndexDataValues",
+                 js::ProfileEntry::Category::STORAGE);
+
+  const uint32_t arrayLength = aIndexValues.Length();
+  if (!arrayLength) {
+    *aCompressedIndexDataValuesLength = 0;
+    return NS_OK;
+  }
+
+  // First calculate the size of the final buffer.
+  uint32_t blobDataLength = 0;
+
+  for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+    const IndexDataValue& info = aIndexValues[arrayIndex];
+    const nsCString& keyBuffer = info.mKey.GetBuffer();
+    const uint32_t keyBufferLength = keyBuffer.Length();
+
+    MOZ_ASSERT(!keyBuffer.IsEmpty());
+
+    // Don't let |infoLength| overflow.
+    if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() <
+                   CompressedByteCountForIndexId(info.mIndexId) +
+                   CompressedByteCountForNumber(keyBufferLength))) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    const uint32_t infoLength =
+      CompressedByteCountForIndexId(info.mIndexId) +
+      CompressedByteCountForNumber(keyBufferLength) +
+      keyBufferLength;
+
+    // Don't let |blobDataLength| overflow.
+    if (NS_WARN_IF(UINT32_MAX - infoLength < blobDataLength)) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    blobDataLength += infoLength;
+  }
+
+  UniqueFreePtr<uint8_t> blobData(
+    static_cast<uint8_t*>(malloc(blobDataLength)));
+  if (NS_WARN_IF(!blobData)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  uint8_t* blobDataIter = blobData.get();
+
+  for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+    const IndexDataValue& info = aIndexValues[arrayIndex];
+    const nsCString& keyBuffer = info.mKey.GetBuffer();
+    const uint32_t keyBufferLength = keyBuffer.Length();
+
+    WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
+    WriteCompressedNumber(keyBuffer.Length(), &blobDataIter);
+
+    memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
+    blobDataIter += keyBufferLength;
+  }
+
+  MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
+
+  aCompressedIndexDataValues.swap(blobData);
+  *aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
+
+  return NS_OK;
+}
+
+nsresult
+ReadCompressedIndexDataValuesFromBlob(
+                                   const uint8_t* aBlobData,
+                                   uint32_t aBlobDataLength,
+                                   FallibleTArray<IndexDataValue>& aIndexValues)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aBlobData);
+  MOZ_ASSERT(aBlobDataLength);
+  MOZ_ASSERT(aIndexValues.IsEmpty());
+
+  PROFILER_LABEL("IndexedDB",
+                 "ReadCompressedIndexDataValuesFromBlob",
+                 js::ProfileEntry::Category::STORAGE);
+
+  const uint8_t* blobDataIter = aBlobData;
+  const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
+
+  while (blobDataIter < blobDataEnd) {
+    int64_t indexId;
+    bool unique;
+    ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
+
+    if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+
+    // Read key buffer length.
+    const uint64_t keyBufferLength =
+      ReadCompressedNumber(&blobDataIter, blobDataEnd);
+
+    if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
+        NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
+        NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+
+    nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
+                        uint32_t(keyBufferLength));
+    blobDataIter += keyBufferLength;
+
+    if (NS_WARN_IF(!aIndexValues.InsertElementSorted(
+                      IndexDataValue(indexId, unique, Key(keyBuffer))))) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  MOZ_ASSERT(blobDataIter == blobDataEnd);
+
+  return NS_OK;
+}
+
+// static
+template <typename T>
+nsresult
+ReadCompressedIndexDataValuesFromSource(
+                                   T* aSource,
+                                   uint32_t aColumnIndex,
+                                   FallibleTArray<IndexDataValue>& aIndexValues)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aSource);
+  MOZ_ASSERT(aIndexValues.IsEmpty());
+
+  int32_t columnType;
+  nsresult rv = aSource->GetTypeOfIndex(aColumnIndex, &columnType);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
+
+  const uint8_t* blobData;
+  uint32_t blobDataLength;
+  rv = aSource->GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(!blobDataLength)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  rv = ReadCompressedIndexDataValuesFromBlob(blobData,
+                                             blobDataLength,
+                                             aIndexValues);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
+                              uint32_t aColumnIndex,
+                              FallibleTArray<IndexDataValue>& aIndexValues)
+{
+  return ReadCompressedIndexDataValuesFromSource(aStatement,
+                                                 aColumnIndex,
+                                                 aIndexValues);
+}
+
+nsresult
+ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
+                              uint32_t aColumnIndex,
+                              FallibleTArray<IndexDataValue>& aIndexValues)
+{
+  return ReadCompressedIndexDataValuesFromSource(aValues,
+                                                 aColumnIndex,
+                                                 aIndexValues);
+}
+
 nsresult
 CreateFileTables(mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "CreateFileTables",
@@ -522,124 +1005,107 @@ CreateTables(mozIStorageConnection* aCon
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "CreateTables",
                  js::ProfileEntry::Category::STORAGE);
 
   // Table `database`
   nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE database ("
-      "name TEXT NOT NULL, "
-      "version INTEGER NOT NULL DEFAULT 0"
-    ");"
+    "CREATE TABLE database"
+      "( name TEXT PRIMARY KEY"
+      ", origin TEXT NOT NULL"
+      ", version INTEGER NOT NULL DEFAULT 0"
+      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
+      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
+      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
+      ") WITHOUT ROWID;"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `object_store`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_store ("
-      "id INTEGER PRIMARY KEY, "
-      "auto_increment INTEGER NOT NULL DEFAULT 0, "
-      "name TEXT NOT NULL, "
-      "key_path TEXT, "
-      "UNIQUE (name)"
-    ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Table `object_data`
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_data ("
-      "id INTEGER PRIMARY KEY, "
-      "object_store_id INTEGER NOT NULL, "
-      "key_value BLOB DEFAULT NULL, "
-      "file_ids TEXT, "
-      "data BLOB NOT NULL, "
-      "UNIQUE (object_store_id, key_value), "
-      "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
-        "CASCADE"
-    ");"
+    "CREATE TABLE object_store"
+      "( id INTEGER PRIMARY KEY"
+      ", auto_increment INTEGER NOT NULL DEFAULT 0"
+      ", name TEXT NOT NULL"
+      ", key_path TEXT"
+      ");"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `index`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE object_store_index ("
-      "id INTEGER PRIMARY KEY, "
-      "object_store_id INTEGER NOT NULL, "
-      "name TEXT NOT NULL, "
-      "key_path TEXT NOT NULL, "
-      "unique_index INTEGER NOT NULL, "
-      "multientry INTEGER NOT NULL, "
-      "UNIQUE (object_store_id, name), "
-      "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
-        "CASCADE"
-    ");"
+    "CREATE TABLE object_store_index"
+      "( id INTEGER PRIMARY KEY"
+      ", object_store_id INTEGER NOT NULL"
+      ", name TEXT NOT NULL"
+      ", key_path TEXT NOT NULL"
+      ", unique_index INTEGER NOT NULL"
+      ", multientry INTEGER NOT NULL"
+      ", FOREIGN KEY (object_store_id) "
+          "REFERENCES object_store(id) "
+      ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Table `object_data`
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE object_data"
+      "( object_store_id INTEGER NOT NULL"
+      ", key BLOB NOT NULL"
+      ", index_data_values BLOB DEFAULT NULL"
+      ", file_ids TEXT"
+      ", data BLOB NOT NULL"
+      ", PRIMARY KEY (object_store_id, key)"
+      ", FOREIGN KEY (object_store_id) "
+          "REFERENCES object_store(id) "
+      ") WITHOUT ROWID;"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `index_data`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE index_data ("
-      "index_id INTEGER NOT NULL, "
-      "value BLOB NOT NULL, "
-      "object_data_key BLOB NOT NULL, "
-      "object_data_id INTEGER NOT NULL, "
-      "PRIMARY KEY (index_id, value, object_data_key), "
-      "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
-        "CASCADE, "
-      "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
-        "CASCADE"
-    ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Need this to make cascading deletes from object_data and object_store fast.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE INDEX index_data_object_data_id_index "
-    "ON index_data (object_data_id);"
+    "CREATE TABLE index_data"
+      "( index_id INTEGER NOT NULL"
+      ", value BLOB NOT NULL"
+      ", object_data_key BLOB NOT NULL"
+      ", object_store_id INTEGER NOT NULL"
+      ", PRIMARY KEY (index_id, value, object_data_key)"
+      ", FOREIGN KEY (index_id) "
+          "REFERENCES object_store_index(id) "
+      ", FOREIGN KEY (object_store_id, object_data_key) "
+          "REFERENCES object_data(object_store_id, key) "
+      ") WITHOUT ROWID;"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Table `unique_index_data`
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE TABLE unique_index_data ("
-      "index_id INTEGER NOT NULL, "
-      "value BLOB NOT NULL, "
-      "object_data_key BLOB NOT NULL, "
-      "object_data_id INTEGER NOT NULL, "
-      "PRIMARY KEY (index_id, value, object_data_key), "
-      "UNIQUE (index_id, value), "
-      "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
-        "CASCADE "
-      "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
-        "CASCADE"
-    ");"
-  ));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Need this to make cascading deletes from object_data and object_store fast.
-  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE INDEX unique_index_data_object_data_id_index "
-    "ON unique_index_data (object_data_id);"
+    "CREATE TABLE unique_index_data"
+      "( index_id INTEGER NOT NULL"
+      ", value BLOB NOT NULL"
+      ", object_store_id INTEGER NOT NULL"
+      ", object_data_key BLOB NOT NULL"
+      ", PRIMARY KEY (index_id, value)"
+      ", FOREIGN KEY (index_id) "
+          "REFERENCES object_store_index(id) "
+      ", FOREIGN KEY (object_store_id, object_data_key) "
+          "REFERENCES object_data(object_store_id, key) "
+      ") WITHOUT ROWID;"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = CreateFileTables(aConnection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -2058,17 +2524,17 @@ UpgradeSchemaFrom12_0To13_0(mozIStorageC
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
                  "UpgradeSchemaFrom12_0To13_0",
                  js::ProfileEntry::Category::STORAGE);
 
   nsresult rv;
 
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#ifdef IDB_MOBILE
   int32_t defaultPageSize;
   rv = aConnection->GetDefaultPageSize(&defaultPageSize);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Enable auto_vacuum mode and update the page size to the platform default.
   nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = ");
@@ -2140,16 +2606,935 @@ UpgradeSchemaFrom16_0To17_0(mozIStorageC
   nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(17, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+class UpgradeSchemaFrom17_0To18_0Helper final
+{
+  class InsertIndexDataValuesFunction;
+  class UpgradeKeyFunction;
+
+public:
+  static nsresult
+  DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin);
+
+private:
+  static nsresult
+  DoUpgradeInternal(mozIStorageConnection* aConnection,
+                    const nsACString& aOrigin);
+
+  UpgradeSchemaFrom17_0To18_0Helper()
+  {
+    MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
+  }
+
+  ~UpgradeSchemaFrom17_0To18_0Helper()
+  {
+    MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
+  }
+};
+
+class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final
+  : public mozIStorageFunction
+{
+public:
+  InsertIndexDataValuesFunction()
+  { }
+
+  NS_DECL_ISUPPORTS
+
+private:
+  ~InsertIndexDataValuesFunction()
+  { }
+
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::
+                    InsertIndexDataValuesFunction,
+                  mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::
+InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+                                              nsIVariant** _retval)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aValues);
+  MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+  {
+    uint32_t argCount;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
+    MOZ_ASSERT(argCount == 4);
+
+    int32_t valueType;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
+    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+               valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &valueType)));
+    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(2, &valueType)));
+    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(3, &valueType)));
+    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+  }
+#endif
+
+  // Read out the previous value. It may be NULL, in which case we'll just end
+  // up with an empty array.
+  AutoFallibleTArray<IndexDataValue, 32> indexValues;
+  nsresult rv = ReadCompressedIndexDataValues(aValues, 0, indexValues);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int64_t indexId;
+  rv = aValues->GetInt64(1, &indexId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int32_t unique;
+  rv = aValues->GetInt32(2, &unique);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  Key value;
+  rv = value.SetFromValueArray(aValues, 3);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the array with the new addition.
+  if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() + 1))) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  MOZ_ALWAYS_TRUE(
+    indexValues.InsertElementSorted(IndexDataValue(indexId, !!unique, value)));
+
+  // Compress the array.
+  UniqueFreePtr<uint8_t> indexValuesBlob;
+  uint32_t indexValuesBlobLength;
+  rv = MakeCompressedIndexDataValues(indexValues,
+                                     indexValuesBlob,
+                                     &indexValuesBlobLength);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The compressed blob is the result of this function.
+  std::pair<uint8_t *, int> indexValuesBlobPair(indexValuesBlob.release(),
+                                                indexValuesBlobLength);
+
+  nsCOMPtr<nsIVariant> result =
+    new storage::AdoptedBlobVariant(indexValuesBlobPair);
+
+  result.forget(_retval);
+  return NS_OK;
+}
+
+class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final
+  : public mozIStorageFunction
+{
+public:
+  UpgradeKeyFunction()
+  { }
+
+  static nsresult
+  CopyAndUpgradeKeyBuffer(const uint8_t* aSource,
+                          const uint8_t* aSourceEnd,
+                          uint8_t* aDestination)
+  {
+    return CopyAndUpgradeKeyBufferInternal(aSource,
+                                           aSourceEnd,
+                                           aDestination,
+                                           0 /* aTagOffset */,
+                                           0 /* aRecursionDepth */);
+  }
+
+  NS_DECL_ISUPPORTS
+
+private:
+  ~UpgradeKeyFunction()
+  { }
+
+  static nsresult
+  CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+                                  const uint8_t* aSourceEnd,
+                                  uint8_t*& aDestination,
+                                  uint8_t aTagOffset,
+                                  uint8_t aRecursionDepth);
+
+  static uint32_t
+  AdjustedSize(uint32_t aMaxSize,
+               const uint8_t* aSource,
+               const uint8_t* aSourceEnd)
+  {
+    MOZ_ASSERT(aMaxSize);
+    MOZ_ASSERT(aSource);
+    MOZ_ASSERT(aSourceEnd);
+    MOZ_ASSERT(aSource <= aSourceEnd);
+
+    return std::min(aMaxSize, uint32_t(aSourceEnd - aSource));
+  }
+
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::
+UpgradeKeyFunction::CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+                                                    const uint8_t* aSourceEnd,
+                                                    uint8_t*& aDestination,
+                                                    uint8_t aTagOffset,
+                                                    uint8_t aRecursionDepth)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aSource);
+  MOZ_ASSERT(*aSource);
+  MOZ_ASSERT(aSourceEnd);
+  MOZ_ASSERT(aSource < aSourceEnd);
+  MOZ_ASSERT(aDestination);
+  MOZ_ASSERT(aTagOffset <=  Key::kMaxArrayCollapse);
+
+  static MOZ_CONSTEXPR_VAR uint8_t kOldNumberTag = 0x1;
+  static MOZ_CONSTEXPR_VAR uint8_t kOldDateTag = 0x2;
+  static MOZ_CONSTEXPR_VAR uint8_t kOldStringTag = 0x3;
+  static MOZ_CONSTEXPR_VAR uint8_t kOldArrayTag = 0x4;
+  static MOZ_CONSTEXPR_VAR uint8_t kOldMaxType = kOldArrayTag;
+
+  if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
+  MOZ_ASSERT(sourceTag);
+
+  if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
+    // Write the new tag.
+    *aDestination++ =
+      (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
+      (aTagOffset * Key::eMaxType);
+    aSource++;
+
+    // Numbers and Dates are encoded as 64-bit integers, but trailing 0
+    // bytes have been removed.
+    const uint32_t byteCount =
+      AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);
+
+    for (uint32_t count = 0; count < byteCount; count++) {
+      *aDestination++ = *aSource++;
+    }
+
+    return NS_OK;
+  }
+
+  if (sourceTag == kOldStringTag) {
+    // Write the new tag.
+    *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType);
+    aSource++;
+
+    while (aSource < aSourceEnd) {
+      const uint8_t byte = *aSource++;
+      *aDestination++ = byte;
+
+      if (!byte) {
+        // Just copied the terminator.
+        break;
+      }
+
+      // Maybe copy one or two extra bytes if the byte is tagged and we have
+      // enough source space.
+      if (byte & 0x80) {
+        const uint32_t byteCount =
+          AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd);
+
+        for (uint32_t count = 0; count < byteCount; count++) {
+          *aDestination++ = *aSource++;
+        }
+      }
+    }
+
+    return NS_OK;
+  }
+
+  if (NS_WARN_IF(sourceTag < kOldArrayTag)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  aTagOffset++;
+
+  if (aTagOffset == Key::kMaxArrayCollapse) {
+    MOZ_ASSERT(sourceTag == kOldArrayTag);
+
+    *aDestination++ = (aTagOffset * Key::eMaxType);
+    aSource++;
+
+    aTagOffset = 0;
+  }
+
+  while (aSource < aSourceEnd &&
+         (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) {
+    nsresult rv = CopyAndUpgradeKeyBufferInternal(aSource,
+                                                  aSourceEnd,
+                                                  aDestination,
+                                                  aTagOffset,
+                                                  aRecursionDepth + 1);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    aTagOffset = 0;
+  }
+
+  if (aSource < aSourceEnd) {
+    MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator);
+    *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType);
+    aSource++;
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction,
+                  mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::
+UpgradeKeyFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+                                   nsIVariant** _retval)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aValues);
+  MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+  {
+    uint32_t argCount;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
+    MOZ_ASSERT(argCount == 1);
+
+    int32_t valueType;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
+    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+  }
+#endif
+
+  // Dig the old key out of the values.
+  const uint8_t* blobData;
+  uint32_t blobDataLength;
+  nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Upgrading the key doesn't change the amount of space needed to hold it.
+  UniqueFreePtr<uint8_t> upgradedBlobData(
+    static_cast<uint8_t*>(malloc(blobDataLength)));
+  if (NS_WARN_IF(!upgradedBlobData)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  rv = CopyAndUpgradeKeyBuffer(blobData,
+                               blobData + blobDataLength,
+                               upgradedBlobData.get());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The upgraded key is the result of this function.
+  std::pair<uint8_t*, int> data(upgradedBlobData.release(),
+                                int(blobDataLength));
+
+  nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
+
+  upgradedBlobData.release();
+
+  result.forget(_retval);
+  return NS_OK;
+}
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection,
+                                             const nsACString& aOrigin)
+{
+  MOZ_ASSERT(aConnection);
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+
+  // Register the |upgrade_key| function.
+  nsRefPtr<UpgradeKeyFunction> updateFunction = new UpgradeKeyFunction();
+
+  NS_NAMED_LITERAL_CSTRING(upgradeKeyFunctionName, "upgrade_key");
+
+  nsresult rv =
+    aConnection->CreateFunction(upgradeKeyFunctionName, 1, updateFunction);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Register the |insert_idv| function.
+  nsRefPtr<InsertIndexDataValuesFunction> insertIDVFunction =
+    new InsertIndexDataValuesFunction();
+
+  NS_NAMED_LITERAL_CSTRING(insertIDVFunctionName, "insert_idv");
+
+  rv = aConnection->CreateFunction(insertIDVFunctionName, 4, insertIDVFunction);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      aConnection->RemoveFunction(upgradeKeyFunctionName)));
+    return rv;
+  }
+
+  rv = DoUpgradeInternal(aConnection, aOrigin);
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    aConnection->RemoveFunction(upgradeKeyFunctionName)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    aConnection->RemoveFunction(insertIDVFunctionName)));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
+                                             mozIStorageConnection* aConnection,
+                                             const nsACString& aOrigin)
+{
+  MOZ_ASSERT(aConnection);
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Drop these triggers to avoid unnecessary work during the upgrade process.
+    "DROP TRIGGER object_data_insert_trigger;"
+    "DROP TRIGGER object_data_update_trigger;"
+    "DROP TRIGGER object_data_delete_trigger;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Drop these indexes before we do anything else to free disk space.
+    "DROP INDEX index_data_object_data_id_index;"
+    "DROP INDEX unique_index_data_object_data_id_index;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Create the new tables and triggers first.
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // This will eventually become the |database| table.
+    "CREATE TABLE database_upgrade "
+      "( name TEXT PRIMARY KEY"
+      ", origin TEXT NOT NULL"
+      ", version INTEGER NOT NULL DEFAULT 0"
+      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
+      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
+      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
+      ") WITHOUT ROWID;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+     // This will eventually become the |object_store| table.
+    "CREATE TABLE object_store_upgrade"
+      "( id INTEGER PRIMARY KEY"
+      ", auto_increment INTEGER NOT NULL DEFAULT 0"
+      ", name TEXT NOT NULL"
+      ", key_path TEXT"
+      ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // This will eventually become the |object_store_index| table.
+    "CREATE TABLE object_store_index_upgrade"
+      "( id INTEGER PRIMARY KEY"
+      ", object_store_id INTEGER NOT NULL"
+      ", name TEXT NOT NULL"
+      ", key_path TEXT NOT NULL"
+      ", unique_index INTEGER NOT NULL"
+      ", multientry INTEGER NOT NULL"
+      ", FOREIGN KEY (object_store_id) "
+          "REFERENCES object_store(id) "
+      ");"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // This will eventually become the |object_data| table.
+    "CREATE TABLE object_data_upgrade"
+      "( object_store_id INTEGER NOT NULL"
+      ", key BLOB NOT NULL"
+      ", index_data_values BLOB DEFAULT NULL"
+      ", file_ids TEXT"
+      ", data BLOB NOT NULL"
+      ", PRIMARY KEY (object_store_id, key)"
+      ", FOREIGN KEY (object_store_id) "
+          "REFERENCES object_store(id) "
+      ") WITHOUT ROWID;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // This will eventually become the |index_data| table.
+    "CREATE TABLE index_data_upgrade"
+      "( index_id INTEGER NOT NULL"
+      ", value BLOB NOT NULL"
+      ", object_data_key BLOB NOT NULL"
+      ", object_store_id INTEGER NOT NULL"
+      ", PRIMARY KEY (index_id, value, object_data_key)"
+      ", FOREIGN KEY (index_id) "
+          "REFERENCES object_store_index(id) "
+      ", FOREIGN KEY (object_store_id, object_data_key) "
+          "REFERENCES object_data(object_store_id, key) "
+      ") WITHOUT ROWID;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // This will eventually become the |unique_index_data| table.
+    "CREATE TABLE unique_index_data_upgrade"
+      "( index_id INTEGER NOT NULL"
+      ", value BLOB NOT NULL"
+      ", object_store_id INTEGER NOT NULL"
+      ", object_data_key BLOB NOT NULL"
+      ", PRIMARY KEY (index_id, value)"
+      ", FOREIGN KEY (index_id) "
+          "REFERENCES object_store_index(id) "
+      ", FOREIGN KEY (object_store_id, object_data_key) "
+          "REFERENCES object_data(object_store_id, key) "
+      ") WITHOUT ROWID;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Temporarily store |index_data_values| that we build during the upgrade of
+    // the index tables. We will later move this to the |object_data| table.
+    "CREATE TEMPORARY TABLE temp_index_data_values "
+      "( object_store_id INTEGER NOT NULL"
+      ", key BLOB NOT NULL"
+      ", index_data_values BLOB DEFAULT NULL"
+      ", PRIMARY KEY (object_store_id, key)"
+      ") WITHOUT ROWID;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // These two triggers help build the |index_data_values| blobs. The nested
+    // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior.
+    "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger "
+      "AFTER INSERT ON unique_index_data_upgrade "
+      "BEGIN "
+        "INSERT OR REPLACE INTO temp_index_data_values "
+          "VALUES "
+          "( NEW.object_store_id"
+          ", NEW.object_data_key"
+          ", insert_idv("
+              "( SELECT index_data_values "
+                  "FROM temp_index_data_values "
+                  "WHERE object_store_id = NEW.object_store_id "
+                  "AND key = NEW.object_data_key "
+              "), NEW.index_id"
+               ", 1" /* unique */
+               ", NEW.value"
+            ")"
+          ");"
+      "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger "
+      "AFTER INSERT ON index_data_upgrade "
+      "BEGIN "
+        "INSERT OR REPLACE INTO temp_index_data_values "
+          "VALUES "
+          "( NEW.object_store_id"
+          ", NEW.object_data_key"
+          ", insert_idv("
+              "("
+                "SELECT index_data_values "
+                  "FROM temp_index_data_values "
+                  "WHERE object_store_id = NEW.object_store_id "
+                  "AND key = NEW.object_data_key "
+              "), NEW.index_id"
+               ", 0" /* not unique */
+               ", NEW.value"
+            ")"
+          ");"
+      "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |unique_index_data| table to change the column order, remove the
+  // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Insert all the data.
+    "INSERT INTO unique_index_data_upgrade "
+      "SELECT "
+        "unique_index_data.index_id, "
+        "upgrade_key(unique_index_data.value), "
+        "object_data.object_store_id, "
+        "upgrade_key(unique_index_data.object_data_key) "
+        "FROM unique_index_data "
+        "JOIN object_data "
+        "ON unique_index_data.object_data_id = object_data.id;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The trigger is no longer needed.
+    "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The old table is no longer needed.
+    "DROP TABLE unique_index_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Rename the table.
+    "ALTER TABLE unique_index_data_upgrade "
+      "RENAME TO unique_index_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |index_data| table to change the column order, remove the ON
+  // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Insert all the data.
+    "INSERT INTO index_data_upgrade "
+      "SELECT "
+        "index_data.index_id, "
+        "upgrade_key(index_data.value), "
+        "upgrade_key(index_data.object_data_key), "
+        "object_data.object_store_id "
+        "FROM index_data "
+        "JOIN object_data "
+        "ON index_data.object_data_id = object_data.id;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The trigger is no longer needed.
+    "DROP TRIGGER index_data_upgrade_insert_trigger;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The old table is no longer needed.
+    "DROP TABLE index_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Rename the table.
+    "ALTER TABLE index_data_upgrade "
+      "RENAME TO index_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |object_data| table to add the |index_data_values| column,
+  // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID
+  // optimization.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Insert all the data.
+    "INSERT INTO object_data_upgrade "
+      "SELECT "
+        "object_data.object_store_id, "
+        "upgrade_key(object_data.key_value), "
+        "temp_index_data_values.index_data_values, "
+        "object_data.file_ids, "
+        "object_data.data "
+        "FROM object_data "
+        "LEFT JOIN temp_index_data_values "
+        "ON object_data.object_store_id = "
+          "temp_index_data_values.object_store_id "
+        "AND upgrade_key(object_data.key_value) = "
+          "temp_index_data_values.key;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The temporary table is no longer needed.
+    "DROP TABLE temp_index_data_values;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // The old table is no longer needed.
+    "DROP TABLE object_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    // Rename the table.
+    "ALTER TABLE object_data_upgrade "
+      "RENAME TO object_data;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |object_store_index| table to remove the UNIQUE constraint and
+  // the ON DELETE CASCADE clause.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO object_store_index_upgrade "
+      "SELECT * "
+        "FROM object_store_index;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE object_store_index;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE object_store_index_upgrade "
+      "RENAME TO object_store_index;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |object_store| table to remove the UNIQUE constraint.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO object_store_upgrade "
+      "SELECT * "
+        "FROM object_store;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE object_store;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE object_store_upgrade "
+      "RENAME TO object_store;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Update the |database| table to include the origin, vacuum information, and
+  // apply the WITHOUT ROWID optimization.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO database_upgrade "
+      "SELECT name, :origin, version, 0, 0, 0 "
+        "FROM database;"
+  ), getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE database;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE database_upgrade "
+      "RENAME TO database;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef DEBUG
+  {
+    // Make sure there's only one entry in the |database| table.
+    nsCOMPtr<mozIStorageStatement> stmt;
+    MOZ_ASSERT(NS_SUCCEEDED(
+      aConnection->CreateStatement(
+        NS_LITERAL_CSTRING("SELECT COUNT(*) "
+                             "FROM database;"),
+        getter_AddRefs(stmt))));
+
+    bool hasResult;
+    MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
+
+    int64_t count;
+    MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count)));
+
+    MOZ_ASSERT(count == 1);
+  }
+#endif
+
+  // Recreate file table triggers.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_insert_trigger "
+      "AFTER INSERT ON object_data "
+      "WHEN NEW.file_ids IS NOT NULL "
+      "BEGIN "
+        "SELECT update_refcount(NULL, NEW.file_ids);"
+      "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_update_trigger "
+      "AFTER UPDATE OF file_ids ON object_data "
+      "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+      "BEGIN "
+        "SELECT update_refcount(OLD.file_ids, NEW.file_ids);"
+      "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_delete_trigger "
+      "AFTER DELETE ON object_data "
+      "WHEN OLD.file_ids IS NOT NULL "
+      "BEGIN "
+        "SELECT update_refcount(OLD.file_ids, NULL);"
+      "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim
+  // disk space on mobile devices (at the cost of some COMMIT speed), and
+  // incremental auto_vacuum mode on desktop builds.
+  rv = aConnection->ExecuteSimpleSQL(
+#ifdef IDB_MOBILE
+    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
+#else
+    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
+#endif
+  );
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(18, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom17_0To18_0(mozIStorageConnection* aConnection,
+                            const nsACString& aOrigin)
+{
+  MOZ_ASSERT(aConnection);
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+
+  PROFILER_LABEL("IndexedDB",
+                 "UpgradeSchemaFrom17_0To18_0",
+                 js::ProfileEntry::Category::STORAGE);
+
+  return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin);
+}
+
 nsresult
 GetDatabaseFileURL(nsIFile* aDatabaseFile,
                    PersistenceType aPersistenceType,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    nsIFileURL** aResult)
 {
   MOZ_ASSERT(aDatabaseFile);
@@ -2164,84 +3549,274 @@ GetDatabaseFileURL(nsIFile* aDatabaseFil
   nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
   MOZ_ASSERT(fileUrl);
 
   nsAutoCString type;
   PersistenceTypeToText(aPersistenceType, type);
 
   rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
                          NS_LITERAL_CSTRING("&group=") + aGroup +
-                         NS_LITERAL_CSTRING("&origin=") + aOrigin);
+                         NS_LITERAL_CSTRING("&origin=") + aOrigin +
+                         NS_LITERAL_CSTRING("&cache=private"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   fileUrl.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 SetDefaultPragmas(mozIStorageConnection* aConnection)
 {
-  MOZ_ASSERT(aConnection);
-
-  static const char query[] =
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
-    // Switch the journaling mode to TRUNCATE to avoid changing the directory
-    // structure at the conclusion of every transaction for devices with slower
-    // file systems.
-    "PRAGMA journal_mode = TRUNCATE; "
-#endif
-    // We use foreign keys in lots of places.
-    "PRAGMA foreign_keys = ON; "
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConnection);
+
+  static const char kBuiltInPragmas[] =
+    // We use foreign keys in DEBUG builds only because there is a performance
+    // cost to using them.
+   "PRAGMA foreign_keys = "
+#ifdef DEBUG
+     "ON"
+#else
+     "OFF"
+#endif
+     ";"
+
     // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
     // instead it fires only the insert trigger. This confuses the update
     // refcount function. This behavior changes with enabled recursive triggers,
     // so the statement fires the delete trigger first and then the insert
     // trigger.
     "PRAGMA recursive_triggers = ON;"
-    // We don't need SQLite's table locks because we manage transaction ordering
-    // ourselves and we know we will never allow a write transaction to modify
-    // an object store that a read transaction is in the process of using.
-    "PRAGMA read_uncommitted = TRUE;"
-    // No more PRAGMAs.
-    ;
-
-  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(query));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+
+    // We aggressively truncate the database file when idle so don't bother
+    // overwriting the WAL with 0 during active periods.
+    "PRAGMA secure_delete = OFF;"
+  ;
+
+  nsresult rv =
+    aConnection->ExecuteSimpleSQL(
+      nsDependentCString(kBuiltInPragmas,
+                         LiteralStringLength(kBuiltInPragmas)));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString pragmaStmt;
+  pragmaStmt.AssignLiteral("PRAGMA synchronous = ");
 
   if (IndexedDatabaseManager::FullSynchronous()) {
-    rv = aConnection->ExecuteSimpleSQL(
-                             NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;"));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult
-CreateDatabaseConnection(nsIFile* aDBFile,
-                         nsIFile* aFMDirectory,
-                         const nsAString& aName,
-                         PersistenceType aPersistenceType,
-                         const nsACString& aGroup,
-                         const nsACString& aOrigin,
-                         mozIStorageConnection** aConnection)
+    pragmaStmt.AppendLiteral("FULL");
+  } else {
+    pragmaStmt.AppendLiteral("NORMAL");
+  }
+  pragmaStmt.Append(';');
+
+  rv = aConnection->ExecuteSimpleSQL(pragmaStmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifndef IDB_MOBILE
+  if (kSQLiteGrowthIncrement) {
+    rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement,
+                                         EmptyCString());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+#endif // IDB_MOBILE
+
+  return NS_OK;
+}
+
+nsresult
+SetJournalMode(mozIStorageConnection* aConnection)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConnection);
+
+  // Try enabling WAL mode. This can fail in various circumstances so we have to
+  // check the results here.
+  NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
+  NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv =
+    aConnection->CreateStatement(journalModeQueryStart + journalModeWAL,
+                                 getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(hasResult);
+
+  nsCString journalMode;
+  rv = stmt->GetUTF8String(0, journalMode);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (journalMode.Equals(journalModeWAL)) {
+    // WAL mode successfully enabled. Maybe set limits on its size here.
+    if (kMaxWALPages >= 0) {
+      nsAutoCString pageCount;
+      pageCount.AppendInt(kMaxWALPages);
+
+      rv = aConnection->ExecuteSimpleSQL(
+        NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  } else {
+    NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
+#ifdef IDB_MOBILE
+    rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart +
+                                       NS_LITERAL_CSTRING("truncate"));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+#endif
+  }
+
+  return NS_OK;
+}
+
+template <class FileOrURLType>
+struct StorageOpenTraits;
+
+template <>
+struct StorageOpenTraits<nsIFileURL*>
+{
+  static nsresult
+  Open(mozIStorageService* aStorageService,
+       nsIFileURL* aFileURL,
+       mozIStorageConnection** aConnection)
+  {
+    return aStorageService->OpenDatabaseWithFileURL(aFileURL, aConnection);
+  }
+
+#ifdef DEBUG
+  static void
+  GetPath(nsIFileURL* aFileURL, nsCString& aPath)
+  {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFileURL->GetFileName(aPath)));
+  }
+#endif
+};
+
+template <>
+struct StorageOpenTraits<nsIFile*>
+{
+  static nsresult
+  Open(mozIStorageService* aStorageService,
+       nsIFile* aFile,
+       mozIStorageConnection** aConnection)
+  {
+    return aStorageService->OpenUnsharedDatabase(aFile, aConnection);
+  }
+
+#ifdef DEBUG
+  static void
+  GetPath(nsIFile* aFile, nsCString& aPath)
+  {
+    nsString path;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFile->GetPath(path)));
+
+    aPath.AssignWithConversion(path);
+  }
+#endif
+};
+
+template <template <class> class SmartPtr, class FileOrURLType>
+struct StorageOpenTraits<SmartPtr<FileOrURLType>>
+  : public StorageOpenTraits<FileOrURLType*>
+{ };
+
+template <class FileOrURLType>
+nsresult
+OpenDatabaseAndHandleBusy(mozIStorageService* aStorageService,
+                          FileOrURLType aFileOrURL,
+                          mozIStorageConnection** aConnection)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aStorageService);
+  MOZ_ASSERT(aFileOrURL);
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  nsresult rv =
+    StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+                                           aFileOrURL,
+                                           getter_AddRefs(connection));
+
+  if (rv == NS_ERROR_STORAGE_BUSY) {
+#ifdef DEBUG
+    {
+      nsCString path;
+      StorageOpenTraits<FileOrURLType>::GetPath(aFileOrURL, path);
+
+      nsPrintfCString message("Received NS_ERROR_STORAGE_BUSY when attempting "
+                              "to open database '%s', retrying for up to 10 "
+                              "seconds",
+                              path.get());
+      NS_WARNING(message.get());
+    }
+#endif
+
+    // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
+    // that to complete.
+    TimeStamp start = TimeStamp::NowLoRes();
+
+    while (true) {
+      PR_Sleep(PR_MillisecondsToInterval(100));
+
+      rv = StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+                                                  aFileOrURL,
+                                                  getter_AddRefs(connection));
+      if (rv != NS_ERROR_STORAGE_BUSY ||
+          TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
+        break;
+      }
+    }
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  connection.forget(aConnection);
+  return NS_OK;
+}
+
+nsresult
+CreateStorageConnection(nsIFile* aDBFile,
+                        nsIFile* aFMDirectory,
+                        const nsAString& aName,
+                        PersistenceType aPersistenceType,
+                        const nsACString& aGroup,
+                        const nsACString& aOrigin,
+                        mozIStorageConnection** aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDBFile);
   MOZ_ASSERT(aFMDirectory);
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
-                 "CreateDatabaseConnection",
+                 "CreateStorageConnection",
                  js::ProfileEntry::Category::STORAGE);
 
   nsresult rv;
   bool exists;
 
   if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
     rv = aDBFile->Exists(&exists);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -2263,17 +3838,17 @@ CreateDatabaseConnection(nsIFile* aDBFil
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
+  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // If we're just opening the database during origin initialization, then
     // we don't want to erase any files. The failure here will fail origin
     // initialization too.
     if (aName.IsVoid()) {
       return rv;
     }
 
@@ -2300,18 +3875,19 @@ CreateDatabaseConnection(nsIFile* aDBFil
       }
 
       rv = aFMDirectory->Remove(true);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
-  }
+    rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
+  }
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = SetDefaultPragmas(connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -2334,71 +3910,108 @@ CreateDatabaseConnection(nsIFile* aDBFil
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   if (schemaVersion > kSQLiteSchemaVersion) {
     IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  bool vacuumNeeded = false;
+  bool journalModeSet = false;
 
   if (schemaVersion != kSQLiteSchemaVersion) {
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
-    if (!schemaVersion) {
-      // Have to do this before opening a transaction.
-      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-        // Turn on auto_vacuum mode to reclaim disk space on mobile devices.
-        "PRAGMA auto_vacuum = FULL; "
-      ));
+    const bool newDatabase = !schemaVersion;
+
+    if (newDatabase) {
+      // Set the page size first.
+      if (kSQLitePageSizeOverride) {
+        rv = connection->ExecuteSimpleSQL(
+          nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
+        );
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+
+      // We have to set the auto_vacuum mode before opening a transaction.
+      rv = connection->ExecuteSimpleSQL(
+#ifdef IDB_MOBILE
+        // Turn on full auto_vacuum mode to reclaim disk space on mobile
+        // devices (at the cost of some COMMIT speed).
+        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
+#else
+        // Turn on incremental auto_vacuum mode on desktop builds.
+        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
+#endif
+      );
       if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
         // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
         // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
         rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
       }
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-    }
-#endif
+
+      rv = SetJournalMode(connection);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      journalModeSet = true;
+    } else {
+#ifdef DEBUG
+    // Disable foreign key support while upgrading. This has to be done before
+    // starting a transaction.
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      connection->ExecuteSimpleSQL(
+        NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;"))));
+#endif
+    }
+
+    bool vacuumNeeded = false;
 
     mozStorageTransaction transaction(connection, false,
                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
-    if (!schemaVersion) {
-      // Brand new file, initialize our tables.
+    if (newDatabase) {
       rv = CreateTables(connection);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)));
       MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
 
       nsCOMPtr<mozIStorageStatement> stmt;
       nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-        "INSERT INTO database (name) "
-        "VALUES (:name)"
+        "INSERT INTO database (name, origin) "
+        "VALUES (:name, :origin)"
       ), getter_AddRefs(stmt));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
+      rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else  {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((17 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((18 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         } else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
         } else if (schemaVersion == 6) {
@@ -2419,16 +4032,19 @@ CreateDatabaseConnection(nsIFile* aDBFil
         } else if (schemaVersion == MakeSchemaVersion(13, 0)) {
           rv = UpgradeSchemaFrom13_0To14_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(14, 0)) {
           rv = UpgradeSchemaFrom14_0To15_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(15, 0)) {
           rv = UpgradeSchemaFrom15_0To16_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(16, 0)) {
           rv = UpgradeSchemaFrom16_0To17_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(17, 0)) {
+          rv = UpgradeSchemaFrom17_0To18_0(connection, aOrigin);
+          vacuumNeeded = true;
         } else {
           IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
                       "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
@@ -2447,20 +4063,169 @@ CreateDatabaseConnection(nsIFile* aDBFil
     if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
       // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
       // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
       rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-  }
-
-  if (vacuumNeeded) {
-    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
+
+#ifdef DEBUG
+    if (!newDatabase) {
+      // Re-enable foreign key support after doing a foreign key check.
+      nsCOMPtr<mozIStorageStatement> checkStmt;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        connection->CreateStatement(
+          NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
+          getter_AddRefs(checkStmt))));
+
+      bool hasResult;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(checkStmt->ExecuteStep(&hasResult)));
+      MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
+
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        connection->ExecuteSimpleSQL(
+          NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;"))));
+    }
+#endif
+
+    if (kSQLitePageSizeOverride && !newDatabase) {
+      nsCOMPtr<mozIStorageStatement> stmt;
+      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+        "PRAGMA page_size;"
+      ), getter_AddRefs(stmt));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      bool hasResult;
+      rv = stmt->ExecuteStep(&hasResult);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(hasResult);
+
+      int32_t pageSize;
+      rv = stmt->GetInt32(0, &pageSize);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
+
+      if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
+        // We must not be in WAL journal mode to change the page size.
+        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "PRAGMA journal_mode = DELETE;"
+        ));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+          "PRAGMA journal_mode;"
+        ), getter_AddRefs(stmt));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        rv = stmt->ExecuteStep(&hasResult);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        MOZ_ASSERT(hasResult);
+
+        nsCString journalMode;
+        rv = stmt->GetUTF8String(0, journalMode);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        if (journalMode.EqualsLiteral("delete")) {
+          // Successfully set to rollback journal mode so changing the page size
+          // is possible with a VACUUM.
+          rv = connection->ExecuteSimpleSQL(
+            nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
+          );
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+
+          // We will need to VACUUM in order to change the page size.
+          vacuumNeeded = true;
+        } else {
+          NS_WARNING("Failed to set journal_mode for database, unable to "
+                     "change the page size!");
+        }
+      }
+    }
+
+    if (vacuumNeeded) {
+      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    if (newDatabase || vacuumNeeded) {
+      if (journalModeSet) {
+        // Make sure we checkpoint to get an accurate file size.
+        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "PRAGMA wal_checkpoint(FULL);"
+        ));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+
+      int64_t fileSize;
+      rv = aDBFile->GetFileSize(&fileSize);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(fileSize > 0);
+
+      PRTime vacuumTime = PR_Now();
+      MOZ_ASSERT(vacuumTime);
+
+      nsCOMPtr<mozIStorageStatement> vacuumTimeStmt;
+      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+        "UPDATE database "
+          "SET last_vacuum_time = :time"
+            ", last_vacuum_size = :size;"
+      ), getter_AddRefs(vacuumTimeStmt));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"),
+                                           vacuumTime);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"),
+                                           fileSize);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = vacuumTimeStmt->Execute();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  if (!journalModeSet) {
+    rv = SetJournalMode(connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   connection.forget(aConnection);
   return NS_OK;
 }
@@ -2478,30 +4243,30 @@ GetFileForPath(const nsAString& aPath)
   if (NS_WARN_IF(NS_FAILED(file->InitWithPath(aPath)))) {
     return nullptr;
   }
 
   return file.forget();
 }
 
 nsresult
-GetDatabaseConnection(const nsAString& aDatabaseFilePath,
-                      PersistenceType aPersistenceType,
-                      const nsACString& aGroup,
-                      const nsACString& aOrigin,
-                      mozIStorageConnection** aConnection)
+GetStorageConnection(const nsAString& aDatabaseFilePath,
+                     PersistenceType aPersistenceType,
+                     const nsACString& aGroup,
+                     const nsACString& aOrigin,
+                     mozIStorageConnection** aConnection)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
   MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")));
   MOZ_ASSERT(aConnection);
 
   PROFILER_LABEL("IndexedDB",
-                 "GetDatabaseConnection",
+                 "GetStorageConnection",
                  js::ProfileEntry::Category::STORAGE);
 
   nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
   if (NS_WARN_IF(!dbFile)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
@@ -2525,31 +4290,794 @@ GetDatabaseConnection(const nsAString& a
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
+  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = SetDefaultPragmas(connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = SetJournalMode(connection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   connection.forget(aConnection);
   return NS_OK;
 }
 
 /*******************************************************************************
+ * ConnectionPool declarations
+ ******************************************************************************/
+
+class DatabaseConnection final
+{
+  friend class ConnectionPool;
+
+public:
+  class AutoSavepoint;
+  class CachedStatement;
+  class UpdateRefcountFunction;
+
+private:
+  nsCOMPtr<mozIStorageConnection> mStorageConnection;
+  nsRefPtr<FileManager> mFileManager;
+  nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
+    mCachedStatements;
+  nsRefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
+
+#ifdef DEBUG
+  uint32_t mDEBUGSavepointCount;
+  PRThread* mDEBUGThread;
+  bool mDEBUGInWriteTransaction;
+#endif
+
+public:
+  void
+  AssertIsOnConnectionThread() const
+  {
+    MOZ_ASSERT(mDEBUGThread);
+    MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGThread);
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
+
+  mozIStorageConnection*
+  GetStorageConnection() const
+  {
+    if (mStorageConnection) {
+      AssertIsOnConnectionThread();
+      return mStorageConnection;
+    }
+
+    return nullptr;
+  }
+
+  UpdateRefcountFunction*
+  GetUpdateRefcountFunction() const
+  {
+    AssertIsOnConnectionThread();
+
+    return mUpdateRefcountFunction;
+  }
+
+  nsresult
+  GetCachedStatement(const nsACString& aQuery,
+                     CachedStatement* aCachedStatement);
+
+  template <size_t N>
+  nsresult
+  GetCachedStatement(const char (&aQuery)[N],
+                     CachedStatement* aCachedStatement)
+  {
+    static_assert(N > 1, "Must have a non-empty string!");
+    AssertIsOnConnectionThread();
+    MOZ_ASSERT(aCachedStatement);
+
+    return GetCachedStatement(NS_LITERAL_CSTRING(aQuery), aCachedStatement);
+  }
+
+  nsresult
+  BeginWriteTransaction();
+
+  void
+  FinishWriteTransaction();
+
+  nsresult
+  StartSavepoint();
+
+  nsresult
+  ReleaseSavepoint();
+
+  nsresult
+  RollbackSavepoint();
+
+  nsresult
+  Checkpoint(bool aIdle);
+
+  void
+  Close();
+
+private:
+  DatabaseConnection(mozIStorageConnection* aStorageConnection,
+                     FileManager* aFileManager);
+
+  ~DatabaseConnection();
+
+  nsresult
+  Init();
+};
+
+class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final
+{
+  DatabaseConnection* mConnection;
+#ifdef DEBUG
+  const TransactionBase* mDEBUGTransaction;
+#endif
+
+public:
+  AutoSavepoint();
+  ~AutoSavepoint();
+
+  nsresult
+  Start(const TransactionBase* aConnection);
+
+  nsresult
+  Commit();
+};
+
+class DatabaseConnection::CachedStatement final
+{
+  friend class DatabaseConnection;
+
+  nsCOMPtr<mozIStorageStatement> mStatement;
+  Maybe<mozStorageStatementScoper> mScoper;
+
+#ifdef DEBUG
+  DatabaseConnection* mDEBUGConnection;
+#endif
+
+public:
+  CachedStatement();
+  ~CachedStatement();
+
+  void
+  AssertIsOnConnectionThread() const
+  {
+#ifdef DEBUG
+    if (mDEBUGConnection) {
+      mDEBUGConnection->AssertIsOnConnectionThread();
+    }
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(!IsOnBackgroundThread());
+#endif
+  }
+
+  operator mozIStorageStatement*() const;
+
+  mozIStorageStatement*
+  operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN;
+
+  void
+  Reset();
+
+private:
+  // Only called by DatabaseConnection.
+  void
+  Assign(DatabaseConnection* aConnection,
+         already_AddRefed<mozIStorageStatement> aStatement);
+
+  // No funny business allowed.
+  CachedStatement(const CachedStatement&) = delete;
+  CachedStatement& operator=(const CachedStatement&) = delete;
+};
+
+class DatabaseConnection::UpdateRefcountFunction final
+  : public mozIStorageFunction
+{
+  class DatabaseUpdateFunction;
+  class FileInfoEntry;
+
+  enum UpdateType
+  {
+    eIncrement,
+    eDecrement
+  };
+
+  DatabaseConnection* mConnection;
+  FileManager* mFileManager;
+  nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
+  nsDataHashtable<nsUint64HashKey, FileInfoEntry*> mSavepointEntriesIndex;
+
+  nsTArray<int64_t> mJournalsToCreateBeforeCommit;
+  nsTArray<int64_t> mJournalsToRemoveAfterCommit;
+  nsTArray<int64_t> mJournalsToRemoveAfterAbort;
+
+  bool mInSavepoint;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+
+  UpdateRefcountFunction(DatabaseConnection* aConnection,
+                         FileManager* aFileManager);
+
+  nsresult
+  WillCommit();
+
+  void
+  DidCommit();
+
+  void
+  DidAbort();
+
+  void
+  StartSavepoint();
+
+  void
+  ReleaseSavepoint();
+
+  void
+  RollbackSavepoint();
+
+  void
+  Reset();
+
+private:
+  ~UpdateRefcountFunction()
+  { }
+
+  nsresult
+  ProcessValue(mozIStorageValueArray* aValues,
+               int32_t aIndex,
+               UpdateType aUpdateType);
+
+  nsresult
+  CreateJournals();
+
+  nsresult
+  RemoveJournals(const nsTArray<int64_t>& aJournals);
+};
+
+class DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction final
+{
+  CachedStatement mUpdateStatement;
+  CachedStatement mSelectStatement;
+  CachedStatement mInsertStatement;
+
+  UpdateRefcountFunction* mFunction;
+
+  nsresult mErrorCode;
+
+public:
+  explicit
+  DatabaseUpdateFunction(UpdateRefcountFunction* aFunction)
+    : mFunction(aFunction)
+    , mErrorCode(NS_OK)
+  {
+    MOZ_COUNT_CTOR(
+      DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
+  }
+
+  ~DatabaseUpdateFunction()
+  {
+    MOZ_COUNT_DTOR(
+      DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
+  }
+
+  bool
+  Update(int64_t aId, int32_t aDelta);
+
+  nsresult
+  ErrorCode() const
+  {
+    return mErrorCode;
+  }
+
+private:
+  nsresult
+  UpdateInternal(int64_t aId, int32_t aDelta);
+};
+
+class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final
+{
+  friend class UpdateRefcountFunction;
+
+  nsRefPtr<FileInfo> mFileInfo;
+  int32_t mDelta;
+  int32_t mSavepointDelta;
+
+public:
+  explicit
+  FileInfoEntry(FileInfo* aFileInfo)
+    : mFileInfo(aFileInfo)
+    , mDelta(0)
+    , mSavepointDelta(0)
+  {
+    MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
+  }
+
+  ~FileInfoEntry()
+  {
+    MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
+  }
+};
+
+class ConnectionPool final
+{
+public:
+  class FinishCallback;
+
+private:
+  class ConnectionRunnable;
+  class CheckpointConnectionRunnable;
+  class CloseConnectionRunnable;
+  struct DatabaseInfo;
+  struct DatabasesCompleteCallback;
+  class FinishCallbackWrapper;
+  struct IdleDatabaseInfo;
+  struct IdleResource;
+  struct IdleThreadInfo;
+  struct ThreadInfo;
+  class ThreadRunnable;
+  struct TransactionInfo;
+  struct TransactionInfoPair;
+
+  // This mutex guards mDatabases, see below.
+  Mutex mDatabasesMutex;
+
+  nsTArray<IdleThreadInfo> mIdleThreads;
+  nsTArray<IdleDatabaseInfo> mIdleDatabases;
+  nsCOMPtr<nsITimer> mIdleTimer;
+  TimeStamp mTargetIdleTime;
+
+  // Only modifed on the owning thread, but read on multiple threads. Therefore
+  // all modifications and all reads off the owning thread must be protected by
+  // mDatabasesMutex.
+  nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
+
+  nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
+  nsTArray<TransactionInfo*> mQueuedTransactions;
+
+  nsTArray<nsAutoPtr<DatabasesCompleteCallback>> mCompleteCallbacks;
+
+  uint64_t mNextTransactionId;
+  uint32_t mTotalThreadCount;
+  bool mShutdownRequested;
+  bool mShutdownComplete;
+
+#ifdef DEBUG
+  PRThread* mDEBUGOwningThread;
+#endif
+
+public:
+  ConnectionPool();
+
+  void
+  AssertIsOnOwningThread() const
+#ifdef DEBUG
+  ;
+#else
+  { }
+#endif
+
+  nsresult
+  GetOrCreateConnection(const Database* aDatabase,
+                        DatabaseConnection** aConnection);
+
+  uint64_t
+  Start(const nsID& aBackgroundChildLoggingId,
+        const nsACString& aDatabaseId,
+        int64_t aLoggingSerialNumber,
+        const nsTArray<nsString>& aObjectStoreNames,
+        bool aIsWriteTransaction,
+        TransactionDatabaseOperationBase* aTransactionOp);
+
+  void
+  Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
+
+  void
+  Finish(uint64_t aTransactionId, FinishCallback* aCallback);
+
+  void
+  CloseDatabaseWhenIdle(const nsACString& aDatabaseId)
+  {
+    unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
+  }
+
+  void
+  WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
+                             nsIRunnable* aCallback);
+
+  void
+  Shutdown();
+
+  NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
+
+private:
+  ~ConnectionPool();
+
+  static void
+  IdleTimerCallback(nsITimer* aTimer, void* aClosure);
+
+  void
+  Cleanup();
+
+  void
+  AdjustIdleTimer();
+
+  void
+  CancelIdleTimer();
+
+  void
+  ShutdownThread(ThreadInfo& aThreadInfo);
+
+  void
+  CloseIdleDatabases();
+
+  void
+  ShutdownIdleThreads();
+
+  bool
+  ScheduleTransaction(TransactionInfo* aTransactionInfo,
+                      bool aFromQueuedTransactions);
+
+  void
+  NoteFinishedTransaction(uint64_t aTransactionId);
+
+  void
+  ScheduleQueuedTransactions(ThreadInfo& aThreadInfo);
+
+  void
+  NoteIdleDatabase(DatabaseInfo* aDatabaseInfo);
+
+  void
+  NoteClosedDatabase(DatabaseInfo* aDatabaseInfo);
+
+  bool
+  MaybeFireCallback(DatabasesCompleteCallback* aCallback);
+
+  void
+  CheckpointDatabase(DatabaseInfo* aDatabaseInfo);
+
+  void
+  CloseDatabase(DatabaseInfo* aDatabaseInfo);
+
+  bool
+  CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
+};
+
+class ConnectionPool::ConnectionRunnable
+  : public nsRunnable
+{
+protected:
+  DatabaseInfo* mDatabaseInfo;
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+
+  explicit
+  ConnectionRunnable(DatabaseInfo* aDatabaseInfo);
+
+  virtual
+  ~ConnectionRunnable()
+  { }
+};
+
+class ConnectionPool::CheckpointConnectionRunnable final
+  : public ConnectionRunnable
+{
+public:
+  explicit
+  CheckpointConnectionRunnable(DatabaseInfo* aDatabaseInfo)
+    : ConnectionRunnable(aDatabaseInfo)
+  { }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~CheckpointConnectionRunnable()
+  { }
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class ConnectionPool::CloseConnectionRunnable final
+  : public ConnectionRunnable
+{
+public:
+  explicit
+  CloseConnectionRunnable(DatabaseInfo* aDatabaseInfo)
+    : ConnectionRunnable(aDatabaseInfo)
+  { }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~CloseConnectionRunnable()
+  { }
+
+  NS_DECL_NSIRUNNABLE
+};
+
+struct ConnectionPool::ThreadInfo
+{
+  nsCOMPtr<nsIThread> mThread;
+  nsRefPtr<ThreadRunnable> mRunnable;
+
+  ThreadInfo();
+
+  explicit
+  ThreadInfo(const ThreadInfo& aOther);
+
+  ~ThreadInfo();
+};
+
+struct ConnectionPool::DatabaseInfo final
+{
+  friend class nsAutoPtr<DatabaseInfo>;
+
+  nsRefPtr<ConnectionPool> mConnectionPool;
+  const nsCString mDatabaseId;
+  nsRefPtr<DatabaseConnection> mConnection;
+  nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
+  nsTArray<TransactionInfo*> mTransactionsScheduledDuringClose;
+  nsTArray<TransactionInfo*> mScheduledWriteTransactions;
+  TransactionInfo* mRunningWriteTransaction;
+  ThreadInfo mThreadInfo;
+  uint32_t mReadTransactionCount;
+  uint32_t mWriteTransactionCount;
+  bool mNeedsCheckpoint;
+  bool mCloseOnIdle;
+  bool mClosing;
+
+#ifdef DEBUG
+  PRThread* mDEBUGConnectionThread;
+#endif
+
+  DatabaseInfo(ConnectionPool* aConnectionPool,
+               const nsACString& aDatabaseId);
+
+  void
+  AssertIsOnConnectionThread() const
+  {
+    MOZ_ASSERT(mDEBUGConnectionThread);
+    MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGConnectionThread);
+  }
+
+  uint64_t
+  TotalTransactionCount() const
+  {
+    return mReadTransactionCount + mWriteTransactionCount;
+  }
+
+private:
+  ~DatabaseInfo();
+
+  DatabaseInfo(const DatabaseInfo&) = delete;
+  DatabaseInfo& operator=(const DatabaseInfo&) = delete;
+};
+
+struct ConnectionPool::DatabasesCompleteCallback final
+{
+  friend class nsAutoPtr<DatabasesCompleteCallback>;
+
+  nsTArray<nsCString> mDatabaseIds;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+  DatabasesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
+                            nsIRunnable* aCallback);
+
+private:
+  ~DatabasesCompleteCallback();
+};
+
+class NS_NO_VTABLE ConnectionPool::FinishCallback
+  : public nsIRunnable
+{
+public:
+  // Called on the owning thread before any additional transactions are
+  // unblocked.
+  virtual void
+  TransactionFinishedBeforeUnblock() = 0;
+
+  // Called on the owning thread after additional transactions may have been
+  // unblocked.
+  virtual void
+  TransactionFinishedAfterUnblock() = 0;
+
+protected:
+  FinishCallback()
+  { }
+
+  virtual ~FinishCallback()
+  { }
+};
+
+class ConnectionPool::FinishCallbackWrapper final
+  : public nsRunnable
+{
+  nsRefPtr<ConnectionPool> mConnectionPool;
+  nsRefPtr<FinishCallback> mCallback;
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  uint64_t mTransactionId;
+  bool mHasRunOnce;
+
+public:
+  FinishCallbackWrapper(ConnectionPool* aConnectionPool,
+                        uint64_t aTransactionId,
+                        FinishCallback* aCallback);
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~FinishCallbackWrapper();
+
+  NS_DECL_NSIRUNNABLE
+};
+
+struct ConnectionPool::IdleResource
+{
+  TimeStamp mIdleTime;
+
+protected:
+  explicit
+  IdleResource(const TimeStamp& aIdleTime);
+
+  explicit
+  IdleResource(const IdleResource& aOther);
+
+  ~IdleResource();
+};
+
+struct ConnectionPool::IdleDatabaseInfo final
+  : public IdleResource
+{
+  DatabaseInfo* mDatabaseInfo;
+
+public:
+  MOZ_IMPLICIT
+  IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo);
+
+  explicit
+  IdleDatabaseInfo(const IdleDatabaseInfo& aOther);
+
+  ~IdleDatabaseInfo();
+
+  bool
+  operator==(const IdleDatabaseInfo& aOther) const
+  {
+    return mDatabaseInfo == aOther.mDatabaseInfo;
+  }
+
+  bool
+  operator<(const IdleDatabaseInfo& aOther) const
+  {
+    return mIdleTime < aOther.mIdleTime;
+  }
+};
+
+struct ConnectionPool::IdleThreadInfo final
+  : public IdleResource
+{
+  ThreadInfo mThreadInfo;
+
+public:
+  // Boo, this is needed because nsTArray::InsertElementSorted() doesn't yet
+  // work with rvalue references.
+  MOZ_IMPLICIT
+  IdleThreadInfo(const ThreadInfo& aThreadInfo);
+
+  explicit
+  IdleThreadInfo(const IdleThreadInfo& aOther);
+
+  ~IdleThreadInfo();
+
+  bool
+  operator==(const IdleThreadInfo& aOther) const
+  {
+    return mThreadInfo.mRunnable == aOther.mThreadInfo.mRunnable &&
+           mThreadInfo.mThread == aOther.mThreadInfo.mThread;
+  }
+
+  bool
+  operator<(const IdleThreadInfo& aOther) const
+  {
+    return mIdleTime < aOther.mIdleTime;
+  }
+};
+
+class ConnectionPool::ThreadRunnable final
+  : public nsRunnable
+{
+  // Only touched on the background thread.
+  static uint32_t sNextSerialNumber;
+
+  // Set at construction for logging.
+  const uint32_t mSerialNumber;
+
+  // These two values are only modified on the connection thread.
+  bool mFirstRun;
+  bool mContinueRunning;
+
+public:
+  ThreadRunnable();
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  uint32_t
+  SerialNumber() const
+  {
+    return mSerialNumber;
+  }
+
+private:
+  ~ThreadRunnable();
+
+  NS_DECL_NSIRUNNABLE
+};
+
+struct ConnectionPool::TransactionInfo final
+{
+  friend class nsAutoPtr<TransactionInfo>;
+
+  DatabaseInfo* mDatabaseInfo;
+  const nsID mBackgroundChildLoggingId;
+  const nsCString mDatabaseId;
+  const uint64_t mTransactionId;
+  const int64_t mLoggingSerialNumber;
+  const nsTArray<nsString> mObjectStoreNames;
+  nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlockedOn;
+  nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlocking;
+  nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
+  const bool mIsWriteTransaction;
+  bool mRunning;
+
+  DebugOnly<bool> mFinished;
+
+  TransactionInfo(DatabaseInfo* aDatabaseInfo,
+                  const nsID& aBackgroundChildLoggingId,
+                  const nsACString& aDatabaseId,
+                  uint64_t aTransactionId,
+                  int64_t aLoggingSerialNumber,
+                  const nsTArray<nsString>& aObjectStoreNames,
+                  bool aIsWriteTransaction,
+                  TransactionDatabaseOperationBase* aTransactionOp);
+
+  void
+  Schedule();
+
+private:
+  ~TransactionInfo();
+};
+
+struct ConnectionPool::TransactionInfoPair final
+{
+  friend class nsAutoPtr<TransactionInfoPair>;
+
+  // Multiple reading transactions can block future writes.
+  nsTArray<TransactionInfo*> mLastBlockingWrites;
+  // But only a single writing transaction can block future reads.
+  TransactionInfo* mLastBlockingReads;
+
+  TransactionInfoPair();
+
+private:
+  ~TransactionInfoPair();
+};
+
+/*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
 class DatabaseOperationBase
   : public nsRunnable
   , public mozIStorageProgressHandler
 {
 protected:
@@ -2660,78 +5188,151 @@ protected:
   static uint64_t
   ReinterpretDoubleAsUInt64(double aDouble);
 
   static nsresult
   GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
                                           uint32_t aDataIndex,
                                           uint32_t aFileIdsIndex,
                                           FileManager* aFileManager,
-                                          StructuredCloneReadInfo* aInfo);
+                                          StructuredCloneReadInfo* aInfo)
+  {
+    return GetStructuredCloneReadInfoFromSource(aStatement,
+                                                aDataIndex,
+                                                aFileIdsIndex,
+                                                aFileManager,
+                                                aInfo);
+  }
+
+  static nsresult
+  GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray* aValues,
+                                           uint32_t aDataIndex,
+                                           uint32_t aFileIdsIndex,
+                                           FileManager* aFileManager,
+                                           StructuredCloneReadInfo* aInfo)
+  {
+    return GetStructuredCloneReadInfoFromSource(aValues,
+                                                aDataIndex,
+                                                aFileIdsIndex,
+                                                aFileManager,
+                                                aInfo);
+  }
 
   static nsresult
   BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
                           mozIStorageStatement* aStatement);
 
   static void
   AppendConditionClause(const nsACString& aColumnName,
                         const nsACString& aArgName,
                         bool aLessThan,
                         bool aEquals,
                         nsAutoCString& aResult);
 
   static nsresult
-  UpdateIndexes(TransactionBase* aTransaction,
-                const UniqueIndexTable& aUniqueIndexTable,
-                const Key& aObjectStoreKey,
-                bool aOverwrite,
-                int64_t aObjectDataId,
-                const nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
-
-private:
+  GetUniqueIndexTableForObjectStore(
+                               TransactionBase* aTransaction,
+                               int64_t aObjectStoreId,
+                               Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable);
+
+  static nsresult
+  IndexDataValuesFromUpdateInfos(
+                              const nsTArray<IndexUpdateInfo>& aUpdateInfos,
+                              const UniqueIndexTable& aUniqueIndexTable,
+                              FallibleTArray<IndexDataValue>& aIndexValues);
+
+  static nsresult
+  InsertIndexTableRows(DatabaseConnection* aConnection,
+                       const int64_t aObjectStoreId,
+                       const Key& aObjectStoreKey,
+                       const FallibleTArray<IndexDataValue>& aIndexValues);
+
+  static nsresult
+  DeleteIndexDataTableRows(DatabaseConnection* aConnection,
+                           const Key& aObjectStoreKey,
+                           const FallibleTArray<IndexDataValue>& aIndexValues);
+
+  static nsresult
+  DeleteObjectStoreDataTableRowsWithIndexes(DatabaseConnection* aConnection,
+                                            const int64_t aObjectStoreId,
+                                            const OptionalKeyRange& aKeyRange);
+
+  static nsresult
+  UpdateIndexValues(DatabaseConnection* aConnection,
+                    const int64_t aObjectStoreId,
+                    const Key& aObjectStoreKey,
+                    const FallibleTArray<IndexDataValue>& aIndexValues);
+
+#ifdef DEBUG
+  static bool
+  ObjectStoreHasIndexes(DatabaseConnection* aConnection,
+                        const int64_t aObjectStoreId);
+#endif
+
+private:
+  template <typename T>
+  static nsresult
+  GetStructuredCloneReadInfoFromSource(T* aSource,
+                                       uint32_t aDataIndex,
+                                       uint32_t aFileIdsIndex,
+                                       FileManager* aFileManager,
+                                       StructuredCloneReadInfo* aInfo);
+
+  static nsresult
+  GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
+                                     uint32_t aBlobDataLength,
+                                     const nsAString& aFileIds,
+                                     FileManager* aFileManager,
+                                     StructuredCloneReadInfo* aInfo);
+
   // Not to be overridden by subclasses.
   NS_DECL_MOZISTORAGEPROGRESSHANDLER
 };
 
 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
 {
   mozIStorageConnection* mConnection;
   DebugOnly<DatabaseOperationBase*> mDEBUGDatabaseOp;
 
 public:
-  AutoSetProgressHandler()
-    : mConnection(nullptr)
-    , mDEBUGDatabaseOp(nullptr)
-  { }
+  AutoSetProgressHandler();
 
   ~AutoSetProgressHandler();
 
   nsresult
-  Register(DatabaseOperationBase* aDatabaseOp,
-           const nsCOMPtr<mozIStorageConnection>& aConnection);
+  Register(mozIStorageConnection* aConnection,
+           DatabaseOperationBase* aDatabaseOp);
 };
 
 class TransactionDatabaseOperationBase
   : public DatabaseOperationBase
 {
   nsRefPtr<TransactionBase> mTransaction;
   const int64_t mTransactionLoggingSerialNumber;
   const bool mTransactionIsAborted;
 
 public:
   void
-  AssertIsOnTransactionThread() const
+  AssertIsOnConnectionThread() const
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
 
   void
-  DispatchToTransactionThreadPool();
+  DispatchToConnectionPool();
+
+  TransactionBase*
+  Transaction() const
+  {
+    MOZ_ASSERT(mTransaction);
+
+    return mTransaction;
+  }
 
   // May be overridden by subclasses if they need to perform work on the
   // background thread before being dispatched. Returning false will kill the
   // child actors and prevent dispatch.
   virtual bool
   Init(TransactionBase* aTransaction);
 
   // This callback will be called on the background thread before releasing the
@@ -2745,41 +5346,41 @@ protected:
   TransactionDatabaseOperationBase(TransactionBase* aTransaction);
 
   TransactionDatabaseOperationBase(TransactionBase* aTransaction,
                                    uint64_t aLoggingSerialNumber);
 
   virtual
   ~TransactionDatabaseOperationBase();
 
+  virtual void
+  RunOnConnectionThread();
+
   // Must be overridden in subclasses. Called on the target thread to allow the
   // subclass to perform necessary database or file operations. A successful
   // return value will trigger a SendSuccessResult callback on the background
   // thread while a failure value will trigger a SendFailureResult callback.
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) = 0;
+  DoDatabaseWork(DatabaseConnection* aConnection) = 0;
 
   // Must be overridden in subclasses. Called on the background thread to allow
   // the subclass to serialize its results and send them to the child actor. A
   // failed return value will trigger a SendFailureResult callback.
   virtual nsresult
   SendSuccessResult() = 0;
 
   // Must be overridden in subclasses. Called on the background thread to allow
   // the subclass to send its failure code. Returning false will cause the
   // transaction to be aborted with aResultCode. Returning true will not cause
   // the transaction to be aborted.
   virtual bool
   SendFailureResult(nsresult aResultCode) = 0;
 
 private:
   void
-  RunOnTransactionThread();
-
-  void
   RunOnOwningThread();
 
   // Not to be overridden by subclasses.
   NS_DECL_NSIRUNNABLE
 };
 
 class Factory final
   : public PBackgroundIDBFactoryParent
@@ -2851,21 +5452,25 @@ private:
                                       override;
 };
 
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
+  class StartTransactionOp;
+
+private:
   nsRefPtr<Factory> mFactory;
   nsRefPtr<FullDatabaseMetadata> mMetadata;
   nsRefPtr<FileManager> mFileManager;
   nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
+  nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
   const PersistenceType mPersistenceType;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
@@ -2880,16 +5485,31 @@ public:
            const PrincipalInfo& aPrincipalInfo,
            const nsACString& aGroup,
            const nsACString& aOrigin,
            FullDatabaseMetadata* aMetadata,
            FileManager* aFileManager,
            already_AddRefed<DatabaseOfflineStorage> aOfflineStorage,
            bool aChromeWriteAccessAllowed);
 
+  void
+  AssertIsOnConnectionThread() const
+  {
+#ifdef DEBUG
+    if (mConnection) {
+      MOZ_ASSERT(mConnection);
+      mConnection->AssertIsOnConnectionThread();
+    } else {
+      MOZ_ASSERT(!NS_IsMainThread());
+      MOZ_ASSERT(!IsOnBackgroundThread());
+      MOZ_ASSERT(mInvalidated);
+    }
+#endif
+  }
+
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Database)
 
   void
   Invalidate();
 
   const PrincipalInfo&
   GetPrincipalInfo() const
   {
@@ -2952,16 +5572,22 @@ public:
   GetLoggingInfo() const
   {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(mFactory);
 
     return mFactory->GetLoggingInfo();
   }
 
+  void
+  ReleaseTransactionThreadObjects();
+
+  void
+  ReleaseBackgroundThreadObjects();
+
   bool
   RegisterTransaction(TransactionBase* aTransaction);
 
   void
   UnregisterTransaction(TransactionBase* aTransaction);
 
   void
   SetActorAlive();
@@ -2993,28 +5619,49 @@ public:
   bool
   IsInvalidated() const
   {
     AssertIsOnBackgroundThread();
 
     return mInvalidated;
   }
 
+  nsresult
+  EnsureConnection();
+
+  DatabaseConnection*
+  GetConnection() const
+  {
+#ifdef DEBUG
+    if (mConnection) {
+      mConnection->AssertIsOnConnectionThread();
+    }
+#endif
+
+    return mConnection;
+  }
+
 private:
   // Reference counted.
   ~Database()
   {
     MOZ_ASSERT(mClosed);
     MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
   }
 
   bool
   CloseInternal();
 
   void
+  CloseConnection();
+
+  void
+  ConnectionClosedCallback();
+
+  void
   CleanupMetadata();
 
   // IPDL methods are only called by IPDL.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual PBackgroundIDBDatabaseFileParent*
   AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
@@ -3061,16 +5708,47 @@ private:
 
   virtual bool
   RecvBlocked() override;
 
   virtual bool
   RecvClose() override;
 };
 
+class Database::StartTransactionOp final
+  : public TransactionDatabaseOperationBase
+{
+  friend class Database;
+
+private:
+  explicit
+  StartTransactionOp(TransactionBase* aTransaction)
+    : TransactionDatabaseOperationBase(aTransaction,
+                                       /* aLoggingSerialNumber */ 0)
+  { }
+
+  ~StartTransactionOp()
+  { }
+
+  virtual void
+  RunOnConnectionThread() override;
+
+  virtual nsresult
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+  virtual nsresult
+  SendSuccessResult() override;
+
+  virtual bool
+  SendFailureResult(nsresult aResultCode) override;
+
+  virtual void
+  Cleanup() override;
+};
+
 class DatabaseFile final
   : public PBackgroundIDBDatabaseFileParent
 {
   friend class Database;
 
   nsRefPtr<FileImpl> mBlobImpl;
   nsRefPtr<FileInfo> mFileInfo;
 
@@ -3139,59 +5817,46 @@ private:
   }
 };
 
 class TransactionBase
 {
   friend class Cursor;
 
   class CommitOp;
-  class UpdateRefcountFunction;
-
-public:
-  class AutoSavepoint;
-  class CachedStatement;
 
 protected:
   typedef IDBTransaction::Mode Mode;
 
 private:
   nsRefPtr<Database> mDatabase;
-  nsCOMPtr<mozIStorageConnection> mConnection;
-  nsRefPtr<UpdateRefcountFunction> mUpdateFileRefcountFunction;
-  nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
-    mCachedStatements;
   nsTArray<nsRefPtr<FullObjectStoreMetadata>>
     mModifiedAutoIncrementObjectStoreMetadataArray;
-  const uint64_t mTransactionId;
+  uint64_t mTransactionId;
   const nsCString mDatabaseId;
   const int64_t mLoggingSerialNumber;
   uint64_t mActiveRequestCount;
   Atomic<bool> mInvalidatedOnAnyThread;
-  Mode mMode;
+  const Mode mMode;
   bool mHasBeenActive;
   bool mActorDestroyed;
   bool mInvalidated;
 
 protected:
   nsresult mResultCode;
   bool mCommitOrAbortReceived;
   bool mCommittedOrAborted;
   bool mForceAborted;
 
-private:
-  DebugOnly<PRThread*> mTransactionThread;
-  DebugOnly<uint32_t> mSavepointCount;
-
-public:
-  void
-  AssertIsOnTransactionThread() const
-  {
-    MOZ_ASSERT(mTransactionThread);
-    MOZ_ASSERT(PR_GetCurrentThread() == mTransactionThread);
+public:
+  void
+  AssertIsOnConnectionThread() const
+  {
+    MOZ_ASSERT(mDatabase);
+    mDatabase->AssertIsOnConnectionThread();
   }
 
   bool
   IsActorDestroyed() const
   {
     AssertIsOnBackgroundThread();
 
     return mActorDestroyed;
@@ -3210,56 +5875,31 @@ public:
   // May be called on any thread, but is more expensive than IsInvalidated().
   bool
   IsInvalidatedOnAnyThread() const
   {
     return mInvalidatedOnAnyThread;
   }
 
   void
-  SetActive()
+  SetActive(uint64_t aTransactionId)
   {
     AssertIsOnBackgroundThread();
-
+    MOZ_ASSERT(aTransactionId);
+
+    mTransactionId = aTransactionId;
     mHasBeenActive = true;
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
     mozilla::dom::indexedDB::TransactionBase)
 
-  nsresult
-  GetCachedStatement(const nsACString& aQuery,
-                     CachedStatement* aCachedStatement);
-
-  template<int N>
-  nsresult
-  GetCachedStatement(const char (&aQuery)[N],
-                     CachedStatement* aCachedStatement)
-  {
-    AssertIsOnTransactionThread();
-    MOZ_ASSERT(aCachedStatement);
-
-    return GetCachedStatement(NS_LITERAL_CSTRING(aQuery), aCachedStatement);
-  }
-
-  nsresult
-  EnsureConnection();
-
   void
   Abort(nsresult aResultCode, bool aForce);
 
-  mozIStorageConnection*
-  Connection() const
-  {
-    AssertIsOnTransactionThread();
-    MOZ_ASSERT(mConnection);
-
-    return mConnection;
-  }
-
   uint64_t
   TransactionId() const
   {
     return mTransactionId;
   }
 
   const nsCString&
   DatabaseId() const
@@ -3271,17 +5911,16 @@ public:
   GetMode() const
   {
     return mMode;
   }
 
   Database*
   GetDatabase() const
   {
-    AssertIsOnBackgroundThread();
     MOZ_ASSERT(mDatabase);
 
     return mDatabase;
   }
 
   DatabaseLoggingInfo*
   GetLoggingInfo() const
   {
@@ -3322,25 +5961,16 @@ public:
   }
 
   void
   NoteModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
 
   void
   ForgetModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
 
-  nsresult
-  StartSavepoint();
-
-  nsresult
-  ReleaseSavepoint();
-
-  nsresult
-  RollbackSavepoint();
-
   void
   NoteActiveRequest();
 
   void
   NoteFinishedRequest();
 
   void
   Invalidate();
@@ -3419,28 +6049,20 @@ protected:
 
   bool
   DeallocCursor(PBackgroundIDBCursorParent* aActor);
 
   virtual void
   UpdateMetadata(nsresult aResult)
   { }
 
-  virtual bool
+  virtual void
   SendCompleteNotification(nsresult aResult) = 0;
 
 private:
-  // Only called by CommitOp.
-  void
-  ReleaseTransactionThreadObjects();
-
-  // Only called by CommitOp.
-  void
-  ReleaseBackgroundThreadObjects();
-
   bool
   VerifyRequestParams(const RequestParams& aParams) const;
 
   bool
   VerifyRequestParams(const OpenCursorParams& aParams) const;
 
   bool
   VerifyRequestParams(const CursorRequestParams& aParams) const;
@@ -3455,17 +6077,17 @@ private:
   VerifyRequestParams(const OptionalKeyRange& aKeyRange) const;
 
   void
   CommitOrAbort();
 };
 
 class TransactionBase::CommitOp final
   : public DatabaseOperationBase
-  , public TransactionThreadPool::FinishCallback
+  , public ConnectionPool::FinishCallback
 {
   friend class TransactionBase;
 
   nsRefPtr<TransactionBase> mTransaction;
   nsresult mResultCode;
 
 private:
   CommitOp(TransactionBase* aTransaction, nsresult aResultCode);
@@ -3476,236 +6098,36 @@ private:
   // Writes new autoIncrement counts to database.
   nsresult
   WriteAutoIncrementCounts();
 
   // Updates counts after a database activity has finished.
   void
   CommitOrRollbackAutoIncrementCounts();
 
+  void
+  AssertForeignKeyConsistency(DatabaseConnection* aConnection)
+#ifdef DEBUG
+  ;
+#else
+  { }
+#endif
+
   NS_DECL_NSIRUNNABLE
 
   virtual void
   TransactionFinishedBeforeUnblock() override;
 
   virtual void
   TransactionFinishedAfterUnblock() override;
 
 public:
-  void
-  AssertIsOnTransactionThread() const
-  {
-    MOZ_ASSERT(mTransaction);
-    mTransaction->AssertIsOnTransactionThread();
-  }
-
   NS_DECL_ISUPPORTS_INHERITED
 };
 
-class TransactionBase::UpdateRefcountFunction final
-  : public mozIStorageFunction
-{
-  class FileInfoEntry
-  {
-    friend class UpdateRefcountFunction;
-
-    nsRefPtr<FileInfo> mFileInfo;
-    int32_t mDelta;
-    int32_t mSavepointDelta;
-
-  public:
-    explicit FileInfoEntry(FileInfo* aFileInfo)
-      : mFileInfo(aFileInfo)
-      , mDelta(0)
-      , mSavepointDelta(0)
-    { }
-  };
-
-  enum UpdateType
-  {
-    eIncrement,
-    eDecrement
-  };
-
-  class DatabaseUpdateFunction
-  {
-    nsCOMPtr<mozIStorageConnection> mConnection;
-    nsCOMPtr<mozIStorageStatement> mUpdateStatement;
-    nsCOMPtr<mozIStorageStatement> mSelectStatement;
-    nsCOMPtr<mozIStorageStatement> mInsertStatement;
-
-    UpdateRefcountFunction* mFunction;
-
-    nsresult mErrorCode;
-
-  public:
-    DatabaseUpdateFunction(mozIStorageConnection* aConnection,
-                           UpdateRefcountFunction* aFunction)
-      : mConnection(aConnection)
-      , mFunction(aFunction)
-      , mErrorCode(NS_OK)
-    { }
-
-    bool
-    Update(int64_t aId, int32_t aDelta);
-
-    nsresult
-    ErrorCode() const
-    {
-      return mErrorCode;
-    }
-
-  private:
-    nsresult
-    UpdateInternal(int64_t aId, int32_t aDelta);
-  };
-
-  FileManager* mFileManager;
-  nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
-  nsDataHashtable<nsUint64HashKey, FileInfoEntry*> mSavepointEntriesIndex;
-
-  nsTArray<int64_t> mJournalsToCreateBeforeCommit;
-  nsTArray<int64_t> mJournalsToRemoveAfterCommit;
-  nsTArray<int64_t> mJournalsToRemoveAfterAbort;
-
-  bool mInSavepoint;
-
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_MOZISTORAGEFUNCTION
-
-  explicit UpdateRefcountFunction(FileManager* aFileManager)
-    : mFileManager(aFileManager)
-    , mInSavepoint(false)
-  { }
-
-  void
-  ClearFileInfoEntries()
-  {
-    mFileInfoEntries.Clear();
-  }
-
-  nsresult
-  WillCommit(mozIStorageConnection* aConnection);
-
-  void
-  DidCommit();
-
-  void
-  DidAbort();
-
-  void
-  StartSavepoint();
-
-  void
-  ReleaseSavepoint();
-
-  void
-  RollbackSavepoint();
-
-private:
-  ~UpdateRefcountFunction()
-  { }
-
-  nsresult
-  ProcessValue(mozIStorageValueArray* aValues,
-               int32_t aIndex,
-               UpdateType aUpdateType);
-
-  nsresult
-  CreateJournals();
-
-  nsresult
-  RemoveJournals(const nsTArray<int64_t>& aJournals);
-
-  static PLDHashOperator
-  DatabaseUpdateCallback(const uint64_t& aKey,
-                         FileInfoEntry* aValue,
-                         void* aUserArg);
-
-  static PLDHashOperator
-  FileInfoUpdateCallback(const uint64_t& aKey,
-                         FileInfoEntry* aValue,
-                         void* aUserArg);
-};
-
-class MOZ_STACK_CLASS TransactionBase::AutoSavepoint final
-{
-  TransactionBase* mTransaction;
-
-public:
-  AutoSavepoint()
-    : mTransaction(nullptr)
-  { }
-
-  ~AutoSavepoint();
-
-  nsresult
-  Start(TransactionBase* aTransaction);
-
-  nsresult
-  Commit();
-};
-
-class TransactionBase::CachedStatement final
-{
-  friend class TransactionBase;
-
-  nsCOMPtr<mozIStorageStatement> mStatement;
-  Maybe<mozStorageStatementScoper> mScoper;
-
-public:
-  CachedStatement()
-  { }
-
-  ~CachedStatement()
-  { }
-
-  operator mozIStorageStatement*()
-  {
-    return mStatement;
-  }
-
-  mozIStorageStatement*
-  operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
-  {
-    MOZ_ASSERT(mStatement);
-    return mStatement;
-  }
-
-  void
-  Reset()
-  {
-    MOZ_ASSERT_IF(mStatement, mScoper);
-
-    if (mStatement) {
-      mScoper.reset();
-      mScoper.emplace(mStatement);
-    }
-  }
-
-private:
-  // Only called by TransactionBase.
-  void
-  Assign(already_AddRefed<mozIStorageStatement> aStatement)
-  {
-    mScoper.reset();
-
-    mStatement = aStatement;
-
-    if (mStatement) {
-      mScoper.emplace(mStatement);
-    }
-  }
-
-  // No funny business allowed.
-  CachedStatement(const CachedStatement&) = delete;
-  CachedStatement& operator=(const CachedStatement&) = delete;
-};
-
 class NormalTransaction final
   : public TransactionBase
   , public PBackgroundIDBTransactionParent
 {
   friend class Database;
 
   nsTArray<nsRefPtr<FullObjectStoreMetadata>> mObjectStores;
 
@@ -3718,17 +6140,17 @@ private:
   // Reference counted.
   ~NormalTransaction()
   { }
 
   bool
   IsSameProcessActor();
 
   // Only called by TransactionBase.
-  virtual bool
+  virtual void
   SendCompleteNotification(nsresult aResult) override;
 
   // IPDL methods are only called by IPDL.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual bool
   RecvDeleteMe() override;
@@ -3792,17 +6214,17 @@ private:
   void
   SetActorAlive();
 
   // Only called by TransactionBase.
   virtual void
   UpdateMetadata(nsresult aResult) override;
 
   // Only called by TransactionBase.
-  virtual bool
+  virtual void
   SendCompleteNotification(nsresult aResult) override;
 
   // IPDL methods are only called by IPDL.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual bool
   RecvDeleteMe() override;
@@ -4137,16 +6559,19 @@ private:
   void
   AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
 
+  void
+  ConnectionClosedCallback();
+
   virtual nsresult
   QuotaManagerOpen() override;
 
   virtual nsresult
   DoDatabaseWork() override;
 
   virtual nsresult
   BeginVersionChange() override;
@@ -4186,17 +6611,17 @@ private:
     MOZ_ASSERT(aOpenDatabaseOp);
     MOZ_ASSERT(mRequestedVersion);
   }
 
   ~VersionChangeOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual nsresult
   SendSuccessResult() override;
 
   virtual bool
   SendFailureResult(nsresult aResultCode) override;
 
   virtual void
@@ -4276,16 +6701,21 @@ private:
   RunOnMainThread();
 
   nsresult
   RunOnIOThread();
 
   void
   RunOnOwningThread();
 
+  nsresult
+  DeleteFile(nsIFile* aDirectory,
+             const nsAString& aFilename,
+             QuotaManager* aQuotaManager);
+
   NS_DECL_NSIRUNNABLE
 };
 
 class VersionChangeTransactionOp
   : public TransactionDatabaseOperationBase
 {
 public:
   virtual void
@@ -4324,50 +6754,55 @@ private:
   {
     MOZ_ASSERT(aMetadata.id());
   }
 
   ~CreateObjectStoreOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class DeleteObjectStoreOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
   const nsRefPtr<FullObjectStoreMetadata> mMetadata;
+  const bool mIsLastObjectStore;
+  const bool mObjectStoreHasIndexes;
 
 private:
   // Only created by VersionChangeTransaction.
   DeleteObjectStoreOp(VersionChangeTransaction* aTransaction,
-                      FullObjectStoreMetadata* const aMetadata)
+                      FullObjectStoreMetadata* const aMetadata,
+                      const bool aIsLastObjectStore)
     : VersionChangeTransactionOp(aTransaction)
     , mMetadata(aMetadata)
+    , mIsLastObjectStore(aIsLastObjectStore)
+    , mObjectStoreHasIndexes(aMetadata->HasLiveIndexes())
   {
     MOZ_ASSERT(aMetadata->mCommonMetadata.id());
   }
 
   ~DeleteObjectStoreOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class CreateIndexOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
   class ThreadLocalJSRuntime;
-  friend class ThreadLocalJSRuntime;
+  class UpdateIndexDataValuesFunction;
 
   static const unsigned int kBadThreadLocalIndex =
     static_cast<unsigned int>(-1);
 
   static unsigned int sThreadLocalIndex;
 
   const IndexMetadata mMetadata;
   Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
@@ -4379,27 +6814,27 @@ private:
   // Only created by VersionChangeTransaction.
   CreateIndexOp(VersionChangeTransaction* aTransaction,
                 const int64_t aObjectStoreId,
                 const IndexMetadata& aMetadata);
 
   ~CreateIndexOp()
   { }
 
-  static void
-  InitThreadLocals();
-
-  nsresult
-  InsertDataFromObjectStore(TransactionBase* aTransaction);
+  nsresult
+  InsertDataFromObjectStore(DatabaseConnection* aConnection);
+
+  nsresult
+  InsertDataFromObjectStoreInternal(DatabaseConnection* aConnection);
 
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class CreateIndexOp::ThreadLocalJSRuntime final
 {
   friend class CreateIndexOp;
   friend class nsAutoPtr<ThreadLocalJSRuntime>;
 
   static const JSClass kGlobalClass;
@@ -4446,38 +6881,74 @@ private:
       JS_DestroyRuntime(mRuntime);
     }
   }
 
   bool
   Init();
 };
 
+class CreateIndexOp::UpdateIndexDataValuesFunction final
+  : public mozIStorageFunction
+{
+  nsRefPtr<CreateIndexOp> mOp;
+  nsRefPtr<DatabaseConnection> mConnection;
+  JSContext* mCx;
+
+public:
+  UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
+                                DatabaseConnection* aConnection,
+                                JSContext* aCx)
+    : mOp(aOp)
+    , mConnection(aConnection)
+    , mCx(aCx)
+  {
+    MOZ_ASSERT(aOp);
+    MOZ_ASSERT(aConnection);
+    aConnection->AssertIsOnConnectionThread();
+    MOZ_ASSERT(aCx);
+  }
+
+  NS_DECL_ISUPPORTS
+
+private:
+  ~UpdateIndexDataValuesFunction()
+  { }
+
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
 class DeleteIndexOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
+  const int64_t mObjectStoreId;
   const int64_t mIndexId;
+  const bool mUnique;
+  const bool mIsLastIndex;
 
 private:
   // Only created by VersionChangeTransaction.
   DeleteIndexOp(VersionChangeTransaction* aTransaction,
-                const int64_t aIndexId)
-    : VersionChangeTransactionOp(aTransaction)
-    , mIndexId(aIndexId)
-  {
-    MOZ_ASSERT(aIndexId);
-  }
+                const int64_t aObjectStoreId,
+                const int64_t aIndexId,
+                const bool aUnique,
+                const bool aIsLastIndex);
 
   ~DeleteIndexOp()
   { }
 
+  nsresult
+  RemoveReferencesToIndex(DatabaseConnection* aConnection,
+                          const Key& aObjectDataKey,
+                          FallibleTArray<IndexDataValue>& aIndexValues);
+
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class NormalTransactionOp
   : public TransactionDatabaseOperationBase
   , public PBackgroundIDBRequestParent
 {
   DebugOnly<bool> mResponseSent;
 
@@ -4531,33 +7002,37 @@ class ObjectStoreAddOrPutRequestOp final
 
   nsRefPtr<FileManager> mFileManager;
 
   Key mResponse;
   const nsCString mGroup;
   const nsCString mOrigin;
   const PersistenceType mPersistenceType;
   const bool mOverwrite;
+  const bool mObjectStoreHasIndexes;
 
 private:
   // Only created by TransactionBase.
   ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp()
   { }
 
   nsresult
+  RemoveOldIndexDataValues(DatabaseConnection* aConnection);
+
+  nsresult
   CopyFileData(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream);
 
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 
   virtual void
   Cleanup() override;
 };
 
@@ -4606,17 +7081,17 @@ private:
   ~ObjectStoreGetRequestOp()
   { }
 
   nsresult
   ConvertResponse(uint32_t aIndex,
                   SerializedStructuredCloneReadInfo& aSerializedInfo);
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class ObjectStoreGetAllKeysRequestOp final
   : public NormalTransactionOp
 {
@@ -4632,70 +7107,66 @@ private:
     : NormalTransactionOp(aTransaction)
     , mParams(aParams)
   { }
 
   ~ObjectStoreGetAllKeysRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class ObjectStoreDeleteRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreDeleteParams mParams;
   ObjectStoreDeleteResponse mResponse;
+  const bool mObjectStoreHasIndexes;
 
 private:
   ObjectStoreDeleteRequestOp(TransactionBase* aTransaction,
-                             const ObjectStoreDeleteParams& aParams)
-    : NormalTransactionOp(aTransaction)
-    , mParams(aParams)
-  { }
+                             const ObjectStoreDeleteParams& aParams);
 
   ~ObjectStoreDeleteRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override
   {
     aResponse = Move(mResponse);
   }
 };
 
 class ObjectStoreClearRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreClearParams mParams;
   ObjectStoreClearResponse mResponse;
+  const bool mObjectStoreHasIndexes;
 
 private:
   ObjectStoreClearRequestOp(TransactionBase* aTransaction,
-                            const ObjectStoreClearParams& aParams)
-    : NormalTransactionOp(aTransaction)
-    , mParams(aParams)
-  { }
+                            const ObjectStoreClearParams& aParams);
 
   ~ObjectStoreClearRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override
   {
     aResponse = Move(mResponse);
   }
 };
 
@@ -4713,17 +7184,17 @@ private:
     : NormalTransactionOp(aTransaction)
     , mParams(aParams)
   { }
 
   ~ObjectStoreCountRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override
   {
     aResponse = Move(mResponse);
   }
 };
 
@@ -4767,17 +7238,17 @@ private:
   IndexGetRequestOp(TransactionBase* aTransaction,
                     const RequestParams& aParams,
                     bool aGetAll);
 
   ~IndexGetRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class IndexGetKeyRequestOp final
   : public IndexRequestOpBase
 {
@@ -4793,17 +7264,17 @@ private:
   IndexGetKeyRequestOp(TransactionBase* aTransaction,
                        const RequestParams& aParams,
                        bool aGetAll);
 
   ~IndexGetKeyRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class IndexCountRequestOp final
   : public IndexRequestOpBase
 {
@@ -4819,17 +7290,17 @@ private:
     : IndexRequestOpBase(aTransaction, aParams)
     , mParams(aParams.get_IndexCountParams())
   { }
 
   ~IndexCountRequestOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override
   {
     aResponse = Move(mResponse);
   }
 };
 
@@ -4958,29 +7429,29 @@ private:
   // Reference counted.
   ~OpenOp()
   { }
 
   void
   GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen);
 
   nsresult
-  DoObjectStoreDatabaseWork(TransactionBase* aTransaction);
-
-  nsresult
-  DoObjectStoreKeyDatabaseWork(TransactionBase* aTransaction);
-
-  nsresult
-  DoIndexDatabaseWork(TransactionBase* aTransaction);
-
-  nsresult
-  DoIndexKeyDatabaseWork(TransactionBase* aTransaction);
+  DoObjectStoreDatabaseWork(DatabaseConnection* aConnection);
+
+  nsresult
+  DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection);
+
+  nsresult
+  DoIndexDatabaseWork(DatabaseConnection* aConnection);
+
+  nsresult
+  DoIndexKeyDatabaseWork(DatabaseConnection* aConnection);
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual nsresult
   SendSuccessResult() override;
 };
 
 class Cursor::ContinueOp final
   : public Cursor::CursorOpBase
 {
@@ -4997,17 +7468,17 @@ private:
     MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
   }
 
   // Reference counted.
   ~ContinueOp()
   { }
 
   virtual nsresult
-  DoDatabaseWork(TransactionBase* aTransaction) override;
+  DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual nsresult
   SendSuccessResult() override;
 };
 
 class PermissionRequestHelper final
   : public PermissionRequestBase
   , public PIndexedDBPermissionRequestParent
@@ -5072,20 +7543,24 @@ class DatabaseLoggingInfo final
 #ifdef DEBUG
   // Just for potential warnings.
   friend class Factory;
 #endif
 
   LoggingInfo mLoggingInfo;
 
 public:
-  explicit DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
+  explicit
+  DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
     : mLoggingInfo(aLoggingInfo)
   {
     AssertIsOnBackgroundThread();
+    MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber());
+    MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber());
+    MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber());
   }
 
   const nsID&
   Id() const
   {
     AssertIsOnBackgroundThread();
 
     return mLoggingInfo.backgroundChildLoggingId();
@@ -5615,19 +8090,17 @@ ConvertBlobsToActors(PBackgroundParent* 
  ******************************************************************************/
 
 // Maps a database id to information about live database actors.
 typedef nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>
         DatabaseActorHashtable;
 
 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
 
-StaticRefPtr<nsRunnable> gStartTransactionRunnable;
-
-StaticRefPtr<TransactionThreadPool> gTransactionThreadPool;
+StaticRefPtr<ConnectionPool> gConnectionPool;
 
 typedef nsDataHashtable<nsIDHashKey, DatabaseLoggingInfo*>
         DatabaseLoggingInfoHashtable;
 
 StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
 
 #ifdef DEBUG
 
@@ -5645,16 +8118,23 @@ PBackgroundIDBFactoryParent*
 AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo)
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread())) {
     return nullptr;
   }
 
+  if (NS_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
+      NS_WARN_IF(!aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
+      NS_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   nsRefPtr<Factory> actor = Factory::Create(aLoggingInfo);
   MOZ_ASSERT(actor);
 
   return actor.forget().take();
 }
 
 bool
 RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor,
@@ -5728,19 +8208,2654 @@ CreateQuotaClient()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<QuotaClient> client = new QuotaClient();
   return client.forget();
 }
 
 /*******************************************************************************
+ * DatabaseConnection implementation
+ ******************************************************************************/
+
+DatabaseConnection::DatabaseConnection(
+                                      mozIStorageConnection* aStorageConnection,
+                                      FileManager* aFileManager)
+  : mStorageConnection(aStorageConnection)
+  , mFileManager(aFileManager)
+#ifdef DEBUG
+  , mDEBUGSavepointCount(0)
+  , mDEBUGThread(PR_GetCurrentThread())
+  , mDEBUGInWriteTransaction(false)
+#endif
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(aStorageConnection);
+  MOZ_ASSERT(aFileManager);
+}
+
+DatabaseConnection::~DatabaseConnection()
+{
+  MOZ_ASSERT(!mStorageConnection);
+  MOZ_ASSERT(!mFileManager);
+  MOZ_ASSERT(!mCachedStatements.Count());
+  MOZ_ASSERT(!mUpdateRefcountFunction);
+  MOZ_ASSERT(!mDEBUGSavepointCount);
+  MOZ_ASSERT(!mDEBUGInWriteTransaction);
+}
+
+nsresult
+DatabaseConnection::Init()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(!mDEBUGInWriteTransaction);
+
+  CachedStatement stmt;
+  nsresult rv = GetCachedStatement("BEGIN", &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::GetCachedStatement(const nsACString& aQuery,
+                                       CachedStatement* aCachedStatement)
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(!aQuery.IsEmpty());
+  MOZ_ASSERT(aCachedStatement);
+  MOZ_ASSERT(mStorageConnection);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::GetCachedStatement",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+
+  if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
+    nsresult rv =
+      mStorageConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
+    if (NS_FAILED(rv)) {
+#ifdef DEBUG
+      nsCString msg;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        mStorageConnection->GetLastErrorString(msg)));
+
+      nsAutoCString error =
+        NS_LITERAL_CSTRING("The statement '") + aQuery +
+        NS_LITERAL_CSTRING("' failed to compile with the error message '") +
+        msg + NS_LITERAL_CSTRING("'.");
+
+      NS_WARNING(error.get());
+#endif
+      return rv;
+    }
+
+    mCachedStatements.Put(aQuery, stmt);
+  }
+
+  aCachedStatement->Assign(this, stmt.forget());
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::BeginWriteTransaction()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(!mDEBUGInWriteTransaction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::BeginWriteTransaction",
+                 js::ProfileEntry::Category::STORAGE);
+
+  // Release our read locks.
+  CachedStatement commitStmt;
+  nsresult rv = GetCachedStatement("ROLLBACK", &commitStmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = commitStmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!mUpdateRefcountFunction) {
+    MOZ_ASSERT(mFileManager);
+
+    nsRefPtr<UpdateRefcountFunction> function =
+      new UpdateRefcountFunction(this, mFileManager);
+
+    rv =
+      mStorageConnection->CreateFunction(NS_LITERAL_CSTRING("update_refcount"),
+                                         /* aNumArguments */ 2,
+                                         function);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mUpdateRefcountFunction.swap(function);
+  }
+
+  CachedStatement beginStmt;
+  rv = GetCachedStatement("BEGIN IMMEDIATE", &beginStmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = beginStmt->Execute();
+  if (rv == NS_ERROR_STORAGE_BUSY) {
+    NS_WARNING("Received NS_ERROR_STORAGE_BUSY when attempting to start write "
+               "transaction, retrying for up to 10 seconds");
+
+    // Another thread must be using the database. Wait up to 10 seconds for
+    // that to complete.
+    TimeStamp start = TimeStamp::NowLoRes();
+
+    while (true) {
+      PR_Sleep(PR_MillisecondsToInterval(100));
+
+      rv = beginStmt->Execute();
+      if (rv != NS_ERROR_STORAGE_BUSY ||
+          TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
+        break;
+      }
+    }
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef DEBUG
+  mDEBUGInWriteTransaction = true;
+#endif
+
+  return NS_OK;
+}
+
+void
+DatabaseConnection::FinishWriteTransaction()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(mUpdateRefcountFunction);
+  MOZ_ASSERT(mDEBUGInWriteTransaction);
+
+  mUpdateRefcountFunction->Reset();
+
+#ifdef DEBUG
+  mDEBUGInWriteTransaction = false;
+#endif
+
+  CachedStatement stmt;
+  nsresult rv = GetCachedStatement("BEGIN", &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+}
+
+nsresult
+DatabaseConnection::StartSavepoint()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(mUpdateRefcountFunction);
+  MOZ_ASSERT(mDEBUGInWriteTransaction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::StartSavepoint",
+                 js::ProfileEntry::Category::STORAGE);
+
+  CachedStatement stmt;
+  nsresult rv = GetCachedStatement(kSavepointClause, &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mUpdateRefcountFunction->StartSavepoint();
+
+#ifdef DEBUG
+  MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX);
+  mDEBUGSavepointCount++;
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::ReleaseSavepoint()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(mUpdateRefcountFunction);
+  MOZ_ASSERT(mDEBUGInWriteTransaction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::ReleaseSavepoint",
+                 js::ProfileEntry::Category::STORAGE);
+
+  CachedStatement stmt;
+  nsresult rv = GetCachedStatement(
+    NS_LITERAL_CSTRING("RELEASE ") + NS_LITERAL_CSTRING(kSavepointClause),
+    &stmt);
+  if (NS_SUCCEEDED(rv)) {
+    rv = stmt->Execute();
+    if (NS_SUCCEEDED(rv)) {
+      mUpdateRefcountFunction->ReleaseSavepoint();
+
+#ifdef DEBUG
+      MOZ_ASSERT(mDEBUGSavepointCount);
+      mDEBUGSavepointCount--;
+#endif
+    }
+  }
+
+  return rv;
+}
+
+nsresult
+DatabaseConnection::RollbackSavepoint()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(mUpdateRefcountFunction);
+  MOZ_ASSERT(mDEBUGInWriteTransaction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::RollbackSavepoint",
+                 js::ProfileEntry::Category::STORAGE);
+
+#ifdef DEBUG
+  MOZ_ASSERT(mDEBUGSavepointCount);
+  mDEBUGSavepointCount--;
+#endif
+
+  mUpdateRefcountFunction->RollbackSavepoint();
+
+  CachedStatement stmt;
+  nsresult rv = GetCachedStatement(
+    NS_LITERAL_CSTRING("ROLLBACK TO ") + NS_LITERAL_CSTRING(kSavepointClause),
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // This may fail if SQLite already rolled back the savepoint so ignore any
+  // errors.
+  unused << stmt->Execute();
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::Checkpoint(bool aIdle)
+{
+  AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::Checkpoint",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsAutoCString checkpointMode;
+  if (aIdle) {
+    // When idle we want to reclaim disk space.
+    checkpointMode.AssignLiteral("TRUNCATE");
+  } else {
+    // We're being called at the end of a READ_WRITE_FLUSH transaction so make
+    // sure that the database is completely checkpointed and flushed to disk.
+    checkpointMode.AssignLiteral("FULL");
+  }
+
+  CachedStatement stmt;
+  nsresult rv =
+    GetCachedStatement(
+      NS_LITERAL_CSTRING("PRAGMA wal_checkpoint(") +
+      checkpointMode +
+      NS_LITERAL_CSTRING(")"),
+      &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+DatabaseConnection::Close()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStorageConnection);
+  MOZ_ASSERT(!mDEBUGSavepointCount);
+  MOZ_ASSERT(!mDEBUGInWriteTransaction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::Close",
+                 js::ProfileEntry::Category::STORAGE);
+
+  if (mUpdateRefcountFunction) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mStorageConnection->RemoveFunction(
+        NS_LITERAL_CSTRING("update_refcount"))));
+    mUpdateRefcountFunction = nullptr;
+  }
+
+  mCachedStatements.Clear();
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mStorageConnection->Close()));
+  mStorageConnection = nullptr;
+
+  mFileManager = nullptr;
+}
+
+DatabaseConnection::
+CachedStatement::CachedStatement()
+#ifdef DEBUG
+  : mDEBUGConnection(nullptr)
+#endif
+{
+  AssertIsOnConnectionThread();
+
+  MOZ_COUNT_CTOR(DatabaseConnection::CachedStatement);
+}
+
+DatabaseConnection::
+CachedStatement::~CachedStatement()
+{
+  AssertIsOnConnectionThread();
+
+  MOZ_COUNT_DTOR(DatabaseConnection::CachedStatement);
+}
+
+DatabaseConnection::
+CachedStatement::operator mozIStorageStatement*() const
+{
+  AssertIsOnConnectionThread();
+
+  return mStatement;
+}
+
+mozIStorageStatement*
+DatabaseConnection::
+CachedStatement::operator->() const
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(mStatement);
+
+  return mStatement;
+}
+
+void
+DatabaseConnection::
+CachedStatement::Reset()
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT_IF(mStatement, mScoper);
+
+  if (mStatement) {
+    mScoper.reset();
+    mScoper.emplace(mStatement);
+  }
+}
+
+void
+DatabaseConnection::
+CachedStatement::Assign(DatabaseConnection* aConnection,
+                        already_AddRefed<mozIStorageStatement> aStatement)
+{
+#ifdef DEBUG
+    MOZ_ASSERT(aConnection);
+    aConnection->AssertIsOnConnectionThread();
+    MOZ_ASSERT_IF(mDEBUGConnection, mDEBUGConnection == aConnection);
+
+    mDEBUGConnection = aConnection;
+#endif
+  AssertIsOnConnectionThread();
+
+  mScoper.reset();
+
+  mStatement = aStatement;
+
+  if (mStatement) {
+    mScoper.emplace(mStatement);
+  }
+}
+
+DatabaseConnection::
+AutoSavepoint::AutoSavepoint()
+  : mConnection(nullptr)
+#ifdef DEBUG
+  , mDEBUGTransaction(nullptr)
+#endif
+{
+  MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint);
+}
+
+DatabaseConnection::
+AutoSavepoint::~AutoSavepoint()
+{
+  MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint);
+
+  if (mConnection) {
+    mConnection->AssertIsOnConnectionThread();
+    MOZ_ASSERT(mDEBUGTransaction);
+    MOZ_ASSERT(mDEBUGTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+               mDEBUGTransaction->GetMode() ==
+                 IDBTransaction::READ_WRITE_FLUSH ||
+               mDEBUGTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+    if (NS_FAILED(mConnection->RollbackSavepoint())) {
+      NS_WARNING("Failed to rollback savepoint!");
+    }
+  }
+}
+
+nsresult
+DatabaseConnection::
+AutoSavepoint::Start(const TransactionBase* aTransaction)
+{
+  MOZ_ASSERT(aTransaction);
+  MOZ_ASSERT(aTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+             aTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
+             aTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+  DatabaseConnection* connection = aTransaction->GetDatabase()->GetConnection();
+  MOZ_ASSERT(connection);
+  connection->AssertIsOnConnectionThread();
+
+  MOZ_ASSERT(!mConnection);
+  MOZ_ASSERT(!mDEBUGTransaction);
+
+  nsresult rv = connection->StartSavepoint();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mConnection = connection;
+#ifdef DEBUG
+  mDEBUGTransaction = aTransaction;
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+AutoSavepoint::Commit()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(mDEBUGTransaction);
+
+  nsresult rv = mConnection->ReleaseSavepoint();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mConnection = nullptr;
+#ifdef DEBUG
+  mDEBUGTransaction = nullptr;
+#endif
+
+  return NS_OK;
+}
+
+DatabaseConnection::
+UpdateRefcountFunction::UpdateRefcountFunction(DatabaseConnection* aConnection,
+                                               FileManager* aFileManager)
+  : mConnection(aConnection)
+  , mFileManager(aFileManager)
+  , mInSavepoint(false)
+{
+  MOZ_ASSERT(aConnection);
+  aConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(aFileManager);
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::WillCommit()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::WillCommit",
+                 js::ProfileEntry::Category::STORAGE);
+
+  struct Helper final
+  {
+    static PLDHashOperator
+    Update(const uint64_t& aKey, FileInfoEntry* aValue, void* aUserArg)
+    {
+      MOZ_ASSERT(aValue);
+
+      auto* function = static_cast<DatabaseUpdateFunction*>(aUserArg);
+      MOZ_ASSERT(function);
+
+      if (aValue->mDelta && !function->Update(aKey, aValue->mDelta)) {
+        return PL_DHASH_STOP;
+      }
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  DatabaseUpdateFunction function(this);
+
+  mFileInfoEntries.EnumerateRead(Helper::Update, &function);
+
+  nsresult rv = function.ErrorCode();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CreateJournals();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::DidCommit()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::DidCommit",
+                 js::ProfileEntry::Category::STORAGE);
+
+  struct Helper final
+  {
+    static PLDHashOperator
+    Update(const uint64_t& aKey, FileInfoEntry* aValue, void* /* aUserArg */)
+    {
+      MOZ_ASSERT(aValue);
+
+      if (aValue->mDelta) {
+        aValue->mFileInfo->UpdateDBRefs(aValue->mDelta);
+      }
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  mFileInfoEntries.EnumerateRead(Helper::Update, nullptr);
+
+  if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterCommit))) {
+    NS_WARNING("RemoveJournals failed!");
+  }
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::DidAbort()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::DidAbort",
+                 js::ProfileEntry::Category::STORAGE);
+
+  if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterAbort))) {
+    NS_WARNING("RemoveJournals failed!");
+  }
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::StartSavepoint()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(!mInSavepoint);
+  MOZ_ASSERT(!mSavepointEntriesIndex.Count());
+
+  mInSavepoint = true;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::ReleaseSavepoint()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(mInSavepoint);
+
+  mSavepointEntriesIndex.Clear();
+  mInSavepoint = false;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::RollbackSavepoint()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(mInSavepoint);
+
+  struct Helper
+  {
+    static PLDHashOperator
+    Rollback(const uint64_t& aKey, FileInfoEntry* aValue, void* /* aUserArg */)
+    {
+      MOZ_ASSERT(!IsOnBackgroundThread());
+      MOZ_ASSERT(aValue);
+
+      aValue->mDelta -= aValue->mSavepointDelta;
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  mSavepointEntriesIndex.EnumerateRead(Helper::Rollback, nullptr);
+
+  mInSavepoint = false;
+  mSavepointEntriesIndex.Clear();
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::Reset()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(!mSavepointEntriesIndex.Count());
+  MOZ_ASSERT(!mInSavepoint);
+
+  mJournalsToCreateBeforeCommit.Clear();
+  mJournalsToRemoveAfterCommit.Clear();
+  mJournalsToRemoveAfterAbort.Clear();
+  mFileInfoEntries.Clear();
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
+                                     int32_t aIndex,
+                                     UpdateType aUpdateType)
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(aValues);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::ProcessValue",
+                 js::ProfileEntry::Category::STORAGE);
+
+  int32_t type;
+  nsresult rv = aValues->GetTypeOfIndex(aIndex, &type);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
+    return NS_OK;
+  }
+
+  nsString ids;
+  rv = aValues->GetString(aIndex, ids);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsTArray<int64_t> fileIds;
+  rv = ConvertFileIdsToArray(ids, fileIds);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  for (uint32_t i = 0; i < fileIds.Length(); i++) {
+    int64_t id = fileIds.ElementAt(i);
+
+    FileInfoEntry* entry;
+    if (!mFileInfoEntries.Get(id, &entry)) {
+      nsRefPtr<FileInfo> fileInfo = mFileManager->GetFileInfo(id);
+      MOZ_ASSERT(fileInfo);
+
+      entry = new FileInfoEntry(fileInfo);
+      mFileInfoEntries.Put(id, entry);
+    }
+
+    if (mInSavepoint) {
+      mSavepointEntriesIndex.Put(id, entry);
+    }
+
+    switch (aUpdateType) {
+      case eIncrement:
+        entry->mDelta++;
+        if (mInSavepoint) {
+          entry->mSavepointDelta++;
+        }
+        break;
+      case eDecrement:
+        entry->mDelta--;
+        if (mInSavepoint) {
+          entry->mSavepointDelta--;
+        }
+        break;
+      default:
+        MOZ_CRASH("Unknown update type!");
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::CreateJournals()
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::CreateJournals",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
+  if (NS_WARN_IF(!journalDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  for (uint32_t i = 0; i < mJournalsToCreateBeforeCommit.Length(); i++) {
+    int64_t id = mJournalsToCreateBeforeCommit[i];
+
+    nsCOMPtr<nsIFile> file =
+      mFileManager->GetFileForId(journalDirectory, id);
+    if (NS_WARN_IF(!file)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mJournalsToRemoveAfterAbort.AppendElement(id);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::RemoveJournals(const nsTArray<int64_t>& aJournals)
+{
+  MOZ_ASSERT(mConnection);
+  mConnection->AssertIsOnConnectionThread();
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::RemoveJournals",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
+  if (NS_WARN_IF(!journalDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  for (uint32_t index = 0; index < aJournals.Length(); index++) {
+    nsCOMPtr<nsIFile> file =
+      mFileManager->GetFileForId(journalDirectory, aJournals[index]);
+    if (NS_WARN_IF(!file)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (NS_FAILED(file->Remove(false))) {
+      NS_WARNING("Failed to removed journal!");
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
+                  mozIStorageFunction)
+
+NS_IMETHODIMP
+DatabaseConnection::
+UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+                                       nsIVariant** _retval)
+{
+  MOZ_ASSERT(aValues);
+  MOZ_ASSERT(_retval);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::OnFunctionCall",
+                 js::ProfileEntry::Category::STORAGE);
+
+  uint32_t numEntries;
+  nsresult rv = aValues->GetNumEntries(&numEntries);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(numEntries == 2);
+
+#ifdef DEBUG
+  {
+    int32_t type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
+    MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &type1)));
+
+    int32_t type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
+    MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &type2)));
+
+    MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
+                 type2 == mozIStorageValueArray::VALUE_TYPE_NULL));
+  }
+#endif
+
+  rv = ProcessValue(aValues, 0, eDecrement);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = ProcessValue(aValues, 1, eIncrement);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+bool
+DatabaseConnection::UpdateRefcountFunction::
+DatabaseUpdateFunction::Update(int64_t aId,
+                               int32_t aDelta)
+{
+  nsresult rv = UpdateInternal(aId, aDelta);
+  if (NS_FAILED(rv)) {
+    mErrorCode = rv;
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+DatabaseConnection::UpdateRefcountFunction::
+DatabaseUpdateFunction::UpdateInternal(int64_t aId,
+                                       int32_t aDelta)
+{
+  MOZ_ASSERT(mFunction);
+
+  PROFILER_LABEL("IndexedDB",
+                 "DatabaseConnection::UpdateRefcountFunction::"
+                 "DatabaseUpdateFunction::UpdateInternal",
+                 js::ProfileEntry::Category::STORAGE);
+
+  DatabaseConnection* connection = mFunction->mConnection;
+  MOZ_ASSERT(connection);
+  connection->AssertIsOnConnectionThread();
+
+  MOZ_ASSERT(connection->GetStorageConnection());
+
+  nsresult rv;
+  if (!mUpdateStatement) {
+    rv = connection->GetCachedStatement(
+      "UPDATE file "
+      "SET refcount = refcount + :delta "
+      "WHERE id = :id",
+      &mUpdateStatement);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mozStorageStatementScoper updateScoper(mUpdateStatement);
+
+  rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mUpdateStatement->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int32_t rows;
+  rv = connection->GetStorageConnection()->GetAffectedRows(&rows);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (rows > 0) {
+    if (!mSelectStatement) {
+      rv = connection->GetCachedStatement(
+        "SELECT id "
+        "FROM file "
+        "WHERE id = :id",
+        &mSelectStatement);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    mozStorageStatementScoper selectScoper(mSelectStatement);
+
+    rv = mSelectStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    bool hasResult;
+    rv = mSelectStatement->ExecuteStep(&hasResult);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!hasResult) {
+      // Don't have to create the journal here, we can create all at once,
+      // just before commit
+      mFunction->mJournalsToCreateBeforeCommit.AppendElement(aId);
+    }
+
+    return NS_OK;
+  }
+
+  if (!mInsertStatement) {
+    rv = connection->GetCachedStatement(
+      "INSERT INTO file (id, refcount) "
+      "VALUES(:id, :delta)",
+      &mInsertStatement);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mozStorageStatementScoper insertScoper(mInsertStatement);
+
+  rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mInsertStatement->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mFunction->mJournalsToRemoveAfterCommit.AppendElement(aId);
+  return NS_OK;
+}
+
+/*******************************************************************************
+ * ConnectionPool implementation
+ ******************************************************************************/
+
+ConnectionPool::ConnectionPool()
+  : mDatabasesMutex("ConnectionPool::mDatabasesMutex")
+  , mIdleTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
+  , mNextTransactionId(0)
+  , mTotalThreadCount(0)
+  , mShutdownRequested(false)
+  , mShutdownComplete(false)
+#ifdef DEBUG
+  , mDEBUGOwningThread(PR_GetCurrentThread())
+#endif
+{
+  AssertIsOnOwningThread();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mIdleTimer);
+}
+
+ConnectionPool::~ConnectionPool()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mIdleThreads.IsEmpty());
+  MOZ_ASSERT(mIdleDatabases.IsEmpty());
+  MOZ_ASSERT(!mIdleTimer);
+  MOZ_ASSERT(mTargetIdleTime.IsNull());
+  MOZ_ASSERT(!mDatabases.Count());
+  MOZ_ASSERT(!mTransactions.Count());
+  MOZ_ASSERT(mQueuedTransactions.IsEmpty());
+  MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+  MOZ_ASSERT(!mTotalThreadCount);
+  MOZ_ASSERT(mShutdownRequested);
+  MOZ_ASSERT(mShutdownComplete);
+}
+
+#ifdef DEBUG
+
+void
+ConnectionPool::AssertIsOnOwningThread() const
+{
+  MOZ_ASSERT(mDEBUGOwningThread);
+  MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGOwningThread);
+}
+
+#endif // DEBUG
+
+// static
+void
+ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+  MOZ_ASSERT(aTimer);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::IdleTimerCallback",
+                 js::ProfileEntry::Category::STORAGE);
+
+  auto* self = static_cast<ConnectionPool*>(aClosure);
+  MOZ_ASSERT(self);
+  MOZ_ASSERT(self->mIdleTimer);
+  MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer));
+  MOZ_ASSERT(!self->mTargetIdleTime.IsNull());
+  MOZ_ASSERT_IF(self->mIdleDatabases.IsEmpty(), !self->mIdleThreads.IsEmpty());
+  MOZ_ASSERT_IF(self->mIdleThreads.IsEmpty(), !self->mIdleDatabases.IsEmpty());
+
+  self->mTargetIdleTime = TimeStamp();
+
+  // Cheat a little.
+  TimeStamp now = TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500);
+
+  uint32_t index = 0;
+
+  for (uint32_t count = self->mIdleDatabases.Length(); index < count; index++) {
+    IdleDatabaseInfo& info = self->mIdleDatabases[index];
+
+    if (now >= info.mIdleTime) {
+      if (info.mDatabaseInfo->mNeedsCheckpoint) {
+        self->CheckpointDatabase(info.mDatabaseInfo);
+      } else {
+        self->CloseDatabase(info.mDatabaseInfo);
+      }
+    } else {
+      break;
+    }
+  }
+
+  if (index) {
+    self->mIdleDatabases.RemoveElementsAt(0, index);
+
+    index = 0;
+  }
+
+  for (uint32_t count = self->mIdleThreads.Length(); index < count; index++) {
+    IdleThreadInfo& info = self->mIdleThreads[index];
+    MOZ_ASSERT(info.mThreadInfo.mThread);
+    MOZ_ASSERT(info.mThreadInfo.mRunnable);
+
+    if (now >= info.mIdleTime) {
+      self->ShutdownThread(info.mThreadInfo);
+    } else {
+      break;
+    }
+  }
+
+  if (index) {
+    self->mIdleThreads.RemoveElementsAt(0, index);
+  }
+
+  self->AdjustIdleTimer();
+}
+
+nsresult
+ConnectionPool::GetOrCreateConnection(const Database* aDatabase,
+                                      DatabaseConnection** aConnection)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aDatabase);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::GetOrCreateConnection",
+                 js::ProfileEntry::Category::STORAGE);
+
+  DatabaseInfo* dbInfo;
+  {
+    MutexAutoLock lock(mDatabasesMutex);
+
+    dbInfo = mDatabases.Get(aDatabase->Id());
+  }
+
+  MOZ_ASSERT(dbInfo);
+
+  nsRefPtr<DatabaseConnection> connection = dbInfo->mConnection;
+  if (!connection) {
+    MOZ_ASSERT(!dbInfo->mDEBUGConnectionThread);
+
+    nsCOMPtr<mozIStorageConnection> storageConnection;
+    nsresult rv =
+      GetStorageConnection(aDatabase->FilePath(),
+                           aDatabase->Type(),
+                           aDatabase->Group(),
+                           aDatabase->Origin(),
+                           getter_AddRefs(storageConnection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    connection =
+      new DatabaseConnection(storageConnection, aDatabase->GetFileManager());
+
+    rv = connection->Init();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    dbInfo->mConnection = connection;
+
+    IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'",
+                   dbInfo->mConnection.get(),
+                   NS_ConvertUTF16toUTF8(aDatabase->FilePath()).get()));
+
+#ifdef DEBUG
+    dbInfo->mDEBUGConnectionThread = PR_GetCurrentThread();
+#endif
+  }
+
+  dbInfo->AssertIsOnConnectionThread();
+
+  connection.forget(aConnection);
+  return NS_OK;
+}
+
+uint64_t
+ConnectionPool::Start(const nsID& aBackgroundChildLoggingId,
+                      const nsACString& aDatabaseId,
+                      int64_t aLoggingSerialNumber,
+                      const nsTArray<nsString>& aObjectStoreNames,
+                      bool aIsWriteTransaction,
+                      TransactionDatabaseOperationBase* aTransactionOp)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!aDatabaseId.IsEmpty());
+  MOZ_ASSERT(mNextTransactionId < UINT64_MAX);
+  MOZ_ASSERT(!mShutdownRequested);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::Start",
+                 js::ProfileEntry::Category::STORAGE);
+
+  const uint64_t transactionId = ++mNextTransactionId;
+
+  DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId);
+
+  const bool databaseInfoIsNew = !dbInfo;
+
+  if (databaseInfoIsNew) {
+    dbInfo = new DatabaseInfo(this, aDatabaseId);
+
+    MutexAutoLock lock(mDatabasesMutex);
+
+    mDatabases.Put(aDatabaseId, dbInfo);
+  }
+
+  TransactionInfo* transactionInfo =
+    new TransactionInfo(dbInfo,
+                        aBackgroundChildLoggingId,
+                        aDatabaseId,
+                        transactionId,
+                        aLoggingSerialNumber,
+                        aObjectStoreNames,
+                        aIsWriteTransaction,
+                        aTransactionOp);
+
+  MOZ_ASSERT(!mTransactions.Get(transactionId));
+  mTransactions.Put(transactionId, transactionInfo);
+
+  if (aIsWriteTransaction) {
+    MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX);
+    dbInfo->mWriteTransactionCount++;
+  } else {
+    MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX);
+    dbInfo->mReadTransactionCount++;
+  }
+
+  auto& blockingTransactions = dbInfo->mBlockingTransactions;
+
+  for (uint32_t nameIndex = 0, nameCount = aObjectStoreNames.Length();
+       nameIndex < nameCount;
+       nameIndex++) {
+    const nsString& objectStoreName = aObjectStoreNames[nameIndex];
+
+    TransactionInfoPair* blockInfo = blockingTransactions.Get(objectStoreName);
+    if (!blockInfo) {
+      blockInfo = new TransactionInfoPair();
+      blockingTransactions.Put(objectStoreName, blockInfo);
+    }
+
+    // Mark what we are blocking on.
+    if (TransactionInfo* blockingRead = blockInfo->mLastBlockingReads) {
+      transactionInfo->mBlockedOn.PutEntry(blockingRead);
+      blockingRead->mBlocking.PutEntry(transactionInfo);
+    }
+
+    if (aIsWriteTransaction) {
+      if (const uint32_t writeCount = blockInfo->mLastBlockingWrites.Length()) {
+        for (uint32_t writeIndex = 0; writeIndex < writeCount; writeIndex++) {
+          TransactionInfo* blockingWrite =
+            blockInfo->mLastBlockingWrites[writeIndex];
+          MOZ_ASSERT(blockingWrite);
+
+          transactionInfo->mBlockedOn.PutEntry(blockingWrite);
+          blockingWrite->mBlocking.PutEntry(transactionInfo);
+        }
+      }
+
+      blockInfo->mLastBlockingReads = transactionInfo;
+      blockInfo->mLastBlockingWrites.Clear();
+    } else {
+      blockInfo->mLastBlockingWrites.AppendElement(transactionInfo);
+    }
+  }
+
+  if (!transactionInfo->mBlockedOn.Count()) {
+    unused << ScheduleTransaction(transactionInfo,
+                                  /* aFromQueuedTransactions */ false);
+  }
+
+  if (!databaseInfoIsNew && mIdleDatabases.RemoveElement(dbInfo)) {
+    AdjustIdleTimer();
+  }
+
+  return transactionId;
+}
+
+void
+ConnectionPool::Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aRunnable);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::Dispatch",
+                 js::ProfileEntry::Category::STORAGE);
+
+  TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+  MOZ_ASSERT(transactionInfo);
+  MOZ_ASSERT(!transactionInfo->mFinished);
+
+  if (transactionInfo->mRunning) {
+    DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
+    MOZ_ASSERT(dbInfo);
+    MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+    MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+    MOZ_ASSERT(!dbInfo->mClosing);
+    MOZ_ASSERT_IF(transactionInfo->mIsWriteTransaction,
+                  dbInfo->mRunningWriteTransaction == transactionInfo);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      dbInfo->mThreadInfo.mThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL)));
+  } else {
+    transactionInfo->mQueuedRunnables.AppendElement(aRunnable);
+  }
+}
+
+void
+ConnectionPool::Finish(uint64_t aTransactionId, FinishCallback* aCallback)
+{
+  AssertIsOnOwningThread();
+
+#ifdef DEBUG
+  TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+  MOZ_ASSERT(transactionInfo);
+  MOZ_ASSERT(!transactionInfo->mFinished);
+#endif
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::Finish",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsRefPtr<FinishCallbackWrapper> wrapper =
+    new FinishCallbackWrapper(this, aTransactionId, aCallback);
+
+  Dispatch(aTransactionId, wrapper);
+
+#ifdef DEBUG
+  MOZ_ASSERT(!transactionInfo->mFinished);
+  transactionInfo->mFinished = true;
+#endif
+}
+
+void
+ConnectionPool::WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
+                                           nsIRunnable* aCallback)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!aDatabaseIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::WaitForDatabasesToComplete",
+                 js::ProfileEntry::Category::STORAGE);
+
+  bool mayRunCallbackImmediately = true;
+
+  for (uint32_t index = 0, count = aDatabaseIds.Length();
+       index < count;
+       index++) {
+    const nsCString& databaseId = aDatabaseIds[index];
+    MOZ_ASSERT(!databaseId.IsEmpty());
+
+    if (CloseDatabaseWhenIdleInternal(databaseId)) {
+      mayRunCallbackImmediately = false;
+    }
+  }
+
+  if (mayRunCallbackImmediately) {
+    unused << aCallback->Run();
+    return;
+  }
+
+  nsAutoPtr<DatabasesCompleteCallback> callback(
+    new DatabasesCompleteCallback(Move(aDatabaseIds), aCallback));
+  mCompleteCallbacks.AppendElement(callback.forget());
+}
+
+void
+ConnectionPool::Shutdown()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mShutdownRequested);
+  MOZ_ASSERT(!mShutdownComplete);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::Shutdown",
+                 js::ProfileEntry::Category::STORAGE);
+
+  mShutdownRequested = true;
+
+  CancelIdleTimer();
+  MOZ_ASSERT(mTargetIdleTime.IsNull());
+
+  mIdleTimer = nullptr;
+
+  CloseIdleDatabases();
+
+  ShutdownIdleThreads();
+
+  if (!mDatabases.Count()) {
+    MOZ_ASSERT(!mTransactions.Count());
+
+    Cleanup();
+
+    MOZ_ASSERT(mShutdownComplete);
+    return;
+  }
+
+  nsIThread* currentThread = NS_GetCurrentThread();
+  MOZ_ASSERT(currentThread);
+
+  while (!mShutdownComplete) {
+    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+  }
+}
+
+void
+ConnectionPool::Cleanup()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mShutdownRequested);
+  MOZ_ASSERT(!mShutdownComplete);
+  MOZ_ASSERT(!mDatabases.Count());
+  MOZ_ASSERT(!mTransactions.Count());
+  MOZ_ASSERT(mIdleThreads.IsEmpty());
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::Cleanup",
+                 js::ProfileEntry::Category::STORAGE);
+
+  if (!mCompleteCallbacks.IsEmpty()) {
+    // Run all callbacks manually now.
+    for (uint32_t count = mCompleteCallbacks.Length(), index = 0;
+         index < count;
+         index++) {
+      nsAutoPtr<DatabasesCompleteCallback> completeCallback(
+        mCompleteCallbacks[index].forget());
+      MOZ_ASSERT(completeCallback);
+      MOZ_ASSERT(completeCallback->mCallback);
+
+      unused << completeCallback->mCallback->Run();
+    }
+
+    mCompleteCallbacks.Clear();
+
+    // And make sure they get processed.
+    nsIThread* currentThread = NS_GetCurrentThread();
+    MOZ_ASSERT(currentThread);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProcessPendingEvents(currentThread)));
+  }
+
+  mShutdownComplete = true;
+}
+
+void
+ConnectionPool::AdjustIdleTimer()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mIdleTimer);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::AdjustIdleTimer",
+                 js::ProfileEntry::Category::STORAGE);
+
+  // Figure out the next time at which we should release idle resources. This
+  // includes both databases and threads.
+  TimeStamp newTargetIdleTime;
+  MOZ_ASSERT(newTargetIdleTime.IsNull());
+
+  if (!mIdleDatabases.IsEmpty()) {
+    newTargetIdleTime = mIdleDatabases[0].mIdleTime;
+  }
+
+  if (!mIdleThreads.IsEmpty()) {
+    const TimeStamp& idleTime = mIdleThreads[0].mIdleTime;
+
+    if (newTargetIdleTime.IsNull() || idleTime < newTargetIdleTime) {
+      newTargetIdleTime = idleTime;
+    }
+  }
+
+  MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty());
+  MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleThreads.IsEmpty());
+
+  // Cancel the timer if it was running and the new target time is different.
+  if (!mTargetIdleTime.IsNull() &&
+      (newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
+    CancelIdleTimer();
+
+    MOZ_ASSERT(mTargetIdleTime.IsNull());
+  }
+
+  // Schedule the timer if we have a target time different than before.
+  if (!newTargetIdleTime.IsNull() &&
+      (mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
+    double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds();
+
+    uint32_t delay;
+    if (delta > 0) {
+      delay = uint32_t(std::min(delta, double(UINT32_MAX)));
+    } else {
+      delay = 0;
+    }
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mIdleTimer->InitWithFuncCallback(IdleTimerCallback,
+                                       this,
+                                       delay,
+                                       nsITimer::TYPE_ONE_SHOT)));
+
+    mTargetIdleTime = newTargetIdleTime;
+  }
+}
+
+void
+ConnectionPool::CancelIdleTimer()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mIdleTimer);
+
+  if (!mTargetIdleTime.IsNull()) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mIdleTimer->Cancel()));
+
+    mTargetIdleTime = TimeStamp();
+    MOZ_ASSERT(mTargetIdleTime.IsNull());
+  }
+}
+
+void
+ConnectionPool::ShutdownThread(ThreadInfo& aThreadInfo)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aThreadInfo.mThread);
+  MOZ_ASSERT(aThreadInfo.mRunnable);
+  MOZ_ASSERT(mTotalThreadCount);
+
+  nsRefPtr<ThreadRunnable> runnable;
+  aThreadInfo.mRunnable.swap(runnable);
+
+  nsCOMPtr<nsIThread> thread;
+  aThreadInfo.mThread.swap(thread);
+
+  IDB_DEBUG_LOG(("ConnectionPool shutting down thread %lu",
+                 runnable->SerialNumber()));
+
+  // This should clean up the thread with the profiler.
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
+
+  nsCOMPtr<nsIRunnable> shutdownRunnable =
+    NS_NewRunnableMethod(thread, &nsIThread::Shutdown);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(shutdownRunnable)));
+
+  mTotalThreadCount--;
+}
+
+void
+ConnectionPool::CloseIdleDatabases()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mShutdownRequested);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::CloseIdleDatabases",
+                 js::ProfileEntry::Category::STORAGE);
+
+  if (!mIdleDatabases.IsEmpty()) {
+    for (uint32_t count = mIdleDatabases.Length(), index = 0;
+         index < count;
+         index++) {
+      CloseDatabase(mIdleDatabases[index].mDatabaseInfo);
+    }
+    mIdleDatabases.Clear();
+  }
+}
+
+void
+ConnectionPool::ShutdownIdleThreads()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mShutdownRequested);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::ShutdownIdleThreads",
+                 js::ProfileEntry::Category::STORAGE);
+
+  if (!mIdleThreads.IsEmpty()) {
+    for (uint32_t threadCount = mIdleThreads.Length(), threadIndex = 0;
+         threadIndex < threadCount;
+         threadIndex++) {
+      ShutdownThread(mIdleThreads[threadIndex].mThreadInfo);
+    }
+    mIdleThreads.Clear();
+  }
+}
+
+bool
+ConnectionPool::ScheduleTransaction(TransactionInfo* aTransactionInfo,
+                                    bool aFromQueuedTransactions)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aTransactionInfo);
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::ScheduleTransaction",
+                 js::ProfileEntry::Category::STORAGE);
+
+  DatabaseInfo* dbInfo = aTransactionInfo->mDatabaseInfo;
+  MOZ_ASSERT(dbInfo);
+
+  if (dbInfo->mClosing) {
+    MOZ_ASSERT(!mIdleDatabases.Contains(dbInfo));
+    MOZ_ASSERT(
+      !dbInfo->mTransactionsScheduledDuringClose.Contains(aTransactionInfo));
+
+    dbInfo->mTransactionsScheduledDuringClose.AppendElement(aTransactionInfo);
+    return true;
+  }
+
+  if (!dbInfo->mThreadInfo.mThread) {
+    MOZ_ASSERT(!dbInfo->mThreadInfo.mRunnable);
+
+    if (mIdleThreads.IsEmpty()) {
+      bool created = false;
+
+      if (mTotalThreadCount < kMaxConnectionThreadCount) {
+        // This will set the thread up with the profiler.
+        nsRefPtr<ThreadRunnable> runnable = new ThreadRunnable();
+
+        nsCOMPtr<nsIThread> newThread;
+        if (NS_SUCCEEDED(NS_NewThread(getter_AddRefs(newThread), runnable))) {
+          MOZ_ASSERT(newThread);
+
+          IDB_DEBUG_LOG(("ConnectionPool created thread %lu",
+                         runnable->SerialNumber()));
+
+          dbInfo->mThreadInfo.mThread.swap(newThread);
+          dbInfo->mThreadInfo.mRunnable.swap(runnable);
+
+          mTotalThreadCount++;
+          created = true;
+        } else {
+          NS_WARNING("Failed to make new thread!");
+        }
+      }
+
+      if (!created) {
+        if (!aFromQueuedTransactions) {
+          MOZ_ASSERT(!mQueuedTransactions.Contains(aTransactionInfo));
+          mQueuedTransactions.AppendElement(aTransactionInfo);
+        }
+        return false;
+      }
+    } else {
+      const uint32_t lastIndex = mIdleThreads.Length() - 1;
+
+      ThreadInfo& threadInfo = mIdleThreads[lastIndex].mThreadInfo;
+
+      dbInfo->mThreadInfo.mRunnable.swap(threadInfo.mRunnable);
+      dbInfo->mThreadInfo.mThread.swap(threadInfo.mThread);
+
+      mIdleThreads.RemoveElementAt(lastIndex);
+
+      AdjustIdleTimer();
+    }
+  }
+
+  MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+  MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+
+  if (aTransactionInfo->mIsWriteTransaction) {
+    if (dbInfo->mRunningWriteTransaction) {
+      // SQLite only allows one write transaction at a time so queue this
+      // transaction for later.
+      MOZ_ASSERT(
+        !dbInfo->mScheduledWriteTransactions.Contains(aTransactionInfo));
+
+      dbInfo->mScheduledWriteTransactions.AppendElement(aTransactionInfo);
+      return true;
+    }
+
+    dbInfo->mRunningWriteTransaction = aTransactionInfo;
+    dbInfo->mNeedsCheckpoint = true;
+  }
+
+  MOZ_ASSERT(!aTransactionInfo->mRunning);
+  aTransactionInfo->mRunning = true;
+
+  nsTArray<nsCOMPtr<nsIRunnable>>& queuedRunnables =
+    aTransactionInfo->mQueuedRunnables;
+
+  if (!queuedRunnables.IsEmpty()) {
+    for (uint32_t index = 0, count = queuedRunnables.Length();
+         index < count;
+         index++) {
+      nsCOMPtr<nsIRunnable> runnable;
+      queuedRunnables[index].swap(runnable);
+
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        dbInfo->mThreadInfo.mThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
+    }
+
+    queuedRunnables.Clear();
+  }
+
+  return true;
+}
+
+void
+ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId)
+{
+  AssertIsOnOwningThread();
+
+  struct Helper
+  {
+    static PLDHashOperator
+    MaybeScheduleTransaction(nsPtrHashKey<TransactionInfo>* aKey,
+                             void* aClosure)
+    {
+      AssertIsOnBackgroundThread();
+
+      TransactionInfo* transactionInfo = aKey->GetKey();
+      MOZ_ASSERT(transactionInfo);
+
+      TransactionInfo* finishedInfo = static_cast<TransactionInfo*>(aClosure);
+      MOZ_ASSERT(finishedInfo);
+
+      MOZ_ASSERT(transactionInfo->mBlockedOn.Contains(finishedInfo));
+
+      transactionInfo->mBlockedOn.RemoveEntry(finishedInfo);
+      if (!transactionInfo->mBlockedOn.Count()) {
+        transactionInfo->Schedule();
+      }
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  PROFILER_LABEL("IndexedDB",
+                 "ConnectionPool::NoteFinishedTransaction",
+                 js::ProfileEntry::Category::STORAGE);
+
+  TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+  MOZ_ASSERT(transactionInfo);
+  MOZ_ASSERT(transactionInfo->mRunning);
+  MOZ_ASSERT(transactionInfo->mFinished);
+
+  transactionInfo->mRunning = false;
+
+  DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
+  MOZ_ASSERT(dbInfo);
+  MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == dbInfo);
+  MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+  MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+
+  // Schedule the next write transaction if there are any queued.
+  if (dbInfo->mRunningWriteTransaction == transactionInfo) {
+    MOZ_ASSERT(transactionInfo->mIsWriteTransaction);
+    MOZ_ASSERT(dbInfo->mNeedsCheckpoint);
+
+    dbInfo->mRunningWriteTransaction = nullptr;
+
+    if (!dbInfo->mScheduledWriteTransactions.IsEmpty()) {
+      TransactionInfo* nextWriteTransaction =
+        dbInfo->mScheduledWriteTransactions[0];
+      MOZ_ASSERT(nextWriteTransaction);
+
+      dbInfo->mScheduledWriteTransactions.RemoveElementAt(0);
+
+      MOZ_ALWAYS_TRUE(ScheduleTransaction(nextWriteTransaction,
+                                          /* aFromQueuedTransactions */ false));
+    }
+  }
+
+  const nsTArray<nsString>& objectStoreNames =
+    transactionInfo->mObjectStoreNames;
+
+  for (uint32_t index = 0, count = objectStoreNames.Length();
+       index < count;
+       index++) {
+    TransactionInfoPair* blockInfo =
+      dbInfo->mBlockingTransactions.Get(objectStoreNames[index]);
+    MOZ_ASSERT(blockInfo);
+
+    if (transactionInfo->mIsWriteTransaction &&
+        blockInfo->mLastBlockingReads == transactionInfo) {
+      blockInfo->mLastBlockingReads = nullptr;