Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Fri, 14 Mar 2014 23:17:32 -0700
changeset 191970 aa52fe58c9cb61508440d89457f361ca9d898deb
parent 191969 b8eecfc351b6a2b302df17050890d8ed297b2d71 (current diff)
parent 191947 82c90c17fc954db2229e051ddc7072888899aaf8 (diff)
child 191971 8ff12456c32b69bb1d1b013d5eee7f97a1be13e4
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Intermittent Android startup crashes leading to test bustage.
+Bug 983185 requires a clobber. This may not affect all platforms.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -855,16 +855,25 @@ pref("b2g.neterror.url", "app://system.g
 
 // Enable Web Speech synthesis API
 pref("media.webspeech.synth.enabled", true);
 
 // Downloads API
 pref("dom.mozDownloads.enabled", true);
 pref("dom.downloads.max_retention_days", 7);
 
+// External Helper Application Handling
+//
+// All external helper application handling can require the docshell to be
+// active before allowing the external helper app service to handle content.
+//
+// To prevent SD card DoS attacks via downloads we disable background handling.
+//
+pref("security.exthelperapp.disable_background_handling", true);
+
 // Inactivity time in milliseconds after which we shut down the OS.File worker.
 pref("osfile.reset_worker_delay", 5000);
 
 // APZC preferences.
 //
 // Gaia relies heavily on scroll events for now, so lets fire them
 // more often than the default value (100).
 pref("apz.asyncscroll.throttle", 40);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,22 +14,22 @@
   <!--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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,19 +12,19 @@
   <!--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="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,20 +10,20 @@
   <!--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="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,22 +14,22 @@
   <!--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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "37b9db0c5bc096893c78468b1a3cf3d02962e231", 
+    "revision": "a7c17d6cce9c60631e386b75885199a59b555a43", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,21 +10,21 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="575fdbf046e966a5915b1f1e800e5d6ad0ea14c0"/>
   <project name="platform/development" path="development" revision="b1025ec93beeb480caaf3049d171283c3846461d"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,21 +14,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="575fdbf046e966a5915b1f1e800e5d6ad0ea14c0"/>
   <project name="platform/development" path="development" revision="b1025ec93beeb480caaf3049d171283c3846461d"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,19 +12,19 @@
   <!--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="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f09ec7d9d0bb7c40998ddb6b5bf397e578add541"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -292,16 +292,17 @@
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
+@BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
 @BINPATH@/components/ucnative.xpt
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -455,16 +455,17 @@ const PanelUI = {
   },
 };
 
 /**
  * Gets the currently selected locale for display.
  * @return  the selected locale or "en-US" if none is selected
  */
 function getLocale() {
+  const PREF_SELECTED_LOCALE = "general.useragent.locale";
   try {
     let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
                                                 Ci.nsIPrefLocalizedString);
     if (locale)
       return locale;
   }
   catch (e) { }
 
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -3676,17 +3676,21 @@ OverflowableToolbar.prototype = {
       CustomizableUI.removeListener(this);
     }
   },
 
   _onLazyResize: function() {
     if (!this._enabled)
       return;
 
-    this._moveItemsBackToTheirOrigin();
+    if (this._target.scrollLeftMax > 0) {
+      this.onOverflow();
+    } else {
+      this._moveItemsBackToTheirOrigin();
+    }
   },
 
   _disable: function() {
     this._enabled = false;
     this._moveItemsBackToTheirOrigin(true);
     if (this._lazyResizeHandler) {
       this._lazyResizeHandler.disarm();
     }
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -60,16 +60,17 @@ skip-if = os == "linux"
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_945739_showInPrivateBrowsing_customize_mode.js]
 [browser_947987_removable_default.js]
 [browser_948985_non_removable_defaultArea.js]
 [browser_952963_areaType_getter_no_area.js]
 [browser_956602_remove_special_widget.js]
+[browser_963639_customizing_attribute_non_customizable_toolbar.js]
 [browser_968447_bookmarks_toolbar_items_in_panel.js]
 [browser_968565_insert_before_hidden_items.js]
 [browser_969427_recreate_destroyed_widget_after_reset.js]
 [browser_969661_character_encoding_navbar_disabled.js]
 [browser_970511_undo_restore_default.js]
 [browser_972267_customizationchange_events.js]
 [browser_973932_addonbar_currentset.js]
 [browser_975719_customtoolbars_behaviour.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js
@@ -0,0 +1,34 @@
+/* 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/. */
+
+"use strict";
+
+const kToolbar = "test-toolbar-963639-non-customizable-customizing-attribute";
+
+add_task(function() {
+  info("Test for Bug 963639 - CustomizeMode _onToolbarVisibilityChange sets @customizing on non-customizable toolbars");
+
+  let toolbar = document.createElement("toolbar");
+  toolbar.id = kToolbar;
+  gNavToolbox.appendChild(toolbar);
+
+  let testToolbar = document.getElementById(kToolbar)
+  ok(testToolbar, "Toolbar was created.");
+  is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 1,
+     "Toolbar was added to the navigator toolbox");
+
+  toolbar.setAttribute("toolbarname", "NonCustomizableToolbarCustomizingAttribute");
+  toolbar.setAttribute("collapsed", "true");
+
+  yield startCustomizing();
+  window.setToolbarVisibility(toolbar, "true");
+  isnot(toolbar.getAttribute("customizing"), "true",
+        "Toolbar doesn't have the customizing attribute");
+
+  yield endCustomizing();
+  gNavToolbox.removeChild(toolbar);
+
+  is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 0,
+     "Toolbar was removed from the navigator toolbox");
+});
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -18,17 +18,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
 
     Task.spawn(function* () {
       try {
 
-        yield waitForSourceShown(gPanel, CODE_URL);
+        yield ensureSourceIs(gPanel, CODE_URL, true);
 
         // Pause and set our breakpoints.
         yield doInterrupt();
         const [bp1, bp2, bp3] = yield promise.all([
           setBreakpoint({
             url: CODE_URL,
             line: 2
           }),
--- a/browser/devtools/netmonitor/test/browser_net_filter-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-03.js
@@ -5,16 +5,19 @@
  * Test if filtering items in the network table works correctly with new requests
  * and while sorting is enabled.
  */
 
 function test() {
   initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
+    // It seems that this test may be slow on Ubuntu builds running on ec2.
+    requestLongerTimeout(2);
+
     let { $, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 7).then(() => {
       EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -305,16 +305,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
+@BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -79,16 +79,20 @@ browser.jar:
   skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
   skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
   skin/classic/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
   skin/classic/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
 * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
   skin/classic/browser/customizableui/subView-arrow-back-inverted.png  (../shared/customizableui/subView-arrow-back-inverted.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
+  skin/classic/browser/customizableui/whimsy.png  (../shared/customizableui/whimsy.png)
+  skin/classic/browser/customizableui/whimsy@2x.png  (../shared/customizableui/whimsy@2x.png)
+  skin/classic/browser/customizableui/whimsy-bw.png  (../shared/customizableui/whimsy-bw.png)
+  skin/classic/browser/customizableui/whimsy-bw@2x.png  (../shared/customizableui/whimsy-bw@2x.png)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png          (downloads/buttons.png)
   skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-glow.png    (downloads/download-glow.png)
   skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -136,16 +136,20 @@ browser.jar:
   skin/classic/browser/customizableui/menuPanel-customizeFinish@2x.png  (../shared/customizableui/menuPanel-customizeFinish@2x.png)
   skin/classic/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
   skin/classic/browser/customizableui/panelarrow-customizeTip@2x.png  (../shared/customizableui/panelarrow-customizeTip@2x.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted.png  (../shared/customizableui/subView-arrow-back-inverted.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted@2x.png  (../shared/customizableui/subView-arrow-back-inverted@2x.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl@2x.png  (../shared/customizableui/subView-arrow-back-inverted-rtl@2x.png)
 * skin/classic/browser/customizableui/panelUIOverlay.css    (customizableui/panelUIOverlay.css)
+  skin/classic/browser/customizableui/whimsy.png          (../shared/customizableui/whimsy.png)
+  skin/classic/browser/customizableui/whimsy@2x.png       (../shared/customizableui/whimsy@2x.png)
+  skin/classic/browser/customizableui/whimsy-bw.png       (../shared/customizableui/whimsy-bw.png)
+  skin/classic/browser/customizableui/whimsy-bw@2x.png    (../shared/customizableui/whimsy-bw@2x.png)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png                (downloads/buttons.png)
   skin/classic/browser/downloads/buttons@2x.png             (downloads/buttons@2x.png)
   skin/classic/browser/downloads/download-glow.png          (downloads/download-glow.png)
   skin/classic/browser/downloads/download-glow@2x.png       (downloads/download-glow@2x.png)
   skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
   skin/classic/browser/downloads/download-notification-finish.png  (downloads/download-notification-finish.png)
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -14,16 +14,54 @@
 %define exitSubviewGutterWidth 38px
 %define buttonStateHover :not(:-moz-any([disabled],[open],:active)):hover
 %define menuStateHover :not(:-moz-any([disabled],:active))[_moz-menuactive]
 %define buttonStateActive :not([disabled]):-moz-any([open],:hover:active)
 %define menuStateActive :not([disabled])[_moz-menuactive]:active
 
 %include ../browser.inc
 
+#PanelUI-popup #PanelUI-contents:empty {
+    height: 128px;
+}
+
+#PanelUI-popup #PanelUI-contents:empty::before {
+  content: "";
+  background-image: url(chrome://browser/skin/customizableui/whimsy-bw.png);
+  display: block;
+  width: 64px;
+  height: 64px;
+  position: absolute;
+  animation: moveX 3.05s linear 0s infinite alternate,
+             moveY 3.4s linear 0s infinite alternate;
+}
+
+#PanelUI-popup #PanelUI-contents:empty:hover::before {
+  background-image: url(chrome://browser/skin/customizableui/whimsy.png);
+}
+
+@media (min-resolution: 2dppx) {
+  #PanelUI-popup #PanelUI-contents:empty::before {
+    background-image: url(chrome://browser/skin/customizableui/whimsy-bw@2x.png);
+    background-size: 64px 64px;
+  }
+  #PanelUI-popup #PanelUI-contents:empty:hover::before {
+    background-image: url(chrome://browser/skin/customizableui/whimsy@2x.png);
+  }
+}
+
+@keyframes moveX {
+  /* These values are adjusted for the padding on the panel. */
+  from { margin-left: -15px; } to { margin-left: calc(100% - 49px); }
+}
+@keyframes moveY {
+  /* These values are adjusted for the padding and height of the panel. */
+  from { margin-top: -6px; } to { margin-top: 58px; }
+}
+
 #PanelUI-button {
   background-image: linear-gradient(to bottom, hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0)),
                     linear-gradient(to bottom, hsla(210,54%,20%,0), hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, hsla(210,54%,20%,0)),
                     linear-gradient(to bottom, hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0));
   background-size: 1px calc(100% - 1px), 1px calc(100% - 1px), 1px  calc(100% - 1px) !important;
   background-position: 0px 0px, 1px 0px, 2px 0px;
   background-repeat: no-repeat;
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..481d3fcd6e61673ad07830b54efdaf4cfdc60768
GIT binary patch
literal 4296
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..09516ab62ee751aad8bb05190feceb207128b88e
GIT binary patch
literal 11126
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
IM6N<$f>=nt3IG5A
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2e9fdd7ebcad9a0933b339ffc43564b8cccae4ac
GIT binary patch
literal 6639
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..61f55f60fff5f5f044d6449bdb08d1a9def95c4a
GIT binary patch
literal 18370
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
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -98,16 +98,20 @@ browser.jar:
         skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
         skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
         skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
         skin/classic/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
         skin/classic/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
 *       skin/classic/browser/customizableui/panelUIOverlay.css       (customizableui/panelUIOverlay.css)
         skin/classic/browser/customizableui/subView-arrow-back-inverted.png  (../shared/customizableui/subView-arrow-back-inverted.png)
         skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
+        skin/classic/browser/customizableui/whimsy.png  (../shared/customizableui/whimsy.png)
+        skin/classic/browser/customizableui/whimsy@2x.png  (../shared/customizableui/whimsy@2x.png)
+        skin/classic/browser/customizableui/whimsy-bw.png  (../shared/customizableui/whimsy-bw.png)
+        skin/classic/browser/customizableui/whimsy-bw@2x.png  (../shared/customizableui/whimsy-bw@2x.png)
 *       skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
         skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
         skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
         skin/classic/browser/downloads/download-glow.png             (downloads/download-glow.png)
         skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png   (downloads/download-glow-menuPanel-XPVista7.png)
         skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
         skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
         skin/classic/browser/downloads/download-summary.png          (downloads/download-summary.png)
@@ -426,16 +430,20 @@ browser.jar:
         skin/classic/aero/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
         skin/classic/aero/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
         skin/classic/aero/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
         skin/classic/aero/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
         skin/classic/aero/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
 *       skin/classic/aero/browser/customizableui/panelUIOverlay.css  (customizableui/panelUIOverlay-aero.css)
         skin/classic/aero/browser/customizableui/subView-arrow-back-inverted.png  (../shared/customizableui/subView-arrow-back-inverted.png)
         skin/classic/aero/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
+        skin/classic/aero/browser/customizableui/whimsy.png  (../shared/customizableui/whimsy.png)
+        skin/classic/aero/browser/customizableui/whimsy@2x.png  (../shared/customizableui/whimsy@2x.png)
+        skin/classic/aero/browser/customizableui/whimsy-bw.png  (../shared/customizableui/whimsy-bw.png)
+        skin/classic/aero/browser/customizableui/whimsy-bw@2x.png  (../shared/customizableui/whimsy-bw@2x.png)
 *       skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css)
         skin/classic/aero/browser/downloads/buttons.png              (downloads/buttons-aero.png)
         skin/classic/aero/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
         skin/classic/aero/browser/downloads/download-glow.png        (downloads/download-glow.png)
         skin/classic/aero/browser/downloads/download-glow-menuPanel.png   (downloads/download-glow-menuPanel.png)
         skin/classic/aero/browser/downloads/download-glow-menuPanel-XPVista7.png   (downloads/download-glow-menuPanel-XPVista7.png)
         skin/classic/aero/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
         skin/classic/aero/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
--- a/content/media/omx/OMXCodecWrapper.cpp
+++ b/content/media/omx/OMXCodecWrapper.cpp
@@ -225,16 +225,82 @@ ConvertPlanarYCbCrToNV12(const PlanarYCb
       vSrc += vPixStride;
     }
     // Pick next source line.
     u += lineStride;
     v += lineStride;
   }
 }
 
+// Convert pixels in graphic buffer to NV12 format. aSource is the layer image
+// containing source graphic buffer, and aDestination is the destination of
+// conversion. Currently only 2 source format are supported:
+// - NV21/HAL_PIXEL_FORMAT_YCrCb_420_SP (from camera preview window).
+// - YV12/HAL_PIXEL_FORMAT_YV12 (from video decoder).
+static
+void
+ConvertGrallocImageToNV12(GrallocImage* aSource, uint8_t* aDestination)
+{
+  // Get graphic buffer.
+  SurfaceDescriptor handle = aSource->GetSurfaceDescriptor();
+  SurfaceDescriptorGralloc gralloc = handle.get_SurfaceDescriptorGralloc();
+  sp<GraphicBuffer> graphicBuffer = GrallocBufferActor::GetFrom(gralloc);
+
+  int pixelFormat = graphicBuffer->getPixelFormat();
+  // Only support NV21 (from camera) or YV12 (from HW decoder output) for now.
+  NS_ENSURE_TRUE_VOID(pixelFormat == HAL_PIXEL_FORMAT_YCrCb_420_SP ||
+                      pixelFormat == HAL_PIXEL_FORMAT_YV12);
+
+  void* imgPtr = nullptr;
+  graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &imgPtr);
+  // Build PlanarYCbCrData for NV21 or YV12 buffer.
+  PlanarYCbCrData yuv;
+  switch (pixelFormat) {
+    case HAL_PIXEL_FORMAT_YCrCb_420_SP: // From camera.
+      yuv.mYChannel = static_cast<uint8_t*>(imgPtr);
+      yuv.mYSkip = 0;
+      yuv.mYSize.width = graphicBuffer->getWidth();
+      yuv.mYSize.height = graphicBuffer->getHeight();
+      yuv.mYStride = graphicBuffer->getStride();
+      // 4:2:0.
+      yuv.mCbCrSize.width = yuv.mYSize.width / 2;
+      yuv.mCbCrSize.height = yuv.mYSize.height / 2;
+      // Interleaved VU plane.
+      yuv.mCrChannel = yuv.mYChannel + (yuv.mYStride * yuv.mYSize.height);
+      yuv.mCrSkip = 1;
+      yuv.mCbChannel = yuv.mCrChannel + 1;
+      yuv.mCbSkip = 1;
+      yuv.mCbCrStride = yuv.mYStride;
+      ConvertPlanarYCbCrToNV12(&yuv, aDestination);
+      break;
+    case HAL_PIXEL_FORMAT_YV12: // From video decoder.
+      // Android YV12 format is defined in system/core/include/system/graphics.h
+      yuv.mYChannel = static_cast<uint8_t*>(imgPtr);
+      yuv.mYSkip = 0;
+      yuv.mYSize.width = graphicBuffer->getWidth();
+      yuv.mYSize.height = graphicBuffer->getHeight();
+      yuv.mYStride = graphicBuffer->getStride();
+      // 4:2:0.
+      yuv.mCbCrSize.width = yuv.mYSize.width / 2;
+      yuv.mCbCrSize.height = yuv.mYSize.height / 2;
+      yuv.mCrChannel = yuv.mYChannel + (yuv.mYStride * yuv.mYSize.height);
+      // Aligned to 16 bytes boundary.
+      yuv.mCbCrStride = (yuv.mYStride / 2 + 15) & ~0x0F;
+      yuv.mCrSkip = 0;
+      yuv.mCbChannel = yuv.mCrChannel + (yuv.mCbCrStride * yuv.mCbCrSize.height);
+      yuv.mCbSkip = 0;
+      ConvertPlanarYCbCrToNV12(&yuv, aDestination);
+      break;
+    default:
+      NS_ERROR("Unsupported input gralloc image type. Should never be here.");
+  }
+
+  graphicBuffer->unlock();
+}
+
 nsresult
 OMXVideoEncoder::Encode(const Image* aImage, int aWidth, int aHeight,
                         int64_t aTimestamp, int aInputFlags)
 {
   MOZ_ASSERT(mStarted, "Configure() should be called before Encode().");
 
   NS_ENSURE_TRUE(aWidth == mWidth && aHeight == mHeight && aTimestamp >= 0,
                  NS_ERROR_INVALID_ARG);
@@ -247,17 +313,16 @@ OMXVideoEncoder::Encode(const Image* aIm
   NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE);
 
   const sp<ABuffer>& inBuf = mInputBufs.itemAt(index);
   uint8_t* dst = inBuf->data();
   size_t dstSize = inBuf->capacity();
 
   size_t yLen = aWidth * aHeight;
   size_t uvLen = yLen / 2;
-
   // Buffer should be large enough to hold input image data.
   MOZ_ASSERT(dstSize >= yLen + uvLen);
 
   inBuf->setRange(0, yLen + uvLen);
 
   if (!aImage) {
     // Generate muted/black image directly in buffer.
     dstSize = yLen + uvLen;
@@ -268,50 +333,17 @@ OMXVideoEncoder::Encode(const Image* aIm
   } else {
     Image* img = const_cast<Image*>(aImage);
     ImageFormat format = img->GetFormat();
 
     MOZ_ASSERT(aWidth == img->GetSize().width &&
                aHeight == img->GetSize().height);
 
     if (format == ImageFormat::GRALLOC_PLANAR_YCBCR) {
-      // Get graphic buffer pointer.
-      void* imgPtr = nullptr;
-      GrallocImage* nativeImage = static_cast<GrallocImage*>(img);
-      SurfaceDescriptor handle = nativeImage->GetSurfaceDescriptor();
-      SurfaceDescriptorGralloc gralloc = handle.get_SurfaceDescriptorGralloc();
-      sp<GraphicBuffer> graphicBuffer = GrallocBufferActor::GetFrom(gralloc);
-      graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &imgPtr);
-      uint8_t* src = static_cast<uint8_t*>(imgPtr);
-
-      // Only support NV21 for now.
-      MOZ_ASSERT(graphicBuffer->getPixelFormat() ==
-                 HAL_PIXEL_FORMAT_YCrCb_420_SP);
-
-      // Build PlanarYCbCrData for NV21 buffer.
-      PlanarYCbCrData nv21;
-      // Y plane.
-      nv21.mYChannel = src;
-      nv21.mYSize.width = aWidth;
-      nv21.mYSize.height = aHeight;
-      nv21.mYStride = aWidth;
-      nv21.mYSkip = 0;
-      // Interleaved VU plane.
-      nv21.mCrChannel = src + yLen;
-      nv21.mCrSkip = 1;
-      nv21.mCbChannel = nv21.mCrChannel + 1;
-      nv21.mCbSkip = 1;
-      nv21.mCbCrStride = aWidth;
-      // 4:2:0.
-      nv21.mCbCrSize.width = aWidth / 2;
-      nv21.mCbCrSize.height = aHeight / 2;
-
-      ConvertPlanarYCbCrToNV12(&nv21, dst);
-
-      graphicBuffer->unlock();
+      ConvertGrallocImageToNV12(static_cast<GrallocImage*>(img), dst);
     } else if (format == ImageFormat::PLANAR_YCBCR) {
       ConvertPlanarYCbCrToNV12(static_cast<PlanarYCbCrImage*>(img)->GetData(),
                              dst);
     } else {
       // TODO: support RGB to YUV color conversion.
       NS_ERROR("Unsupported input image type.");
     }
   }
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -64,16 +64,24 @@ DOM4_MSG_DEF(InvalidStateError, "A mutat
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to IDBTransaction.abort.", NS_ERROR_DOM_INDEXEDDB_ABORT_ERR)
 DOM4_MSG_DEF(TimeoutError, "A lock for the transaction could not be obtained in a reasonable time.", NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR)
 DOM4_MSG_DEF(QuotaExceededError, "The current transaction exceeded its quota limitations.", NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR)
 
 /* A Non-standard IndexedDB error */
 
 DOM_MSG_DEF(NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR, "The operation failed because the database was prevented from taking an action. The operation might be able to succeed if the application performs some recovery steps and retries the entire transaction. For example, there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database.")
 
+/* FileSystem DOM errors. */
+DOM4_MSG_DEF(InvalidAccessError, "Invalid file system path.", NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR)
+DOM4_MSG_DEF(InvalidModificationError, "Failed to modify the file.", NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR)
+DOM4_MSG_DEF(NoModificationAllowedError, "Modifications are not allowed for this file", NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR)
+DOM4_MSG_DEF(AbortError, "File already exists.", NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR)
+DOM4_MSG_DEF(TypeMismatchError, "The type of the file is incompatible with the expected type.", NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR)
+DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the file system itself and not covered by any other error code.", NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR)
+
 /* DOM error codes defined by us */
 
 /* XXX string should be specified by norris */
 DOM_MSG_DEF(NS_ERROR_DOM_SECMAN_ERR, "Unable to obtain security manager")
 DOM_MSG_DEF(NS_ERROR_DOM_WRONG_TYPE_ERR, "Object is of wrong type")
 DOM_MSG_DEF(NS_ERROR_DOM_NOT_OBJECT_ERR, "Parameter is not an object")
 DOM_MSG_DEF(NS_ERROR_DOM_NOT_XPC_OBJECT_ERR, "Parameter is not a XPConnect object")
 DOM_MSG_DEF(NS_ERROR_DOM_NOT_NUMBER_ERR, "Parameter is not a number")
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -21,17 +21,16 @@
 
 #include "jsapi.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/UnixSocket.h"
-#include "mozilla/LazyIdleThread.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOM.h"
@@ -66,17 +65,16 @@
 #endif
 
 #define MOZSETTINGS_CHANGED_ID      "mozsettings-changed"
 #define BLUETOOTH_ENABLED_SETTING   "bluetooth.enabled"
 #define BLUETOOTH_DEBUGGING_SETTING "bluetooth.debugging.enabled"
 
 #define PROP_BLUETOOTH_ENABLED      "bluetooth.isEnabled"
 
-#define DEFAULT_THREAD_TIMEOUT_MS 3000
 #define DEFAULT_SHUTDOWN_TIMER_MS 5000
 
 bool gBluetoothDebugFlag = false;
 
 using namespace mozilla;
 using namespace mozilla::dom;
 USING_BLUETOOTH_NAMESPACE
 
@@ -128,19 +126,17 @@ GetAllBluetoothActors(InfallibleTArray<B
     }
   }
 }
 
 } // anonymous namespace
 
 BluetoothService::ToggleBtAck::ToggleBtAck(bool aEnabled)
   : mEnabled(aEnabled)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-}
+{ }
 
 NS_METHOD
 BluetoothService::ToggleBtAck::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // This is requested in Bug 836516. With settings this property, WLAN
   // firmware could be aware of Bluetooth has been turned on/off, so that the
@@ -174,70 +170,16 @@ BluetoothService::ToggleBtAck::Run()
   sBluetoothService->DistributeSignal(signal);
 
   // Event 'AdapterAdded' has to be fired after firing 'Enabled'
   sBluetoothService->TryFiringAdapterAdded();
 
   return NS_OK;
 }
 
-class BluetoothService::ToggleBtTask : public nsRunnable
-{
-public:
-  ToggleBtTask(bool aEnabled, bool aIsStartup)
-    : mEnabled(aEnabled)
-    , mIsStartup(aIsStartup)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    /**
-     * mEnabled: expected status of bluetooth
-     * sBluetoothService->IsEnabled(): real status of bluetooth
-     *
-     * When two values are the same, we don't switch on/off bluetooth
-     * but we still do ToggleBtAck task. One special case happens at startup
-     * stage. At startup, the initialization of BluetoothService still has to
-     * be done even if mEnabled is equal to the status of Bluetooth firmware.
-     *
-     * Please see bug 892392 for more information.
-     */
-    if (!mIsStartup && mEnabled == sBluetoothService->IsEnabledInternal()) {
-      BT_WARNING("Bluetooth has already been enabled/disabled before.");
-      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(mEnabled);
-      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-        BT_WARNING("Failed to dispatch to main thread!");
-      }
-    } else {
-      // Switch on/off bluetooth
-      if (mEnabled) {
-        if (NS_FAILED(sBluetoothService->StartInternal())) {
-          BT_WARNING("Bluetooth service failed to start!");
-          mEnabled = !mEnabled;
-        }
-      } else {
-        if (NS_FAILED(sBluetoothService->StopInternal())) {
-          BT_WARNING("Bluetooth service failed to stop!");
-          mEnabled = !mEnabled;
-        }
-      }
-    }
-
-    return NS_OK;
-  }
-
-private:
-  bool mEnabled;
-  bool mIsStartup;
-};
-
 class BluetoothService::StartupTask : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
   {
     MOZ_ASSERT(NS_IsMainThread());
@@ -446,83 +388,124 @@ BluetoothService::DistributeSignal(const
 #endif
     return;
   }
   MOZ_ASSERT(ol->Length());
   ol->Broadcast(aSignal);
 }
 
 nsresult
-BluetoothService::StartStopBluetooth(bool aStart, bool aIsStartup)
+BluetoothService::StartBluetooth(bool aIsStartup)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sInShutdown) {
-    if (aStart) {
-      // Don't try to start if we're already shutting down.
-      MOZ_ASSERT(false, "Start called while in shutdown!");
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!mBluetoothThread) {
-      // Don't create a new thread after we've begun shutdown since bluetooth
-      // can't be running.
-      return NS_OK;
-    }
-  }
-
-  if (!aStart) {
-    BluetoothProfileManagerBase* profile;
-    profile = BluetoothHfpManager::Get();
-    NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
-    if (profile->IsConnected()) {
-      profile->Disconnect(nullptr);
-    } else {
-      profile->Reset();
-    }
-
-    profile = BluetoothOppManager::Get();
-    NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
-    if (profile->IsConnected()) {
-      profile->Disconnect(nullptr);
-    }
-
-    profile = BluetoothA2dpManager::Get();
-    NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
-    if (profile->IsConnected()) {
-      profile->Disconnect(nullptr);
-    } else {
-      profile->Reset();
-    }
-
-    profile = BluetoothHidManager::Get();
-    NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
-    if (profile->IsConnected()) {
-      profile->Disconnect(nullptr);
-    } else {
-      profile->Reset();
-    }
-
-  }
-
-  if (!mBluetoothThread) {
-    mBluetoothThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
-                                          NS_LITERAL_CSTRING("Bluetooth"),
-                                          LazyIdleThread::ManualShutdown);
+    // Don't try to start if we're already shutting down.
+    MOZ_ASSERT(false, "Start called while in shutdown!");
+    return NS_ERROR_FAILURE;
   }
 
   mAdapterAddedReceived = false;
 
-  nsCOMPtr<nsIRunnable> runnable = new ToggleBtTask(aStart, aIsStartup);
-  nsresult rv = mBluetoothThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
+  /* When IsEnabled() is true, we don't switch on Bluetooth but we still
+   * send ToggleBtAck task. One special case happens at startup stage. At
+   * startup, the initialization of BluetoothService still has to be done
+   * even if Bluetooth is already enabled.
+   *
+   * Please see bug 892392 for more information.
+   */
+  if (aIsStartup || !sBluetoothService->IsEnabled()) {
+    // Switch Bluetooth on
+    if (NS_FAILED(sBluetoothService->StartInternal())) {
+      BT_WARNING("Bluetooth service failed to start!");
+    }
+  } else {
+    BT_WARNING("Bluetooth has already been enabled before.");
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(true);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+  }
 
   return NS_OK;
 }
 
+nsresult
+BluetoothService::StopBluetooth(bool aIsStartup)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothProfileManagerBase* profile;
+  profile = BluetoothHfpManager::Get();
+  NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
+  if (profile->IsConnected()) {
+    profile->Disconnect(nullptr);
+  } else {
+    profile->Reset();
+  }
+
+  profile = BluetoothOppManager::Get();
+  NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
+  if (profile->IsConnected()) {
+    profile->Disconnect(nullptr);
+  }
+
+  profile = BluetoothA2dpManager::Get();
+  NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
+  if (profile->IsConnected()) {
+    profile->Disconnect(nullptr);
+  } else {
+    profile->Reset();
+  }
+
+  profile = BluetoothHidManager::Get();
+  NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
+  if (profile->IsConnected()) {
+    profile->Disconnect(nullptr);
+  } else {
+    profile->Reset();
+  }
+
+  mAdapterAddedReceived = false;
+
+  /* When IsEnabled() is false, we don't switch off Bluetooth but we still
+   * send ToggleBtAck task. One special case happens at startup stage. At
+   * startup, the initialization of BluetoothService still has to be done
+   * even if Bluetooth is disabled.
+   *
+   * Please see bug 892392 for more information.
+   */
+  if (aIsStartup || sBluetoothService->IsEnabled()) {
+    // Switch Bluetooth off
+    if (NS_FAILED(sBluetoothService->StopInternal())) {
+      BT_WARNING("Bluetooth service failed to stop!");
+    }
+  } else {
+    BT_WARNING("Bluetooth has already been enabled/disabled before.");
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+BluetoothService::StartStopBluetooth(bool aStart, bool aIsStartup)
+{
+  nsresult rv;
+  if (aStart) {
+    rv = StartBluetooth(aIsStartup);
+  } else {
+    rv = StopBluetooth(aIsStartup);
+  }
+  return rv;
+}
+
 void
 BluetoothService::SetEnabled(bool aEnabled)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   AutoInfallibleTArray<BluetoothParent*, 10> childActors;
   GetAllBluetoothActors(childActors);
 
@@ -725,17 +708,17 @@ BluetoothService::HandleShutdown()
         }
       }
       else {
         MOZ_ASSERT(false, "Failed to initialize shutdown timer!");
       }
     }
   }
 
-  if (IsEnabled() && NS_FAILED(StartStopBluetooth(false, false))) {
+  if (IsEnabled() && NS_FAILED(StopBluetooth(false))) {
     MOZ_ASSERT(false, "Failed to deliver stop message!");
   }
 
   return NS_OK;
 }
 
 // static
 BluetoothService*
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -9,17 +9,16 @@
 
 #include "BluetoothCommon.h"
 #include "BluetoothProfileManagerBase.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 #include "nsIDOMFile.h"
 #include "nsIObserver.h"
-#include "nsIThread.h"
 #include "nsTObserverArray.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace ipc {
 class UnixSocketConsumer;
 }
 }
@@ -329,16 +328,22 @@ protected:
 
   bool
   Init();
 
   void
   Cleanup();
 
   nsresult
+  StartBluetooth(bool aIsStartup);
+
+  nsresult
+  StopBluetooth(bool aIsStartup);
+
+  nsresult
   StartStopBluetooth(bool aStart, bool aIsStartup);
 
   /**
    * Platform specific startup functions go here. Usually deals with member
    * variables, so not static. Guaranteed to be called outside of main thread.
    *
    * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
    */
@@ -350,25 +355,16 @@ protected:
    * variables, so not static. Guaranteed to be called outside of main thread.
    *
    * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   StopInternal() = 0;
 
   /**
-   * Platform specific startup functions go here. Usually deals with member
-   * variables, so not static. Guaranteed to be called outside of main thread.
-   *
-   * @return true if Bluetooth is enabled, false otherwise
-   */
-  virtual bool
-  IsEnabledInternal() = 0;
-
-  /**
    * Called when XPCOM first creates this service.
    */
   virtual nsresult
   HandleStartup();
 
   /**
    * Called when the startup settings check has completed.
    */
@@ -398,22 +394,14 @@ protected:
   typedef nsClassHashtable<nsStringHashKey, BluetoothSignalObserverList >
   BluetoothSignalObserverTable;
 
   BluetoothSignalObserverTable mBluetoothSignalObserverTable;
 
   bool mEnabled;
 
 private:
-  /**
-   * Due to the fact that the startup and shutdown of the Bluetooth system
-   * can take an indefinite amount of time, a command thread is created
-   * that can run blocking calls. The thread is not intended for regular
-   * Bluetooth operations though.
-   */
-  nsCOMPtr<nsIThread> mBluetoothThread;
-
   bool mAdapterAddedReceived;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -661,17 +661,17 @@ EnsureBluetoothHalLoad()
   }
 
   return true;
 }
 
 static nsresult
 StartStopGonkBluetooth(bool aShouldEnable)
 {
-  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
 
   if (sIsBtEnabled == aShouldEnable) {
     // Keep current enable status
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(sIsBtEnabled);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
@@ -732,17 +732,17 @@ BluetoothServiceBluedroid::BluetoothServ
 
 BluetoothServiceBluedroid::~BluetoothServiceBluedroid()
 {
 }
 
 nsresult
 BluetoothServiceBluedroid::StartInternal()
 {
-  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread());
 
   nsresult ret = StartStopGonkBluetooth(true);
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
@@ -750,39 +750,31 @@ BluetoothServiceBluedroid::StartInternal
   }
 
   return ret;
 }
 
 nsresult
 BluetoothServiceBluedroid::StopInternal()
 {
-  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread());
 
   nsresult ret = StartStopGonkBluetooth(false);
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(true);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     BT_LOGR("Error");
   }
 
   return ret;
 }
 
-bool
-BluetoothServiceBluedroid::IsEnabledInternal()
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  return sIsBtEnabled;
-}
-
 nsresult
 BluetoothServiceBluedroid::GetDefaultAdapterPathInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<BluetoothReplyRunnable> runnable(aRunnable);
 
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -19,17 +19,16 @@ class BluetoothServiceBluedroid : public
 public:
   static const bt_interface_t* GetBluetoothInterface();
 
   BluetoothServiceBluedroid();
   ~BluetoothServiceBluedroid();
 
   virtual nsresult StartInternal();
   virtual nsresult StopInternal();
-  virtual bool IsEnabledInternal();
 
   virtual nsresult GetDefaultAdapterPathInternal(
                                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
                                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult GetPairedDevicePropertiesInternal(
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -37,16 +37,17 @@
 #include "nsDataHashtable.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/Hal.h"
 #include "mozilla/ipc/UnixSocket.h"
 #include "mozilla/ipc/DBusUtils.h"
 #include "mozilla/ipc/RawDBusConnection.h"
+#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/NullPtr.h"
 #include "mozilla/StaticMutex.h"
 
 #if defined(MOZ_WIDGET_GONK)
 #include "cutils/properties.h"
 #include <dlfcn.h>
 #endif
@@ -82,16 +83,18 @@ USING_BLUETOOTH_NAMESPACE
 
 /**
  * To not lock Bluetooth switch button on Settings UI because of any accident,
  * we will force disabling Bluetooth 5 seconds after the user requesting to
  * turn off Bluetooth.
  */
 #define TIMEOUT_FORCE_TO_DISABLE_BT 5
 
+#define BT_LAZY_THREAD_TIMEOUT_MS 3000
+
 #ifdef MOZ_WIDGET_GONK
 class Bluedroid
 {
   struct ScopedDlHandleTraits
   {
     typedef void* type;
     static void* empty()
     {
@@ -320,16 +323,28 @@ BluetoothDBusService::BluetoothDBusServi
 }
 
 BluetoothDBusService::~BluetoothDBusService()
 {
   sStopBluetoothMonitor = nullptr;
   sGetPropertyMonitor = nullptr;
 }
 
+nsresult
+BluetoothDBusService::DispatchToBtThread(nsIRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mBluetoothThread) {
+    mBluetoothThread = new LazyIdleThread(BT_LAZY_THREAD_TIMEOUT_MS,
+                                          NS_LITERAL_CSTRING("BluetoothDBusService"),
+                                          LazyIdleThread::ManualShutdown);
+  }
+  return mBluetoothThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
 static bool
 GetConnectedDevicesFilter(const BluetoothValue& aValue)
 {
   // We don't have to filter device here
   return true;
 }
 
 static bool
@@ -1891,79 +1906,93 @@ public:
     }
   }
 
 private:
   RawDBusConnection* mConnection;
   bool mQueryDefaultAdapter;
 };
 
+class StartBluetoothRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+    // This could block. It should never be run on the main thread.
+    MOZ_ASSERT(!NS_IsMainThread()); // BT thread
+
+#ifdef MOZ_WIDGET_GONK
+    if (!sBluedroid.Enable()) {
+      BT_WARNING("Bluetooth not available.");
+      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
+      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
+        BT_WARNING("Failed to dispatch to main thread!");
+      }
+      return NS_ERROR_FAILURE;
+    }
+#endif
+
+    RawDBusConnection* connection = new RawDBusConnection();
+    nsresult rv = connection->EstablishDBusConnection();
+    if (NS_FAILED(rv)) {
+      BT_WARNING("Failed to establish connection to BlueZ daemon");
+      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
+      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
+        BT_WARNING("Failed to dispatch to main thread!");
+      }
+      return NS_ERROR_FAILURE;
+    }
+
+    DBusError err;
+    dbus_error_init(&err);
+
+    // Set which messages will be processed by this dbus connection.
+    // Since we are maintaining a single thread for all the DBus bluez
+    // signals we want, register all of them in this thread at startup.
+    // The event handler will sort the destinations out as needed.
+    for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) {
+      dbus_bus_add_match(connection->GetConnection(),
+                         sBluetoothDBusSignals[i],
+                         &err);
+      if (dbus_error_is_set(&err)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+      }
+    }
+
+    // Add a filter for all incoming messages_base
+    if (!dbus_connection_add_filter(connection->GetConnection(),
+                                    EventFilter, nullptr, nullptr)) {
+      BT_WARNING("Cannot create DBus Event Filter for DBus Thread!");
+      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
+      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
+        BT_WARNING("Failed to dispatch to main thread!");
+      }
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!sPairingReqTable) {
+      sPairingReqTable = new nsDataHashtable<nsStringHashKey, DBusMessage* >;
+    }
+
+    Task* task = new StartDBusConnectionTask(connection, sAdapterPath.IsEmpty());
+    DispatchToDBusThread(task);
+
+    return NS_OK;
+  }
+};
+
 nsresult
 BluetoothDBusService::StartInternal()
 {
-  // This could block. It should never be run on the main thread.
-  MOZ_ASSERT(!NS_IsMainThread()); // BT thread
-
-#ifdef MOZ_WIDGET_GONK
-  if (!sBluedroid.Enable()) {
-    BT_WARNING("Bluetooth not available.");
-    nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
-    if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-      BT_WARNING("Failed to dispatch to main thread!");
-    }
-    return NS_ERROR_FAILURE;
-  }
-#endif
-
-  RawDBusConnection* connection = new RawDBusConnection();
-  nsresult rv = connection->EstablishDBusConnection();
+  nsRefPtr<nsRunnable> runnable = new StartBluetoothRunnable();
+  nsresult rv = DispatchToBtThread(runnable);
   if (NS_FAILED(rv)) {
-    BT_WARNING("Failed to establish connection to BlueZ daemon");
-    nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
-    if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-      BT_WARNING("Failed to dispatch to main thread!");
-    }
-    return NS_ERROR_FAILURE;
+    BT_WARNING("Failed to dispatch to BT thread!");
   }
-
-  DBusError err;
-  dbus_error_init(&err);
-
-  // Set which messages will be processed by this dbus connection.
-  // Since we are maintaining a single thread for all the DBus bluez
-  // signals we want, register all of them in this thread at startup.
-  // The event handler will sort the destinations out as needed.
-  for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) {
-    dbus_bus_add_match(connection->GetConnection(),
-                       sBluetoothDBusSignals[i],
-                       &err);
-    if (dbus_error_is_set(&err)) {
-      LOG_AND_FREE_DBUS_ERROR(&err);
-    }
-  }
-
-  // Add a filter for all incoming messages_base
-  if (!dbus_connection_add_filter(connection->GetConnection(),
-                                  EventFilter, nullptr, nullptr)) {
-    BT_WARNING("Cannot create DBus Event Filter for DBus Thread!");
-    nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
-    if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-      BT_WARNING("Failed to dispatch to main thread!");
-    }
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!sPairingReqTable) {
-    sPairingReqTable = new nsDataHashtable<nsStringHashKey, DBusMessage* >;
-  }
-
-  Task* task = new StartDBusConnectionTask(connection, sAdapterPath.IsEmpty());
-  DispatchToDBusThread(task);
-
-  return NS_OK;
+  return rv;
 }
 
 PLDHashOperator
 UnrefDBusMessages(const nsAString& key, DBusMessage* value, void* arg)
 {
   dbus_message_unref(value);
 
   return PL_DHASH_NEXT;
@@ -1986,106 +2015,109 @@ public:
     // DBusWatch will be removed and free'd.
     delete mConnection;
   }
 
 private:
   RawDBusConnection* mConnection;
 };
 
-nsresult
-BluetoothDBusService::StopInternal()
+class StopBluetoothRunnable MOZ_FINAL : public nsRunnable
 {
-  // This could block. It should never be run on the main thread.
-  MOZ_ASSERT(!NS_IsMainThread());
-
+public:
+  NS_IMETHOD Run()
   {
-    MonitorAutoLock lock(*sStopBluetoothMonitor);
-    if (sConnectedDeviceCount > 0) {
-      lock.Wait(PR_SecondsToInterval(TIMEOUT_FORCE_TO_DISABLE_BT));
+    // This could block. It should never be run on the main thread.
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    {
+      MonitorAutoLock lock(*sStopBluetoothMonitor);
+      if (sConnectedDeviceCount > 0) {
+        lock.Wait(PR_SecondsToInterval(TIMEOUT_FORCE_TO_DISABLE_BT));
+      }
+    }
+
+    if (!sDBusConnection) {
+      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
+      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
+        BT_WARNING("Failed to dispatch to main thread!");
+      }
+      return NS_OK;
+    }
+
+    DBusError err;
+    dbus_error_init(&err);
+    for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) {
+      dbus_bus_remove_match(sDBusConnection->GetConnection(),
+                            sBluetoothDBusSignals[i],
+                            &err);
+      if (dbus_error_is_set(&err)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+      }
     }
-  }
-
-  if (!sDBusConnection) {
+
+    dbus_connection_remove_filter(sDBusConnection->GetConnection(),
+                                  EventFilter, nullptr);
+
+    if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(),
+                                                KEY_LOCAL_AGENT)) {
+      BT_WARNING("%s: Can't unregister object path %s for agent!",
+          __FUNCTION__, KEY_LOCAL_AGENT);
+    }
+
+    if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(),
+                                                KEY_REMOTE_AGENT)) {
+      BT_WARNING("%s: Can't unregister object path %s for agent!",
+          __FUNCTION__, KEY_REMOTE_AGENT);
+    }
+
+    // unref stored DBusMessages before clear the hashtable
+    sPairingReqTable->EnumerateRead(UnrefDBusMessages, nullptr);
+    sPairingReqTable->Clear();
+
+    sIsPairing = 0;
+    sConnectedDeviceCount = 0;
+
+    sAuthorizedServiceClass.Clear();
+    sControllerArray.Clear();
+
+    RawDBusConnection* connection = sDBusConnection;
+    sDBusConnection = nullptr;
+
+    DispatchToDBusThread(new DeleteDBusConnectionTask(connection));
+
+#ifdef MOZ_WIDGET_GONK
+    MOZ_ASSERT(sBluedroid.IsEnabled());
+    if (!sBluedroid.Disable()) {
+      nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(true);
+      if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
+        BT_WARNING("Failed to dispatch to main thread!");
+      }
+      return NS_ERROR_FAILURE;
+    }
+#endif
+
     nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
+
     return NS_OK;
   }
-
-  DBusError err;
-  dbus_error_init(&err);
-  for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) {
-    dbus_bus_remove_match(sDBusConnection->GetConnection(),
-                          sBluetoothDBusSignals[i],
-                          &err);
-    if (dbus_error_is_set(&err)) {
-      LOG_AND_FREE_DBUS_ERROR(&err);
-    }
-  }
-
-  dbus_connection_remove_filter(sDBusConnection->GetConnection(),
-                                EventFilter, nullptr);
-
-  if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(),
-                                              KEY_LOCAL_AGENT)) {
-    BT_WARNING("%s: Can't unregister object path %s for agent!",
-        __FUNCTION__, KEY_LOCAL_AGENT);
-  }
-
-  if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(),
-                                              KEY_REMOTE_AGENT)) {
-    BT_WARNING("%s: Can't unregister object path %s for agent!",
-        __FUNCTION__, KEY_REMOTE_AGENT);
+};
+
+nsresult
+BluetoothDBusService::StopInternal()
+{
+  nsRefPtr<nsRunnable> runnable = new StopBluetoothRunnable();
+  nsresult rv = DispatchToBtThread(runnable);
+  if (NS_FAILED(rv)) {
+    BT_WARNING("Failed to dispatch to BT thread!");
   }
-
-  // unref stored DBusMessages before clear the hashtable
-  sPairingReqTable->EnumerateRead(UnrefDBusMessages, nullptr);
-  sPairingReqTable->Clear();
-
-  sIsPairing = 0;
-  sConnectedDeviceCount = 0;
-
-  sAuthorizedServiceClass.Clear();
-  sControllerArray.Clear();
-
-  RawDBusConnection* connection = sDBusConnection;
-  sDBusConnection = nullptr;
-
-  DispatchToDBusThread(new DeleteDBusConnectionTask(connection));
-
-#ifdef MOZ_WIDGET_GONK
-  MOZ_ASSERT(sBluedroid.IsEnabled());
-  if (!sBluedroid.Disable()) {
-    nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(true);
-    if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-      BT_WARNING("Failed to dispatch to main thread!");
-    }
-    return NS_ERROR_FAILURE;
-  }
-#endif
-
-  nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(false);
-  if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
-    BT_WARNING("Failed to dispatch to main thread!");
-  }
-  return NS_OK;
-}
-
-bool
-BluetoothDBusService::IsEnabledInternal()
-{
-  MOZ_ASSERT(!NS_IsMainThread()); // BT thread
-
-#ifdef MOZ_WIDGET_GONK
-  return sBluedroid.IsEnabled();
-#else
-  return mEnabled;
-#endif
+  return rv;
 }
 
 class DefaultAdapterPathReplyHandler : public DBusReplyHandler
 {
 public:
   DefaultAdapterPathReplyHandler(BluetoothReplyRunnable* aRunnable)
     : mRunnable(aRunnable)
   {
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_bluetooth_bluetoothdbusservice_h__
 #define mozilla_dom_bluetooth_bluetoothdbusservice_h__
 
 #include "mozilla/Attributes.h"
 #include "BluetoothCommon.h"
 #include "mozilla/ipc/RawDBusConnection.h"
 #include "BluetoothService.h"
+#include "nsIThread.h"
 
 class DBusMessage;
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 /**
  * BluetoothDBusService is the implementation of BluetoothService for DBus on
  * linux/android/B2G. Function comments are in BluetoothService.h
@@ -39,24 +40,24 @@ public:
     EVENT_SYSTEM_STATUS_CHANGED              = 0x07,
     EVENT_PLAYER_APPLICATION_SETTING_CHANGED = 0x08,
     EVENT_UNKNOWN
   };
 
   BluetoothDBusService();
   ~BluetoothDBusService();
 
+  nsresult DispatchToBtThread(nsIRunnable* aRunnable);
+
   bool IsReady();
 
   virtual nsresult StartInternal() MOZ_OVERRIDE;
 
   virtual nsresult StopInternal() MOZ_OVERRIDE;
 
-  virtual bool IsEnabledInternal() MOZ_OVERRIDE;
-
   virtual nsresult GetDefaultAdapterPathInternal(
                                              BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                              BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
@@ -194,13 +195,21 @@ private:
                                   BluetoothReplyRunnable* aRunnable);
 
   void UpdateNotification(ControlEventId aEventId, uint64_t aData);
 
   nsresult SendAsyncDBusMessage(const nsAString& aObjectPath,
                                 const char* aInterface,
                                 const nsAString& aMessage,
                                 mozilla::ipc::DBusReplyCallback aCallback);
+
+  /**
+   * Due to the fact that the startup and shutdown of the Bluetooth system
+   * can take an indefinite amount of time, a separate thread is used for
+   * running blocking blocking calls. The thread is not intended for regular
+   * Bluetooth operations though.
+   */
+  nsCOMPtr<nsIThread> mBluetoothThread;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -385,22 +385,16 @@ BluetoothServiceChildProcess::StartInter
 
 nsresult
 BluetoothServiceChildProcess::StopInternal()
 {
   MOZ_CRASH("This should never be called!");
 }
 
 bool
-BluetoothServiceChildProcess::IsEnabledInternal()
-{
-  MOZ_CRASH("This should never be called!");
-}
-
-bool
 BluetoothServiceChildProcess::IsConnected(uint16_t aServiceUuid)
 {
   MOZ_CRASH("This should never be called!");
 }
 
 nsresult
 BluetoothServiceChildProcess::SendSinkMessage(const nsAString& aDeviceAddresses,
                                               const nsAString& aMessage)
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -202,20 +202,16 @@ private:
   // This method should never be called.
   virtual nsresult
   StartInternal() MOZ_OVERRIDE;
 
   // This method should never be called.
   virtual nsresult
   StopInternal() MOZ_OVERRIDE;
 
-  // This method should never be called.
-  virtual bool
-  IsEnabledInternal() MOZ_OVERRIDE;
-
   bool
   IsSignalRegistered(const nsAString& aNodeName) {
     return !!mBluetoothSignalObserverTable.Get(aNodeName);
   }
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -26,16 +26,18 @@
 class DeviceStorageFile;
 class nsIInputStream;
 
 namespace mozilla {
 namespace dom {
 class DeviceStorageEnumerationParameters;
 class DOMCursor;
 class DOMRequest;
+class Promise;
+class DeviceStorageFileSystem;
 } // namespace dom
 namespace ipc {
 class FileDescriptor;
 }
 } // namespace mozilla
 
 class DeviceStorageFile MOZ_FINAL
   : public nsISupports {
@@ -152,16 +154,18 @@ class nsDOMDeviceStorage MOZ_FINAL
   , public nsIDOMDeviceStorage
   , public nsIObserver
 {
   typedef mozilla::ErrorResult ErrorResult;
   typedef mozilla::dom::DeviceStorageEnumerationParameters
     EnumerationParameters;
   typedef mozilla::dom::DOMCursor DOMCursor;
   typedef mozilla::dom::DOMRequest DOMRequest;
+  typedef mozilla::dom::Promise Promise;
+  typedef mozilla::dom::DeviceStorageFileSystem DeviceStorageFileSystem;
 public:
   typedef nsTArray<nsString> VolumeNameArray;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMDEVICESTORAGE
 
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMEVENTTARGET
@@ -250,16 +254,19 @@ public:
   already_AddRefed<DOMRequest> StorageStatus(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Mount(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Unmount(ErrorResult& aRv);
 
   bool Default();
 
   // Uses XPCOM GetStorageName
 
+  already_AddRefed<Promise>
+  GetRoot();
+
   static void
   CreateDeviceStorageFor(nsPIDOMWindow* aWin,
                          const nsAString& aType,
                          nsDOMDeviceStorage** aStore);
 
   static void
   CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
                           const nsAString& aType,
@@ -327,11 +334,13 @@ private:
 #endif
 
   // nsIDOMDeviceStorage.type
   enum {
       DEVICE_STORAGE_TYPE_DEFAULT = 0,
       DEVICE_STORAGE_TYPE_SHARED,
       DEVICE_STORAGE_TYPE_EXTERNAL
   };
+
+  nsRefPtr<DeviceStorageFileSystem> mFileSystem;
 };
 
 #endif
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -6,21 +6,25 @@
 
 #include "nsDeviceStorage.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DeviceStorageBinding.h"
+#include "mozilla/dom/DeviceStorageFileSystem.h"
 #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/PContentPermissionRequestChild.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Services.h"
 
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
@@ -992,24 +996,17 @@ DeviceStorageFile::IsSafePath(const nsAS
       return false;
     }
   }
   return true;
 }
 
 void
 DeviceStorageFile::NormalizeFilePath() {
-#if defined(XP_WIN)
-  char16_t* cur = mPath.BeginWriting();
-  char16_t* end = mPath.EndWriting();
-  for (; cur < end; ++cur) {
-    if (char16_t('\\') == *cur)
-      *cur = char16_t('/');
-  }
-#endif
+  FileSystemUtils::LocalPathToNormalizedPath(mPath, mPath);
 }
 
 void
 DeviceStorageFile::AppendRelativePath(const nsAString& aPath) {
   if (!mFile) {
     return;
   }
   if (!IsSafePath(aPath)) {
@@ -1017,33 +1014,19 @@ DeviceStorageFile::AppendRelativePath(co
     // valid and return PERMISSION_DENIED if a non-safe path is entered.
     // This check is done in the parent and prevents a compromised
     // child from bypassing the check. It shouldn't be possible for this
     // code path to be taken with a non-compromised child.
     NS_WARNING("Unsafe path detected - ignoring");
     NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get());
     return;
   }
-#if defined(XP_WIN)
-  // replace forward slashes with backslashes,
-  // since nsLocalFileWin chokes on them
-  nsString temp;
-  temp.Assign(aPath);
-
-  char16_t* cur = temp.BeginWriting();
-  char16_t* end = temp.EndWriting();
-
-  for (; cur < end; ++cur) {
-    if (char16_t('/') == *cur)
-      *cur = char16_t('\\');
-  }
-  mFile->AppendRelativePath(temp);
-#else
-  mFile->AppendRelativePath(aPath);
-#endif
+  nsString localPath;
+  FileSystemUtils::NormalizedPathToLocalPath(aPath, localPath);
+  mFile->AppendRelativePath(localPath);
 }
 
 nsresult
 DeviceStorageFile::CreateFileDescriptor(FileDescriptor& aFileDescriptor)
 {
   ScopedPRFileDesc fd;
   nsresult rv = mFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
                                         0660, &fd.rwget());
@@ -3174,16 +3157,21 @@ nsDOMDeviceStorage::~nsDOMDeviceStorage(
 {
 }
 
 void
 nsDOMDeviceStorage::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mFileSystem) {
+    mFileSystem->Shutdown();
+    mFileSystem = nullptr;
+  }
+
   if (!mStorageName.IsEmpty()) {
     UnregisterForSDCardChanges(this);
   }
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->RemoveObserver(this, "file-watcher-update");
   obs->RemoveObserver(this, "disk-space-watcher");
 }
@@ -3912,16 +3900,26 @@ nsDOMDeviceStorage::CreateFileDescriptor
 bool
 nsDOMDeviceStorage::Default()
 {
   nsString defaultStorageName;
   GetDefaultStorageName(mStorageType, defaultStorageName);
   return mStorageName.Equals(defaultStorageName);
 }
 
+already_AddRefed<Promise>
+nsDOMDeviceStorage::GetRoot()
+{
+  if (!mFileSystem) {
+    mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
+    mFileSystem->Init(this);
+  }
+  return mozilla::dom::Directory::GetRoot(mFileSystem);
+}
+
 NS_IMETHODIMP
 nsDOMDeviceStorage::GetDefault(bool* aDefault)
 {
   *aDefault = Default();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/devicestorage/test/chrome.ini
+++ b/dom/devicestorage/test/chrome.ini
@@ -1,3 +1,4 @@
 [DEFAULT]
 
 [test_app_permissions.html]
+[test_fs_app_permissions.html]
--- a/dom/devicestorage/test/mochitest.ini
+++ b/dom/devicestorage/test/mochitest.ini
@@ -18,8 +18,13 @@ support-files = devicestorage_common.js
 [test_enumerateOptions.html]
 [test_freeSpace.html]
 [test_lastModificationFilter.html]
 [test_overwrite.html]
 [test_sanity.html]
 [test_usedSpace.html]
 [test_watch.html]
 [test_watchOther.html]
+
+# FileSystem API tests
+[test_fs_basic.html]
+[test_fs_createDirectory.html]
+[test_fs_get.html]
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_fs_app_permissions.html
@@ -0,0 +1,428 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=910412
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Permission test of FileSystem API for Device Storage</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>
+<a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+function randomFilename(l) {
+  let set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ";
+  let result = "";
+  for (let i=0; i<l; i++) {
+    let r = Math.floor(set.length * Math.random());
+    result += set.substring(r, r + 1);
+  }
+  return result;
+}
+
+let MockPermissionPrompt = SpecialPowers.MockPermissionPrompt;
+MockPermissionPrompt.init();
+
+SimpleTest.waitForExplicitFinish();
+
+function TestCreateDirectory(iframe, data) {
+  function cbError(e) {
+    is(e.name, "SecurityError", "[TestCreateDirectory] Should fire a SecurityError for type " + data.type);
+    is(data.shouldPass, false, "[TestCreateDirectory] Error callback was called for type " + data.type + '. Error: ' + e.name);
+    testComplete(iframe, data);
+  }
+
+  let storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "[TestCreateDirectory] Should be able to get storage object for " + data.type);
+
+  if (!storage) {
+    testComplete(iframe, data);
+    return;
+  }
+
+  storage.getRoot().then(function(root) {
+    is(data.shouldPass, true, "[TestCreateDirectory] Success callback was called for type " + data.type);
+    let filename = randomFilename(100);
+    root.createDirectory(filename).then(function(d) {
+      let passed = d && (d.name === filename);
+      is(data.shouldPass, passed, "[TestCreateDirectory] Success callback was called for type " + data.type);
+      testComplete(iframe, data);
+    }, cbError);
+  }, cbError);
+}
+
+function TestGet(iframe, data) {
+  function cbError(e) {
+    is(e.name, "SecurityError", "[TestGet] Should fire a SecurityError for type " + data.type);
+    is(data.shouldPass, false, "[TestGet] Error callback was called for type " + data.type + '. Error: ' + e.name);
+    testComplete(iframe, data);
+  }
+
+  createTestFile(data.fileExtension);
+
+  let storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "[TestGet] Should be able to get storage object for " + data.type);
+
+  if (!storage) {
+    testComplete(iframe, data);
+    return;
+  }
+
+  storage.getRoot().then(function(root) {
+    ok(true, "[TestGet] Success callback of getRoot was called for type " + data.type);
+    root.get("testfile" + data.fileExtension).then(function() {
+      is(data.shouldPass, true, "[TestGet] Success callback was called for type " + data.type);
+      testComplete(iframe, data);
+    }, cbError);
+  }, cbError);
+}
+
+let gTestUri = "https://example.com/tests/dom/devicestorage/test/test_fs_app_permissions.html"
+
+let gData = [
+
+  // Directory#get
+
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    shouldPass: false,
+    fileExtension: '.png',
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    fileExtension: '.ogv',
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    fileExtension: '.ogg',
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.ogg',
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.txt',
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: false,
+    fileExtension: '.txt',
+    test: TestGet
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.txt',
+
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
+  // Directory#createDirectory
+
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    shouldPass: false,
+    test: TestCreateDirectory
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    test: TestCreateDirectory
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    test: TestCreateDirectory
+  },
+  {
+    type: 'sdcard',
+    shouldPass: false,
+    test: TestCreateDirectory
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    permissions: ["device-storage:videos"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    permissions: ["device-storage:music"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestCreateDirectory
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestCreateDirectory
+  }
+
+];
+
+function setupTest(iframe,data) {
+  if (data.permissions) {
+    for (let j in data.permissions) {
+      SpecialPowers.addPermission(data.permissions[j], true, iframe.contentDocument);
+    }
+  }
+}
+
+function testComplete(iframe, data) {
+  if (data.permissions) {
+    for (let j in data.permissions) {
+      SpecialPowers.removePermission(data.permissions[j], iframe.contentDocument);
+    }
+  }
+
+  document.getElementById('content').removeChild(iframe);
+
+  if (gData.length == 0) {
+    SimpleTest.finish();
+  } else {
+    gTestRunner.next();
+  }
+}
+
+function runTest() {
+  while (gData.length > 0) {
+    let iframe = document.createElement('iframe');
+    let data = gData.shift();
+
+    iframe.setAttribute('mozbrowser', '');
+    if (data.app) {
+      iframe.setAttribute('mozapp', data.app);
+    }
+
+    iframe.src = gTestUri;
+
+    iframe.addEventListener('load', function(e) {
+      setupTest(iframe, data)
+      data.test(iframe, data);
+    });
+
+    document.getElementById('content').appendChild(iframe);
+    yield undefined;
+  }
+}
+
+function createTestFile(extension) {
+  try {
+    const Cc = SpecialPowers.Cc;
+    const Ci = SpecialPowers.Ci;
+    let directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+    let f = directoryService.get("TmpD", Ci.nsIFile);
+    f.appendRelativePath("device-storage-testing");
+    f.remove(true);
+    f.appendRelativePath("testfile" + extension);
+    f.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+  } catch(e) {}
+}
+
+let gTestRunner = runTest();
+SpecialPowers.addPermission("browser", true, gTestUri);
+
+// We are more permissive with CSP in our testing environment....
+const DEFAULT_CSP_PRIV = "default-src *; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'";
+const DEFAULT_CSP_CERT = "default-src *; script-src 'self'; style-src 'self'; object-src 'none'";
+
+SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true],
+                                   ["device.storage.enabled", true],
+                                   ["device.storage.testing", true],
+                                   ["device.storage.prompt.testing", false],
+                                   ["security.apps.privileged.CSP.default", DEFAULT_CSP_PRIV],
+                                   ["security.apps.certified.CSP.default", DEFAULT_CSP_CERT]]},
+  function() { gTestRunner.next(); });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_fs_basic.html
@@ -0,0 +1,70 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html> <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=910412
+-->
+<head>
+  <title>Test for the FileSystem API for device storage</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="devicestorage_common.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+var gFileName = randomFilename(12);
+
+// The root directory object.
+var gRoot;
+
+function getRootSuccess(r) {
+  ok(r && r.name === storage.storageName, "Failed to get the root directory.");
+
+  gRoot = r;
+
+  // Create a new directory under the root.
+  gRoot.createDirectory(gFileName).then(createDirectorySuccess, cbError);
+}
+
+function createDirectorySuccess(d) {
+  ok(d.name === gFileName, "Failed to create directory: name mismatch.");
+
+  // Get the new created directory from the root.
+  gRoot.get(gFileName).then(getSuccess, cbError);
+}
+
+function getSuccess(d) {
+  ok(d.name === gFileName, "Should get directory - " + gFileName + ".");
+  devicestorage_cleanup();
+}
+
+function cbError(e) {
+  ok(false,  "Should not arrive here! Error: " + e.name);
+  devicestorage_cleanup();
+}
+
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+var storage = navigator.getDeviceStorage("pictures");
+ok(storage, "Should have gotten a storage");
+
+var promise = storage.getRoot();
+ok(promise, "Should have a non-null promise");
+
+promise.then(getRootSuccess, cbError);
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_fs_createDirectory.html
@@ -0,0 +1,105 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html> <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=910412
+-->
+<head>
+  <title>Test createDirectory of the FileSystem API for device storage</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="devicestorage_common.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+// The root directory object.
+var gRoot;
+var gTestCount = 0;
+var gPath = '';
+var gName = '';
+
+function testCreateDirectory(rootDir, path) {
+  rootDir.createDirectory(path).then(createDirectorySuccess, cbError);
+}
+
+function createDirectorySuccess(d) {
+  ok(d.name === gName, "Failed to create directory: name mismatch.");
+
+  // Get the new created directory from the root.
+  gRoot.get(gPath).then(getSuccess, cbError);
+}
+
+function getSuccess(d) {
+  ok(d.name === gName, "Should get directory - " + (gPath || "[root]") + ".");
+  switch (gTestCount) {
+    case 0:
+      gRoot = d;
+      // Create a new directory under the root.
+      gName = gPath = randomFilename(12);
+      testCreateDirectory(gRoot, gName);
+      break;
+    case 1:
+      // Create a sub-directory under current directory.
+      gName = randomFilename(12);
+      gPath = gPath + '/' + gName;
+      testCreateDirectory(d, gName);
+      break;
+    case 2:
+      // Create directory with an existing path.
+      gRoot.createDirectory(gPath).then(function(what) {
+        ok(false, "Should not overwrite an existing directory.");
+        devicestorage_cleanup();
+      }, function(e) {
+        ok(true, "Creating directory should fail if it already exists.");
+
+        // Create a directory whose intermediate directory doesn't exit.
+        gName = randomFilename(12);
+        gPath = 'sub1/sub2/' + gName;
+        testCreateDirectory(gRoot, gPath);
+      });
+      break;
+    default:
+      // Create the parent directory.
+      d.createDirectory('..').then(function(what) {
+        ok(false, "Should not overwrite an existing directory.");
+        devicestorage_cleanup();
+      }, function(e) {
+        ok(true, "Accessing parent directory with '..' is not allowed.");
+        devicestorage_cleanup();
+      });
+      break;
+  }
+  gTestCount++;
+}
+
+function cbError(e) {
+  ok(false, e.name + " error should not arrive here!");
+  devicestorage_cleanup();
+}
+
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
+
+var storage = navigator.getDeviceStorage("pictures");
+ok(storage, "Should have gotten a storage.");
+
+var promise = storage.getRoot();
+ok(promise, "Should have a non-null promise for getRoot.");
+
+gName = storage.storageName;
+promise.then(getSuccess, cbError);
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_fs_get.html
@@ -0,0 +1,204 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html> <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=910412
+-->
+<head>
+  <title>Test Directory#get of the FileSystem API for device storage</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="devicestorage_common.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+SimpleTest.requestCompleteLog();
+
+// The root directory object.
+var gRoot = null;
+var gSub1 = null;
+var gSub2 = null;
+var gTestCount = 0;
+var gPath = "/";
+
+function testGetSuccess(dir, path) {
+  dir.get(path).then(getSuccess, cbError);
+}
+
+function testGetFailure(dir, path) {
+  dir.get(path).then(cbSuccess, getFailure);
+}
+
+function getSuccess(r) {
+  ok(r, "[" + gTestCount +"] Should get the file - " + gPath + ".");
+  switch (gTestCount) {
+    case 0:
+      gRoot = r;
+      // Get sub1/sub2/text.png from root.
+      gPath = "sub1/sub2/test.png";
+      testGetSuccess(gRoot, "sub1/sub2/test.png");
+      break;
+    case 1:
+      // Get sub1 from root.
+      gPath = "sub1";
+      testGetSuccess(gRoot, "sub1");
+      break;
+    case 2:
+      // Get sub1/sub2 from root.
+      gSub1 = r;
+      gPath = "sub1/sub2";
+      testGetSuccess(gRoot, "sub1/sub2");
+      break;
+    case 3:
+      // Get sub1/sub2 from sub2.
+      gSub2 = r;
+      gPath = "sub1/sub2";
+      testGetSuccess(gSub1, "sub2");
+      break;
+    case 4:
+      // Test path with leading and trailing white spaces.
+      gPath = "sub1/sub2";
+      testGetSuccess(gSub1, "\t sub2 ");
+      break;
+    case 5:
+      // Get sub1 from sub1/sub2 with "..".
+      gPath = "sub1/sub2/..";
+      testGetFailure(gSub2, "..");
+      break;
+    default:
+      ok(false, "Should not arrive at getSuccess!");
+      devicestorage_cleanup();
+      break;
+  }
+  gTestCount++;
+}
+
+function getFailure(e) {
+  ok(true, "[" + gTestCount +"] Should not get the file - " + gPath + ". Error: " + e.name);
+  switch (gTestCount) {
+    case 6:
+      // Test special path "..".
+      gPath = "sub1/sub2/../sub2";
+      testGetFailure(gSub2, "../sub2");
+      break;
+    case 7:
+      gPath = "sub1/sub2/../sub2";
+      testGetFailure(gRoot, "sub1/sub2/../sub2");
+      break;
+    case 8:
+      // Test special path ".".
+      gPath = "sub1/./sub2";
+      testGetFailure(gRoot, "sub1/./sub2");
+      break;
+    case 9:
+      gPath = "./sub1/sub2";
+      testGetFailure(gRoot, "./sub1/sub2");
+      break;
+    case 10:
+      gPath = "././sub1/sub2";
+      testGetFailure(gRoot, "././sub1/sub2");
+      break;
+    case 11:
+      gPath = "sub1/sub2/.";
+      testGetFailure(gRoot, "sub1/sub2/.");
+      break;
+    case 12:
+      gPath = "sub1/.";
+      testGetFailure(gSub1, "./");
+      break;
+    case 13:
+      // Test path starting with "/".
+      gPath = "sub1/";
+      testGetFailure(gSub1, "/");
+      break;
+    case 14:
+      // Test path ending with "/".
+      gPath = "sub1/";
+      testGetFailure(gSub1, "sub2/");
+      break;
+    case 15:
+      // Test empty path.
+      gPath = "sub2";
+      testGetFailure(gSub2, "");
+      break;
+    case 16:
+      // Test special path "//".
+      gPath = "sub1//sub2";
+      testGetFailure(gRoot, "sub1//sub2");
+      break;
+    case 17:
+      devicestorage_cleanup();
+      break;
+    default:
+      ok(false, "Should not arrive here!");
+      devicestorage_cleanup();
+      break;
+  }
+  gTestCount++;
+}
+
+function cbError(e) {
+  ok(false, "Should not arrive at cbError! Error: " + e.name);
+  devicestorage_cleanup();
+}
+
+function cbSuccess(e) {
+  ok(false, "Should not arrive at cbSuccess!");
+  devicestorage_cleanup();
+}
+
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
+
+var gStorage = navigator.getDeviceStorage("pictures");
+ok(gStorage, "Should have gotten a storage.");
+
+function createTestFile(path, callback) {
+  function addNamed() {
+    var req = gStorage.addNamed(createRandomBlob("image/png"), path);
+
+    req.onsuccess = function() {
+      ok(true, path + " was created.");
+      callback();
+    };
+
+    req.onerror = function(e) {
+      ok(false, "Failed to create " + path + ": " + e.target.error.name);
+      devicestorage_cleanup();
+    };
+  }
+
+  // Bug 980136. Check if the file exists before we create.
+  var req = gStorage.get(path);
+
+  req.onsuccess = function() {
+    ok(true, path + " exists. Do not need to create.");
+    callback();
+  };
+
+  req.onerror = function(e) {
+    ok(true, path + " does not exists: " + e.target.error.name);
+    addNamed();
+  };
+}
+
+createTestFile("sub1/sub2/test.png", function() {
+  var promise = gStorage.getRoot();
+  ok(promise, "Should have a non-null promise for getRoot.");
+  promise.then(getSuccess, cbError);
+});
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/CreateDirectoryTask.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CreateDirectoryTask.h"
+
+#include "DOMError.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace dom {
+
+CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem,
+                                         const nsAString& aPath)
+  : FileSystemTaskBase(aFileSystem)
+  , mTargetRealPath(aPath)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem);
+  nsCOMPtr<nsIGlobalObject> globalObject =
+    do_QueryInterface(aFileSystem->GetWindow());
+  if (!globalObject) {
+    return;
+  }
+  mPromise = new Promise(globalObject);
+}
+
+CreateDirectoryTask::CreateDirectoryTask(
+  FileSystemBase* aFileSystem,
+  const FileSystemCreateDirectoryParams& aParam,
+  FileSystemRequestParent* aParent)
+  : FileSystemTaskBase(aFileSystem, aParam, aParent)
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem);
+  mTargetRealPath = aParam.realPath();
+}
+
+CreateDirectoryTask::~CreateDirectoryTask()
+{
+  MOZ_ASSERT(!mPromise || NS_IsMainThread(),
+             "mPromise should be released on main thread!");
+}
+
+already_AddRefed<Promise>
+CreateDirectoryTask::GetPromise()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  return nsRefPtr<Promise>(mPromise).forget();
+}
+
+FileSystemParams
+CreateDirectoryTask::GetRequestParams(const nsString& aFileSystem) const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  return FileSystemCreateDirectoryParams(aFileSystem, mTargetRealPath);
+}
+
+FileSystemResponseValue
+CreateDirectoryTask::GetSuccessRequestResult() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  return FileSystemDirectoryResponse(mTargetRealPath);
+}
+
+void
+CreateDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  FileSystemDirectoryResponse r = aValue;
+  mTargetRealPath = r.realPath();
+}
+
+nsresult
+CreateDirectoryTask::Work()
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
+
+  if (mFileSystem->IsShutdown()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIFile> file = mFileSystem->GetLocalFile(mTargetRealPath);
+  if (!file) {
+    return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+  }
+
+  bool fileExists;
+  nsresult rv = file->Exists(&fileExists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (fileExists) {
+    return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
+  }
+
+  rv = file->Create(nsIFile::DIRECTORY_TYPE, 0770);
+  return rv;
+}
+
+void
+CreateDirectoryTask::HandlerCallback()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (mFileSystem->IsShutdown()) {
+    mPromise = nullptr;
+    return;
+  }
+
+  if (HasError()) {
+    nsRefPtr<DOMError> domError = new DOMError(mFileSystem->GetWindow(),
+      mErrorValue);
+    mPromise->MaybeReject(domError);
+    mPromise = nullptr;
+    return;
+  }
+  nsRefPtr<Directory> dir = new Directory(mFileSystem, mTargetRealPath);
+  mPromise->MaybeResolve(dir);
+  mPromise = nullptr;
+}
+
+void
+CreateDirectoryTask::GetPermissionAccessType(nsCString& aAccess) const
+{
+  aAccess.AssignLiteral("create");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/CreateDirectoryTask.h
@@ -0,0 +1,61 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CreateDirectoryTask_h
+#define mozilla_dom_CreateDirectoryTask_h
+
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+class CreateDirectoryTask MOZ_FINAL
+  : public FileSystemTaskBase
+{
+public:
+  CreateDirectoryTask(FileSystemBase* aFileSystem,
+                      const nsAString& aPath);
+  CreateDirectoryTask(FileSystemBase* aFileSystem,
+                      const FileSystemCreateDirectoryParams& aParam,
+                      FileSystemRequestParent* aParent);
+
+  virtual
+  ~CreateDirectoryTask();
+
+  already_AddRefed<Promise>
+  GetPromise();
+
+  virtual void
+  GetPermissionAccessType(nsCString& aAccess) const MOZ_OVERRIDE;
+
+protected:
+  virtual FileSystemParams
+  GetRequestParams(const nsString& aFileSystem) const MOZ_OVERRIDE;
+
+  virtual FileSystemResponseValue
+  GetSuccessRequestResult() const MOZ_OVERRIDE;
+
+  virtual void
+  SetSuccessRequestResult(const FileSystemResponseValue& aValue) MOZ_OVERRIDE;
+
+  virtual nsresult
+  Work() MOZ_OVERRIDE;
+
+  virtual void
+  HandlerCallback() MOZ_OVERRIDE;
+
+private:
+  nsRefPtr<Promise> mPromise;
+  nsString mTargetRealPath;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CreateDirectoryTask_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/DeviceStorageFileSystem.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DeviceStorageFileSystem.h"
+
+#include "DeviceStorage.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDeviceStorage.h"
+#include "nsIFile.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+DeviceStorageFileSystem::DeviceStorageFileSystem(
+  const nsAString& aStorageType,
+  const nsAString& aStorageName)
+  : mDeviceStorage(nullptr)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+
+  mStorageType = aStorageType;
+  mStorageName = aStorageName;
+
+  // Generate the string representation of the file system.
+  mString.AppendLiteral("devicestorage-");
+  mString.Append(mStorageType);
+  mString.AppendLiteral("-");
+  mString.Append(mStorageName);
+
+  mIsTesting =
+    mozilla::Preferences::GetBool("device.storage.prompt.testing", false);
+
+  // Get the permission name required to access the file system.
+  nsresult rv =
+    DeviceStorageTypeChecker::GetPermissionForType(mStorageType, mPermission);
+  NS_WARN_IF(NS_FAILED(rv));
+
+  // Get the local path of the file system root.
+  // Since the child process is not allowed to access the file system, we only
+  // do this from the parent process.
+  if (!FileSystemUtils::IsParentProcess()) {
+    return;
+  }
+  nsCOMPtr<nsIFile> rootFile;
+  DeviceStorageFile::GetRootDirectoryForType(aStorageType,
+                                             aStorageName,
+                                             getter_AddRefs(rootFile));
+
+  NS_WARN_IF(!rootFile || NS_FAILED(rootFile->GetPath(mLocalRootPath)));
+  FileSystemUtils::LocalPathToNormalizedPath(mLocalRootPath,
+    mNormalizedLocalRootPath);
+
+  // DeviceStorageTypeChecker is a singleton object and must be initialized on
+  // the main thread. We initialize it here so that we can use it on the worker
+  // thread.
+  DebugOnly<DeviceStorageTypeChecker*> typeChecker
+    = DeviceStorageTypeChecker::CreateOrGet();
+  MOZ_ASSERT(typeChecker);
+}
+
+DeviceStorageFileSystem::~DeviceStorageFileSystem()
+{
+}
+
+void
+DeviceStorageFileSystem::Init(nsDOMDeviceStorage* aDeviceStorage)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aDeviceStorage);
+  mDeviceStorage = aDeviceStorage;
+}
+
+void
+DeviceStorageFileSystem::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  mDeviceStorage = nullptr;
+  mShutdown = true;
+}
+
+nsPIDOMWindow*
+DeviceStorageFileSystem::GetWindow() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (!mDeviceStorage) {
+    return nullptr;
+  }
+  return mDeviceStorage->GetOwner();
+}
+
+already_AddRefed<nsIFile>
+DeviceStorageFileSystem::GetLocalFile(const nsAString& aRealPath) const
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Should be on parent process!");
+  nsAutoString localPath;
+  FileSystemUtils::NormalizedPathToLocalPath(aRealPath, localPath);
+  localPath = mLocalRootPath + localPath;
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_NewLocalFile(localPath, false, getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+  return file.forget();
+}
+
+const nsAString&
+DeviceStorageFileSystem::GetRootName() const
+{
+  return mStorageName;
+}
+
+bool
+DeviceStorageFileSystem::IsSafeFile(nsIFile* aFile) const
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Should be on parent process!");
+  MOZ_ASSERT(aFile);
+
+  // Check if this file belongs to this storage.
+  nsAutoString path;
+  if (NS_FAILED(aFile->GetPath(path))) {
+    return false;
+  }
+  FileSystemUtils::LocalPathToNormalizedPath(path, path);
+  if (!FileSystemUtils::IsDescendantPath(mNormalizedLocalRootPath, path)) {
+    return false;
+  }
+
+  // Check if the file type is compatible with the storage type.
+  DeviceStorageTypeChecker* typeChecker
+    = DeviceStorageTypeChecker::CreateOrGet();
+  MOZ_ASSERT(typeChecker);
+  return typeChecker->Check(mStorageType, aFile);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/DeviceStorageFileSystem.h
@@ -0,0 +1,62 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DeviceStorageFileSystem_h
+#define mozilla_dom_DeviceStorageFileSystem_h
+
+#include "mozilla/dom/FileSystemBase.h"
+#include "nsString.h"
+
+class nsDOMDeviceStorage;
+
+namespace mozilla {
+namespace dom {
+
+class DeviceStorageFileSystem
+  : public FileSystemBase
+{
+public:
+  DeviceStorageFileSystem(const nsAString& aStorageType,
+                          const nsAString& aStorageName);
+
+  void
+  Init(nsDOMDeviceStorage* aDeviceStorage);
+
+  // Overrides FileSystemBase
+
+  virtual void
+  Shutdown() MOZ_OVERRIDE;
+
+  virtual nsPIDOMWindow*
+  GetWindow() const MOZ_OVERRIDE;
+
+  virtual already_AddRefed<nsIFile>
+  GetLocalFile(const nsAString& aRealPath) const MOZ_OVERRIDE;
+
+  virtual const nsAString&
+  GetRootName() const MOZ_OVERRIDE;
+
+  virtual bool
+  IsSafeFile(nsIFile* aFile) const MOZ_OVERRIDE;
+
+private:
+  virtual
+  ~DeviceStorageFileSystem();
+
+  nsString mStorageType;
+  nsString mStorageName;
+
+  // The local path of the root. Only available in the parent process.
+  // In the child process, we don't use it and its value should be empty.
+  nsString mLocalRootPath;
+  nsString mNormalizedLocalRootPath;
+  nsDOMDeviceStorage* mDeviceStorage;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DeviceStorageFileSystem_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/Directory.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Directory.h"
+
+#include "CreateDirectoryTask.h"
+#include "FileSystemPermissionRequest.h"
+#include "GetFileOrDirectoryTask.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsString.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemUtils.h"
+
+// Resolve the name collision of Microsoft's API name with macros defined in
+// Windows header files. Undefine the macro of CreateDirectory to avoid
+// Directory#CreateDirectory being replaced by Directory#CreateDirectoryW.
+#ifdef CreateDirectory
+#undef CreateDirectory
+#endif
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Directory)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Directory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Directory)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Directory)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// static
+already_AddRefed<Promise>
+Directory::GetRoot(FileSystemBase* aFileSystem)
+{
+  nsRefPtr<GetFileOrDirectoryTask> task = new GetFileOrDirectoryTask(
+    aFileSystem, EmptyString(), true);
+  FileSystemPermissionRequest::RequestForTask(task);
+  return task->GetPromise();
+}
+
+Directory::Directory(FileSystemBase* aFileSystem,
+                     const nsAString& aPath)
+  : mFileSystem(aFileSystem)
+  , mPath(aPath)
+{
+  MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+  // Remove the trailing "/".
+  mPath.Trim(FILESYSTEM_DOM_PATH_SEPARATOR, false, true);
+
+  SetIsDOMBinding();
+}
+
+Directory::~Directory()
+{
+}
+
+nsPIDOMWindow*
+Directory::GetParentObject() const
+{
+  return mFileSystem->GetWindow();
+}
+
+JSObject*
+Directory::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return DirectoryBinding::Wrap(aCx, aScope, this);
+}
+
+void
+Directory::GetName(nsString& aRetval) const
+{
+  aRetval.Truncate();
+
+  if (mPath.IsEmpty()) {
+    aRetval = mFileSystem->GetRootName();
+    return;
+  }
+
+  aRetval = Substring(mPath,
+                      mPath.RFindChar(FileSystemUtils::kSeparatorChar) + 1);
+}
+
+already_AddRefed<Promise>
+Directory::CreateDirectory(const nsAString& aPath)
+{
+  nsresult error = NS_OK;
+  nsString realPath;
+  if (!DOMPathToRealPath(aPath, realPath)) {
+    error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+  }
+  nsRefPtr<CreateDirectoryTask> task = new CreateDirectoryTask(
+    mFileSystem, realPath);
+  task->SetError(error);
+  FileSystemPermissionRequest::RequestForTask(task);
+  return task->GetPromise();
+}
+
+already_AddRefed<Promise>
+Directory::Get(const nsAString& aPath)
+{
+  nsresult error = NS_OK;
+  nsString realPath;
+  if (!DOMPathToRealPath(aPath, realPath)) {
+    error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+  }
+  nsRefPtr<GetFileOrDirectoryTask> task = new GetFileOrDirectoryTask(
+    mFileSystem, realPath, false);
+  task->SetError(error);
+  FileSystemPermissionRequest::RequestForTask(task);
+  return task->GetPromise();
+}
+
+bool
+Directory::DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const
+{
+  aRealPath.Truncate();
+
+  nsString relativePath;
+  relativePath = aPath;
+
+  // Trim white spaces.
+  static const char kWhitespace[] = "\b\t\r\n ";
+  relativePath.Trim(kWhitespace);
+
+  if (!IsValidRelativePath(relativePath)) {
+    return false;
+  }
+
+  aRealPath = mPath + NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR) +
+    relativePath;
+
+  return true;
+}
+
+// static
+bool
+Directory::IsValidRelativePath(const nsString& aPath)
+{
+  // We don't allow empty relative path to access the root.
+  if (aPath.IsEmpty()) {
+    return false;
+  }
+
+  // Leading and trailing "/" are not allowed.
+  if (aPath.First() == FileSystemUtils::kSeparatorChar ||
+      aPath.Last() == FileSystemUtils::kSeparatorChar) {
+    return false;
+  }
+
+  NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
+  NS_NAMED_LITERAL_STRING(kParentDir, "..");
+
+  // Split path and check each path component.
+  nsCharSeparatedTokenizer tokenizer(aPath, FileSystemUtils::kSeparatorChar);
+  while (tokenizer.hasMoreTokens()) {
+    nsDependentSubstring pathComponent = tokenizer.nextToken();
+    // The path containing empty components, such as "foo//bar", is invalid.
+    // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
+    // to walk up the directory.
+    if (pathComponent.IsEmpty() ||
+        pathComponent.Equals(kCurrentDir) ||
+        pathComponent.Equals(kParentDir)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/Directory.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Directory_h
+#define mozilla_dom_Directory_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+
+// Resolve the name collision of Microsoft's API name with macros defined in
+// Windows header files. Undefine the macro of CreateDirectory to avoid
+// Directory#CreateDirectory being replaced by Directory#CreateDirectoryW.
+#ifdef CreateDirectory
+#undef CreateDirectory
+#endif
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemBase;
+class Promise;
+
+class Directory MOZ_FINAL
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory)
+
+public:
+  static already_AddRefed<Promise>
+  GetRoot(FileSystemBase* aFileSystem);
+
+  Directory(FileSystemBase* aFileSystem, const nsAString& aPath);
+  ~Directory();
+
+  // ========= Begin WebIDL bindings. ===========
+
+  nsPIDOMWindow*
+  GetParentObject() const;
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  void
+  GetName(nsString& aRetval) const;
+
+  already_AddRefed<Promise>
+  CreateDirectory(const nsAString& aPath);
+
+  already_AddRefed<Promise>
+  Get(const nsAString& aPath);
+
+  // =========== End WebIDL bindings.============
+private:
+  static bool
+  IsValidRelativePath(const nsString& aPath);
+
+  /*
+   * Convert relative DOM path to the absolute real path.
+   * @return true if succeed. false if the DOM path is invalid.
+   */
+  bool
+  DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const;
+
+  nsRefPtr<FileSystemBase> mFileSystem;
+  nsString mPath;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Directory_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemBase.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FileSystemBase.h"
+
+#include "DeviceStorageFileSystem.h"
+#include "nsCharSeparatedTokenizer.h"
+
+namespace mozilla {
+namespace dom {
+
+// static
+already_AddRefed<FileSystemBase>
+FileSystemBase::FromString(const nsAString& aString)
+{
+  if (StringBeginsWith(aString, NS_LITERAL_STRING("devicestorage-"))) {
+    // The string representation of devicestorage file system is of the format:
+    // devicestorage-StorageType-StorageName
+
+    nsCharSeparatedTokenizer tokenizer(aString, char16_t('-'));
+    tokenizer.nextToken();
+
+    nsString storageType;
+    if (tokenizer.hasMoreTokens()) {
+      storageType = tokenizer.nextToken();
+    }
+
+    nsString storageName;
+    if (tokenizer.hasMoreTokens()) {
+      storageName = tokenizer.nextToken();
+    }
+
+    nsRefPtr<DeviceStorageFileSystem> f =
+      new DeviceStorageFileSystem(storageType, storageName);
+    return f.forget();
+  }
+  return nullptr;
+}
+
+FileSystemBase::FileSystemBase()
+  : mShutdown(false)
+  , mIsTesting(false)
+{
+}
+
+FileSystemBase::~FileSystemBase()
+{
+}
+
+void
+FileSystemBase::Shutdown()
+{
+  mShutdown = true;
+}
+
+nsPIDOMWindow*
+FileSystemBase::GetWindow() const
+{
+  return nullptr;
+}
+
+bool
+FileSystemBase::IsSafeFile(nsIFile* aFile) const
+{
+  return false;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemBase.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileSystemBase_h
+#define mozilla_dom_FileSystemBase_h
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemBase
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemBase)
+public:
+
+  // Create file system object from its string representation.
+  static already_AddRefed<FileSystemBase>
+  FromString(const nsAString& aString);
+
+  FileSystemBase();
+
+  virtual void
+  Shutdown();
+
+  // Get the string representation of the file system.
+  const nsString&
+  ToString() const
+  {
+    return mString;
+  }
+
+  virtual nsPIDOMWindow*
+  GetWindow() const;
+
+  /*
+   * Create nsIFile object with the given real path (absolute DOM path).
+   */
+  virtual already_AddRefed<nsIFile>
+  GetLocalFile(const nsAString& aRealPath) const = 0;
+
+  /*
+   * Get the virtual name of the root directory. This name will be exposed to
+   * the content page.
+   */
+  virtual const nsAString&
+  GetRootName() const = 0;
+
+  bool
+  IsShutdown() const
+  {
+    return mShutdown;
+  }
+
+  virtual bool
+  IsSafeFile(nsIFile* aFile) const;
+
+  /*
+   * Get the permission name required to access this file system.
+   */
+  const nsCString&
+  GetPermission() const
+  {
+    return mPermission;
+  }
+
+  bool
+  IsTesting() const
+  {
+    return mIsTesting;
+  }
+protected:
+  virtual ~FileSystemBase();
+
+  // The string representation of the file system.
+  nsString mString;
+
+  bool mShutdown;
+
+  // The permission name required to access the file system.
+  nsCString mPermission;
+
+  bool mIsTesting;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemBase_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemPermissionRequest.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "FileSystemPermissionRequest.h"
+
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS2(FileSystemPermissionRequest, nsIRunnable, nsIContentPermissionRequest)
+
+// static
+void
+FileSystemPermissionRequest::RequestForTask(FileSystemTaskBase* aTask)
+{
+  MOZ_ASSERT(aTask, "aTask should not be null!");
+  MOZ_ASSERT(NS_IsMainThread());
+  nsRefPtr<FileSystemPermissionRequest> request =
+    new FileSystemPermissionRequest(aTask);
+  NS_DispatchToCurrentThread(request);
+}
+
+FileSystemPermissionRequest::FileSystemPermissionRequest(
+  FileSystemTaskBase* aTask)
+  : mTask(aTask)
+{
+  MOZ_ASSERT(mTask, "aTask should not be null!");
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mTask->GetPermissionAccessType(mPermissionAccess);
+
+  nsRefPtr<FileSystemBase> filesystem = mTask->GetFileSystem();
+  if (!filesystem) {
+    return;
+  }
+
+  mPermissionType = filesystem->GetPermission();
+
+  mWindow = filesystem->GetWindow();
+  if (!mWindow) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
+  if (!doc) {
+    return;
+  }
+
+  mPrincipal = doc->NodePrincipal();
+}
+
+FileSystemPermissionRequest::~FileSystemPermissionRequest()
+{
+}
+
+bool
+FileSystemPermissionRequest::Recv__delete__(const bool& aAllow,
+               const InfallibleTArray<PermissionChoice>& aChoices)
+{
+  MOZ_ASSERT(aChoices.IsEmpty(),
+             "FileSystemPermissionRequest doesn't support permission choice");
+  if (aAllow) {
+    Allow(JS::UndefinedHandleValue);
+  } else {
+    Cancel();
+  }
+  return true;
+}
+
+void
+FileSystemPermissionRequest::IPDLRelease()
+{
+  Release();
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::GetTypes(nsIArray** aTypes)
+{
+  nsTArray<nsString> emptyOptions;
+  return CreatePermissionArray(mPermissionType,
+                               mPermissionAccess,
+                               emptyOptions,
+                               aTypes);
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal)
+{
+  NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow)
+{
+  NS_IF_ADDREF(*aRequestingWindow = mWindow);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::GetElement(nsIDOMElement** aRequestingElement)
+{
+  *aRequestingElement = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::Cancel()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mTask->SetError(NS_ERROR_DOM_SECURITY_ERR);
+  mTask->Start();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::Allow(JS::HandleValue aChoices)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aChoices.isUndefined());
+  mTask->Start();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FileSystemPermissionRequest::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<FileSystemBase> filesystem = mTask->GetFileSystem();
+  if (!filesystem) {
+    Cancel();
+    return NS_OK;
+  }
+
+  if (filesystem->IsTesting()) {
+    Allow(JS::UndefinedHandleValue);
+    return NS_OK;
+  }
+
+  if (FileSystemUtils::IsParentProcess()) {
+    nsCOMPtr<nsIContentPermissionPrompt> prompt
+      = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+    if (!prompt || NS_FAILED(prompt->Prompt(this))) {
+      Cancel();
+    }
+    return NS_OK;
+  }
+
+  if (!mWindow) {
+    Cancel();
+    return NS_OK;
+  }
+
+  // because owner implements nsITabChild, we can assume that it is
+  // the one and only TabChild.
+  TabChild* child = TabChild::GetFrom(mWindow->GetDocShell());
+  if (!child) {
+    Cancel();
+    return NS_OK;
+  }
+
+  // Retain a reference so the object isn't deleted without IPDL's
+  // knowledge. Corresponding release occurs in
+  // DeallocPContentPermissionRequest.
+  AddRef();
+
+  nsTArray<PermissionRequest> permArray;
+  nsTArray<nsString> emptyOptions;
+  permArray.AppendElement(PermissionRequest(mPermissionType,
+                                            mPermissionAccess,
+                                            emptyOptions));
+  child->SendPContentPermissionRequestConstructor(
+    this, permArray, IPC::Principal(mPrincipal));
+
+  Sendprompt();
+  return NS_OK;
+}
+
+} /* namespace dom */
+} /* namespace mozilla */
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemPermissionRequest.h
@@ -0,0 +1,61 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileSystemPermissionRequest_h
+#define mozilla_dom_FileSystemPermissionRequest_h
+
+#include "PCOMContentPermissionRequestChild.h"
+#include "nsAutoPtr.h"
+#include "nsContentPermissionHelper.h"
+#include "nsIRunnable.h"
+
+class nsCString;
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemTaskBase;
+
+class FileSystemPermissionRequest MOZ_FINAL
+  : public nsIContentPermissionRequest
+  , public nsIRunnable
+  , public PCOMContentPermissionRequestChild
+{
+public:
+  // Request permission for the given task.
+  static void
+  RequestForTask(FileSystemTaskBase* aTask);
+
+  // Overrides PCOMContentPermissionRequestChild
+
+  virtual void
+  IPDLRelease() MOZ_OVERRIDE;
+
+  bool
+  Recv__delete__(const bool& aAllow,
+    const InfallibleTArray<PermissionChoice>& aChoices) MOZ_OVERRIDE;
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+  NS_DECL_NSIRUNNABLE
+private:
+  FileSystemPermissionRequest(FileSystemTaskBase* aTask);
+
+  virtual
+  ~FileSystemPermissionRequest();
+
+  nsCString mPermissionType;
+  nsCString mPermissionAccess;
+  nsRefPtr<FileSystemTaskBase> mTask;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemPermissionRequest_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemRequestParent.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/FileSystemRequestParent.h"
+
+#include "CreateDirectoryTask.h"
+#include "GetFileOrDirectoryTask.h"
+
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/dom/FileSystemBase.h"
+
+namespace mozilla {
+namespace dom {
+
+FileSystemRequestParent::FileSystemRequestParent()
+{
+}
+
+FileSystemRequestParent::~FileSystemRequestParent()
+{
+}
+
+#define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name)                         \
+    case FileSystemParams::TFileSystem##name##Params: {                        \
+      const FileSystem##name##Params& p = aParams;                             \
+      mFileSystem = FileSystemBase::FromString(p.filesystem());                \
+      task = new name##Task(mFileSystem, p, this);                             \
+      break;                                                                   \
+    }
+
+bool
+FileSystemRequestParent::Dispatch(ContentParent* aParent,
+                                  const FileSystemParams& aParams)
+{
+  MOZ_ASSERT(aParent, "aParent should not be null.");
+  nsRefPtr<FileSystemTaskBase> task;
+  switch (aParams.type()) {
+
+    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateDirectory)
+    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFileOrDirectory)
+
+    default: {
+      NS_RUNTIMEABORT("not reached");
+      break;
+    }
+  }
+
+  if (NS_WARN_IF(!task || !mFileSystem)) {
+    // Should never reach here.
+    return false;
+  }
+
+  if (!mFileSystem->IsTesting()) {
+    // Check the content process permission.
+
+    nsCString access;
+    task->GetPermissionAccessType(access);
+
+    nsAutoCString permissionName;
+    permissionName = mFileSystem->GetPermission();
+    permissionName.AppendLiteral("-");
+    permissionName.Append(access);
+
+    if (!AssertAppProcessPermission(aParent, permissionName.get())) {
+      return false;
+    }
+  }
+
+  task->Start();
+  return true;
+}
+
+void
+FileSystemRequestParent::ActorDestroy(ActorDestroyReason why)
+{
+  if (!mFileSystem) {
+    return;
+  }
+  mFileSystem->Shutdown();
+  mFileSystem = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemRequestParent.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileSystemRequestParent_h
+#define mozilla_dom_FileSystemRequestParent_h
+
+#include "mozilla/dom/PFileSystemRequestParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemBase;
+
+class FileSystemRequestParent
+  : public PFileSystemRequestParent
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemRequestParent)
+public:
+  FileSystemRequestParent();
+
+  virtual
+  ~FileSystemRequestParent();
+
+  bool
+  IsRunning()
+  {
+    return state() == PFileSystemRequest::__Start;
+  }
+
+  bool
+  Dispatch(ContentParent* aParent, const FileSystemParams& aParams);
+
+  virtual void
+  ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
+private:
+  nsRefPtr<FileSystemBase> mFileSystem;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemRequestParent_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -0,0 +1,226 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FileSystemTaskBase.h"
+
+#include "nsNetUtil.h" // Stream transport service.
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/unused.h"
+#include "nsDOMFile.h"
+
+namespace mozilla {
+namespace dom {
+
+FileSystemTaskBase::FileSystemTaskBase(FileSystemBase* aFileSystem)
+  : mErrorValue(NS_OK)
+  , mFileSystem(aFileSystem)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+}
+
+FileSystemTaskBase::FileSystemTaskBase(FileSystemBase* aFileSystem,
+                                       const FileSystemParams& aParam,
+                                       FileSystemRequestParent* aParent)
+  : mErrorValue(NS_OK)
+  , mFileSystem(aFileSystem)
+  , mRequestParent(aParent)
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+}
+
+FileSystemTaskBase::~FileSystemTaskBase()
+{
+}
+
+FileSystemBase*
+FileSystemTaskBase::GetFileSystem() const
+{
+  return mFileSystem.get();
+}
+
+void
+FileSystemTaskBase::Start()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+
+  if (HasError()) {
+    NS_DispatchToMainThread(this);
+    return;
+  }
+
+  if (FileSystemUtils::IsParentProcess()) {
+    // Run in parent process.
+    // Start worker thread.
+    nsCOMPtr<nsIEventTarget> target
+      = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    NS_ASSERTION(target, "Must have stream transport service.");
+    target->Dispatch(this, NS_DISPATCH_NORMAL);
+    return;
+  }
+
+  // Run in child process.
+  if (mFileSystem->IsShutdown()) {
+    return;
+  }
+
+  // Retain a reference so the task object isn't deleted without IPDL's
+  // knowledge. The reference will be released by
+  // mozilla::dom::ContentChild::DeallocPFileSystemRequestChild.
+  NS_ADDREF_THIS();
+  ContentChild::GetSingleton()->SendPFileSystemRequestConstructor(this,
+    GetRequestParams(mFileSystem->ToString()));
+}
+
+NS_IMETHODIMP
+FileSystemTaskBase::Run()
+{
+  if (!NS_IsMainThread()) {
+    // Run worker thread tasks
+    nsresult rv = Work();
+    if (NS_FAILED(rv)) {
+      SetError(rv);
+    }
+    // Dispatch itself to main thread
+    NS_DispatchToMainThread(this);
+    return NS_OK;
+  }
+
+  // Run main thread tasks
+  HandleResult();
+  return NS_OK;
+}
+
+void
+FileSystemTaskBase::HandleResult()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (mFileSystem->IsShutdown()) {
+    return;
+  }
+  if (mRequestParent && mRequestParent->IsRunning()) {
+    unused << mRequestParent->Send__delete__(mRequestParent,
+      GetRequestResult());
+  } else {
+    HandlerCallback();
+  }
+}
+
+FileSystemResponseValue
+FileSystemTaskBase::GetRequestResult() const
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (HasError()) {
+    return FileSystemErrorResponse(mErrorValue);
+  } else {
+    return GetSuccessRequestResult();
+  }
+}
+
+void
+FileSystemTaskBase::SetRequestResult(const FileSystemResponseValue& aValue)
+{
+  MOZ_ASSERT(!FileSystemUtils::IsParentProcess(),
+             "Only call from child process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) {
+    FileSystemErrorResponse r = aValue;
+    mErrorValue = r.error();
+  } else {
+    SetSuccessRequestResult(aValue);
+  }
+}
+
+bool
+FileSystemTaskBase::Recv__delete__(const FileSystemResponseValue& aValue)
+{
+  SetRequestResult(aValue);
+  HandlerCallback();
+  return true;
+}
+
+BlobParent*
+FileSystemTaskBase::GetBlobParent(nsIDOMFile* aFile) const
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFile);
+
+  // Load the lazy dom file data from the parent before sending to the child.
+  nsString mimeType;
+  aFile->GetType(mimeType);
+  uint64_t fileSize;
+  aFile->GetSize(&fileSize);
+  uint64_t lastModifiedDate;
+  aFile->GetMozLastModifiedDate(&lastModifiedDate);
+
+  ContentParent* cp = static_cast<ContentParent*>(mRequestParent->Manager());
+  return cp->GetOrCreateActorForBlob(aFile);
+}
+
+void
+FileSystemTaskBase::SetError(const nsresult& aErrorValue)
+{
+  uint16_t module = NS_ERROR_GET_MODULE(aErrorValue);
+  if (module == NS_ERROR_MODULE_DOM_FILESYSTEM ||
+      module == NS_ERROR_MODULE_DOM_FILE ||
+      module == NS_ERROR_MODULE_DOM) {
+    mErrorValue = aErrorValue;
+    return;
+  }
+
+  switch (aErrorValue) {
+    case NS_OK:
+      mErrorValue = NS_OK;
+      return;
+
+    case NS_ERROR_FILE_INVALID_PATH:
+    case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+      return;
+
+    case NS_ERROR_FILE_DESTINATION_NOT_DIR:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR;
+      return;
+
+    case NS_ERROR_FILE_ACCESS_DENIED:
+    case NS_ERROR_FILE_DIR_NOT_EMPTY:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR;
+      return;
+
+    case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
+    case NS_ERROR_NOT_AVAILABLE:
+      mErrorValue = NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+      return;
+
+    case NS_ERROR_FILE_ALREADY_EXISTS:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
+      return;
+
+    case NS_ERROR_FILE_NOT_DIRECTORY:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+      return;
+
+    case NS_ERROR_UNEXPECTED:
+    default:
+      mErrorValue = NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
+      return;
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemTaskBase.h
@@ -0,0 +1,244 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileSystemTaskBase_h
+#define mozilla_dom_FileSystemTaskBase_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/PFileSystemRequestChild.h"
+#include "mozilla/dom/ipc/Blob.h"
+
+class nsIDOMFile;
+
+namespace mozilla {
+namespace dom {
+
+class FileSystemBase;
+class FileSystemParams;
+class Promise;
+
+/*
+ * The base class to implement a Task class.
+ * The task is used to handle the OOP (out of process) operations.
+ * The file system operations can only be performed in the parent process. When
+ * performing such a parent-process-only operation, a task will delivered the
+ * operation to the parent process if needed.
+ *
+ * The following diagram illustrates the how a API call from the content page
+ * starts a task and gets call back results.
+ *
+ * The left block is the call sequence inside the child process, while the
+ * right block is the call sequence inside the parent process.
+ *
+ * There are two types of API call. One is from the content page of the child
+ * process and we mark the steps as (1) to (8). The other is from the content
+ * page of the parent process and we mark the steps as (1') to (4').
+ *
+ *       Page                                             Page
+ *        |                                                |
+ *        | (1)                                            |  (1')
+ *  ______|________________     |     _____________________|_____________
+ * |      |                |    |    |                     |             |
+ * |      |  Task in       |    |    |  Task in            |             |
+ * |      |  Child Process |    |    |  Parent Process     |             |
+ * |      V                |   IPC   |                     V             |
+ * [new FileSystemTaskBase()]   |    |     [new FileSystemTaskBase()]    |
+ * |         |             |    |    |                         |         |
+ * |         | (2)         |         |                         | (2')    |
+ * |         V             |   (3)   |                         |         |
+ * |    [GetRequestParams]------------->[new FileSystemTaskBase(...)]    |
+ * |                       |         |          |              |         |
+ * |                       |    |    |          | (4)          |         |
+ * |                       |    |    |          |              V         |
+ * |                       |    |    |          -----------> [Work]      |
+ * |                       |   IPC   |                         |         |
+ * |                       |    |    |                     (5) | (3')    |
+ * |                       |    |    |                         V         |
+ * |                       |    |    |          --------[HandleResult]   |
+ * |                       |    |    |          |              |         |
+ * |                       |         |          | (6)          |         |
+ * |                       |   (7)   |          V              |         |
+ * |   [SetRequestResult]<-------------[GetRequestResult]      |         |
+ * |       |               |         |                         | (4')    |
+ * |       | (8)           |    |    |                         |         |
+ * |       V               |    |    |                         V         |
+ * |[HandlerCallback]      |   IPC   |               [HandlerCallback]   |
+ * |_______|_______________|    |    |_________________________|_________|
+ *         |                    |                              |
+ *         V                                                   V
+ *        Page                                                Page
+ *
+ * 1. From child process page
+ * Child:
+ *   (1) Call FileSystem API from content page with JS. Create a task and run.
+ *   The base constructor [FileSystemTaskBase()] of the task should be called.
+ *   (2) Forward the task to the parent process through the IPC and call
+ *   [GetRequestParams] to prepare the parameters of the IPC.
+ * Parent:
+ *   (3) The parent process receives IPC and handle it in
+ *   FileystemRequestParent.
+ *   Get the IPC parameters and create a task to run the IPC task. The base
+ *   constructor [FileSystemTaskBase(aParam, aParent)] of the task should be
+ *   called to set the task as an IPC task.
+ *   (4) The task operation will be performed in the member function of [Work].
+ *   A worker thread will be created to run that function. If error occurs
+ *   during the operation, call [SetError] to record the error and then abort.
+ *   (5) After finishing the task operation, call [HandleResult] to send the
+ *   result back to the child process though the IPC.
+ *   (6) Call [GetRequestResult] request result to prepare the parameters of the
+ *   IPC. Because the formats of the error result for different task are the
+ *   same, FileSystemTaskBase can handle the error message without interfering.
+ *   Each task only needs to implement its specific success result preparation
+ *   function -[GetSuccessRequestResult].
+ * Child:
+ *   (7) The child process receives IPC and calls [SetRequestResult] to get the
+ *   task result. Each task needs to implement its specific success result
+ *   parsing function [SetSuccessRequestResult] to get the success result.
+ *   (8) Call [HandlerCallback] to send the task result to the content page.
+ * 2. From parent process page
+ * We don't need to send the task parameters and result to other process. So
+ * there are less steps, but their functions are the same. The correspondence
+ * between the two types of steps is:
+ *   (1') = (1),
+ *   (2') = (4),
+ *   (3') = (5),
+ *   (4') = (8).
+ */
+class FileSystemTaskBase
+  : public nsRunnable
+  , public PFileSystemRequestChild
+{
+public:
+  /*
+   * Start the task. If the task is running the child process, it will be
+   * forwarded to parent process by IPC, or else, creates a worker thread to
+   * do the task work.
+   */
+  void
+  Start();
+
+  /*
+   * The error codes are defined in xpcom/base/ErrorList.h and their
+   * corresponding error name and message are defined in dom/base/domerr.msg.
+   */
+  void
+  SetError(const nsresult& aErrorCode);
+
+  FileSystemBase*
+  GetFileSystem() const;
+
+  /*
+   * Get the type of permission access required to perform this task.
+   */
+  virtual void
+  GetPermissionAccessType(nsCString& aAccess) const = 0;
+
+  NS_DECL_NSIRUNNABLE
+protected:
+  /*
+   * To create a task to handle the page content request.
+   */
+  FileSystemTaskBase(FileSystemBase* aFileSystem);
+
+  /*
+   * To create a parent process task delivered from the child process through
+   * IPC.
+   */
+  FileSystemTaskBase(FileSystemBase* aFileSystem,
+                     const FileSystemParams& aParam,
+                     FileSystemRequestParent* aParent);
+
+  virtual
+  ~FileSystemTaskBase();
+
+  /*
+   * The function to perform task operation. It will be run on the worker
+   * thread of the parent process.
+   * Overrides this function to define the task operation for individual task.
+   */
+  virtual nsresult
+  Work() = 0;
+
+  /*
+   * After the task is completed, this function will be called to pass the task
+   * result to the content page.
+   * Override this function to handle the call back to the content page.
+   */
+  virtual void
+  HandlerCallback() = 0;
+
+  /*
+   * Wrap the task parameter to FileSystemParams for sending it through IPC.
+   * It will be called when we need to forward a task from the child process to
+   * the prarent process.
+   * @param filesystem The string representation of the file system.
+   */
+  virtual FileSystemParams
+  GetRequestParams(const nsString& aFileSystem) const = 0;
+
+  /*
+   * Wrap the task success result to FileSystemResponseValue for sending it
+   * through IPC.
+   * It will be called when the task is completed successfully and we need to
+   * send the task success result back to the child process.
+   */
+  virtual FileSystemResponseValue
+  GetSuccessRequestResult() const = 0;
+
+  /*
+   * Unwrap the IPC message to get the task success result.
+   * It will be called when the task is completed successfully and an IPC
+   * message is received in the child process and we want to get the task
+   * success result.
+   */
+  virtual void
+  SetSuccessRequestResult(const FileSystemResponseValue& aValue) = 0;
+
+  bool
+  HasError() const { return mErrorValue != NS_OK; }
+
+  // Overrides PFileSystemRequestChild
+  virtual bool
+  Recv__delete__(const FileSystemResponseValue& value) MOZ_OVERRIDE;
+
+  BlobParent*
+  GetBlobParent(nsIDOMFile* aFile) const;
+
+  nsresult mErrorValue;
+
+  nsRefPtr<FileSystemBase> mFileSystem;
+  nsRefPtr<FileSystemRequestParent> mRequestParent;
+private:
+  /*
+   * After finishing the task operation, handle the task result.
+   * If it is an IPC task, send back the IPC result. Or else, send the result
+   * to the content page.
+   */
+  void
+  HandleResult();
+
+  /*
+   * Wrap the task result to FileSystemResponseValue for sending it through IPC.
+   * It will be called when the task is completed and we need to
+   * send the task result back to the child process.
+   */
+  FileSystemResponseValue
+  GetRequestResult() const;
+
+  /*
+   * Unwrap the IPC message to get the task result.
+   * It will be called when the task is completed and an IPC message is received
+   * in the child process and we want to get the task result.
+   */
+  void
+  SetRequestResult(const FileSystemResponseValue& aValue);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemTaskBase_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemUtils.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FileSystemUtils.h"
+
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace dom {
+
+// static
+void
+FileSystemUtils::LocalPathToNormalizedPath(const nsAString& aLocal,
+                                           nsAString& aNorm)
+{
+  nsString result;
+  result = aLocal;
+#if defined(XP_WIN)
+  char16_t* cur = result.BeginWriting();
+  char16_t* end = result.EndWriting();
+  for (; cur < end; ++cur) {
+    if (char16_t('\\') == *cur)
+      *cur = char16_t('/');
+  }
+#endif
+  aNorm = result;
+}
+
+// static
+void
+FileSystemUtils::NormalizedPathToLocalPath(const nsAString& aNorm,
+                                           nsAString& aLocal)
+{
+  nsString result;
+  result = aNorm;
+#if defined(XP_WIN)
+  char16_t* cur = result.BeginWriting();
+  char16_t* end = result.EndWriting();
+  for (; cur < end; ++cur) {
+    if (char16_t('/') == *cur)
+      *cur = char16_t('\\');
+  }
+#endif
+  aLocal = result;
+}
+
+// static
+bool
+FileSystemUtils::IsDescendantPath(const nsAString& aPath,
+                                  const nsAString& aDescendantPath)
+{
+  // The descendant path should begin with its ancestor path.
+  nsAutoString prefix;
+  prefix = aPath + NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR);
+
+  // Check the sub-directory path to see if it has the parent path as prefix.
+  if (aDescendantPath.Length() < prefix.Length() ||
+      !StringBeginsWith(aDescendantPath, prefix)) {
+    return false;
+  }
+
+  return true;
+}
+
+// static
+bool
+FileSystemUtils::IsParentProcess()
+{
+  return XRE_GetProcessType() == GeckoProcessType_Default;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/FileSystemUtils.h
@@ -0,0 +1,53 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileSystemUtils_h
+#define mozilla_dom_FileSystemUtils_h
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+#define FILESYSTEM_DOM_PATH_SEPARATOR "/"
+
+/*
+ * This class is for error handling.
+ * All methods in this class are static.
+ */
+class FileSystemUtils
+{
+public:
+  /*
+   * Convert the path separator to "/".
+   */
+  static void
+  LocalPathToNormalizedPath(const nsAString& aLocal, nsAString& aNorm);
+
+  /*
+   * Convert the normalized path separator "/" to the system dependent path
+   * separator, which is "/" on Mac and Linux, and "\" on Windows.
+   */
+  static void
+  NormalizedPathToLocalPath(const nsAString& aNorm, nsAString& aLocal);
+
+  /*
+   * Return true if aDescendantPath is a descendant of aPath. Both aPath and
+   * aDescendantPath are absolute DOM path.
+   */
+  static bool
+  IsDescendantPath(const nsAString& aPath, const nsAString& aDescendantPath);
+
+  static bool
+  IsParentProcess();
+
+  static const char16_t kSeparatorChar = char16_t('/');
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileSystemUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFileOrDirectoryTask.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GetFileOrDirectoryTask.h"
+
+#include "js/Value.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "nsDOMFile.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace dom {
+
+GetFileOrDirectoryTask::GetFileOrDirectoryTask(
+  FileSystemBase* aFileSystem,
+  const nsAString& aTargetPath,
+  bool aDirectoryOnly)
+  : FileSystemTaskBase(aFileSystem)
+  , mTargetRealPath(aTargetPath)
+  , mIsDirectory(aDirectoryOnly)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem);
+  nsCOMPtr<nsIGlobalObject> globalObject =
+    do_QueryInterface(aFileSystem->GetWindow());
+  if (!globalObject) {
+    return;
+  }
+  mPromise = new Promise(globalObject);
+}
+
+GetFileOrDirectoryTask::GetFileOrDirectoryTask(
+  FileSystemBase* aFileSystem,
+  const FileSystemGetFileOrDirectoryParams& aParam,
+  FileSystemRequestParent* aParent)
+  : FileSystemTaskBase(aFileSystem, aParam, aParent)
+  , mIsDirectory(false)
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  MOZ_ASSERT(aFileSystem);
+  mTargetRealPath = aParam.realPath();
+}
+
+GetFileOrDirectoryTask::~GetFileOrDirectoryTask()
+{
+  MOZ_ASSERT(!mPromise || NS_IsMainThread(),
+             "mPromise should be released on main thread!");
+}
+
+already_AddRefed<Promise>
+GetFileOrDirectoryTask::GetPromise()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  return nsRefPtr<Promise>(mPromise).forget();
+}
+
+FileSystemParams
+GetFileOrDirectoryTask::GetRequestParams(const nsString& aFileSystem) const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  return FileSystemGetFileOrDirectoryParams(aFileSystem, mTargetRealPath);
+}
+
+FileSystemResponseValue
+GetFileOrDirectoryTask::GetSuccessRequestResult() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (mIsDirectory) {
+    return FileSystemDirectoryResponse(mTargetRealPath);
+  }
+  BlobParent* actor = GetBlobParent(mTargetFile);
+  if (!actor) {
+    return FileSystemErrorResponse(NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR);
+  }
+  FileSystemFileResponse response;
+  response.blobParent() = actor;
+  return response;
+}
+
+void
+GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  switch (aValue.type()) {
+    case FileSystemResponseValue::TFileSystemFileResponse: {
+      FileSystemFileResponse r = aValue;
+      BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
+      nsCOMPtr<nsIDOMBlob> blob = actor->GetBlob();
+      mTargetFile = do_QueryInterface(blob);
+      mIsDirectory = false;
+      break;
+    }
+    case FileSystemResponseValue::TFileSystemDirectoryResponse: {
+      FileSystemDirectoryResponse r = aValue;
+      mTargetRealPath = r.realPath();
+      mIsDirectory = true;
+      break;
+    }
+    default: {
+      NS_RUNTIMEABORT("not reached");
+      break;
+    }
+  }
+}
+
+nsresult
+GetFileOrDirectoryTask::Work()
+{
+  MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
+             "Only call from parent process!");
+  MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
+
+  if (mFileSystem->IsShutdown()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Whether we want to get the root directory.
+  bool getRoot = mTargetRealPath.IsEmpty();
+
+  nsCOMPtr<nsIFile> file = mFileSystem->GetLocalFile(mTargetRealPath);
+  if (!file) {
+    return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+  }
+
+  bool exists;
+  nsresult rv = file->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!exists) {
+    if (!getRoot) {
+      return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+    }
+
+    // If the root directory doesn't exit, create it.
+    rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Get isDirectory.
+  rv = file->IsDirectory(&mIsDirectory);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mIsDirectory) {
+    return NS_OK;
+  }
+
+  // Check if the root is a directory.
+  if (getRoot) {
+    return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+  }
+
+  bool isFile;
+  // Get isFile
+  rv = file->IsFile(&isFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!isFile) {
+    // Neither directory or file.
+    return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+  }
+
+  if (!mFileSystem->IsSafeFile(file)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  mTargetFile = new nsDOMFileFile(file);
+
+  return NS_OK;
+}
+
+void
+GetFileOrDirectoryTask::HandlerCallback()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  if (mFileSystem->IsShutdown()) {
+    mPromise = nullptr;
+    return;
+  }
+
+  if (HasError()) {
+    nsRefPtr<DOMError> domError = new DOMError(mFileSystem->GetWindow(),
+      mErrorValue);
+    mPromise->MaybeReject(domError);
+    mPromise = nullptr;
+    return;
+  }
+
+  if (mIsDirectory) {
+    nsRefPtr<Directory> dir = new Directory(mFileSystem, mTargetRealPath);
+    mPromise->MaybeResolve(dir);
+    mPromise = nullptr;
+    return;
+  }
+
+  mPromise->MaybeResolve(mTargetFile);
+  mPromise = nullptr;
+}
+
+void
+GetFileOrDirectoryTask::GetPermissionAccessType(nsCString& aAccess) const
+{
+  aAccess.AssignLiteral("read");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFileOrDirectoryTask.h
@@ -0,0 +1,63 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GetFileOrDirectory_h
+#define mozilla_dom_GetFileOrDirectory_h
+
+#include "mozilla/dom/FileSystemTaskBase.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class GetFileOrDirectoryTask MOZ_FINAL
+  : public FileSystemTaskBase
+{
+public:
+  // If aDirectoryOnly is set, we should ensure that the target is a directory.
+  GetFileOrDirectoryTask(FileSystemBase* aFileSystem,
+                         const nsAString& aTargetPath,
+                         bool aDirectoryOnly);
+  GetFileOrDirectoryTask(FileSystemBase* aFileSystem,
+                         const FileSystemGetFileOrDirectoryParams& aParam,
+                         FileSystemRequestParent* aParent);
+
+  virtual
+  ~GetFileOrDirectoryTask();
+
+  already_AddRefed<Promise>
+  GetPromise();
+
+  virtual void
+  GetPermissionAccessType(nsCString& aAccess) const MOZ_OVERRIDE;
+protected:
+  virtual FileSystemParams
+  GetRequestParams(const nsString& aFileSystem) const MOZ_OVERRIDE;
+
+  virtual FileSystemResponseValue
+  GetSuccessRequestResult() const MOZ_OVERRIDE;
+
+  virtual void
+  SetSuccessRequestResult(const FileSystemResponseValue& aValue) MOZ_OVERRIDE;
+
+  virtual nsresult
+  Work() MOZ_OVERRIDE;
+
+  virtual void
+  HandlerCallback() MOZ_OVERRIDE;
+
+private:
+  nsRefPtr<Promise> mPromise;
+  nsString mTargetRealPath;
+  // Whether we get a directory.
+  bool mIsDirectory;
+  nsCOMPtr<nsIDOMFile> mTargetFile;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GetFileOrDirectory_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/PFileSystemRequest.ipdl
@@ -0,0 +1,44 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBlob;
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+
+struct FileSystemFileResponse
+{
+  PBlob blob;
+};
+
+struct FileSystemDirectoryResponse
+{
+  nsString realPath;
+};
+
+struct FileSystemErrorResponse
+{
+  nsresult error;
+};
+
+union FileSystemResponseValue
+{
+  FileSystemDirectoryResponse;
+  FileSystemFileResponse;
+  FileSystemErrorResponse;
+};
+
+sync protocol PFileSystemRequest
+{
+  manager PContent;
+
+child:
+  __delete__(FileSystemResponseValue response);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+    'DeviceStorageFileSystem.h',
+    'Directory.h',
+    'FileSystemBase.h',
+    'FileSystemRequestParent.h',
+    'FileSystemTaskBase.h',
+    'FileSystemUtils.h',
+]
+
+SOURCES += [
+    'CreateDirectoryTask.cpp',
+    'DeviceStorageFileSystem.cpp',
+    'Directory.cpp',
+    'FileSystemBase.cpp',
+    'FileSystemPermissionRequest.cpp',
+    'FileSystemRequestParent.cpp',
+    'FileSystemTaskBase.cpp',
+    'FileSystemUtils.cpp',
+    'GetFileOrDirectoryTask.cpp',
+]
+
+FINAL_LIBRARY = 'gklayout'
+
+IPDL_SOURCES += [
+    'PFileSystemRequest.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+    '/dom/base',
+]
+
--- a/dom/inputmethod/mochitest/file_test_app.html
+++ b/dom/inputmethod/mochitest/file_test_app.html
@@ -1,10 +1,11 @@
 <!DOCTYPE HTML>
 <html>
 <body>
 <input id="test-input" type="text" value="Yuan" x-inputmode="verbatim" lang="zh"/>
 <script type="application/javascript;version=1.7">
   let input = document.getElementById('test-input');
   input.focus();
+  dump('file_test_app.html was loaded.');
 </script>
 </body>
 </html>
--- a/dom/inputmethod/mochitest/inputmethod_common.js
+++ b/dom/inputmethod/mochitest/inputmethod_common.js
@@ -1,10 +1,11 @@
 function inputmethod_setup(callback) {
   SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestCompleteLog();
   let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1']
                 .getService(SpecialPowers.Ci.nsIXULAppInfo);
   if (appInfo.name != 'B2G') {
     SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", this);
   }
 
   let permissions = [];
   ['input-manage', 'browser'].forEach(function(name) {
--- a/dom/inputmethod/mochitest/test_bug944397.html
+++ b/dom/inputmethod/mochitest/test_bug944397.html
@@ -18,46 +18,43 @@ https://bugzilla.mozilla.org/show_bug.cg
 inputmethod_setup(function() {
   runTest();
 });
 
 // The frame script running in file_test_app.html.
 function appFrameScript() {
   let input = content.document.getElementById('test-input');
   input.oninput = function() {
+    dump('oninput was called in file_test_app.html.');
     sendAsyncMessage('test:InputMethod:oninput', {});
   };
 
   /*
    * Bug 957213. Sometimes we need to refocus the input field to avoid
    * intermittent test failure.
    */
   content.setInterval(function() {
     input.focus();
   }, 500);
 }
 
 function runTest() {
-  var timeoutId = null;
+  let timeoutId = null;
 
   // Create an app frame to recieve keyboard inputs.
   let app = document.createElement('iframe');
   app.src = 'file_test_app.html';
   app.setAttribute('mozbrowser', true);
   document.body.appendChild(app);
   app.addEventListener('mozbrowserloadend', function() {
     let mm = SpecialPowers.getBrowserFrameMessageManager(app);
     mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
     mm.addMessageListener("test:InputMethod:oninput", function() {
-      if (!timeoutId) {
-        return;
-      }
       ok(true, 'Keyboard input was received.');
       clearTimeout(timeoutId);
-      timeoutId = null;
       inputmethod_cleanup();
     });
   });
 
   // Create a browser frame to load the input method app.
   let keyboard = document.createElement('iframe');
   keyboard.setAttribute('mozbrowser', true);
   document.body.appendChild(keyboard);
@@ -83,31 +80,31 @@ function runTest() {
     type: 'input',
     allow: true,
     context: imeUrl
   }], function() {
      let req = keyboard.setInputMethodActive(true);
 
      req.onsuccess = function() {
        ok(true, 'setInputMethodActive succeeded.');
-       timeoutId = setTimeout(function() {
-        inputmethod_cleanup();
-        ok(false, 'Failed to generate keyboard input.');
-       }, 20000);
      };
 
      req.onerror = function() {
        ok(false, 'setInputMethodActive failed: ' + this.error.name);
        inputmethod_cleanup();
      };
 
      // Loads the input method app to the browser frame after a delay.
      SpecialPowers.DOMWindowUtils.focus(app);
      setTimeout(function() {
        keyboard.src = imeUrl;
+       timeoutId = setTimeout(function() {
+         inputmethod_cleanup();
+         ok(false, 'Failed to generate keyboard input.');
+       }, 20000);
      }, 100);
   });
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -112,16 +112,18 @@
 #ifdef MOZ_NUWA_PROCESS
 #include <setjmp.h>
 #include "ipc/Nuwa.h"
 #endif
 
 #include "mozilla/dom/indexedDB/PIndexedDBChild.h"
 #include "mozilla/dom/mobilemessage/SmsChild.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
+#include "mozilla/dom/PFileSystemRequestChild.h"
+#include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/bluetooth/PBluetoothChild.h"
 #include "mozilla/dom/PFMRadioChild.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/PSpeechSynthesisChild.h"
 #endif
 
@@ -1033,16 +1035,34 @@ ContentChild::AllocPDeviceStorageRequest
 
 bool
 ContentChild::DeallocPDeviceStorageRequestChild(PDeviceStorageRequestChild* aDeviceStorage)
 {
     delete aDeviceStorage;
     return true;
 }
 
+PFileSystemRequestChild*
+ContentChild::AllocPFileSystemRequestChild(const FileSystemParams& aParams)
+{
+    NS_NOTREACHED("Should never get here!");
+    return nullptr;
+}
+
+bool
+ContentChild::DeallocPFileSystemRequestChild(PFileSystemRequestChild* aFileSystem)
+{
+    mozilla::dom::FileSystemTaskBase* child =
+      static_cast<mozilla::dom::FileSystemTaskBase*>(aFileSystem);
+    // The reference is increased in FileSystemTaskBase::Start of
+    // FileSystemTaskBase.cpp. We should decrease it after IPC.
+    NS_RELEASE(child);
+    return true;
+}
+
 PNeckoChild*
 ContentChild::AllocPNeckoChild()
 {
     return new NeckoChild();
 }
 
 bool
 ContentChild::DeallocPNeckoChild(PNeckoChild* necko)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -94,16 +94,19 @@ public:
 
     virtual PBrowserChild* AllocPBrowserChild(const IPCTabContext &aContext,
                                               const uint32_t &chromeFlags);
     virtual bool DeallocPBrowserChild(PBrowserChild*);
 
     virtual PDeviceStorageRequestChild* AllocPDeviceStorageRequestChild(const DeviceStorageParams&);
     virtual bool DeallocPDeviceStorageRequestChild(PDeviceStorageRequestChild*);
 
+    virtual PFileSystemRequestChild* AllocPFileSystemRequestChild(const FileSystemParams&);
+    virtual bool DeallocPFileSystemRequestChild(PFileSystemRequestChild*);
+
     virtual PBlobChild* AllocPBlobChild(const BlobConstructorParams& aParams);
     virtual bool DeallocPBlobChild(PBlobChild*);
 
     virtual PCrashReporterChild*
     AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id,
                              const uint32_t& processType) MOZ_OVERRIDE;
     virtual bool
     DeallocPCrashReporterChild(PCrashReporterChild*) MOZ_OVERRIDE;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
 #include "mozilla/dom/PFMRadioParent.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #include "mozilla/dom/telephony/TelephonyParent.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "SmsParent.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/TestShellParent.h"
@@ -2223,16 +2224,34 @@ ContentParent::AllocPDeviceStorageReques
 bool
 ContentParent::DeallocPDeviceStorageRequestParent(PDeviceStorageRequestParent* doomed)
 {
   DeviceStorageRequestParent *parent = static_cast<DeviceStorageRequestParent*>(doomed);
   NS_RELEASE(parent);
   return true;
 }
 
+PFileSystemRequestParent*
+ContentParent::AllocPFileSystemRequestParent(const FileSystemParams& aParams)
+{
+  nsRefPtr<FileSystemRequestParent> result = new FileSystemRequestParent();
+  if (!result->Dispatch(this, aParams)) {
+    return nullptr;
+  }
+  return result.forget().get();
+}
+
+bool
+ContentParent::DeallocPFileSystemRequestParent(PFileSystemRequestParent* doomed)
+{
+  FileSystemRequestParent* parent = static_cast<FileSystemRequestParent*>(doomed);
+  NS_RELEASE(parent);
+  return true;
+}
+
 PBlobParent*
 ContentParent::AllocPBlobParent(const BlobConstructorParams& aParams)
 {
   return BlobParent::Create(this, aParams);
 }
 
 bool
 ContentParent::DeallocPBlobParent(PBlobParent* aActor)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -336,16 +336,22 @@ private:
     virtual PBrowserParent* AllocPBrowserParent(const IPCTabContext& aContext,
                                                 const uint32_t& aChromeFlags) MOZ_OVERRIDE;
     virtual bool DeallocPBrowserParent(PBrowserParent* frame) MOZ_OVERRIDE;
 
     virtual PDeviceStorageRequestParent*
     AllocPDeviceStorageRequestParent(const DeviceStorageParams&) MOZ_OVERRIDE;
     virtual bool DeallocPDeviceStorageRequestParent(PDeviceStorageRequestParent*) MOZ_OVERRIDE;
 
+    virtual PFileSystemRequestParent*
+    AllocPFileSystemRequestParent(const FileSystemParams&) MOZ_OVERRIDE;
+
+    virtual bool
+    DeallocPFileSystemRequestParent(PFileSystemRequestParent*) MOZ_OVERRIDE;
+
     virtual PBlobParent* AllocPBlobParent(const BlobConstructorParams& aParams) MOZ_OVERRIDE;
     virtual bool DeallocPBlobParent(PBlobParent*) MOZ_OVERRIDE;
 
     virtual bool DeallocPCrashReporterParent(PCrashReporterParent* crashreporter) MOZ_OVERRIDE;
 
     virtual bool RecvGetRandomValues(const uint32_t& length,
                                      InfallibleTArray<uint8_t>* randomValues) MOZ_OVERRIDE;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -9,16 +9,17 @@ include protocol PBackground;
 include protocol PBlob;
 include protocol PBluetooth;
 include protocol PBrowser;
 include protocol PCompositor;
 include protocol PCrashReporter;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFMRadio;
+include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PImageBridge;
 include protocol PIndexedDB;
 include protocol PMemoryReportRequest;
 include protocol PNecko;
 include protocol PSms;
 include protocol PSpeechSynthesis;
 include protocol PStorage;
@@ -188,16 +189,34 @@ union FMRadioRequestParams
 {
   FMRadioRequestEnableParams;
   FMRadioRequestDisableParams;
   FMRadioRequestSetFrequencyParams;
   FMRadioRequestSeekParams;
   FMRadioRequestCancelSeekParams;
 };
 
+struct FileSystemCreateDirectoryParams
+{
+  nsString filesystem;
+  nsString realPath;
+};
+
+struct FileSystemGetFileOrDirectoryParams
+{
+  nsString filesystem;
+  nsString realPath;
+};
+
+union FileSystemParams
+{
+  FileSystemCreateDirectoryParams;
+  FileSystemGetFileOrDirectoryParams;
+};
+
 union PrefValue {
   nsCString;
   int32_t;
   bool;
 };
 
 union MaybePrefValue {
   PrefValue;
@@ -217,16 +236,17 @@ intr protocol PContent
     child opens PBackground;
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
     manages PBrowser;
     manages PCrashReporter;
     manages PDeviceStorageRequest;
+    manages PFileSystemRequest;
     manages PExternalHelperApp;
     manages PFMRadio;
     manages PHal;
     manages PIndexedDB;
     manages PMemoryReportRequest;
     manages PNecko;
     manages PSms;
     manages PSpeechSynthesis;
@@ -366,16 +386,18 @@ parent:
      */
     sync GetProcessAttributes()
         returns (uint64_t id, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
         returns (bool isOffline);
 
     PDeviceStorageRequest(DeviceStorageParams params);
 
+    PFileSystemRequest(FileSystemParams params);
+
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
 
     sync GetRandomValues(uint32_t length)
         returns (uint8_t[] randomValues);
 
     PHal();
 
     PIndexedDB();
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -94,16 +94,17 @@ LOCAL_INCLUDES += [
     '/content/base/src',
     '/content/media/webspeech/synth/ipc',
     '/docshell/base',
     '/dom/base',
     '/dom/bluetooth',
     '/dom/bluetooth/ipc',
     '/dom/devicestorage',
     '/dom/events',
+    '/dom/filesystem',
     '/dom/fmradio/ipc',
     '/dom/indexedDB',
     '/dom/indexedDB/ipc',
     '/dom/mobilemessage/src/ipc',
     '/extensions/cookie',
     '/hal/sandbox',
     '/js/ipc',
     '/layout/base',
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -45,16 +45,17 @@ PARALLEL_DIRS += [
     'contacts',
     'phonenumberutils',
     'alarm',
     'datastore',
     'devicestorage',
     'encoding',
     'events',
     'file',
+    'filesystem',
     'fmradio',
     'asmjscache',
     'media',
     'messages',
     'power',
     'push',
     'quota',
     'settings',
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -242,16 +242,32 @@ private:
     JSObject* abv = aArgument.Create(aCx, scope);
     if (!abv) {
       return false;
     }
     aValue.setObject(*abv);
     return true;
   }
 
+  // Accept objects that inherit from nsISupports but not nsWrapperCache (e.g.
+  // nsIDOMFile).
+  template <class T>
+  typename EnableIf<!IsBaseOf<nsWrapperCache, T>::value &&
+                    IsBaseOf<nsISupports, T>::value, bool>::Type
+  ArgumentToJSValue(T& aArgument,
+                    JSContext* aCx,
+                    JSObject* aScope,
+                    JS::MutableHandle<JS::Value> aValue)
+  {
+    JS::Rooted<JSObject*> scope(aCx, aScope);
+
+    nsresult rv = nsContentUtils::WrapNative(aCx, scope, &aArgument, aValue);
+    return NS_SUCCEEDED(rv);
+  }
+
   template <template <typename> class SmartPtr, typename T>
   bool
   ArgumentToJSValue(const SmartPtr<T>& aArgument,
                     JSContext* aCx,
                     JSObject* aScope,
                     JS::MutableHandle<JS::Value> aValue)
   {
     return ArgumentToJSValue(*aArgument.get(), aCx, aScope, aValue);
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -704,16 +704,18 @@ static const dom::ConstantSpec gWinPrope
   INT_CONSTANT(ERROR_INVALID_HANDLE),
   INT_CONSTANT(ERROR_ACCESS_DENIED),
   INT_CONSTANT(ERROR_DIR_NOT_EMPTY),
   INT_CONSTANT(ERROR_FILE_EXISTS),
   INT_CONSTANT(ERROR_ALREADY_EXISTS),
   INT_CONSTANT(ERROR_FILE_NOT_FOUND),
   INT_CONSTANT(ERROR_NO_MORE_FILES),
   INT_CONSTANT(ERROR_PATH_NOT_FOUND),
+  INT_CONSTANT(ERROR_BAD_ARGUMENTS),
+  INT_CONSTANT(ERROR_NOT_SUPPORTED),
 
   PROP_END
 };
 #endif // defined(XP_WIN)
 
 
 /**
  * Get a field of an object as an object.
--- a/dom/webidl/DeviceStorage.webidl
+++ b/dom/webidl/DeviceStorage.webidl
@@ -50,9 +50,12 @@ interface DeviceStorage : EventTarget {
 
   // Note that the storageName is just a name (like sdcard), and doesn't
   // include any path information.
   readonly attribute DOMString storageName;
 
   // Determines if this storage area is the one which will be used by default
   // for storing new files.
   readonly attribute boolean default;
+
+  [NewObject]
+  Promise getRoot();
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Directory.webidl
@@ -0,0 +1,49 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+interface File;
+
+/*
+ * All functions on Directory that accept DOMString arguments for file or
+ * directory names only allow relative path to current directory itself. The
+ * path should be a descendent path like "path/to/file.txt" and not contain a
+ * segment of ".." or ".". So the paths aren't allowed to walk up the directory
+ * tree. For example, paths like "../foo", "..", "/foo/bar" or "foo/../bar" are
+ * not allowed.
+ */
+[NoInterfaceObject]
+interface Directory {
+  /*
+   * The leaf name of the directory.
+   */
+  readonly attribute DOMString name;
+
+  /*
+   * Creates a descendent directory. This method will create any intermediate
+   * directories specified by the path segments.
+   *
+   * @param path The relative path of the new directory to current directory.
+   * If path exists, createDirectory must fail.
+   * @return If succeeds, the promise is resolved with the new created
+   * Directory object. Otherwise, rejected with a DOM error.
+   */
+  [NewObject]
+  // Promise<Directory>
+  Promise createDirectory(DOMString path);
+
+  /*
+   * Gets a descendent file or directory with the given path.
+   *
+   * @param path The descendent entry's relative path to current directory.
+   * @return If the path exists and no error occurs, the promise is resolved
+   * with a File or Directory object, depending on the entry's type. Otherwise,
+   * rejected with a DOM error.
+   */
+  [NewObject]
+  // Promise<(File or Directory)>
+  Promise get(DOMString path);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/webidl/NativeOSFileInternals.webidl
@@ -0,0 +1,21 @@
+/* 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 obtaone at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Options for nsINativeOSFileInternals::Read
+ */
+dictionary NativeOSFileReadOptions
+{
+  /**
+   * If specified, convert the raw bytes to a String
+   * with the specified encoding. Otherwise, return
+   * the raw bytes as a TypedArray.
+   */
+  DOMString? encoding;
+
+  /**
+   * If specified, limit the number of bytes to read.
+   */
+  unsigned long long? bytes;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -68,16 +68,17 @@ WEBIDL_FILES = [
     'DataContainerEvent.webidl',
     'DataStore.webidl',
     'DataTransfer.webidl',
     'DedicatedWorkerGlobalScope.webidl',
     'DelayNode.webidl',
     'DesktopNotification.webidl',
     'DeviceMotionEvent.webidl',
     'DeviceStorage.webidl',
+    'Directory.webidl',
     'Document.webidl',
     'DocumentFragment.webidl',
     'DocumentType.webidl',
     'DOMCursor.webidl',
     'DOMError.webidl',
     'DOMException.webidl',
     'DOMImplementation.webidl',
     'DOMMMIError.webidl',
@@ -239,16 +240,17 @@ WEBIDL_FILES = [
     'MozMobileConnection.webidl',
     'MozNamedAttrMap.webidl',
     'MozNetworkStats.webidl',
     'MozPowerManager.webidl',
     'MozTimeManager.webidl',
     'MozWakeLock.webidl',
     'MutationEvent.webidl',
     'MutationObserver.webidl',
+    'NativeOSFileInternals.webidl',
     'NetDashboard.webidl',
     'NetworkOptions.webidl',
     'Node.webidl',
     'NodeFilter.webidl',
     'NodeIterator.webidl',
     'NodeList.webidl',
     'Notification.webidl',
     'NotifyAudioAvailableEvent.webidl',
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -244,16 +244,18 @@ public:
 
     nsRefPtr<RasterImage> image = weakImage.get();
     if (!image) {
       return false;
     }
 
     bool success = false;
     if (dstLocked) {
+      if (DiscardingEnabled())
+        dstFrame->SetDiscardable();
       success = NS_SUCCEEDED(dstFrame->UnlockImageData());
 
       dstLocked = false;
       srcData = nullptr;
       dstData = nullptr;
       srcSurface = nullptr;
       dstSurface = nullptr;
     }
@@ -2558,80 +2560,96 @@ RasterImage::ScalingDone(ScaleRequest* r
   // If we were waiting for this scale to come through, forget the scale
   // request. Otherwise, we still have a scale outstanding that it's possible
   // for us to (want to) stop.
   if (mScaleRequest == request) {
     mScaleRequest = nullptr;
   }
 }
 
-void
+bool
 RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                           gfxContext *aContext,
                                           GraphicsFilter aFilter,
                                           const gfxMatrix &aUserSpaceToImageSpace,
                                           const gfxRect &aFill,
                                           const nsIntRect &aSubimage,
                                           uint32_t aFlags)
 {
   imgFrame *frame = aFrame;
   nsIntRect framerect = frame->GetRect();
   gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
   gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
   imageSpaceToUserSpace.Invert();
   gfx::Size scale = ToSize(imageSpaceToUserSpace.ScaleFactors(true));
   nsIntRect subimage = aSubimage;
-
-  if (CanScale(aFilter, scale, aFlags)) {
+  nsRefPtr<gfxASurface> surf;
+
+  if (CanScale(aFilter, scale, aFlags) && !frame->IsSinglePixel()) {
     // If scale factor is still the same that we scaled for and
     // ScaleWorker isn't still working, then we can use pre-downscaled frame.
     // If scale factor has changed, order new request.
     // FIXME: Current implementation doesn't support pre-downscale
     // mechanism for multiple sizes from same src, since we cache
     // pre-downscaled frame only for the latest requested scale.
     // The solution is to cache more than one scaled image frame
     // for each RasterImage.
+    bool needScaleReq;
     if (mScaleResult.status == SCALE_DONE && mScaleResult.scale == scale) {
-      frame = mScaleResult.frame;
-      userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, scale.height));
-
-      // Since we're switching to a scaled image, we need to transform the
-      // area of the subimage to draw accordingly, since imgFrame::Draw()
-      // doesn't know about scaled frames.
-      subimage.ScaleRoundOut(scale.width, scale.height);
+      // Grab and hold the surface to make sure the OS didn't destroy it
+      mScaleResult.frame->GetSurface(getter_AddRefs(surf));
+      needScaleReq = !surf;
+      if (surf) {
+        frame = mScaleResult.frame;
+        userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width,
+                                                         scale.height));
+
+        // Since we're switching to a scaled image, we need to transform the
+        // area of the subimage to draw accordingly, since imgFrame::Draw()
+        // doesn't know about scaled frames.
+        subimage.ScaleRoundOut(scale.width, scale.height);
+      }
+    } else {
+      needScaleReq = !(mScaleResult.status == SCALE_PENDING &&
+                       mScaleResult.scale == scale);
     }
 
     // If we're not waiting for exactly this result, and there's only one
     // instance of this image on this page, ask for a scale.
-    else if (!(mScaleResult.status == SCALE_PENDING && mScaleResult.scale == scale) &&
-             mLockCount == 1) {
-      // If we have an oustanding request, signal it to stop (if it can).
+    if (needScaleReq && mLockCount == 1) {
+      if (NS_FAILED(frame->LockImageData())) {
+        frame->UnlockImageData();
+        return false;
+      }
+
+      // If we have an outstanding request, signal it to stop (if it can).
       if (mScaleRequest) {
         mScaleRequest->stopped = true;
       }
 
       nsRefPtr<ScaleRunner> runner = new ScaleRunner(this, scale, frame);
       if (runner->IsOK()) {
         if (!sScaleWorkerThread) {
           NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
           ClearOnShutdown(&sScaleWorkerThread);
         }
 
         sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL);
       }
+      frame->UnlockImageData();
     }
   }
 
   nsIntMargin padding(framerect.y,
                       mSize.width - framerect.XMost(),
                       mSize.height - framerect.YMost(),
                       framerect.x);
 
-  frame->Draw(aContext, aFilter, userSpaceToImageSpace, aFill, padding, subimage,
-              aFlags);
+  return frame->Draw(aContext, aFilter, userSpaceToImageSpace,
+                     aFill, padding, subimage, aFlags);
 }
 
 //******************************************************************************
 /* [noscript] void draw(in gfxContext aContext,
  *                      in gfxGraphicsFilter aFilter,
  *                      [const] in gfxMatrix aUserSpaceToImageSpace,
  *                      [const] in gfxRect aFill,
  *                      [const] in nsIntRect aSubimage,
@@ -2713,29 +2731,26 @@ RasterImage::Draw(gfxContext *aContext,
 
   uint32_t frameIndex = aWhichFrame == FRAME_FIRST ? 0
                                                    : GetCurrentImgFrameIndex();
   imgFrame* frame = GetDrawableImgFrame(frameIndex);
   if (!frame) {
     return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
   }
 
-  nsRefPtr<gfxASurface> surf;
-  if (!frame->IsSinglePixel()) {
-    frame->GetSurface(getter_AddRefs(surf));
-    if (!surf) {
-      // The OS threw out some or all of our buffer. Start decoding again.
-      ForceDiscard();
-      WantDecodedFrames();
-      return NS_OK;
-    }
+  bool drawn = DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter,
+                                            aUserSpaceToImageSpace, aFill,
+                                            aSubimage, aFlags);
+  if (!drawn) {
+    // The OS threw out some or all of our buffer. Start decoding again.
+    ForceDiscard();
+    WantDecodedFrames();
+    return NS_OK;
   }
 
-  DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, aUserSpaceToImageSpace, aFill, aSubimage, aFlags);
-
   if (mDecoded && !mDrawStartTime.IsNull()) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds()));
       // clear the value of mDrawStartTime
       mDrawStartTime = TimeStamp();
   }
 
   return NS_OK;
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -540,17 +540,17 @@ private:
   private: /* members */
 
     nsRefPtr<RasterImage> mImage;
   };
 
   nsresult FinishedSomeDecoding(eShutdownIntent intent = eShutdownIntent_Done,
                                 DecodeRequest* request = nullptr);
 
-  void DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
+  bool DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                     gfxContext *aContext,
                                     GraphicsFilter aFilter,
                                     const gfxMatrix &aUserSpaceToImageSpace,
                                     const gfxRect &aFill,
                                     const nsIntRect &aSubimage,
                                     uint32_t aFlags);
 
   nsresult CopyFrame(uint32_t aWhichFrame,
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -341,18 +341,43 @@ nsresult imgFrame::Optimize()
 #endif
 
 #ifdef XP_MACOSX
   if (mQuartzSurface) {
     mQuartzSurface->Flush();
   }
 #endif
 
+#ifdef ANDROID
+  gfxImageFormat optFormat =
+    gfxPlatform::GetPlatform()->
+      OptimalFormatForContent(gfxASurface::ContentFromFormat(mFormat));
+
+  if (optFormat == gfxImageFormat::RGB16_565) {
+    RefPtr<VolatileBuffer> buf =
+      LockedImageSurface::AllocateBuffer(mSize, optFormat);
+    if (!buf)
+      return NS_OK;
+
+    nsRefPtr<gfxImageSurface> surf =
+      LockedImageSurface::CreateSurface(buf, mSize, optFormat);
+
+    gfxContext ctx(surf);
+    ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
+    ctx.SetSource(mImageSurface);
+    ctx.Paint();
+
+    mImageSurface = surf;
+    mVBuf = buf;
+    mFormat = optFormat;
+  }
+#else
   if (mOptSurface == nullptr)
     mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat);
+#endif
 
   if (mOptSurface) {
     mVBuf = nullptr;
     mImageSurface = nullptr;
 #ifdef USE_WIN_SURFACE
     mWinSurface = nullptr;
 #endif
 #ifdef XP_MACOSX
@@ -389,22 +414,23 @@ imgFrame::SurfaceWithFormat
 imgFrame::SurfaceForDrawing(bool               aDoPadding,
                             bool               aDoPartialDecode,
                             bool               aDoTile,
                             const nsIntMargin& aPadding,
                             gfxMatrix&         aUserSpaceToImageSpace,
                             gfxRect&           aFill,
                             gfxRect&           aSubimage,
                             gfxRect&           aSourceRect,
-                            gfxRect&           aImageRect)
+                            gfxRect&           aImageRect,
+                            gfxASurface*       aSurface)
 {
   IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
   if (!aDoPadding && !aDoPartialDecode) {
     NS_ASSERTION(!mSinglePixel, "This should already have been handled");
-    return SurfaceWithFormat(new gfxSurfaceDrawable(ThebesSurface(), ThebesIntSize(size)), mFormat);
+    return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat);
   }
 
   gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
 
   if (aDoTile || mSinglePixel) {
     // Create a temporary surface.
     // Give this surface an alpha channel because there are
     // transparent pixels in the padding or undecoded area
@@ -415,17 +441,17 @@ imgFrame::SurfaceForDrawing(bool        
       return SurfaceWithFormat();
 
     // Fill 'available' with whatever we've got
     gfxContext tmpCtx(surface);
     tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
     if (mSinglePixel) {
       tmpCtx.SetDeviceColor(mSinglePixelColor);
     } else {
-      tmpCtx.SetSource(ThebesSurface(), gfxPoint(aPadding.left, aPadding.top));
+      tmpCtx.SetSource(aSurface, gfxPoint(aPadding.left, aPadding.top));
     }
     tmpCtx.Rectangle(available);
     tmpCtx.Fill();
 
     return SurfaceWithFormat(new gfxSurfaceDrawable(surface, ThebesIntSize(size)), format);
   }
 
   // Not tiling, and we have a surface, so we can account for
@@ -437,62 +463,69 @@ imgFrame::SurfaceForDrawing(bool        
   aFill = imageSpaceToUserSpace.Transform(aSourceRect);
 
   aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top);
   aUserSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top)));
   aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top);
   aImageRect = gfxRect(0, 0, mSize.width, mSize.height);
 
   gfxIntSize availableSize(mDecoded.width, mDecoded.height);
-  return SurfaceWithFormat(new gfxSurfaceDrawable(ThebesSurface(),
-                                                  availableSize),
+  return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize),
                            mFormat);
 }
 
-void imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter,
+bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter,
                     const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill,
                     const nsIntMargin &aPadding, const nsIntRect &aSubimage,
                     uint32_t aImageFlags)
 {
   PROFILER_LABEL("image", "imgFrame::Draw");
   NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller");
   NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
   bool doPadding = aPadding != nsIntMargin(0,0,0,0);
   bool doPartialDecode = !ImageComplete();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     DoSingleColorFastPath(aContext, mSinglePixelColor, aFill);
-    return;
+    return true;
   }
 
   gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
   gfxRect sourceRect = userSpaceToImageSpace.TransformBounds(aFill);
   gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(),
                     mSize.height + aPadding.TopBottom());
   gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
   gfxRect fill = aFill;
 
   NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
 
+  nsRefPtr<gfxASurface> surf;
+  if (!mSinglePixel) {
+    surf = ThebesSurface();
+    if (!surf)
+      return false;
+  }
+
   bool doTile = !imageRect.Contains(sourceRect) &&
                 !(aImageFlags & imgIContainer::FLAG_CLAMP);
   SurfaceWithFormat surfaceResult =
     SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding,
                       userSpaceToImageSpace, fill, subimage, sourceRect,
-                      imageRect);
+                      imageRect, surf);
 
   if (surfaceResult.IsValid()) {
     gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
                                userSpaceToImageSpace,
                                subimage, sourceRect, imageRect, fill,
                                surfaceResult.mFormat, aFilter, aImageFlags);
   }
+  return true;
 }
 
 // This can be called from any thread, but not simultaneously.
 nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
 {
   MutexAutoLock lock(mDirtyMutex);
 
   mDecoded.UnionRect(mDecoded, aUpdateRect);
@@ -627,35 +660,47 @@ nsresult imgFrame::LockImageData()
     if (mVBuf) {
       VolatileBufferPtr<uint8_t> ref(mVBuf);
       if (ref.WasBufferPurged())
         return NS_ERROR_FAILURE;
 
       mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
       if (!mImageSurface || mImageSurface->CairoStatus())
         return NS_ERROR_OUT_OF_MEMORY;
-    } else if (mOptSurface || mSinglePixel) {
+    }
+    if (mOptSurface || mSinglePixel || mFormat == gfxImageFormat::RGB16_565) {
+      gfxImageFormat format = mFormat;
+      if (mFormat == gfxImageFormat::RGB16_565)
+        format = gfxImageFormat::ARGB32;
+
       // Recover the pixels
-      mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
-      if (!mVBuf) {
+      RefPtr<VolatileBuffer> buf =
+        LockedImageSurface::AllocateBuffer(mSize, format);
+      if (!buf) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
 
-      mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
-      if (!mImageSurface || mImageSurface->CairoStatus())
+      RefPtr<gfxImageSurface> surf =
+        LockedImageSurface::CreateSurface(buf, mSize, mFormat);
+      if (!surf || surf->CairoStatus())
         return NS_ERROR_OUT_OF_MEMORY;
 
-      gfxContext context(mImageSurface);
+      gfxContext context(surf);
       context.SetOperator(gfxContext::OPERATOR_SOURCE);
       if (mSinglePixel)
         context.SetDeviceColor(mSinglePixelColor);
+      else if (mFormat == gfxImageFormat::RGB16_565)
+        context.SetSource(mImageSurface);
       else
         context.SetSource(mOptSurface);
       context.Paint();
 
+      mFormat = format;
+      mVBuf = buf;
+      mImageSurface = surf;
       mOptSurface = nullptr;
 #ifdef USE_WIN_SURFACE
       mWinSurface = nullptr;
 #endif
 #ifdef XP_MACOSX
       mQuartzSurface = nullptr;
 #endif
     }
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -44,17 +44,17 @@ class imgFrame
 {
 public:
   imgFrame();
   ~imgFrame();
 
   nsresult Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, gfxImageFormat aFormat, uint8_t aPaletteDepth = 0);
   nsresult Optimize();
 
-  void Draw(gfxContext *aContext, GraphicsFilter aFilter,
+  bool Draw(gfxContext *aContext, GraphicsFilter aFilter,
             const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill,
             const nsIntMargin &aPadding, const nsIntRect &aSubimage,
             uint32_t aImageFlags = imgIContainer::FLAG_NONE);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
   bool GetIsDirty() const;
 
   nsIntRect GetRect() const;
@@ -177,17 +177,18 @@ private: // methods
   SurfaceWithFormat SurfaceForDrawing(bool               aDoPadding,
                                       bool               aDoPartialDecode,
                                       bool               aDoTile,
                                       const nsIntMargin& aPadding,
                                       gfxMatrix&         aUserSpaceToImageSpace,
                                       gfxRect&           aFill,
                                       gfxRect&           aSubimage,
                                       gfxRect&           aSourceRect,
-                                      gfxRect&           aImageRect);
+                                      gfxRect&           aImageRect,
+                                      gfxASurface*       aSurface);
 
 private: // data
   nsRefPtr<gfxImageSurface> mImageSurface;
   nsRefPtr<gfxASurface> mOptSurface;
 #if defined(XP_WIN)
   nsRefPtr<gfxWindowsSurface> mWinSurface;
 #elif defined(XP_MACOSX)
   nsRefPtr<gfxQuartzImageSurface> mQuartzSurface;
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -48,16 +48,17 @@ LOCAL_INCLUDES += [
     '/content/xul/document/src',
     '/content/xul/templates/src',
     '/docshell/base',
     '/dom/audiochannel',
     '/dom/base',
     '/dom/camera',
     '/dom/events',
     '/dom/file',
+    '/dom/filesystem',
     '/dom/media',
     '/dom/speakermanager',
     '/dom/src/geolocation',
     '/dom/src/json',
     '/dom/src/jsurl',
     '/dom/src/offline',
     '/dom/src/storage',
     '/dom/telephony',
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -15,16 +15,17 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
 import org.mozilla.gecko.gfx.BitmapUtils;
@@ -54,16 +55,17 @@ import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.ButtonToast;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
@@ -380,39 +382,48 @@ abstract public class BrowserApp extends
                     return;
                 }
 
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
             }
         });
     }
 
-    void handleReaderAdded(int result, final String title, final String url) {
+    private void handleReaderAdded(int result, final ContentValues values) {
         if (result != READER_ADD_SUCCESS) {
             if (result == READER_ADD_FAILED) {
                 showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
             } else if (result == READER_ADD_DUPLICATE) {
                 showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
             }
 
             return;
         }
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                BrowserDB.addReadingListItem(getContentResolver(), title, url);
+                BrowserDB.addReadingListItem(getContentResolver(), values);
                 showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
 
                 final int count = BrowserDB.getReadingListCount(getContentResolver());
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count)));
             }
         });
     }
 
+    private ContentValues messageToReadingListContentValues(JSONObject message) {
+        final ContentValues values = new ContentValues();
+        values.put(ReadingListItems.URL, message.optString("url"));
+        values.put(ReadingListItems.TITLE, message.optString("title"));
+        values.put(ReadingListItems.LENGTH, message.optInt("length"));
+        values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
+        return values;
+    }
+
     void handleReaderRemoved(final String url) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 BrowserDB.removeReadingListItemWithURL(getContentResolver(), url);
                 showToast(R.string.reading_list_removed, Toast.LENGTH_SHORT);
 
                 final int count = BrowserDB.getReadingListCount(getContentResolver());
@@ -1122,19 +1133,17 @@ abstract public class BrowserApp extends
                 Telemetry.HistogramAdd("FENNEC_FAVICONS_COUNT", BrowserDB.getCount(getContentResolver(), "favicons"));
                 Telemetry.HistogramAdd("FENNEC_THUMBNAILS_COUNT", BrowserDB.getCount(getContentResolver(), "thumbnails"));
             } else if (event.equals("Reader:ListCountRequest")) {
                 handleReaderListCountRequest();
             } else if (event.equals("Reader:ListStatusRequest")) {
                 handleReaderListStatusRequest(message.getString("url"));
             } else if (event.equals("Reader:Added")) {
                 final int result = message.getInt("result");
-                final String title = message.getString("title");
-                final String url = message.getString("url");
-                handleReaderAdded(result, title, url);
+                handleReaderAdded(result, messageToReadingListContentValues(message));
             } else if (event.equals("Reader:Removed")) {
                 final String url = message.getString("url");
                 handleReaderRemoved(url);
             } else if (event.equals("Reader:Share")) {
                 final String title = message.getString("title");
                 final String url = message.getString("url");
                 GeckoAppShell.openUriExternal(url, "text/plain", "", "",
                                               Intent.ACTION_SEND, title);
@@ -1640,27 +1649,31 @@ abstract public class BrowserApp extends
         if (mDynamicToolbar.isEnabled()) {
             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
         }
 
         if (mHomePager == null) {
             final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
             mHomePager = (HomePager) homePagerStub.inflate();
 
-            final HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner);
-            mHomePager.setBanner(homeBanner);
-
-            // Remove the banner from the view hierarchy if it is dismissed.
-            homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
-                @Override
-                public void onDismiss() {
-                    mHomePager.setBanner(null);
-                    mHomePagerContainer.removeView(homeBanner);
-                }
-            });
+            // Don't show the banner in guest mode.
+            if (!getProfile().inGuestMode()) {
+                final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
+                final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
+                mHomePager.setBanner(homeBanner);
+
+                // Remove the banner from the view hierarchy if it is dismissed.
+                homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+                        mHomePager.setBanner(null);
+                        mHomePagerContainer.removeView(homeBanner);
+                    }
+                });
+            }
         }
 
         mHomePagerContainer.setVisibility(View.VISIBLE);
         mHomePager.load(getSupportLoaderManager(),
                         getSupportFragmentManager(),
                         pageId, animator);
 
         // Hide the web content so it cannot be focused by screen readers.
--- a/mobile/android/base/DynamicToolbar.java
+++ b/mobile/android/base/DynamicToolbar.java
@@ -101,17 +101,17 @@ public class DynamicToolbar {
 
     public void setVisible(boolean visible, VisibilityTransition transition) {
         ThreadUtils.assertOnUiThread();
 
         if (layerView == null) {
             return;
         }
 
-        final boolean immediate = transition.equals(VisibilityTransition.ANIMATE);
+        final boolean immediate = transition.equals(VisibilityTransition.IMMEDIATE);
         if (visible) {
             layerView.getLayerMarginsAnimator().showMargins(immediate);
         } else {
             layerView.getLayerMarginsAnimator().hideMargins(immediate);
         }
     }
 
     public void setPinned(boolean pinned, PinReason reason) {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1192,20 +1192,20 @@ public class GeckoAppShell
      *
      * @param context a <code>Context</code> instance.
      * @param targetURI the string spec of the URI to open.
      * @param mimeType an optional MIME type string.
      * @param title the title to use in <code>ACTION_SEND</code> intents.
      * @return an <code>Intent</code>, or <code>null</code> if none could be
      *         produced.
      */
-    static Intent getShareIntent(final Context context,
-                                 final String targetURI,
-                                 final String mimeType,
-                                 final String title) {
+    public static Intent getShareIntent(final Context context,
+                                        final String targetURI,
+                                        final String mimeType,
+                                        final String title) {
         Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
         shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
         shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
 
         // Note that EXTRA_TITLE is intended to be used for share dialog
         // titles. Common usage (e.g., Pocket) suggests that it's sometimes
         // interpreted as an alternate to EXTRA_SUBJECT, so we include it.
         shareIntent.putExtra(Intent.EXTRA_TITLE, title);
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -391,12 +391,15 @@ public class BrowserContract {
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/readinglistitem";
 
         public static final String EXCERPT = "excerpt";
         public static final String READ = "read";
         public static final String LENGTH = "length";
         public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
         public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH };
 
+        // Minimum fields required to create a reading list item.
+        public static final String[] REQUIRED_FIELDS = { Bookmarks.URL, Bookmarks.TITLE };
+
         public static final String TABLE_NAME = "reading_list";
     }
 
 }
--- a/mobile/android/base/db/BrowserDB.java
+++ b/mobile/android/base/db/BrowserDB.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko.db;
 import java.util.List;
 
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
 import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.CursorWrapper;
 import android.graphics.drawable.BitmapDrawable;
 import android.util.SparseArray;
 
 public class BrowserDB {
     private static boolean sAreContentProvidersEnabled = true;
@@ -93,17 +94,17 @@ public class BrowserDB {
         public void removeBookmark(ContentResolver cr, int id);
 
         @RobocopTarget
         public void removeBookmarksWithURL(ContentResolver cr, String uri);
 
         @RobocopTarget
         public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
 
-        public void addReadingListItem(ContentResolver cr, String title, String uri);
+        public void addReadingListItem(ContentResolver cr, ContentValues values);
 
         public void removeReadingListItemWithURL(ContentResolver cr, String uri);
 
         public void removeReadingListItem(ContentResolver cr, int id);
 
         public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String uri);
 
         public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url);
@@ -266,18 +267,18 @@ public class BrowserDB {
         sDb.removeBookmarksWithURL(cr, uri);
     }
 
     @RobocopTarget
     public static void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
         sDb.updateBookmark(cr, id, uri, title, keyword);
     }
 
-    public static void addReadingListItem(ContentResolver cr, String title, String uri) {
-        sDb.addReadingListItem(cr, title, uri);
+    public static void addReadingListItem(ContentResolver cr, ContentValues values) {
+        sDb.addReadingListItem(cr, values);
     }
 
     public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
         sDb.removeReadingListItemWithURL(cr, uri);
     }
 
     public static void removeReadingListItem(ContentResolver cr, int id) {
         sDb.removeReadingListItem(cr, id);
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -694,32 +694,37 @@ public class LocalBrowserDB implements B
         // Toggling bookmark on an URL should not affect the items in the reading list
         final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID) };
         final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ?";
 
         cr.delete(contentUri, urlEquals, urlArgs);
     }
 
     @Override
-    public void addReadingListItem(ContentResolver cr, String title, String uri) {
-        final ContentValues values = new ContentValues();
+    public void addReadingListItem(ContentResolver cr, ContentValues values) {
+        // Check that required fields are present.
+        for (String field: ReadingListItems.REQUIRED_FIELDS) {
+            if (!values.containsKey(field)) {
+                throw new IllegalArgumentException("Missing required field for reading list item: " + field);
+            }
+        }
+
+        // Clear delete flag if necessary
         values.put(ReadingListItems.IS_DELETED, 0);
-        values.put(ReadingListItems.URL, uri);
-        values.put(ReadingListItems.TITLE, title);
 
         // Restore deleted record if possible
         final Uri insertUri = mReadingListUriWithProfile
                               .buildUpon()
                               .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
                               .build();
 
         final int updated = cr.update(insertUri,
                                       values,
                                       ReadingListItems.URL + " = ? ",
-                                      new String[] { uri });
+                                      new String[] { values.getAsString(ReadingListItems.URL) });
 
         debug("Updated " + updated + " rows to new modified time.");
     }
 
     @Override
     public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
         cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
     }
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -65,17 +65,17 @@ public class HomeBanner extends LinearLa
 
     public HomeBanner(Context context) {
         this(context, null);
     }
 
     public HomeBanner(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        LayoutInflater.from(context).inflate(R.layout.home_banner, this);
+        LayoutInflater.from(context).inflate(R.layout.home_banner_content, this);
 
         mTextView = (TextView) findViewById(R.id.text);
         mIconView = (ImageView) findViewById(R.id.icon);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -330,16 +330,20 @@ size. -->
      as an advisory message on how to add content to the reading list when the reading list empty.
      The placeholder &formatI; will be replaced by a small image of the icon described, and can be moved to wherever
      it is applicable. -->
 <!ENTITY home_reading_list_hint2 "TIP: Save articles to your reading list by long pressing the &formatI; icon when it appears in the title bar.">
 <!-- Localization note (home_reading_list_hint_accessible): This string is used
      as alternate text for accessibility. It is not visible in the UI. -->
 <!ENTITY home_reading_list_hint_accessible "TIP: Save articles to your reading list by long pressing the reader mode button when it appears in the title bar.">
 
+<!-- Localization note (home_default_empty): This string is used as the default text when there
+     is no data to show in an about:home panel that was created by an add-on. -->
+<!ENTITY home_default_empty "No content could be found for this panel.">
+
 <!ENTITY pin_site_dialog_hint "Enter a search keyword">
 
 <!ENTITY filepicker_title "Choose File">
 <!ENTITY filepicker_audio_title "Choose or record a sound">
 <!ENTITY filepicker_image_title "Choose or take a picture">
 <!ENTITY filepicker_video_title "Choose or record a video">
 
 <!-- Site identity popup -->
--- a/mobile/android/base/menu/MenuItemActionView.java
+++ b/mobile/android/base/menu/MenuItemActionView.java
@@ -97,16 +97,35 @@ public class MenuItemActionView extends 
         }
     }
 
     @Override
     public void setShowIcon(boolean show) {
         mMenuItem.setShowIcon(show);
     }
 
+    public void setIcon(Drawable icon) {
+        mMenuItem.setIcon(icon);
+        mMenuButton.setIcon(icon);
+    }
+
+    public void setIcon(int icon) {
+        mMenuItem.setIcon(icon);
+        mMenuButton.setIcon(icon);
+    }
+
+    public void setTitle(CharSequence title) {
+        mMenuItem.setTitle(title);
+        mMenuButton.setContentDescription(title);
+    }
+
+    public void setSubMenuIndicator(boolean hasSubMenu) {
+        mMenuItem.setSubMenuIndicator(hasSubMenu);
+    }
+
     public void addActionButton(Drawable drawable) {
         // If this is the first icon, retain the text.
         // If not, make the menu item an icon.
         final int count = mActionButtons.size();
         mMenuItem.setVisibility(View.GONE);
         mMenuButton.setVisibility(View.VISIBLE);
 
         if (drawable != null) {
--- a/mobile/android/base/menu/MenuItemDefault.java
+++ b/mobile/android/base/menu/MenuItemDefault.java
@@ -137,15 +137,15 @@ public class MenuItemDefault extends Tex
     @Override
     public void setShowIcon(boolean show) {
         if (mShowIcon != show) {
             mShowIcon = show;
             refreshIcon();
         }
     }
 
-    private void setSubMenuIndicator(boolean hasSubMenu) {
+    void setSubMenuIndicator(boolean hasSubMenu) {
         if (mHasSubMenu != hasSubMenu) {
             mHasSubMenu = hasSubMenu;
             refreshDrawableState();
         }
     }
 }
--- a/mobile/android/base/prompts/IntentChooserPrompt.java
+++ b/mobile/android/base/prompts/IntentChooserPrompt.java
@@ -56,17 +56,17 @@ public class IntentChooserPrompt {
         if (mItems.isEmpty()) {
             Log.i(LOGTAG, "No activities for the intent chooser!");
             handler.onCancelled();
             return;
         }
 
         // If there's only one item in the intent list, just return it
         if (mItems.size() == 1) {
-            handler.onIntentSelected(mItems.get(0).intent, 0);
+            handler.onIntentSelected(mItems.get(0).getIntent(), 0);
             return;
         }
 
         final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
             @Override
             public void onPromptFinished(String promptServiceResult) {
                 if (handler == null) {
                     return;
@@ -77,17 +77,17 @@ public class IntentChooserPrompt {
                     itemId = new JSONObject(promptServiceResult).getInt("button");
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "result from promptservice was invalid: ", e);
                 }
 
                 if (itemId == -1) {
                     handler.onCancelled();
                 } else {
-                    handler.onIntentSelected(mItems.get(itemId).intent, itemId);
+                    handler.onIntentSelected(mItems.get(itemId).getIntent(), itemId);
                 }
             }
         });
 
         PromptListItem[] arrays = new PromptListItem[mItems.size()];
         mItems.toArray(arrays);
         prompt.show(title, "", arrays, ListView.CHOICE_MODE_NONE);
 
@@ -123,22 +123,24 @@ public class IntentChooserPrompt {
             items.add(getItemForResolveInfo(info, packageManager, provider.getIntent()));
         }
 
         return items;
     }
 
     private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) {
         PromptListItem item = new PromptListItem(info.loadLabel(pm).toString());
-        item.icon = info.loadIcon(pm);
-        item.intent = new Intent(intent);
+        item.setIcon(info.loadIcon(pm));
 
+        Intent i = new Intent(intent);
         // These intents should be implicit.
-        item.intent.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
-                                                   info.activityInfo.name));
+        i.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
+                                         info.activityInfo.name));
+        item.setIntent(new Intent(i));
+
         return item;
     }
 
     private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
         ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
         PackageManager pm = context.getPackageManager();
         List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
 
--- a/mobile/android/base/prompts/PromptListAdapter.java
+++ b/mobile/android/base/prompts/PromptListAdapter.java
@@ -1,48 +1,56 @@
 package org.mozilla.gecko.prompts;
 
+import org.mozilla.gecko.menu.MenuItemActionView;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.AdapterView;
 import android.widget.CheckedTextView;
 import android.widget.TextView;
 import android.widget.ListView;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+import android.util.TypedValue;
 
 import java.util.ArrayList;
 
 public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
     private static final int VIEW_TYPE_ITEM = 0;
     private static final int VIEW_TYPE_GROUP = 1;
-    private static final int VIEW_TYPE_COUNT = 2;
+    private static final int VIEW_TYPE_ACTIONS = 2;
+    private static final int VIEW_TYPE_COUNT = 3;
 
     private static final String LOGTAG = "GeckoPromptListAdapter";
 
     private final int mResourceId;
     private Drawable mBlankDrawable;
     private Drawable mMoreDrawable;
     private static int mGroupPaddingSize;
     private static int mLeftRightTextWithIconPadding;
     private static int mTopBottomTextWithIconPadding;
     private static int mIconSize;
     private static int mMinRowSize;
     private static int mIconTextPadding;
+    private static float mTextSize;
     private static boolean mInitialized = false;
 
     PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
         super(context, textViewResourceId, objects);
         mResourceId = textViewResourceId;
         init();
     }
 
@@ -50,25 +58,29 @@ public class PromptListAdapter extends A
         if (!mInitialized) {
             Resources res = getContext().getResources();
             mGroupPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_group_padding_size));
             mLeftRightTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_left_right_text_with_icon_padding));
             mTopBottomTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_top_bottom_text_with_icon_padding));
             mIconTextPadding = (int) (res.getDimension(R.dimen.prompt_service_icon_text_padding));
             mIconSize = (int) (res.getDimension(R.dimen.prompt_service_icon_size));
             mMinRowSize = (int) (res.getDimension(R.dimen.prompt_service_min_list_item_height));
+            mTextSize = res.getDimension(R.dimen.menu_item_textsize);
+
             mInitialized = true;
         }
     }
 
     @Override
     public int getItemViewType(int position) {
         PromptListItem item = getItem(position);
         if (item.isGroup) {
             return VIEW_TYPE_GROUP;
+        } else if (item.showAsActions) {
+            return VIEW_TYPE_ACTIONS;
         } else {
             return VIEW_TYPE_ITEM;
         }
     }
 
     @Override
     public int getViewTypeCount() {
         return VIEW_TYPE_COUNT;
@@ -85,33 +97,33 @@ public class PromptListAdapter extends A
         if (mBlankDrawable == null) {
             mBlankDrawable = res.getDrawable(R.drawable.blank);
         }
         return mBlankDrawable;
     }
 
     public void toggleSelected(int position) {
         PromptListItem item = getItem(position);
-        item.selected = !item.selected;
+        item.setSelected(!item.getSelected());
     }
 
     private void maybeUpdateIcon(PromptListItem item, TextView t) {
-        if (item.icon == null && !item.inGroup && !item.isParent) {
+        if (item.getIcon() == null && !item.inGroup && !item.isParent) {
             t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
             return;
         }
 
         Drawable d = null;
         Resources res = getContext().getResources();
         // Set the padding between the icon and the text.
         t.setCompoundDrawablePadding(mIconTextPadding);
-        if (item.icon != null) {
+        if (item.getIcon() != null) {
             // We want the icon to be of a specific size. Some do not
             // follow this rule so we have to resize them.
-            Bitmap bitmap = ((BitmapDrawable) item.icon).getBitmap();
+            Bitmap bitmap = ((BitmapDrawable) item.getIcon()).getBitmap();
             d = new BitmapDrawable(res, Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true));
         } else if (item.inGroup) {
             // We don't currently support "indenting" items with icons
             d = getBlankDrawable(res);
         }
 
         Drawable moreDrawable = null;
         if (item.isParent) {
@@ -125,22 +137,22 @@ public class PromptListAdapter extends A
 
     private void maybeUpdateCheckedState(ListView list, int position, PromptListItem item, ViewHolder viewHolder) {
         viewHolder.textView.setEnabled(!item.disabled && !item.isGroup);
         viewHolder.textView.setClickable(item.isGroup || item.disabled);
         if (viewHolder.textView instanceof CheckedTextView) {
             // Apparently just using ct.setChecked(true) doesn't work, so this
             // is stolen from the android source code as a way to set the checked
             // state of these items
-            list.setItemChecked(position, item.selected);
+            list.setItemChecked(position, item.getSelected());
         }
     }
 
     boolean isSelected(int position){
-        return getItem(position).selected;
+        return getItem(position).getSelected();
     }
 
     ArrayList<Integer> getSelected() {
         int length = getCount();
 
         ArrayList<Integer> selected = new ArrayList<Integer>();
         for (int i = 0; i< length; i++) {
             if (isSelected(i)) {
@@ -156,43 +168,88 @@ public class PromptListAdapter extends A
         for (int i = 0; i< length; i++) {
             if (isSelected(i)) {
                 return i;
             }
         }
         return -1;
     }
 
+    private View getActionView(PromptListItem item) {
+        GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
+        provider.setIntent(item.getIntent());
+        return provider.onCreateActionView();
+    }
+
+    private void updateActionView(final PromptListItem item, final MenuItemActionView view, final ListView list, final int position) {
+        view.setTitle(item.label);
+        view.setIcon(item.getIcon());
+        view.setSubMenuIndicator(item.isParent);
+        view.setMenuItemClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ListView.OnItemClickListener listener = list.getOnItemClickListener();
+                if (listener != null) {
+                    listener.onItemClick(list, view, position, position);
+                }
+
+                final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
+                IntentChooserPrompt prompt = new IntentChooserPrompt(getContext(), provider);
+                prompt.show(item.label, getContext(), new IntentHandler() {
+                    @Override
+                    public void onIntentSelected(final Intent intent, final int p) {
+                        provider.chooseActivity(p);
+                    }
+
+                    @Override
+                    public void onCancelled() {
+                        // do nothing
+                    }
+                });
+            }
+        });
+    }
+
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         PromptListItem item = getItem(position);
         int type = getItemViewType(position);
         ViewHolder viewHolder = null;
 
         if (convertView == null) {
-            int resourceId = mResourceId;
-            if (item.isGroup) {
-                resourceId = R.layout.list_item_header;
+            if (type == VIEW_TYPE_ACTIONS) {
+                convertView = getActionView(item);
+            } else {
+                int resourceId = mResourceId;
+                if (item.isGroup) {
+                    resourceId = R.layout.list_item_header;
+                }
+
+                LayoutInflater mInflater = LayoutInflater.from(getContext());
+                convertView = mInflater.inflate(resourceId, null);
+                convertView.setMinimumHeight(mMinRowSize);
+
+                TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
+                tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
+                viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
+                                            tv.getPaddingTop(), tv.getPaddingBottom());
+
+                convertView.setTag(viewHolder);
             }
-            LayoutInflater mInflater = LayoutInflater.from(getContext());
-            convertView = mInflater.inflate(resourceId, null);
-            convertView.setMinimumHeight(mMinRowSize);
-
-            TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
-            viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
-                                        tv.getPaddingTop(), tv.getPaddingBottom());
-
-            convertView.setTag(viewHolder);
         } else {
             viewHolder = (ViewHolder) convertView.getTag();
         }
 
-        viewHolder.textView.setText(item.label);
-        maybeUpdateCheckedState((ListView) parent, position, item, viewHolder);
-        maybeUpdateIcon(item, viewHolder.textView);
+        if (type == VIEW_TYPE_ACTIONS) {
+            updateActionView(item, (MenuItemActionView) convertView, (ListView) parent, position);
+        } else {
+            viewHolder.textView.setText(item.label);
+            maybeUpdateCheckedState((ListView) parent, position, item, viewHolder);
+            maybeUpdateIcon(item, viewHolder.textView);
+        }
 
         return convertView;
     }
 
     private static class ViewHolder {
         public final TextView textView;
         public final int paddingLeft;
         public final int paddingRight;
--- a/mobile/android/base/prompts/PromptListItem.java
+++ b/mobile/android/base/prompts/PromptListItem.java
@@ -1,50 +1,100 @@
 package org.mozilla.gecko.prompts;
 
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.GeckoAppShell;
+
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.util.Log;
 
 import java.util.List;
 import java.util.ArrayList;
 
 // This class should die and be replaced with normal menu items
 public class PromptListItem {
     private static final String LOGTAG = "GeckoPromptListItem";
     public final String label;
     public final boolean isGroup;
     public final boolean inGroup;
     public final boolean disabled;
     public final int id;
-    public boolean selected;
-    public Intent intent;
+    public final boolean showAsActions;
+    public final boolean isParent;
 
-    public boolean isParent;
-    public Drawable icon;
+    public Intent mIntent;
+    public boolean mSelected;
+    public Drawable mIcon;
 
     PromptListItem(JSONObject aObject) {
-        label = aObject.optString("label");
+        label = aObject.isNull("label") ? "" : aObject.optString("label");
         isGroup = aObject.optBoolean("isGroup");
         inGroup = aObject.optBoolean("inGroup");
         disabled = aObject.optBoolean("disabled");
         id = aObject.optInt("id");
-        isParent = aObject.optBoolean("isParent");
-        selected = aObject.optBoolean("selected");
+        mSelected = aObject.optBoolean("selected");
+
+        JSONObject obj = aObject.optJSONObject("shareData");
+        if (obj != null) {
+            showAsActions = true;
+            String uri = obj.isNull("uri") ? "" : obj.optString("uri");
+            String type = obj.isNull("type") ? "" : obj.optString("type");
+            mIntent = GeckoAppShell.getShareIntent(GeckoAppShell.getContext(), uri, type, "");
+            isParent = true;
+        } else {
+            mIntent = null;
+            showAsActions = false;
+            isParent = aObject.optBoolean("isParent");
+        }
+
+        BitmapUtils.getDrawable(GeckoAppShell.getContext(), aObject.optString("icon"), new BitmapUtils.BitmapLoader() {
+            @Override
+            public void onBitmapFound(Drawable d) {
+                mIcon = d;
+            }
+        });
+    }
+
+    public void setIntent(Intent i) {
+        mIntent = i;
+    }
+
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    public void setSelected(boolean selected) {
+        mSelected = selected;
+    }
+
+    public boolean getSelected() {
+        return mSelected;
     }
 
     public PromptListItem(String aLabel) {
         label = aLabel;
         isGroup = false;
         inGroup = false;
+        isParent = false;
         disabled = false;
         id = 0;
+        showAsActions = false;
     }
 
     static PromptListItem[] getArray(JSONArray items) {
         if (items == null) {
             return new PromptListItem[0];
         }
 
         int length = items.length();
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -31,27 +31,22 @@
                          android:layout_height="fill_parent"
                          android:visibility="gone">
 
                 <ViewStub android:id="@+id/home_pager_stub"
                           android:layout="@layout/home_pager"
                           android:layout_width="fill_parent"
                           android:layout_height="fill_parent"/>
 
-                <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
-                                                   style="@style/Widget.HomeBanner"
-                                                   android:layout_width="fill_parent"
-                                                   android:layout_height="@dimen/home_banner_height"
-                                                   android:background="@drawable/home_banner"
-                                                   android:layout_gravity="bottom"
-                                                   android:gravity="center_vertical"
-                                                   android:visibility="gone"
-                                                   android:clickable="true"
-                                                   android:focusable="true"
-                                                   android:translationY="@dimen/home_banner_height"/>
+                <ViewStub android:id="@+id/home_banner_stub"
+                          android:layout="@layout/home_banner"
+                          android:layout_width="fill_parent"
+                          android:layout_height="@dimen/home_banner_height"
+                          android:layout_gravity="bottom"
+                          android:translationY="@dimen/home_banner_height"/>
 
             </FrameLayout>
 
         </RelativeLayout>
 
         <org.mozilla.gecko.FindInPageBar android:id="@+id/find_in_page"
                                          android:layout_width="fill_parent"
                                          android:layout_height="wrap_content"
--- a/mobile/android/base/resources/layout/home_banner.xml
+++ b/mobile/android/base/resources/layout/home_banner.xml
@@ -1,36 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-     <ImageView android:id="@+id/icon"
-                android:layout_width="48dip"
-                android:layout_height="48dip"
-                android:layout_marginLeft="10dp"
-                android:scaleType="centerInside"/>
-
-     <TextView android:id="@+id/text"
-               android:layout_width="0dip"
-               android:layout_height="fill_parent"
-               android:layout_weight="1"
-               android:layout_marginLeft="10dp"
-               android:paddingTop="7dp"
-               android:paddingBottom="7dp"
-               android:textAppearance="@style/TextAppearance.Widget.HomeBanner"
-               android:layout_gravity="bottom"
-               android:singleLine="false"
-               android:maxLines="3"
-               android:ellipsize="end"
-               android:gravity="center_vertical"/>
-
-    <ImageButton android:id="@+id/close"
-                 android:layout_width="34dip"
-                 android:layout_height="fill_parent"
-                 android:background="@drawable/home_banner"
-                 android:scaleType="center"
-                 android:contentDescription="@string/close_tab"
-                 android:src="@drawable/tab_close"/>
-
-</merge>
+<org.mozilla.gecko.home.HomeBanner xmlns:android="http://schemas.android.com/apk/res/android"
+                                   android:id="@+id/home_banner"
+                                   style="@style/Widget.HomeBanner"
+                                   android:layout_width="fill_parent"
+                                   android:layout_height="fill_parent"
+                                   android:background="@drawable/home_banner"
+                                   android:gravity="center_vertical"
+                                   android:visibility="gone"
+                                   android:clickable="true"
+                                   android:focusable="true"/>
copy from mobile/android/base/resources/layout/home_banner.xml
copy to mobile/android/base/resources/layout/home_banner_content.xml
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -41,28 +41,30 @@
 
     <dimen name="doorhanger_input_width">250dp</dimen>
     <dimen name="doorhanger_spinner_textsize">9sp</dimen>
     <dimen name="doorhanger_padding">15dp</dimen>
     <dimen name="doorhanger_textsize_small">8sp</dimen>
 
     <dimen name="flow_layout_spacing">6dp</dimen>
     <dimen name="menu_item_icon">21dp</dimen>
+    <dimen name="menu_item_textsize">16sp</dimen>
     <dimen name="menu_item_state_icon">18dp</dimen>
     <dimen name="menu_item_row_height">44dp</dimen>
     <dimen name="menu_item_row_width">240dp</dimen>
     <dimen name="menu_item_more_offset">5dp</dimen>
+    <dimen name="menu_item_textsize">16sp</dimen>
     <dimen name="menu_popup_arrow_offset">8dp</dimen>
     <dimen name="menu_popup_arrow_margin">5dip</dimen>
     <dimen name="menu_popup_arrow_width">40dip</dimen>
     <dimen name="menu_popup_offset">12dp</dimen>
     <dimen name="menu_popup_width">256dp</dimen>
     <dimen name="nav_button_border_width">0.75dp</dimen>
     <dimen name="prompt_service_group_padding_size">32dp</dimen>
-    <dimen name="prompt_service_icon_size">72dp</dimen>
+    <dimen name="prompt_service_icon_size">36dp</dimen>
     <dimen name="prompt_service_icon_text_padding">10dp</dimen>
     <dimen name="prompt_service_inputs_padding">16dp</dimen>
     <dimen name="prompt_service_left_right_text_with_icon_padding">10dp</dimen>
     <dimen name="prompt_service_top_bottom_text_with_icon_padding">8dp</dimen>
     <dimen name="prompt_service_min_list_item_height">48dp</dimen>
     <dimen name="remote_tab_child_row_height">64dp</dimen>
     <dimen name="remote_tab_group_row_height">26dp</dimen>
     <dimen name="searchpreferences_icon_size">32dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -102,16 +102,17 @@
     <style name="Widget.MenuItemDefault">
         <item name="android:paddingLeft">10dip</item>
         <item name="android:paddingRight">10dip</item>
         <item name="android:drawablePadding">6dip</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:textAppearance">@style/TextAppearance</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">middle</item>
+        <item name="android:textSize">@dimen/menu_item_textsize</item>
     </style>
 
     <style name="Widget.TwoLinePageRow" />
 
     <style name="Widget.TwoLinePageRow.Title">
         <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">none</item>
@@ -253,17 +254,17 @@
         Hence, Gecko's TextAppearance is based on text over light theme and
         TextAppearance.Inverse is based on text over dark theme.
     -->
     <style name="TextAppearance">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textColorHighlight">@color/text_color_highlight</item>
         <item name="android:textColorHint">?android:attr/textColorHint</item>
         <item name="android:textColorLink">?android:attr/textColorLink</item>
-        <item name="android:textSize">16sp</item>
+        <item name="android:textSize">@dimen/menu_item_textsize</item>
         <item name="android:textStyle">normal</item>
     </style>
 
     <style name="TextAppearance.Inverse">
         <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
         <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
         <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
         <item name="android:textColorLink">?android:attr/textColorLink</item>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -299,16 +299,17 @@
   <string name="home_last_tabs_title">&home_last_tabs_title;</string>
   <string name="home_last_tabs_open">&home_last_tabs_open;</string>
   <string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
   <string name="home_most_recent_title">&home_most_recent_title;</string>
   <string name="home_most_recent_empty">&home_most_recent_empty;</string>
   <string name="home_reading_list_empty">&home_reading_list_empty;</string>
   <string name="home_reading_list_hint">&home_reading_list_hint2;</string>
   <string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
+  <string name="home_default_empty">&home_default_empty;</string>
   <string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
 
   <string name="filepicker_title">&filepicker_title;</string>
   <string name="filepicker_audio_title">&filepicker_audio_title;</string>
   <string name="filepicker_image_title">&filepicker_image_title;</string>
   <string name="filepicker_video_title">&filepicker_video_title;</string>
 
   <!-- Default bookmarks. We used to use bookmark titles shared with XUL from mobile's
--- a/mobile/android/base/widget/GeckoActionProvider.java
+++ b/mobile/android/base/widget/GeckoActionProvider.java
@@ -15,16 +15,17 @@ import android.graphics.drawable.Drawabl
 import android.view.ActionProvider;
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.View.OnClickListener;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 
 public class GeckoActionProvider extends ActionProvider {
     private static int MAX_HISTORY_SIZE = 2;
 
     /**
      * A listener to know when a target was selected.
      * When setting a provider, the activity can listen to this,
      * to close the menu.
@@ -39,16 +40,30 @@ public class GeckoActionProvider extends
 
     //  History file.
     private String mHistoryFileName = DEFAULT_HISTORY_FILE_NAME;
 
     private OnTargetSelectedListener mOnTargetListener;
 
     private final Callbacks mCallbacks = new Callbacks();
 
+    private static HashMap<String, GeckoActionProvider> mProviders = new HashMap<String, GeckoActionProvider>();
+
+    // Gets the action provider for a particular mimetype
+    public static GeckoActionProvider getForType(String type, Context context) {
+        if (!mProviders.keySet().contains(type)) {
+            GeckoActionProvider provider = new GeckoActionProvider(context);
+
+            String subType = type.substring(0, type.indexOf("/"));
+            provider.setHistoryFileName("history-" + subType + ".xml");
+            mProviders.put(type, provider);
+        }
+        return mProviders.get(type);
+    }
+
     public GeckoActionProvider(Context context) {
         super(context);
         mContext = context;
     }
 
     @Override
     public View onCreateActionView() {
         // Create the view and set its data model.
@@ -136,16 +151,20 @@ public class GeckoActionProvider extends
         // Populate the sub-menu with a sub set of the activities.
         final int count = dataModel.getActivityCount();
         for (int i = 0; i < count; i++) {
             infos.add(dataModel.getActivity(i));
         }
         return infos;
     }
 
+    public void chooseActivity(int position) {
+        mCallbacks.chooseActivity(position);
+    }
+
     /**
      * Listener for handling default activity / menu item clicks.
      */
     private class Callbacks implements OnMenuItemClickListener,
                                        OnClickListener {
         private void chooseActivity(int index) { 
             ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
             Intent launchIntent = dataModel.chooseActivity(index);
@@ -163,13 +182,12 @@ public class GeckoActionProvider extends
         public boolean onMenuItemClick(MenuItem item) {
             chooseActivity(item.getItemId());
             return true;
         }
 
         @Override
         public void onClick(View view) {
             Integer index = (Integer) view.getTag();
-            ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
             chooseActivity(index);
         }
     }
 }
--- a/mobile/android/chrome/content/Readability.js
+++ b/mobile/android/chrome/content/Readability.js
@@ -714,16 +714,87 @@ Readability.prototype = {
         }
 
         return articleContent;
       }
     }
   },
 
   /**
+   * Attempts to get the excerpt from these
+   * sources in the following order:
+   * - meta description tag
+   * - open-graph description
+   * - twitter cards description
+   * - article's first paragraph
+   * If no excerpt is found, an empty string will be
+   * returned.
+   *
+   * @param Element - root element of the processed version page
+   * @return String - excerpt of the article
+  **/
+  _getExcerpt: function(articleContent) {
+    let values = {};
+    let metaElements = this._doc.getElementsByTagName("meta");
+
+    // Match "description", or Twitter's "twitter:description" (Cards)
+    // in name attribute.
+    let namePattern = /^\s*((twitter)\s*:\s*)?description\s*$/gi;
+
+    // Match Facebook's og:description (Open Graph) in property attribute.
+    let propertyPattern = /^\s*og\s*:\s*description\s*$/gi;
+
+    // Find description tags.
+    for (let i = 0; i < metaElements.length; i++) {
+      let element = metaElements[i];
+      let elementName = element.getAttribute("name");
+      let elementProperty = element.getAttribute("property");
+
+      let name;
+      if (namePattern.test(elementName)) {
+        name = elementName;
+      } else if (propertyPattern.test(elementProperty)) {
+        name = elementProperty;
+      }
+
+      if (name) {
+        let content = element.getAttribute("content");
+        if (content) {
+          // Convert to lowercase and remove any whitespace
+          // so we can match below.
+          name = name.toLowerCase().replace(/\s/g, '');
+          values[name] = content.trim();
+        }
+      }
+    }
+
+    if ("description" in values) {
+      return values["description"];
+    }
+
+    if ("og:description" in values) {
+      // Use facebook open graph description.
+      return values["og:description"];
+    }
+
+    if ("twitter:description" in values) {
+      // Use twitter cards description.
+      return values["twitter:description"];
+    }
+
+    // No description meta tags, use the article's first paragraph.
+    let paragraphs = articleContent.getElementsByTagName("p");
+    if (paragraphs.length > 0) {
+      return paragraphs[0].textContent;
+    }
+
+    return "";
+  },
+
+  /**
    * Removes script tags from the document.
    *
    * @param Element
   **/
   _removeScripts: function(doc) {
     let scripts = doc.getElementsByTagName('script');
     for (let i = scripts.length - 1; i >= 0; i -= 1) {
       scripts[i].nodeValue="";
@@ -1429,14 +1500,18 @@ Readability.prototype = {
     // if (nextPageLink) {
     //   // Append any additional pages after a small timeout so that people
     //   // can start reading without having to wait for this to finish processing.
     //   setTimeout((function() {
     //     this._appendNextPage(nextPageLink);
     //   }).bind(this), 500);
     // }
 
+    let excerpt = this._getExcerpt(articleContent);
+
     return { title: articleTitle,
              byline: this._articleByline,
              dir: this._articleDir,
-             content: articleContent.innerHTML };
+             content: articleContent.innerHTML,
+             length: articleContent.textContent.length,
+             excerpt: excerpt };
   }
 };
--- a/mobile/android/chrome/content/aboutReader.js
+++ b/mobile/android/chrome/content/aboutReader.js
@@ -344,16 +344,18 @@ AboutReader.prototype = {
         let json = JSON.stringify({ fromAboutReader: true, url: this._article.url });
         Services.obs.notifyObservers(null, "Reader:Add", json);
 
         gChromeWin.sendMessageToJava({
           type: "Reader:Added",
           result: result,
           title: this._article.title,
           url: this._article.url,
+          length: this._article.length,
+          excerpt: this._article.excerpt
         });
       }.bind(this));
     } else {
       // In addition to removing the article from the cache (handled in
       // browser.js), sending this message will cause the toggle button to be
       // updated (handled in this file).
       Services.obs.notifyObservers(null, "Reader:Remove", this._article.url);
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -7428,63 +7428,67 @@ let Reader = {
         } else if ('url' in args) {
           let uri = Services.io.newURI(args.url, null, null);
           url = uri.spec;
           urlWithoutRef = uri.specIgnoringRef;
         } else {
           throw new Error("Reader:Add requires a tabID or an URL as argument");
         }
 
-        let sendResult = function(result, title) {
-          this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + title);
+        let sendResult = function(result, article) {
+          article = article || {};
+          this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + article.title + ", excerpt=" + article.excerpt);
 
           sendMessageToJava({
             type: "Reader:Added",
             result: result,
-            title: title,
+            title: article.title,
             url: url,
+            length: article.length,
+            excerpt: article.excerpt
           });
         }.bind(this);
 
         let handleArticle = function(article) {
           if (!article) {
-            sendResult(this.READER_ADD_FAILED, "");
+            sendResult(this.READER_ADD_FAILED, null);
             return;
           }
 
           this.storeArticleInCache(article, function(success) {
             let result = (success ? this.READER_ADD_SUCCESS : this.READER_ADD_FAILED);
-            sendResult(result, article.title);
+            sendResult(result, article);
           }.bind(this));
         }.bind(this);
 
         this.getArticleFromCache(urlWithoutRef, function (article) {
           // If the article is already in reading list, bail
           if (article) {
-            sendResult(this.READER_ADD_DUPLICATE, "");
+            sendResult(this.READER_ADD_DUPLICATE, null);
             return;
           }
 
           if (tabID != null) {
             this.getArticleForTab(tabID, urlWithoutRef, handleArticle);
           } else {
             this.parseDocumentFromURL(urlWithoutRef, handleArticle);
           }
         }.bind(this));
         break;
       }
 
       case "Reader:Remove": {
-        this.removeArticleFromCache(aData, function(success) {
-          this.log("Reader:Remove success=" + success + ", url=" + aData);
+        let url = aData;
+        this.removeArticleFromCache(url, function(success) {
+          this.log("Reader:Remove success=" + success + ", url=" + url);
 
           if (success) {
             sendMessageToJava({
               type: "Reader:Removed",
-              url: aData
+              url: url
             });
           }
         }.bind(this));
         break;
       }
 
       case "nsPref:changed": {
         if (aData.startsWith("reader.parse-on-load.")) {
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -241,16 +241,17 @@
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
+@BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -315,8 +315,15 @@ getUserMedia.sharingCameraAndMicrophone.
 readerMode.enter = Enter Reader Mode
 readerMode.exit = Exit Reader Mode
 
 #Open in App
 openInApp.pageAction = Open in App
 openInApp.ok = OK
 openInApp.cancel = Cancel
 
+#Tabs in context menus
+browser.menu.context.default = Link
+browser.menu.context.img = Image
+browser.menu.context.video = Video
+browser.menu.context.audio = Audio
+browser.menu.context.tel = Phone
+browser.menu.context.mailto = Mail
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -393,17 +393,17 @@ FxAccountsInternal.prototype = {
 
   /**
    * returns a promise that fires with the assertion.  If there is no verified
    * signed-in user, fires with null.
    */
   getAssertion: function getAssertion(audience) {
     log.debug("enter getAssertion()");
     let currentState = this.currentAccountState;
-    let mustBeValidUntil = this.now() + ASSERTION_LIFETIME;
+    let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
     return currentState.getUserAccountData().then(data => {
       if (!data) {
         // No signed-in user
         return null;
       }
       if (!this.isUserEmailVerified(data)) {
         // Signed-in user has not verified email
         return null;
@@ -535,16 +535,17 @@ FxAccountsInternal.prototype = {
     }.bind(this)).then(result => currentState.resolve(result));
   },
 
   getAssertionFromCert: function(data, keyPair, cert, audience) {
     log.debug("getAssertionFromCert");
     let payload = {};
     let d = Promise.defer();
     let options = {
+      duration: ASSERTION_LIFETIME,
       localtimeOffsetMsec: this.localtimeOffsetMsec,
       now: this.now()
     };
     let currentState = this.currentAccountState;
     // "audience" should look like "http://123done.org".
     // The generated assertion will expire in two minutes.
     jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
       if (err) {
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -28,17 +28,24 @@ XPCOMUtils.defineLazyGetter(this, 'log',
 
   return log;
 });
 
 this.DATA_FORMAT_VERSION = 1;
 this.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
 
 // Token life times.
-this.ASSERTION_LIFETIME = 1000 * 60 * 5;    // 5 minutes
+// Having this parameter be short has limited security value and can cause
+// spurious authentication values if the client's clock is skewed and
+// we fail to adjust. See Bug 983256.
+this.ASSERTION_LIFETIME = 1000 * 3600 * 24 * 365 * 25; // 25 years
+// This is a time period we want to guarantee that the assertion will be
+// valid after we generate it (e.g., the signed cert won't expire in this
+// period).
+this.ASSERTION_USE_PERIOD = 1000 * 60 * 5; // 5 minutes
 this.CERT_LIFETIME      = 1000 * 3600 * 6;  // 6 hours
 this.KEY_LIFETIME       = 1000 * 3600 * 12; // 12 hours
 
 // Polling timings.
 this.POLL_SESSION       = 1000 * 60 * 5;    // 5 minutes
 this.POLL_STEP          = 1000 * 3;         // 3 seconds
 
 // Observer notifications.
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -413,29 +413,29 @@ add_task(function test_getAssertion() {
   let payload = JSON.parse(atob(p2[1]));
   _("PAYLOAD: " + JSON.stringify(payload) + "\n");
   do_check_eq(payload.aud, "audience.example.com");
   do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
   do_check_eq(cert.validUntil, start + CERT_LIFETIME);
   _("delta: " + Date.parse(payload.exp - start) + "\n");
   let exp = Number(payload.exp);
 
-  do_check_eq(exp, now + TWO_MINUTES_MS);
+  do_check_eq(exp, now + ASSERTION_LIFETIME);
 
   // Reset for next call.
   fxa.internal._d_signCertificate = Promise.defer();
 
   // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for
   // a new audience, should not provoke key generation or a signing request.
   assertion = yield fxa.getAssertion("other.example.com");
 
   // There were no additional calls - same number of getcert calls as before
   do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
 
-  // Wait an hour; assertion expires, but not the certificate
+  // Wait an hour; assertion use period expires, but not the certificate
   now += ONE_HOUR_MS;
   fxa.internal._now_is = now;
 
   // This won't block on anything - will make an assertion, but not get a
   // new certificate.
   assertion = yield fxa.getAssertion("third.example.com");
 
   // Test will time out if that failed (i.e., if that had to go get a new cert)
@@ -451,17 +451,17 @@ add_task(function test_getAssertion() {
   // the initial start time, to which they are relative, not the current value
   // of "now".
 
   keyPair = fxa.internal.currentAccountState.keyPair;
   cert = fxa.internal.currentAccountState.cert;
   do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
   do_check_eq(cert.validUntil, start + CERT_LIFETIME);
   exp = Number(payload.exp);
-  do_check_eq(exp, now + TWO_MINUTES_MS);
+  do_check_eq(exp, now + ASSERTION_LIFETIME);
 
   // Now we wait even longer, and expect both assertion and cert to expire.  So
   // we will have to get a new keypair and cert.
   now += ONE_DAY_MS;
   fxa.internal._now_is = now;
   d = fxa.getAssertion("fourth.example.com");
   fxa.internal._d_signCertificate.resolve("cert2");
   assertion = yield d;
@@ -474,17 +474,17 @@ add_task(function test_getAssertion() {
   payload = JSON.parse(atob(p2[1]));
   do_check_eq(payload.aud, "fourth.example.com");
   keyPair = fxa.internal.currentAccountState.keyPair;
   cert = fxa.internal.currentAccountState.cert;
   do_check_eq(keyPair.validUntil, now + KEY_LIFETIME);
   do_check_eq(cert.validUntil, now + CERT_LIFETIME);
   exp = Number(payload.exp);
 
-  do_check_eq(exp, now + TWO_MINUTES_MS);
+  do_check_eq(exp, now + ASSERTION_LIFETIME);
   _("----- DONE ----\n");
 });
 
 add_task(function test_resend_email_not_signed_in() {
   let fxa = new MockFxAccounts();
 
   try {
     yield fxa.resendVerificationEmail();
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -30,16 +30,17 @@
 #include "nsUrlClassifierDBService.h"
 #include "nsUrlClassifierStreamUpdater.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsUrlClassifierPrefixSet.h"
 #endif
 
 #include "nsBrowserStatusFilter.h"
 #include "mozilla/FinalizationWitnessService.h"
+#include "mozilla/NativeOSFileInternals.h"
 
 using namespace mozilla;
 
 /////////////////////////////////////////////////////////////////////////////
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
@@ -84,16 +85,17 @@ nsUrlClassifierDBServiceConstructor(nsIS
 }
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
 #if defined(USE_MOZ_UPDATER)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
@@ -109,16 +111,17 @@ NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTRE
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID);
 #if defined(USE_MOZ_UPDATER)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
 
 static const Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
   { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
   { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceWinConstructor },
 #endif
@@ -135,16 +138,17 @@ static const Module::CIDEntry kToolkitCI
   { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
 #endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
   { &kNS_CHARSETMENU_CID, false, nullptr, NS_NewCharsetMenu },
 #if defined(USE_MOZ_UPDATER)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
   { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
+  { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
   { nullptr }
 };
 
 static const Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -164,16 +168,17 @@ static const Module::ContractIDEntry kTo
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
 #endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
   { NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_CHARSETMENU_PID, &kNS_CHARSETMENU_CID },
 #if defined(USE_MOZ_UPDATER)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
   { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
+  { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
   { nullptr }
 };
 
 static const Module kToolkitModule = {
   Module::kVersion,
   kToolkitCIDs,
   kToolkitContracts
 };
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -598,17 +598,17 @@ CrashStore.prototype = Object.freeze({
         v: 1,
         crashes: new Map(),
         corruptDate: null,
       };
       this._countsByDay = new Map();
 
       try {
         let decoder = new TextDecoder();
-        let data = yield OS.File.read(this._storePath, null, {compression: "lz4"});
+        let data = yield OS.File.read(this._storePath, {compression: "lz4"});
         data = JSON.parse(decoder.decode(data));
 
         if (data.corruptDate) {
           this._data.corruptDate = new Date(data.corruptDate);
         }
 
         // actualCounts is used to validate that the derived counts by
         // days stored in the payload matches up to actual data.
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -108,16 +108,18 @@ private:
   nsCOMPtr<nsIApplicationReputationQuery> mQuery;
 
   // The callback with which to report the verdict.
   nsCOMPtr<nsIApplicationReputationCallback> mCallback;
 
   // An array of strings created from certificate information used to whitelist
   // the downloaded file.
   nsTArray<nsCString> mAllowlistSpecs;
+  // The source URI of the download, the referrer and possibly any redirects.
+  nsTArray<nsCString> mAnylistSpecs;
 
   // When we started this query
   TimeStamp mStartTime;
 
   // The protocol buffer used to store signature information extracted using
   // the Windows Authenticode API, if the binary is signed.
   ClientDownloadRequest_SignatureInfo mSignatureInfo;
 
@@ -194,55 +196,55 @@ public:
 
   // Constructor and destructor
   PendingDBLookup(PendingLookup* aPendingLookup);
   ~PendingDBLookup();
 
   // Look up the given URI in the safebrowsing DBs, optionally on both the allow
   // list and the blocklist. If there is a match, call
   // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext.
-  nsresult LookupSpec(const nsACString& aSpec, bool aAllowListOnly);
+  nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly);
 private:
   // The download appeared on the allowlist, blocklist, or no list (and thus
   // could trigger a remote query.
   enum LIST_TYPES {
     ALLOW_LIST = 0,
     BLOCK_LIST = 1,
     NO_LIST = 2,
   };
 
   nsCString mSpec;
-  bool mAllowListOnly;
+  bool mAllowlistOnly;
   nsRefPtr<PendingLookup> mPendingLookup;
   nsresult LookupSpecInternal(const nsACString& aSpec);
 };
 
 NS_IMPL_ISUPPORTS1(PendingDBLookup,
                    nsIUrlClassifierCallback)
 
 PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) :
-  mAllowListOnly(false),
+  mAllowlistOnly(false),
   mPendingLookup(aPendingLookup)
 {
   LOG(("Created pending DB lookup [this = %p]", this));
 }
 
 PendingDBLookup::~PendingDBLookup()
 {
   LOG(("Destroying pending DB lookup [this = %p]", this));
   mPendingLookup = nullptr;
 }
 
 nsresult
 PendingDBLookup::LookupSpec(const nsACString& aSpec,
-                            bool aAllowListOnly)
+                            bool aAllowlistOnly)
 {
   LOG(("Checking principal %s", aSpec.Data()));
   mSpec = aSpec;
-  mAllowListOnly = aAllowListOnly;
+  mAllowlistOnly = aAllowlistOnly;
   nsresult rv = LookupSpecInternal(aSpec);
   if (NS_FAILED(rv)) {
     LOG(("Error in LookupSpecInternal"));
     return mPendingLookup->OnComplete(false, NS_OK);
   }
   // LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is
   // guaranteed to call HandleEvent.
   return rv;
@@ -275,33 +277,33 @@ PendingDBLookup::LookupSpecInternal(cons
 }
 
 NS_IMETHODIMP
 PendingDBLookup::HandleEvent(const nsACString& tables)
 {
   // HandleEvent is guaranteed to call either:
   // 1) PendingLookup::OnComplete if the URL can be classified locally, or
   // 2) PendingLookup::LookupNext if the URL can be cannot classified locally.
-  // Allow listing trumps block listing.
+  // Blocklisting trumps allowlisting.
+  nsAutoCString blockList;
+  Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList);
+  if (!mAllowlistOnly && FindInReadable(tables, blockList)) {
+    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
+    LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
+    return mPendingLookup->OnComplete(true, NS_OK);
+  }
+
   nsAutoCString allowList;
   Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList);
   if (FindInReadable(tables, allowList)) {
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
     LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
     return mPendingLookup->OnComplete(false, NS_OK);
   }
 
-  nsAutoCString blockList;
-  Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList);
-  if (!mAllowListOnly && FindInReadable(tables, blockList)) {
-    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
-    LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
-    return mPendingLookup->OnComplete(true, NS_OK);
-  }
-
   LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this));
   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
   return mPendingLookup->LookupNext();
 }
 
 NS_IMPL_ISUPPORTS2(PendingLookup,
                    nsIStreamListener,
                    nsIRequestObserver)
@@ -319,28 +321,36 @@ PendingLookup::~PendingLookup()
   LOG(("Destroying pending lookup [this = %p]", this));
 }
 
 nsresult
 PendingLookup::LookupNext()
 {
   // We must call LookupNext or SendRemoteQuery upon return.
   // Look up all of the URLs that could whitelist this download.
-  int index = mAllowlistSpecs.Length() - 1;
+  // Blacklist first.
+  int index = mAnylistSpecs.Length() - 1;
+  nsCString spec;
+  bool allowlistOnly = false;
   if (index >= 0) {
-    nsCString spec = mAllowlistSpecs[index];
-    mAllowlistSpecs.RemoveElementAt(index);
+    // Check the source URI and referrer.
+    spec = mAnylistSpecs[index];
+    mAnylistSpecs.RemoveElementAt(index);
+  } else {
+    // Check the allowlists next.
+    index = mAllowlistSpecs.Length() - 1;
+    if (index >= 0) {
+      allowlistOnly = true;
+      spec = mAllowlistSpecs[index];
+      mAllowlistSpecs.RemoveElementAt(index);
+    }
+  }
+  if (index >= 0) {
     nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
-    bool allowListOnly = true;
-    if (index == 0) {
-      // The last URI is the target URI, which may be used for blacklisting as
-      // well as whitelisting.
-      allowListOnly = false;
-    }
-    return lookup->LookupSpec(spec, allowListOnly);
+    return lookup->LookupSpec(spec, allowlistOnly);
   }
   // There are no more URIs to check against local list, so send the remote
   // query if we can.
   // Revert to just ifdef XP_WIN when remote lookups are enabled (bug 933432)
 #if 0
   nsresult rv = SendRemoteQuery();
   if (NS_FAILED(rv)) {
     return OnComplete(false, rv);
@@ -501,17 +511,26 @@ PendingLookup::DoLookupInternal()
   // We want to check the target URI against the local lists.
   nsCOMPtr<nsIURI> uri = nullptr;
   nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString spec;
   rv = uri->GetSpec(spec);
   NS_ENSURE_SUCCESS(rv, rv);
-  mAllowlistSpecs.AppendElement(spec);
+  mAnylistSpecs.AppendElement(spec);
+
+  nsCOMPtr<nsIURI> referrer = nullptr;
+  rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
+  if (referrer) {
+    nsCString spec;
+    rv = referrer->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mAnylistSpecs.AppendElement(spec);
+  }
 
   // Extract the signature and parse certificates so we can use it to check
   // whitelists.
   nsCOMPtr<nsIArray> sigArray = nullptr;
   rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (sigArray) {
--- a/toolkit/components/downloads/nsIApplicationReputation.idl
+++ b/toolkit/components/downloads/nsIApplicationReputation.idl
@@ -39,24 +39,29 @@ interface nsIApplicationReputationServic
                        in nsIApplicationReputationCallback aCallback);
 };
 
 /**
  * A single-use, write-once interface for recording the metadata of the
  * downloaded file. nsIApplicationReputationService.Start() may only be called
  * once with a single query.
  */
-[scriptable, uuid(5a054991-e489-4a1c-a0aa-ea7c69b20e3d)]
+[scriptable, uuid(2c781cbe-ab0c-4c53-b06e-f0cb56f8a30b)]
 interface nsIApplicationReputationQuery : nsISupports {
   /*
    * The nsIURI from which the file was downloaded. This may not be null.
    */
   readonly attribute nsIURI sourceURI;
 
   /*
+   * The reference, if any.
+   */
+  readonly attribute nsIURI referrerURI;
+
+  /*
    * The target filename for the downloaded file, as inferred from the source
    * URI or provided by the Content-Disposition attachment file name. If this
    * is not set by the caller, it will be passed as an empty string but the
    * query won't produce any useful information.
    */
   readonly attribute AString suggestedFileName;
 
   /*
new file mode 100644
--- /dev/null
+++ b/toolkit/components/downloads/test/unit/data/block_digest.chunk
@@ -0,0 +1,2 @@
+a:5:32:37
+,AJ,AJ8Wbb_e;OτCV
\ No newline at end of file
--- a/toolkit/components/downloads/test/unit/test_app_rep.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep.js
@@ -129,44 +129,85 @@ add_test(function test_local_list() {
     response.setStatusLine(request.httpVersion, 200, "OK");
     response.bodyOutputStream.write(blob, blob.length);
   });
 
   let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
     .getService(Ci.nsIUrlClassifierStreamUpdater);
   streamUpdater.updateUrl = "http://localhost:4444/downloads";
 
-  // Load up some update chunks for the safebrowsing server to serve. This
-  // particular chunk contains the hash of whitelisted.com/.
+  // Load up some update chunks for the safebrowsing server to serve.
+  // This chunk contains the hash of whitelisted.com/.
+  registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk");
+  // This chunk contains the hash of blocklisted.com/.
   registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
 
   // Download some updates, and don't continue until the downloads are done.
   function updateSuccess(aEvent) {
     // Timeout of n:1000 is constructed in processUpdateRequest above and
     // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
     do_check_eq("1000", aEvent);
     do_print("All data processed");
     run_next_test();
   }
   // Just throw if we ever get an update or download error.
   function handleError(aEvent) {
     do_throw("We didn't download or update correctly: " + aEvent);
   }
   streamUpdater.downloadUpdates(
-    "goog-downloadwhite-digest256",
-    "goog-downloadwhite-digest256;\n",
+    "goog-downloadwhite-digest256,goog-badbinurl-shavar",
+    "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n",
     updateSuccess, handleError, handleError);
 });
 
-// After being whitelisted, we shouldn't throw.
-add_test(function test_local_whitelist() {
+add_test(function test_unlisted() {
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   gAppRep.queryReputation({
-    sourceURI: createURI("http://whitelisted.com"),
+    sourceURI: createURI("http://example.com"),
     fileSize: 12,
   }, function onComplete(aShouldBlock, aStatus) {
-    // We would get garbage if this query made it to the remote server.
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_false(aShouldBlock);
     run_next_test();
   });
 });
+
+add_test(function test_local_blacklist() {
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  gAppRep.queryReputation({
+    sourceURI: createURI("http://blocklisted.com"),
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    run_next_test();
+  });
+});
+
+add_test(function test_referer_blacklist() {
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  gAppRep.queryReputation({
+    sourceURI: createURI("http://example.com"),
+    referrerURI: createURI("http://blocklisted.com"),
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    run_next_test();
+  });
+});
+
+add_test(function test_blocklist_trumps_allowlist() {
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  gAppRep.queryReputation({
+    sourceURI: createURI("http://whitelisted.com"),
+    referrerURI: createURI("http://blocklisted.com"),
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    run_next_test();
+  });
+});
--- a/toolkit/components/downloads/test/unit/xpcshell.ini
+++ b/toolkit/components/downloads/test/unit/xpcshell.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 head = head_download_manager.js
 tail = tail_download_manager.js
 firefox-appdir = browser
 support-files =
   downloads_manifest.js
   test_downloads.manifest
   data/digest.chunk
+  data/block_digest.chunk
   data/signed_win.exe
 
 [test_app_rep.js]
 [test_app_rep_windows.js]
 run-if = os == "win"
 [test_bug_382825.js]
 [test_bug_384744.js]
 [test_bug_395092.js]
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -508,27 +508,32 @@ this.DownloadIntegration = {
   shouldBlockForReputationCheck: function (aDownload) {
     if (this.dontCheckApplicationReputation) {
       return Promise.resolve(this.shouldBlockInTestForApplicationReputation);
     }
     let hash;
     let sigInfo;
     try {
       hash = aDownload.saver.getSha256Hash();
-       sigInfo = aDownload.saver.getSignatureInfo();
+      sigInfo = aDownload.saver.getSignatureInfo();
     } catch (ex) {
       // Bail if DownloadSaver doesn't have a hash.
       return Promise.resolve(false);
     }
     if (!hash || !sigInfo) {
       return Promise.resolve(false);
     }
     let deferred = Promise.defer();
+    let aReferrer = null;
+    if (aDownload.source.referrer) {
+      aReferrer: NetUtil.newURI(aDownload.source.referrer);
+    }
     gApplicationReputationService.queryReputation({
       sourceURI: NetUtil.newURI(aDownload.source.url),
+      referrerURI: aReferrer,
       fileSize: aDownload.currentBytes,
       sha256Hash: hash,
       signatureInfo: sigInfo },
       function onComplete(aShouldBlock, aRv) {
         deferred.resolve(aShouldBlock);
       });
     return deferred.promise;
   },
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/NativeOSFileInternals.cpp
@@ -0,0 +1,921 @@
+/* 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/. */
+
+/**
+ * Native implementation of some OS.File operations.
+ */
+
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsProxyRelease.h"
+
+#include "nsINativeOSFileInternals.h"
+#include "NativeOSFileInternals.h"
+#include "mozilla/dom/NativeOSFileInternalsBinding.h"
+
+#include "nsIUnicodeDecoder.h"
+#include "nsIEventTarget.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/TimeStamp.h"
+
+#include "prio.h"
+#include "prerror.h"
+#include "private/pprio.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Utility.h"
+#include "xpcpublic.h"
+
+#include <algorithm>
+#if defined(XP_UNIX)
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#endif // defined (XP_UNIX)
+
+#if defined(XP_WIN)
+#include <Windows.h>
+#endif // defined (XP_WIN)
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close)
+
+namespace {
+
+// Utilities for safely manipulating ArrayBuffer contents even in the
+// absence of a JSContext.
+
+/**
+ * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
+ * this instead of a void* buffer, as this lets us transfer data across threads
+ * and into JavaScript without copy.
+ */
+struct ArrayBufferContents {
+  /**
+   * The header of the ArrayBuffer. This is the pointer actually used by JSAPI.
+   */
+  void* header;
+  /**
+   * The data of the ArrayBuffer. This is the pointer manipulated to
+   * read/write the contents of the buffer.
+   */
+  uint8_t* data;
+  /**
+   * The number of bytes in the ArrayBuffer.
+   */
+  size_t nbytes;
+};
+
+/**
+ * RAII for ArrayBufferContents.
+ */
+struct ScopedArrayBufferContentsTraits {
+  typedef ArrayBufferContents type;
+  const static type empty() {
+    type result = {0, 0, 0};
+    return result;
+  }
+  const static void release(type ptr) {
+    js_free(ptr.header);
+    ptr.header = nullptr;
+    ptr.data = nullptr;
+    ptr.nbytes = 0;
+  }
+};
+
+struct ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> {
+  ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM):
+    Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
+  { }
+  ScopedArrayBufferContents(const ArrayBufferContents& v
+                            MOZ_GUARD_OBJECT_NOTIFIER_PARAM):
+    Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
+  { }
+  ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
+    Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
+    return *this;
+  }
+
+  /**
+   * Request memory for this ArrayBufferContent. This memory may later
+   * be used to create an ArrayBuffer object (possibly on another
+   * thread) without copy.
+   *
+   * @return true In case of success, false otherwise.
+   */
+  bool Allocate(uint32_t length) {
+    dispose();
+    ArrayBufferContents& value = rwget();
+    if (JS_AllocateArrayBufferContents(/*no context available*/nullptr,
+                                       length,
+                                       &value.header,
+                                       &value.data)) {
+      value.nbytes = length;
+      return true;
+    }
+    return false;
+  }
+private:
+  explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE;
+  ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE;
+};
+
+///////// Cross-platform issues
+
+// Platform specific constants. As OS.File always uses OS-level
+// errors, we need to map a few high-level errors to OS-level
+// constants.
+#if defined(XP_UNIX)
+#define OS_ERROR_NOMEM ENOMEM
+#define OS_ERROR_INVAL EINVAL
+#define OS_ERROR_TOO_LARGE EFBIG
+#define OS_ERROR_RACE EIO
+#elif defined(XP_WIN)
+#define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
+#define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
+#define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
+#define OS_ERROR_RACE ERROR_SHARING_VIOLATION
+#else
+#error "We do not have platform-specific constants for this platform"
+#endif
+
+///////// Results of OS.File operations
+
+/**
+ * Base class for results passed to the callbacks.
+ *
+ * This base class implements caching of JS values returned to the client.
+ * We make use of this caching in derived classes e.g. to avoid accidents
+ * when we transfer data allocated on another thread into JS. Note that
+ * this caching can lead to cycles (e.g. if a client adds a back-reference
+ * in the JS value), so we implement all Cycle Collector primitives in
+ * AbstractResult.
+ */
+class AbstractResult: public nsINativeOSFileResult {
+public:
+  NS_DECL_NSINATIVEOSFILERESULT
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
+
+  /**
+   * Construct the result object. Must be called on the main thread
+   * as the AbstractResult is cycle-collected.
+   *
+   * @param aStartDate The instant at which the operation was
+   * requested.  Used to collect Telemetry statistics.
+   */
+  AbstractResult(TimeStamp aStartDate)
+    : mStartDate(aStartDate)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mozilla::HoldJSObjects(this);
+  }
+  virtual ~AbstractResult() {
+    MOZ_ASSERT(NS_IsMainThread());
+    DropJSData();
+    mozilla::DropJSObjects(this);
+  }
+
+  /**
+   * Setup the AbstractResult once data is available.
+   *
+   * @param aDispatchDate The instant at which the IO thread received
+   * the operation request. Used to collect Telemetry statistics.
+   * @param aExecutionDuration The duration of the operation on the
+   * IO thread.
+   */
+  void Init(TimeStamp aDispatchDate,
+            TimeDuration aExecutionDuration) {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mDispatchDuration = (aDispatchDate - mStartDate);
+    mExecutionDuration = aExecutionDuration;
+  }
+
+  /**
+   * Drop any data that could lead to a cycle.
+   */
+  void DropJSData() {
+    mCachedResult = JS::UndefinedValue();
+  }
+
+protected:
+  virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0;
+
+private:
+  TimeStamp mStartDate;
+  TimeDuration mDispatchDuration;
+  TimeDuration mExecutionDuration;
+  JS::Heap<JS::Value> mCachedResult;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
+  NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
+  tmp->DropJSData();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMETHODIMP
+AbstractResult::GetDispatchDurationMS(double *aDispatchDuration)
+{
+  *aDispatchDuration = mDispatchDuration.ToMilliseconds();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetExecutionDurationMS(double *aExecutionDuration)
+{
+  *aExecutionDuration = mExecutionDuration.ToMilliseconds();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult)
+{
+  if (mCachedResult.isUndefined()) {
+    nsresult rv = GetCacheableResult(cx, aResult);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    mCachedResult = aResult;
+    return NS_OK;
+  }
+  aResult.set(mCachedResult);
+  return NS_OK;
+}
+
+/**
+ * Return a result as a string.
+ *
+ * In this implementation, attribute |result| is a string. Strings are
+ * passed to JS without copy.
+ */
+class StringResult MOZ_FINAL : public AbstractResult
+{
+public:
+  StringResult(TimeStamp aStartDate)
+    : AbstractResult(aStartDate)
+  {
+  }
+
+  /**
+   * Initialize the object once the contents of the result as available.
+   *
+   * @param aContents The string to pass to JavaScript. Ownership of the
+   * string and its contents is passed to StringResult. The string must
+   * be valid UTF-16.
+   */
+  void Init(TimeStamp aDispatchDate,
+            TimeDuration aExecutionDuration,
+            nsString& aContents) {
+    AbstractResult::Init(aDispatchDate, aExecutionDuration);
+    mContents = aContents;
+  }
+
+protected:
+  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE;
+
+private:
+  nsString mContents;
+};
+
+nsresult
+StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mContents.get());
+
+  // Convert mContents to a js string without copy. Note that this
+  // may have the side-effect of stealing the contents of the string
+  // from XPCOM and into JS.
+  if (!xpc::StringToJsval(cx, mContents, aResult)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+
+/**
+ * Return a result as a Uint8Array.
+ *
+ * In this implementation, attribute |result| is a Uint8Array. The array
+ * is passed to JS without memory copy.
+ */
+class TypedArrayResult MOZ_FINAL : public AbstractResult
+{
+public:
+  TypedArrayResult(TimeStamp aStartDate)
+    : AbstractResult(aStartDate)
+  {
+  }
+
+  /**
+   * @param aContents The contents to pass to JS. Calling this method.
+   * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
+   * Do not reuse this value anywhere else.
+   */
+  void Init(TimeStamp aDispatchDate,
+            TimeDuration aExecutionDuration,
+            ArrayBufferContents aContents) {
+    AbstractResult::Init(aDispatchDate, aExecutionDuration);
+    mContents = aContents;
+  }
+
+protected:
+  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE;
+private:
+  ScopedArrayBufferContents mContents;
+};
+
+nsresult
+TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // We cannot simply construct a typed array using contents.header as
+  // this would allow us to have several otherwise unrelated
+  // ArrayBuffers with the same underlying C buffer. As this would be
+  // very unsafe, we need to cache the result once we have it.
+
+  const ArrayBufferContents& contents = mContents.get();
+  MOZ_ASSERT(contents.data);
+
+  JS::Rooted<JSObject*>
+    arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.header));
+  if (!arrayBuffer) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  JS::Rooted<JSObject*>
+    result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer,
+                                          0, contents.nbytes));
+  if (!result) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  // The memory of contents has been allocated on a thread that
+  // doesn't have a JSRuntime, hence without a context. Now that we
+  // have a context, attach the memory to where it belongs.
+  JS_updateMallocCounter(cx, contents.nbytes);
+  mContents.forget();
+
+  aResult.setObject(*result);
+  return NS_OK;
+}
+
+//////// Callback events
+
+/**
+ * An event used to notify asynchronously of an error.
+ */
+class ErrorEvent MOZ_FINAL : public nsRunnable {
+public:
+  /**
+   * @param aOnSuccess The success callback.
+   * @param aOnError The error callback.
+   * @param aDiscardedResult The discarded result.
+   * @param aOperation The name of the operation, used for error reporting.
+   * @param aOSError The OS error of the operation, as returned by errno/
+   * GetLastError().
+   *
+   * Note that we pass both the success callback and the error
+   * callback, as well as the discarded result to ensure that they are
+   * all released on the main thread, rather than on the IO thread
+   * (which would hopefully segfault). Also, we pass the callbacks as
+   * alread_AddRefed to ensure that we do not manipulate main-thread
+   * only refcounters off the main thread.
+   */
+  ErrorEvent(already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+             already_AddRefed<nsINativeOSFileErrorCallback> aOnError,
+             already_AddRefed<AbstractResult> aDiscardedResult,
+             const nsACString& aOperation,
+             int32_t aOSError)
+    : mOnSuccess(aOnSuccess)
+    , mOnError(aOnError)
+    , mDiscardedResult(aDiscardedResult)
+    , mOSError(aOSError)
+    , mOperation(aOperation)
+    {
+      MOZ_ASSERT(!NS_IsMainThread());
+    }
+
+  NS_METHOD Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    (void)mOnError->Complete(mOperation, mOSError);
+
+    // Ensure that the callbacks are released on the main thread.
+    mOnSuccess = nullptr;
+    mOnError = nullptr;
+    mDiscardedResult = nullptr;
+
+    return NS_OK;
+  }
+ private:
+  // The callbacks. Maintained as nsRefPtr as they are generally
+  // xpconnect values, which cannot be manipulated with nsCOMPtr off
+  // the main thread. We store both the success callback and the
+  // error callback to ensure that they are safely released on the
+  // main thread.
+  nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
+  nsRefPtr<nsINativeOSFileErrorCallback> mOnError;
+  nsRefPtr<AbstractResult> mDiscardedResult;
+  int32_t mOSError;
+  nsCString mOperation;
+};
+
+/**
+ * An event used to notify of a success.
+ */
+class SuccessEvent MOZ_FINAL : public nsRunnable {
+public:
+  /**
+   * @param aOnSuccess The success callback.
+   * @param aOnError The error callback.
+   *
+   * Note that we pass both the success callback and the error
+   * callback to ensure that they are both released on the main
+   * thread, rather than on the IO thread (which would hopefully
+   * segfault). Also, we pass them as alread_AddRefed to ensure that
+   * we do not manipulate xpconnect refcounters off the main thread
+   * (which is illegal).
+   */
+  SuccessEvent(already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+               already_AddRefed<nsINativeOSFileErrorCallback> aOnError,
+               already_AddRefed<nsINativeOSFileResult> aResult)
+    : mOnSuccess(aOnSuccess)
+    , mOnError(aOnError)
+    , mResult(aResult)
+    {
+      MOZ_ASSERT(!NS_IsMainThread());
+    }
+
+  NS_METHOD Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    (void)mOnSuccess->Complete(mResult);
+
+    // Ensure that the callbacks are released on the main thread.
+    mOnSuccess = nullptr;
+    mOnError = nullptr;
+    mResult = nullptr;
+
+    return NS_OK;
+  }
+ private:
+  // The callbacks. Maintained as nsRefPtr as they are generally
+  // xpconnect values, which cannot be manipulated with nsCOMPtr off
+  // the main thread. We store both the success callback and the
+  // error callback to ensure that they are safely released on the
+  // main thread.
+  nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
+  nsRefPtr<nsINativeOSFileErrorCallback> mOnError;
+  nsRefPtr<nsINativeOSFileResult> mResult;
+};
+
+
+//////// Action events
+
+/**
+ * Base class shared by actions.
+ */
+class AbstractDoEvent: public nsRunnable {
+public:
+  AbstractDoEvent(already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+                  already_AddRefed<nsINativeOSFileErrorCallback> aOnError)
+    : mOnSuccess(aOnSuccess)
+    , mOnError(aOnError)
+#if defined(DEBUG)
+    , mResolved(false)
+#endif // defined(DEBUG)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  /**
+   * Fail, asynchronously.
+   */
+  void Fail(const nsACString& aOperation,
+            already_AddRefed<AbstractResult> aDiscardedResult,
+            int32_t aOSError = 0) {
+    Resolve();
+    nsRefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess.forget(),
+                                                mOnError.forget(),
+                                                aDiscardedResult,
+                                                aOperation,
+                                                aOSError);
+    nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      // Last ditch attempt to release on the main thread - some of
+      // the members of event are not thread-safe, so letting the
+      // pointer go out of scope would cause a crash.
+      nsCOMPtr<nsIThread> main = do_GetMainThread();
+      NS_ProxyRelease(main, event);
+    }
+  }
+
+  /**
+   * Succeed, asynchronously.
+   */
+  void Succeed(already_AddRefed<nsINativeOSFileResult> aResult) {
+    Resolve();
+    nsRefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess.forget(),
+                                                    mOnError.forget(),
+                                                    aResult);
+    nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      // Last ditch attempt to release on the main thread - some of
+      // the members of event are not thread-safe, so letting the
+      // pointer go out of scope would cause a crash.
+      nsCOMPtr<nsIThread> main = do_GetMainThread();
+      NS_ProxyRelease(main, event);
+    }
+
+  }
+
+private:
+
+  /**
+   * Mark the event as complete, for debugging purposes.
+   */
+  void Resolve() {
+#if defined(DEBUG)
+    MOZ_ASSERT(!mResolved);
+    mResolved = true;
+#endif // defined(DEBUG)
+  }
+
+private:
+  nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
+  nsRefPtr<nsINativeOSFileErrorCallback> mOnError;
+#if defined(DEBUG)
+  // |true| once the action is complete
+  bool mResolved;
+#endif // defined(DEBUG)
+};
+
+/**
+ * An abstract event implementing reading from a file.
+ *
+ * Concrete subclasses are responsible for handling the
+ * data obtained from the file and possibly post-processing it.
+ */
+class AbstractReadEvent: public AbstractDoEvent {
+public:
+  /**
+   * @param aPath The path of the file.
+   */
+  AbstractReadEvent(const nsAString& aPath,
+                    const uint64_t aBytes,
+                    already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+                    already_AddRefed<nsINativeOSFileErrorCallback>  aOnError)
+    : AbstractDoEvent(aOnSuccess, aOnError)
+    , mPath(aPath)
+    , mBytes(aBytes)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_METHOD Run() MOZ_OVERRIDE {
+    MOZ_ASSERT(!NS_IsMainThread());
+    TimeStamp dispatchDate = TimeStamp::Now();
+
+    nsresult rv = BeforeRead();
+    if (NS_FAILED(rv)) {
+      // Error reporting is handled by BeforeRead();
+      return NS_OK;
+    }
+
+    ScopedArrayBufferContents buffer;
+    rv = Read(buffer);
+    if (NS_FAILED(rv)) {
+      // Error reporting is handled by Read();
+      return NS_OK;
+    }
+
+    AfterRead(dispatchDate, buffer);
+    return NS_OK;
+  }
+
+ private:
+  /**
+   * Read synchronously.
+   *
+   * Must be called off the main thread.
+   *
+   * @param aBuffer The destination buffer.
+   */
+  nsresult Read(ScopedArrayBufferContents& aBuffer)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    ScopedPRFileDesc file;
+#if defined(XP_WIN)
+    // On Windows, we can't use PR_OpenFile because it doesn't
+    // handle UTF-16 encoding, which is pretty bad. In addition,
+    // PR_OpenFile opens files without sharing, which is not the
+    // general semantics of OS.File.
+    HANDLE handle =
+      ::CreateFileW(mPath.get(),
+                    GENERIC_READ,
+                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                    /*Security attributes*/nullptr,
+                    OPEN_EXISTING,
+                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+                    /*Template file*/ nullptr);
+
+    if (handle == INVALID_HANDLE_VALUE) {
+      Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
+      return NS_ERROR_FAILURE;
+    }
+
+    file = PR_ImportFile((PROsfd)handle);
+    if (!file) {
+      // |file| is closed by PR_ImportFile
+      Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+
+#else
+    // On other platforms, PR_OpenFile will do.
+    NS_ConvertUTF16toUTF8 path(mPath);
+    file = PR_OpenFile(path.get(), PR_RDONLY, 0);
+    if (!file) {
+      Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+
+#endif // defined(XP_XIN)
+
+    PRFileInfo64 stat;
+    if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
+      Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+
+    uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
+    if (bytes > UINT32_MAX) {
+      Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL);
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!aBuffer.Allocate(bytes)) {
+      Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM);
+      return NS_ERROR_FAILURE;
+    }
+
+    uint64_t total_read = 0;
+    int32_t just_read = 0;
+    char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
+    do {
+      just_read = PR_Read(file, dest_chars + total_read,
+                          std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
+      if (just_read == -1) {
+        Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError());
+        return NS_ERROR_FAILURE;
+      }
+      total_read += just_read;
+    } while (just_read != 0 && total_read < bytes);
+    if (total_read != bytes) {
+      // We seem to have a race condition here.
+      Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE);
+      return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  /**
+   * Any steps that need to be taken before reading.
+   *
+   * In case of error, this method should call Fail() and return
+   * a failure code.
+   */
+  virtual
+  nsresult BeforeRead() {
+    return NS_OK;
+  }
+
+  /**
+   * Proceed after reading.
+   */
+  virtual
+  void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0;
+
+ protected:
+  const nsString mPath;
+  const uint64_t mBytes;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a TypedArray.
+ */
+class DoReadToTypedArrayEvent MOZ_FINAL : public AbstractReadEvent {
+public:
+  DoReadToTypedArrayEvent(const nsAString& aPath,
+                          const uint32_t aBytes,
+                          already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+                          already_AddRefed<nsINativeOSFileErrorCallback>  aOnError)
+    : AbstractReadEvent(aPath, aBytes,
+                        aOnSuccess, aOnError)
+    , mResult(new TypedArrayResult(TimeStamp::Now()))
+  { }
+
+  ~DoReadToTypedArrayEvent() {
+    // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
+    // mResult, which is main-thread only data
+    if (!mResult) {
+      return;
+    }
+    nsCOMPtr<nsIThread> main = do_GetMainThread();
+    (void)NS_ProxyRelease(main, mResult);
+  }
+
+protected:
+  void AfterRead(TimeStamp aDispatchDate,
+                 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE {
+    MOZ_ASSERT(!NS_IsMainThread());
+    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget());
+    Succeed(mResult.forget());
+  }
+
+ private:
+  nsRefPtr<TypedArrayResult> mResult;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a JavaScript string.
+ */
+class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent {
+public:
+  DoReadToStringEvent(const nsAString& aPath,
+                      const nsACString& aEncoding,
+                      const uint32_t aBytes,
+                      already_AddRefed<nsINativeOSFileSuccessCallback> aOnSuccess,
+                      already_AddRefed<nsINativeOSFileErrorCallback>  aOnError)
+    : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError)
+    , mEncoding(aEncoding)
+    , mResult(new StringResult(TimeStamp::Now()))
+  { }
+
+  ~DoReadToStringEvent() {
+    // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
+    // mResult, which is main-thread only data
+    if (!mResult) {
+      return;
+    }
+    nsCOMPtr<nsIThread> main = do_GetMainThread();
+    (void)NS_ProxyRelease(main, mResult);
+  }
+
+protected:
+  nsresult BeforeRead() MOZ_OVERRIDE {
+    // Obtain the decoder. We do this before reading to avoid doing
+    // any unnecessary I/O in case the name of the encoding is incorrect.
+    MOZ_ASSERT(!NS_IsMainThread());
+    nsAutoCString encodingName;
+    if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) {
+      Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL);
+      return NS_ERROR_FAILURE;
+    }
+    mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName);
+    if (!mDecoder) {
+      Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL);
+      return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+  }
+
+  void AfterRead(TimeStamp aDispatchDate,
+                 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    int32_t maxChars;
+    const char* sourceChars = reinterpret_cast<const char*>(aBuffer.get().data);
+    int32_t sourceBytes = aBuffer.get().nbytes;
+    if (sourceBytes < 0) {
+      Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
+      return;
+    }
+
+    nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars);
+    if (NS_FAILED(rv)) {
+      Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL);
+      return;
+    }
+
+    if (maxChars < 0) {
+      Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
+      return;
+    }
+
+    nsString resultString;
+    resultString.SetLength(maxChars);
+    if (resultString.Length() != (nsString::size_type)maxChars) {
+      Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
+      return;
+    }
+
+
+    rv = mDecoder->Convert(sourceChars, &sourceBytes,
+                           resultString.BeginWriting(), &maxChars);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    resultString.SetLength(maxChars);
+
+    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString);
+    Succeed(mResult.forget());
+  }
+
+ private:
+  nsCString mEncoding;
+  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+  nsRefPtr<StringResult> mResult;
+};
+
+} // osfile
+
+// The OS.File service
+
+NS_IMPL_ISUPPORTS1(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
+
+NS_IMETHODIMP
+NativeOSFileInternalsService::Read(const nsAString& aPath,
+                                   JS::HandleValue aOptions,
+                                   nsINativeOSFileSuccessCallback *aOnSuccess,
+                                   nsINativeOSFileErrorCallback *aOnError,
+                                   JSContext* cx)
+{
+  // Extract options
+  nsCString encoding;
+  uint64_t bytes = UINT64_MAX;
+
+  if (aOptions.isObject()) {
+    dom::NativeOSFileReadOptions dict;
+    if (!dict.Init(cx, aOptions)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    if (dict.mEncoding.WasPassed()) {
+      CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
+    }
+
+    if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
+      bytes = dict.mBytes.Value().Value();
+    }
+  }
+
+  // Prepare the off main thread event and dispatch it
+  nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
+  nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
+
+  nsRefPtr<AbstractDoEvent> event;
+  if (encoding.IsEmpty()) {
+    event = new DoReadToTypedArrayEvent(aPath, bytes,
+                                        onSuccess.forget(),
+                                        onError.forget());
+  } else {
+    event = new DoReadToStringEvent(aPath, encoding, bytes,
+                                    onSuccess.forget(),
+                                    onError.forget());
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/NativeOSFileInternals.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_nativeosfileinternalservice_h__
+#define mozilla_nativeosfileinternalservice_h__
+
+#include "nsINativeOSFileInternals.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+class NativeOSFileInternalsService MOZ_FINAL : public nsINativeOSFileInternalsService {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSINATIVEOSFILEINTERNALSSERVICE
+private:
+  // Avoid accidental use of built-in operator=
+  void operator=(const NativeOSFileInternalsService& other) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_finalizationwitnessservice_h__
--- a/toolkit/components/osfile/modules/moz.build
+++ b/toolkit/components/osfile/modules/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JS_MODULES_PATH = 'modules/osfile'
 
 EXTRA_JS_MODULES += [
     '_PromiseWorker.jsm',
     'osfile_async_front.jsm',
     'osfile_async_worker.js',
+    'osfile_native.jsm',
     'osfile_shared_allthreads.jsm',
     'osfile_shared_front.jsm',
     'osfile_unix_allthreads.jsm',
     'osfile_unix_back.jsm',
     'osfile_unix_front.jsm',
     'osfile_win_allthreads.jsm',
     'osfile_win_back.jsm',
     'osfile_win_front.jsm',
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -53,16 +53,17 @@ Cu.import("resource://gre/modules/Promis
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 // The implementation of communications
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+let Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});
 
 /**
  * Constructors for decoding standard exceptions
  * received from the worker.
  */
 const EXCEPTION_CONSTRUCTORS = {
   EvalError: function(error) {
     return new EvalError(error.message, error.fileName, error.lineNumber);
@@ -343,17 +344,17 @@ const PREF_OSFILE_LOG_REDIRECT = "toolki
 
 /**
  * Safely read a PREF_OSFILE_LOG preference.
  * Returns a value read or, in case of an error, oldPref or false.
  *
  * @param bool oldPref
  *        An optional value that the DEBUG flag was set to previously.
  */
-let readDebugPref = function readDebugPref(prefName, oldPref = false) {
+function readDebugPref(prefName, oldPref = false) {
   let pref = oldPref;
   try {
     pref = Services.prefs.getBoolPref(prefName);
   } catch (x) {
     // In case of an error when reading a pref keep it as is.
   }
   // If neither pref nor oldPref were set, default it to false.
   return pref;
@@ -374,16 +375,29 @@ Services.prefs.addObserver(PREF_OSFILE_L
 SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, false);
 
 Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
   function prefObserver(aSubject, aTopic, aData) {
     SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
   }, false);
 SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false);
 
+
+/**
+ * If |true|, use the native implementaiton of OS.File methods
+ * whenever possible. Otherwise, force the use of the JS version.
+ */
+let nativeWheneverAvailable = true;
+const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
+Services.prefs.addObserver(PREF_OSFILE_NATIVE,
+  function prefObserver(aSubject, aTopic, aData) {
+    nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
+  }, false);
+
+
 // Update worker's DEBUG flag if it's true.
 // Don't start the worker just for this, though.
 if (SharedAll.Config.DEBUG && Scheduler.launched) {
   Scheduler.post("SET_DEBUG", [true]);
 }
 
 // Observer topics used for monitoring shutdown
 const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
@@ -908,22 +922,42 @@ File.makeDir = function makeDir(path, op
  * - {number} bytes An upper bound to the number of bytes to read.
  * - {string} compression If "lz4" and if the file is compressed using the lz4
  * compression algorithm, decompress the file contents on the fly.
  *
  * @resolves {Uint8Array} A buffer holding the bytes
  * read from the file.
  */
 File.read = function read(path, bytes, options = {}) {
-  let promise = Scheduler.post("read",
-    [Type.path.toMsg(path), bytes, options], path);
-  return promise.then(
-    function onSuccess(data) {
-      return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
-    });
+  if (typeof bytes == "object") {
+    // Passing |bytes| as an argument is deprecated.
+    // We should now be passing it as a field of |options|.
+    options = bytes || {};
+  } else {
+    options = clone(options, ["outExecutionDuration"]);
+    if (typeof bytes != "undefined") {
+      options.bytes = bytes;
+    }
+  }
+
+  if (options.compression || !nativeWheneverAvailable) {
+    // We need to use the JS implementation.
+    let promise = Scheduler.post("read",
+      [Type.path.toMsg(path), bytes, options], path);
+    return promise.then(
+      function onSuccess(data) {
+        if (typeof data == "string") {
+          return data;
+        }
+        return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+      });
+  }
+
+  // Otherwise, use the native implementation.
+  return Scheduler.push(() => Native.read(path, options));
 };
 
 /**
  * Find outs if a file exists.
  *
  * @param {string} path The path to the file.
  *
  * @return {bool} true if the file exists, false otherwise.
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -357,16 +357,19 @@ const EXCEPTION_NAMES = {
 
      return {
        path: openedFile.path,
        file: resourceId
      };
    },
    read: function read(path, bytes, options) {
      let data = File.read(Type.path.fromMsg(path), bytes, options);
+     if (typeof data == "string") {
+       return data;
+     }
      return new Meta({
          buffer: data.buffer,
          byteOffset: data.byteOffset,
          byteLength: data.byteLength
      }, {
        transfers: [data.buffer]
      });
    },
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_native.jsm
@@ -0,0 +1,70 @@
+/* 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/. */
+
+/**
+ * Native (xpcom) implementation of key OS.File functions
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["read"];
+
+let {results: Cr, utils: Cu, interfaces: Ci} = Components;
+
+let SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {});
+
+let SysAll = {};
+if (SharedAll.Constants.Win) {
+  Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+  Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+  throw new Error("I am neither under Windows nor under a Posix system");
+}
+let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+/**
+ * The native service holding the implementation of the functions.
+ */
+XPCOMUtils.defineLazyServiceGetter(this,
+  "Internals",
+  "@mozilla.org/toolkit/osfile/native-internals;1",
+  "nsINativeOSFileInternalsService");
+
+/**
+ * Native implementation of OS.File.read
+ *
+ * This implementation does not handle option |compression|.
+ */
+this.read = function(path, options = {}) {
+  // Sanity check on types of options
+  if ("encoding" in options && typeof options.encoding != "string") {
+    return Promise.reject(new TypeError("Invalid type for option encoding"));
+  }
+  if ("compression" in options && typeof options.compression != "string") {
+    return Promise.reject(new TypeError("Invalid type for option compression"));
+  }
+  if ("bytes" in options && typeof options.bytes != "number") {
+    return Promise.reject(new TypeError("Invalid type for option bytes"));
+  }
+
+  let deferred = Promise.defer();
+  Internals.read(path,
+    options,
+    function onSuccess(success) {
+      success.QueryInterface(Ci.nsINativeOSFileResult);
+      if ("outExecutionDuration" in options) {
+        options.outExecutionDuration =
+          success.executionDurationMS +
+          (options.outExecutionDuration || 0);
+      }
+      deferred.resolve(success.result);
+    },
+    function onError(operation, oserror) {
+      deferred.reject(new SysAll.Error(operation, oserror, path));
+    }
+  );
+  return deferred.promise;
+};
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -326,24 +326,45 @@ AbstractFile.normalizeOpenMode = functio
  * @return {Uint8Array} A buffer holding the bytes
  * and the number of bytes read from the file.
  */
 AbstractFile.read = function read(path, bytes, options = {}) {
   if (bytes && typeof bytes == "object") {
     options = bytes;
     bytes = options.bytes || null;
   }
+  if ("encoding" in options && typeof options.encoding != "string") {
+    throw new TypeError("Invalid type for option encoding");
+  }
+  if ("compression" in options && typeof options.compression != "string") {
+    throw new TypeError("Invalid type for option compression: " + options.compression);
+  }
+  if ("bytes" in options && typeof options.bytes != "number") {
+    throw new TypeError("Invalid type for option bytes");
+  }
   let file = exports.OS.File.open(path);
   try {
     let buffer = file.read(bytes, options);
-    if ("compression" in options && options.compression == "lz4") {
-      return Lz4.decompressFileContent(buffer, options);
-    } else {
+    if ("compression" in options) {
+      if (options.compression == "lz4") {
+        buffer = Lz4.decompressFileContent(buffer, options);
+      } else {
+        throw OS.File.Error.invalidArgument("Compression");
+      }
+    }
+    if (!("encoding" in options)) {
       return buffer;
     }
+    let decoder;
+    try {
+      decoder = new TextDecoder(options.encoding);
+    } catch (ex if ex instanceof TypeError) {
+      throw OS.File.Error.invalidArgument("Decode");
+    }
+    return decoder.decode(buffer);
   } finally {
     file.close();
   }
 };
 
 /**
  * Write a file, atomically.
  *
--- a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
@@ -134,16 +134,25 @@ Object.defineProperty(OSError.prototype,
  * |true| if the error was raised because permission is denied to
  * access a file or directory, |false| otherwise.
  */
 Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
   get: function becauseAccessDenied() {
     return this.unixErrno == Const.EACCES;
   }
 });
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+  get: function becauseInvalidArgument() {
+    return this.unixErrno == Const.EINVAL;
+  }
+});
 
 /**
  * Serialize an instance of OSError to something that can be
  * transmitted across threads (not necessarily a string).
  */
 OSError.toMsg = function toMsg(error) {
   return {
     operation: error.operation,
@@ -326,16 +335,20 @@ OSError.closed = function closed(operati
 OSError.exists = function exists(operation, path) {
   return new OSError(operation, Const.EEXIST, path);
 };
 
 OSError.noSuchFile = function noSuchFile(operation, path) {
   return new OSError(operation, Const.ENOENT, path);
 };
 
+OSError.invalidArgument = function invalidArgument(operation) {
+  return new OSError(operation, Const.EINVAL);
+};
+
 let EXPORTED_SYMBOLS = [
   "declareFFI",
   "libc",
   "Error",
   "AbstractInfo",
   "AbstractEntry",
   "Type",
   "POS_START",
--- a/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
@@ -156,16 +156,26 @@ Object.defineProperty(OSError.prototype,
  * |true| if the error was raised because permission is denied to
  * access a file or directory, |false| otherwise.
  */
 Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
   get: function becauseAccessDenied() {
     return this.winLastError == Const.ERROR_ACCESS_DENIED;
   }
 });
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+  get: function becauseInvalidArgument() {
+    return this.winLastError == Const.ERROR_NOT_SUPPORTED ||
+           this.winLastError == Const.ERROR_BAD_ARGUMENTS;
+  }
+});
 
 /**
  * Serialize an instance of OSError to something that can be
  * transmitted across threads (not necessarily a string).
  */
 OSError.toMsg = function toMsg(error) {
   return {
     operation: error.operation,
@@ -363,16 +373,20 @@ OSError.closed = function closed(operati
 OSError.exists = function exists(operation, path) {
   return new OSError(operation, Const.ERROR_FILE_EXISTS, path);
 };
 
 OSError.noSuchFile = function noSuchFile(operation, path) {
   return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path);
 };
 
+OSError.invalidArgument = function invalidArgument(operation) {
+  return new OSError(operation, Const.ERROR_NOT_SUPPORTED);
+};
+
 let EXPORTED_SYMBOLS = [
   "declareFFI",
   "libc",
   "Error",
   "AbstractInfo",
   "AbstractEntry",
   "Type",
   "POS_START",
--- a/toolkit/components/osfile/moz.build
+++ b/toolkit/components/osfile/moz.build
@@ -6,12 +6,27 @@
 
 DIRS += [
     'modules',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochi/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 
+SOURCES += [
+    'NativeOSFileInternals.cpp',
+]
+
+XPIDL_MODULE = 'toolkit_osfile'
+
+XPIDL_SOURCES += [
+    'nsINativeOSFileInternals.idl',
+]
+
+EXPORTS.mozilla += [
+    'NativeOSFileInternals.h',
+]
+
 EXTRA_PP_JS_MODULES += [
     'osfile.jsm',
 ]
 
+FINAL_LIBRARY = 'toolkitcomps'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/nsINativeOSFileInternals.idl
@@ -0,0 +1,93 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 "nsISupports.idl"
+
+/**
+ * The result of a successful asynchronous operation.
+ */
+[scriptable, builtinclass, uuid(08B4CF29-3D65-4E79-B522-A694C322ED07)]
+interface nsINativeOSFileResult: nsISupports
+{
+  /**
+   * The actual value produced by the operation.
+   *
+   * Actual type of this value depends on the options passed to the
+   * operation.
+   */
+  [implicit_jscontext]
+  readonly attribute jsval result;
+
+  /**
+   * Delay between when the operation was requested on the main thread and
+   * when the operation was started off main thread.
+   */
+  readonly attribute double dispatchDurationMS;
+
+  /**
+   * Duration of the off main thread execution.
+   */
+  readonly attribute double executionDurationMS;
+};
+
+/**
+ * A callback invoked in case of success.
+ */
+[scriptable, function, uuid(2C1922CA-CA1B-4099-8B61-EC23CFF49412)]
+interface nsINativeOSFileSuccessCallback: nsISupports
+{
+  void complete(in nsINativeOSFileResult result);
+};
+
+/**
+ * A callback invoked in case of error.
+ */
+[scriptable, function, uuid(F612E0FC-6736-4D24-AA50-FD661B3B40B6)]
+interface nsINativeOSFileErrorCallback: nsISupports
+{
+  /**
+   * @param operation The name of the failed operation. Provided to aid
+   * debugging only, may change without notice.
+   * @param OSstatus The OS status of the operation (errno under Unix,
+   * GetLastError under Windows).
+   */
+  void complete(in ACString operation, in long OSstatus);
+};
+
+/**
+ * A service providing native implementations of some of the features
+ * of OS.File.
+ */
+[scriptable, builtinclass, uuid(913362AD-1526-4623-9E6B-A2EB08AFBBB9)]
+interface nsINativeOSFileInternalsService: nsISupports
+{
+  /**
+   * Implementation of OS.File.read
+   *
+   * @param path The absolute path to the file to read.
+   * @param options An object that may contain some of the following fields
+   * - {number} bytes The maximal number of bytes to read.
+   * - {string} encoding If provided, return the result as a string, decoded
+   *   using this encoding. Otherwise, pass the result as an ArrayBuffer.
+   *   Invalid encodings cause onError to be called with the platform-specific
+   *   "invalid argument" constant.
+   * - {string} compression Unimplemented at the moment.
+   * @param onSuccess The success callback.
+   * @param onError The error callback.
+   */
+  [implicit_jscontext]
+  void read(in AString path, in jsval options,
+            in nsINativeOSFileSuccessCallback onSuccess,
+            in nsINativeOSFileErrorCallback onError);
+};
+
+
+%{ C++
+
+#define NATIVE_OSFILE_INTERNALS_SERVICE_CID {0x63A69303,0x8A64,0x45A9,{0x84, 0x8C, 0xD4, 0xE2, 0x79, 0x27, 0x94, 0xE6}}
+#define NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID "@mozilla.org/toolkit/osfile/native-internals;1"
+
+%}
--- a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
+++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
@@ -150,17 +150,16 @@ let test = maketest("Main", function mai
   return Task.spawn(function() {
     SimpleTest.waitForExplicitFinish();
     yield test_constants();
     yield test_path();
     yield test_stat();
     yield test_debug();