Bug 740485 - Implement Device Storage. r=sicking
☠☠ backed out by 3472ef7a9d7b ☠ ☠
authorDoug Turner <dougt@dougt.org>
Mon, 21 May 2012 09:18:30 -0700
changeset 94473 027ed1748c8151c09afc7c750778161f950c6b98
parent 94472 a693c64dc64e856839039b1d5614bf3e33ef2884
child 94474 595b826d6d3ac29cdbbdbd62b367ce4531c39d3f
push id1116
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 19:38:18 +0000
treeherdermozilla-esr52@26dcd1b1a208 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs740485
milestone15.0a1
Bug 740485 - Implement Device Storage. r=sicking
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
browser/installer/removed-files.in
content/base/public/nsDOMFile.h
dom/Makefile.in
dom/base/DOMRequest.h
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/devicestorage/Makefile.in
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
dom/interfaces/devicestorage/Makefile.in
dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
dom/interfaces/devicestorage/nsIDOMDeviceStorageCursor.idl
dom/interfaces/devicestorage/nsIDOMNavigatorDeviceStorage.idl
dom/tests/mochitest/Makefile.in
dom/tests/mochitest/devicestorage/Makefile.in
dom/tests/mochitest/devicestorage/devicestorage_common.js
dom/tests/mochitest/devicestorage/test_basic.html
dom/tests/mochitest/devicestorage/test_dotdot.html
dom/tests/mochitest/devicestorage/test_enumerate.html
dom/tests/mochitest/devicestorage/test_enumerateMultipleContinue.html
dom/tests/mochitest/devicestorage/test_enumerateNoParam.html
dom/tests/mochitest/devicestorage/test_overwrite.html
dom/tests/mochitest/devicestorage/test_sanity.html
layout/build/Makefile.in
mobile/xul/installer/package-manifest.in
modules/libpref/src/init/all.js
toolkit/toolkit-makefiles.sh
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -159,16 +159,17 @@
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
+@BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -163,16 +163,17 @@
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
+@BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
--- a/browser/installer/removed-files.in
+++ b/browser/installer/removed-files.in
@@ -1174,16 +1174,17 @@ xpicleanup@BIN_SUFFIX@
   components/dom_wifi.xpt
   components/dom_system_b2g.xpt
 #endif
   components/dom_canvas.xpt
   components/dom_core.xpt
   components/dom_css.xpt
   components/dom_events.xpt
   components/dom_geolocation.xpt
+  components/dom_devicestorage.xpt
   components/dom_html.xpt
   components/dom_json.xpt
   components/dom_loadsave.xpt
   components/dom_offline.xpt
   components/dom_range.xpt
   components/dom_sidebar.xpt
   components/dom_smil.xpt
   components/dom_storage.xpt
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -127,24 +127,35 @@ public:
     NS_ASSERTION(mFile, "must have file");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mFile->GetLeafName(mName);
   }
 
   // Create as a blob
   nsDOMFileFile(nsIFile *aFile, const nsAString& aContentType,
-                nsISupports *aCacheToken = nsnull)
+                nsISupports *aCacheToken)
     : nsDOMFileBase(aContentType, UINT64_MAX),
       mFile(aFile), mWholeFile(true), mStoredFile(false),
       mCacheToken(aCacheToken)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
+  // Create as a file with custom name
+  nsDOMFileFile(nsIFile *aFile, const nsAString& aName)
+    : nsDOMFileBase(EmptyString(), EmptyString(), UINT64_MAX),
+      mFile(aFile), mWholeFile(true), mStoredFile(false)
+  {
+    NS_ASSERTION(mFile, "must have file");
+    // Lazily get the content type and size
+    mContentType.SetIsVoid(true);
+    mName.Assign(aName);
+  }
+
   // Create as a stored file
   nsDOMFileFile(const nsAString& aName, const nsAString& aContentType,
                 PRUint64 aLength, nsIFile* aFile,
                 FileInfo* aFileInfo)
     : nsDOMFileBase(aName, aContentType, aLength),
       mFile(aFile), mWholeFile(true), mStoredFile(true)
   {
     NS_ASSERTION(mFile, "must have file");
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -11,16 +11,17 @@ VPATH		= @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= dom
 
 DIRS = \
   interfaces/base \
   interfaces/canvas \
   interfaces/core \
+  interfaces/devicestorage \
   interfaces/html \
   interfaces/events \
   interfaces/contacts \
   interfaces/settings \
   interfaces/stylesheets \
   interfaces/sidebar \
   interfaces/css \
   interfaces/traversal \
@@ -43,16 +44,17 @@ DIRS += \
   $(NULL)
 
 DIRS += \
   apps \
   base \
   bindings \
   battery \
   contacts \
+  devicestorage \
   power \
   settings \
   sms \
   src \
   locales \
   network \
   plugins/base \
   plugins/ipc \
--- a/dom/base/DOMRequest.h
+++ b/dom/base/DOMRequest.h
@@ -15,21 +15,16 @@
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 class DOMRequest : public nsDOMEventTargetHelper,
                    public nsIDOMDOMRequest
 {
-  bool mDone;
-  jsval mResult;
-  nsCOMPtr<nsIDOMDOMError> mError;
-  bool mRooted;
-
   NS_DECL_EVENT_HANDLER(success)
   NS_DECL_EVENT_HANDLER(error)
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMDOMREQUEST
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
@@ -41,16 +36,21 @@ public:
 
   DOMRequest(nsIDOMWindow* aWindow);
 
   virtual ~DOMRequest()
   {
     UnrootResultVal();
   }
 
+  bool mDone;
+  jsval mResult;
+  nsCOMPtr<nsIDOMDOMError> mError;
+  bool mRooted;
+
 private:
   void FireEvent(const nsAString& aType);
 
   void RootResultVal()
   {
     if (!mRooted) {
       NS_HOLD_JS_OBJECTS(this, DOMRequest);
       mRooted = true;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -8,16 +8,17 @@
 #include "base/basictypes.h"
 
 #include "Navigator.h"
 #include "nsIXULAppInfo.h"
 #include "nsPluginArray.h"
 #include "nsMimeTypeArray.h"
 #include "nsDesktopNotification.h"
 #include "nsGeolocation.h"
+#include "nsDeviceStorage.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsICachingChannel.h"
 #include "nsIDocShell.h"
 #include "nsIWebContentHandlerRegistrar.h"
 #include "nsICookiePermission.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIJSContextStack.h"
 #include "nsCharSeparatedTokenizer.h"
@@ -84,16 +85,17 @@ Navigator::~Navigator()
 {
   Invalidate();
 }
 
 NS_INTERFACE_MAP_BEGIN(Navigator)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNavigator)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigator)
   NS_INTERFACE_MAP_ENTRY(nsIDOMClientInformation)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDeviceStorage)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorGeolocation)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorBattery)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDesktopNotification)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorSms)
 #ifdef MOZ_B2G_RIL
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorTelephony)
 #endif
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorNetwork)
@@ -835,16 +837,36 @@ Navigator::MozIsLocallyAvailable(const n
     return NS_OK;
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
   return httpChannel->GetRequestSucceeded(aIsAvailable);
 }
 
 //*****************************************************************************
+//    Navigator::nsIDOMNavigatorDeviceStorage
+//*****************************************************************************
+
+NS_IMETHODIMP Navigator::GetDeviceStorage(const nsAString &aType, nsIVariant** _retval)
+{
+  if (!Preferences::GetBool("device.storage.enabled", false)) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mWindow));
+
+  if (!win || !win->GetOuterWindow() || !win->GetDocShell()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsDOMDeviceStorage::CreateDeviceStoragesFor(win, aType, _retval);
+  return NS_OK;
+}
+
+//*****************************************************************************
 //    Navigator::nsIDOMNavigatorGeolocation
 //*****************************************************************************
 
 NS_IMETHODIMP Navigator::GetGeolocation(nsIDOMGeoGeolocation** _retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = nsnull;
 
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -4,16 +4,17 @@
  * 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_Navigator_h
 #define mozilla_dom_Navigator_h
 
 #include "nsIDOMNavigator.h"
 #include "nsIDOMNavigatorGeolocation.h"
+#include "nsIDOMNavigatorDeviceStorage.h"
 #include "nsIDOMNavigatorDesktopNotification.h"
 #include "nsIDOMClientInformation.h"
 #include "nsIDOMNavigatorBattery.h"
 #include "nsIDOMNavigatorSms.h"
 #include "nsIDOMNavigatorNetwork.h"
 #include "nsAutoPtr.h"
 #include "nsWeakReference.h"
 
@@ -55,16 +56,17 @@ class MobileConnection;
 } // namespace Connection;
 
 namespace power {
 class PowerManager;
 } // namespace power
 
 class Navigator : public nsIDOMNavigator
                 , public nsIDOMClientInformation
+                , public nsIDOMNavigatorDeviceStorage
                 , public nsIDOMNavigatorGeolocation
                 , public nsIDOMNavigatorDesktopNotification
                 , public nsIDOMMozNavigatorBattery
                 , public nsIDOMMozNavigatorSms
 #ifdef MOZ_B2G_RIL
                 , public nsIDOMNavigatorTelephony
 #endif
                 , public nsIDOMMozNavigatorNetwork
@@ -74,16 +76,17 @@ class Navigator : public nsIDOMNavigator
 {
 public:
   Navigator(nsPIDOMWindow *aInnerWindow);
   virtual ~Navigator();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMNAVIGATOR
   NS_DECL_NSIDOMCLIENTINFORMATION
+  NS_DECL_NSIDOMNAVIGATORDEVICESTORAGE
   NS_DECL_NSIDOMNAVIGATORGEOLOCATION
   NS_DECL_NSIDOMNAVIGATORDESKTOPNOTIFICATION
   NS_DECL_NSIDOMMOZNAVIGATORBATTERY
   NS_DECL_NSIDOMMOZNAVIGATORSMS
 #ifdef MOZ_B2G_RIL
   NS_DECL_NSIDOMNAVIGATORTELEPHONY
 #endif
   NS_DECL_NSIDOMMOZNAVIGATORNETWORK
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -409,16 +409,20 @@
 #include "nsIDOMCanvasRenderingContext2D.h"
 #include "nsIDOMWebGLRenderingContext.h"
 
 #include "nsIImageDocument.h"
 
 // Storage includes
 #include "nsDOMStorage.h"
 
+// Device Storage
+#include "nsIDOMDeviceStorage.h"
+#include "nsIDOMDeviceStorageCursor.h"
+
 // Drag and drop
 #include "nsIDOMDataTransfer.h"
 
 // Geolocation
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMGeoPosition.h"
 #include "nsIDOMGeoPositionCoords.h"
 #include "nsIDOMGeoPositionError.h"
@@ -429,16 +433,17 @@
 #include "nsDOMFile.h"
 #include "nsDOMFileReader.h"
 #include "nsIDOMFormData.h"
 
 #include "nsIDOMDOMStringMap.h"
 
 #include "nsIDOMDesktopNotification.h"
 #include "nsIDOMNavigatorDesktopNotification.h"
+#include "nsIDOMNavigatorDeviceStorage.h"
 #include "nsIDOMNavigatorGeolocation.h"
 #include "Navigator.h"
 
 #include "nsPluginArray.h"
 #include "nsMimeTypeArray.h"
 
 // Simple gestures include
 #include "nsIDOMSimpleGestureEvent.h"
@@ -1389,16 +1394,22 @@ static nsDOMClassInfoData sClassInfoData
                            WINDOW_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DataContainerEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MessageEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(DeviceStorage, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+  NS_DEFINE_CLASSINFO_DATA(DeviceStorageCursor, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(GeoGeolocation, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   
   NS_DEFINE_CLASSINFO_DATA(GeoPosition, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS) 
   
   NS_DEFINE_CLASSINFO_DATA(GeoPositionCoords, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -2426,16 +2437,17 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Location, nsIDOMLocation)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMLocation)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Navigator, nsIDOMNavigator)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigator)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorDeviceStorage)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorGeolocation)
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMNavigatorDesktopNotification,
                                         Navigator::HasDesktopNotificationSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMClientInformation)
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMMozNavigatorBattery,
                                         battery::BatteryManager::HasSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozNavigatorSms)
 #ifdef MOZ_B2G_RIL
@@ -4029,16 +4041,26 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MessageEvent, nsIDOMMessageEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMessageEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(DeviceStorage, nsIDOMDeviceStorage)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorage)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(DeviceStorageCursor, nsIDOMDeviceStorageCursor)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageCursor)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(GeoGeolocation, nsIDOMGeoGeolocation)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoGeolocation)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(GeoPosition, nsIDOMGeoPosition)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoPosition)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -382,16 +382,19 @@ DOMCI_CLASS(ModalContentWindow)
 
 // Data Events
 DOMCI_CLASS(DataContainerEvent)
 
 // event used for cross-domain message-passing and for server-sent events in
 // HTML5
 DOMCI_CLASS(MessageEvent)
 
+DOMCI_CLASS(DeviceStorage)
+DOMCI_CLASS(DeviceStorageCursor)
+
 // Geolocation
 DOMCI_CLASS(GeoGeolocation)
 DOMCI_CLASS(GeoPosition)
 DOMCI_CLASS(GeoPositionCoords)
 DOMCI_CLASS(GeoPositionError)
 
 DOMCI_CLASS(MozBatteryManager)
 
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/Makefile.in
@@ -0,0 +1,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/.
+
+DEPTH            = ../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE           = dom
+LIBRARY_NAME     = domdevicestorage_s
+XPIDL_MODULE     = dom_devicestorage
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+CPPSRCS		= \
+		nsDeviceStorage.cpp \
+		$(NULL)
+
+EXPORTS         = \
+		nsDeviceStorage.h \
+		$(NULL)
+
+LOCAL_INCLUDES = \
+		-I$(topsrcdir)/dom/base \
+		-I$(topsrcdir)/dom/ipc \
+		-I$(topsrcdir)/content/base/src \
+		-I$(topsrcdir)/content/events/src \
+		$(NULL)
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+include $(topsrcdir)/config/rules.mk
+
+DEFINES += -D_IMPL_NS_LAYOUT
+
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -0,0 +1,1153 @@
+/* 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 "nsDeviceStorage.h"
+#include "DOMRequest.h"
+#include "nsServiceManagerUtils.h"
+#include "nsILocalFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIDOMFile.h"
+#include "nsDOMBlobBuilder.h"
+#include "nsNetUtil.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContentPermissionPrompt.h"
+#include "nsIPrincipal.h"
+#include "mozilla/Preferences.h"
+#include "nsJSUtils.h"
+
+using namespace mozilla::dom;
+
+#include "nsDirectoryServiceDefs.h"
+
+class DeviceStorageFile : public nsISupports {
+public:
+  DeviceStorageFile(nsIFile* aFile, const nsAString& aPath)
+    : mFile(aFile)
+    , mPath(aPath)
+    {
+      NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
+    }
+  DeviceStorageFile(nsIFile* aFile)
+    : mFile(aFile)
+    {
+      NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
+    }
+    nsCOMPtr<nsIFile> mFile;
+    nsString mPath;
+
+    NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
+
+// we want to make sure that the names of file can't reach
+// outside of the type of storage the user asked for.
+bool
+isSafePath(const nsAString& aPath)
+{
+  nsAString::const_iterator start, end;
+  aPath.BeginReading(start);
+  aPath.EndReading(end);
+
+  // if the path has a ~ or \ in it, return false.
+  NS_NAMED_LITERAL_STRING(tilde, "~");
+  NS_NAMED_LITERAL_STRING(bslash, "\\");
+  if (FindInReadable(tilde, start, end) ||
+      FindInReadable(bslash, start, end)) {
+    return false;
+  }
+
+  // split on /.  if any token is "", ., or .., return false.
+  NS_ConvertUTF16toUTF8 cname(aPath);
+  char* buffer = cname.BeginWriting();
+  const char* token;
+
+  while ((token = nsCRT::strtok(buffer, "/", &buffer))) {
+    if (PL_strcmp(token, "") == 0 ||
+        PL_strcmp(token, ".") == 0 ||
+        PL_strcmp(token, "..") == 0 ) {
+          return false;
+    }
+  }
+  return true;
+}
+
+// TODO - eventually, we will want to factor this method
+// out into different system specific subclasses (or
+// something)
+PRInt32
+nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType, const PRInt32 aIndex)
+{
+  PRInt32 typeResult = DEVICE_STORAGE_TYPE_DEFAULT;
+
+  nsCOMPtr<nsILocalFile> f;
+  nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+  NS_ASSERTION(dirService, "Must have directory service");
+
+  // Picture directory
+  if (aType.Equals(NS_LITERAL_STRING("pictures"))) {
+#ifdef MOZ_WIDGET_GONK
+    if (aIndex == 0) {
+      NS_NewLocalFile(NS_LITERAL_STRING("/data/pictures"), false, getter_AddRefs(f));
+    }
+    else if (aIndex == 1) {
+      NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/DCIM"), false, getter_AddRefs(f));
+      typeResult = DEVICE_STORAGE_TYPE_EXTERNAL;
+    }
+#elif defined (MOZ_WIDGET_COCOA)
+    if (aIndex == 0) {
+      dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f));
+    }
+#elif defined (XP_UNIX)
+    if (aIndex == 0) {
+      dirService->Get(NS_UNIX_XDG_PICTURES_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f));
+    }
+#endif
+  }
+
+  // in testing, we have access to a few more directory locations
+  if (mozilla::Preferences::GetBool("device.storage.testing", false)) {
+
+    // Temp directory
+    if (aType.Equals(NS_LITERAL_STRING("temp")) && aIndex == 0) {
+      dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f));
+    }
+
+    // Profile directory
+    else if (aType.Equals(NS_LITERAL_STRING("profile")) && aIndex == 0) {
+      dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f));
+    }
+  } 
+
+  mFile = f;
+  return typeResult;
+}
+
+static jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile, bool aEditable)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWindow, "Null Window");
+
+  if (aEditable) {
+    // TODO - needs janv's file handle support.
+    return JSVAL_NULL;
+  }
+
+  if (aFile == nsnull) {
+    return JSVAL_NULL;
+  }
+
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
+
+  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
+  if (!sgo) {
+    return JSVAL_NULL;
+  }
+    
+  nsIScriptContext *scriptContext = sgo->GetScriptContext();
+  if (!scriptContext) {
+    return JSVAL_NULL;
+  }
+
+  JSContext *cx = scriptContext->GetNativeContext();
+  if (!cx) {
+    return JSVAL_NULL;
+  }
+
+  jsval wrappedFile;
+  nsresult rv = nsContentUtils::WrapNative(cx,
+                                           JS_GetGlobalObject(cx),
+                                           blob,
+                                           &NS_GET_IID(nsIDOMFile),
+                                           &wrappedFile);
+  if (NS_FAILED(rv)) {
+    return JSVAL_NULL;
+  }
+
+  return wrappedFile;
+}
+
+
+static jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWindow, "Null Window");
+
+  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
+  if (!sgo) {
+    return JSVAL_NULL;
+  }
+    
+  nsIScriptContext *scriptContext = sgo->GetScriptContext();
+  if (!scriptContext) {
+    return JSVAL_NULL;
+  }
+
+  JSContext *cx = scriptContext->GetNativeContext();
+  if (!cx) {
+    return JSVAL_NULL;
+  }
+
+  JSAutoRequest ar(cx);
+
+  jsval result = JSVAL_NULL;
+  if (!xpc::StringToJsval(cx, aString, &result)) {
+    return JSVAL_NULL;
+  }
+
+  return result;
+}
+
+
+class nsDOMDeviceStorageCursor
+  : public nsIDOMDeviceStorageCursor
+  , public DOMRequest
+  , public nsIContentPermissionRequest
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+  NS_DECL_NSIDOMDEVICESTORAGECURSOR
+
+  nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow,
+                           nsIURI* aURI,
+                           DeviceStorageFile* aFile,
+                           bool aEditable);
+
+private:
+  ~nsDOMDeviceStorageCursor();
+
+protected:
+  nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
+
+  bool mOkToCallContinue;
+  nsRefPtr<DeviceStorageFile> mFile;
+  bool mAllowed;
+  nsCOMPtr<nsIURI> mURI;
+  bool mEditable;
+
+  // to access mFiles
+  friend class InitCursorEvent;
+  friend class ContinueCursorEvent;
+};
+
+#define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "File location doesn't exists"
+#define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "File location is not enumerable"
+#define POST_ERROR_EVENT_PERMISSION_DENIED           "Permission Denied"
+#define POST_ERROR_EVENT_ILLEGAL_FILE_NAME           "Illegal file name"
+#define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
+#define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported"
+
+class PostErrorEvent : public nsRunnable
+{
+public:
+  PostErrorEvent(nsRefPtr<DOMRequest>& aRequest, const char* aMessage, DeviceStorageFile* aFile)
+  {
+    mRequest.swap(aRequest);
+    BuildErrorString(aMessage, aFile);
+  }
+
+  PostErrorEvent(DOMRequest* aRequest, const char* aMessage, DeviceStorageFile* aFile)
+    : mRequest(aRequest)
+  {
+    BuildErrorString(aMessage, aFile);
+  }
+
+  ~PostErrorEvent() {}
+
+  void BuildErrorString(const char* aMessage, DeviceStorageFile* aFile)
+  {
+    nsAutoString fullPath;
+
+    if (aFile && aFile->mFile) {
+      aFile->mFile->GetPath(fullPath);
+    }
+    else {
+      fullPath.Assign(NS_LITERAL_STRING("null file"));
+    }
+      
+    mError = NS_ConvertASCIItoUTF16(aMessage);
+    mError.Append(NS_LITERAL_STRING(" file path = "));
+    mError.Append(fullPath.get());
+    mError.Append(NS_LITERAL_STRING(" path = "));
+
+    if (aFile) {
+      mError.Append(aFile->mPath);
+    }
+    else {
+      mError.Append(NS_LITERAL_STRING("null path"));
+    }
+  }
+
+  NS_IMETHOD Run() {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    mRequest->FireError(mError);
+    mRequest = nsnull;
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DOMRequest> mRequest;
+  nsString mError;
+};
+
+class ContinueCursorEvent : public nsRunnable
+{
+public:
+
+  ContinueCursorEvent(nsRefPtr<DOMRequest>& aRequest)
+  {
+    mRequest.swap(aRequest);
+  }
+
+  ContinueCursorEvent(DOMRequest* aRequest)
+    : mRequest(aRequest)
+  {
+  }
+
+  ~ContinueCursorEvent() {}
+
+  NS_IMETHOD Run() {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    jsval val;
+
+    nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
+    if (cursor->mFiles.Length() == 0) {
+      val = JSVAL_NULL;
+    }
+    else {
+      nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
+      cursor->mFiles.RemoveElementAt(0);
+      val = nsIFileToJsval(cursor->GetOwner(), file, cursor->mEditable);
+      cursor->mOkToCallContinue = true;
+    }
+
+    mRequest->FireSuccess(val);
+    mRequest = nsnull;
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+
+class InitCursorEvent : public nsRunnable
+{
+public:
+    InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile)
+    : mFile(aFile)
+    , mRequest(aRequest)
+  {
+  }
+
+  ~InitCursorEvent() {}
+
+  NS_IMETHOD Run() {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    bool check;
+    mFile->mFile->IsDirectory(&check);
+    if (!check) {
+      nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
+                                                          POST_ERROR_EVENT_FILE_NOT_ENUMERABLE,
+                                                          mFile);
+      NS_DispatchToMainThread(event);
+      return NS_OK;
+    }
+
+    nsString fullpath;
+    mFile->mFile->GetPath(fullpath);
+    collectFiles(fullpath, mFile);
+
+    nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(mRequest);
+    NS_DispatchToMainThread(event);
+
+    return NS_OK;
+  }
+
+  void collectFiles(const nsString& aInitialFullPath, DeviceStorageFile* aFile)
+  {
+      // TODO - we may want to do this incrementally.
+    if (!aFile)
+      return;
+
+    nsCOMPtr<nsISimpleEnumerator> e;
+    aFile->mFile->GetDirectoryEntries(getter_AddRefs(e));
+
+    nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
+    nsCOMPtr<nsIFile> f;
+
+    while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
+      bool isDir;
+      f->IsDirectory(&isDir);
+
+      bool isFile;
+      f->IsFile(&isFile);
+
+      nsString fullpath;
+      f->GetPath(fullpath);
+
+      nsAString::size_type len = aInitialFullPath.Length() + 1; // +1 for the trailing /
+      nsDependentSubstring newPath = Substring(fullpath, len);
+
+      if (!StringBeginsWith(fullpath, aInitialFullPath)) {
+	NS_WARNING("collectFiles returned a path that does not belong!");
+	continue;
+      }
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f, newPath);
+      if (isDir) {
+	 collectFiles(aInitialFullPath, dsf);
+      }
+      else if (isFile) {
+        nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
+        cursor->mFiles.AppendElement(dsf);
+      }
+    }
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+DOMCI_DATA(DeviceStorageCursor, nsDOMDeviceStorageCursor)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorageCursor)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDeviceStorageCursor)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorageCursor)
+  NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMDOMRequest)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorageCursor)
+NS_INTERFACE_MAP_END_INHERITING(DOMRequest)
+
+NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorageCursor, DOMRequest)
+NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMRequest)
+
+nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow,
+                                                   nsIURI* aURI,
+                                                   DeviceStorageFile* aFile,
+                                                   bool aEditable)
+  : DOMRequest(aWindow)
+  , mOkToCallContinue(false)
+  , mFile(aFile)
+  , mURI(aURI)
+  , mEditable(aEditable)
+{
+  if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
+    Allow();
+  }
+}
+
+nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
+{
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::GetType(nsACString & aType)
+{
+  aType = "device-storage";
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::GetUri(nsIURI * *aRequestingURI)
+{
+  mURI.forget(aRequestingURI);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow * *aRequestingWindow)
+{
+  NS_IF_ADDREF(*aRequestingWindow = GetOwner());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::GetElement(nsIDOMElement * *aRequestingElement)
+{
+  *aRequestingElement = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::Cancel()
+{
+  nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(this,
+                                                      POST_ERROR_EVENT_PERMISSION_DENIED,
+                                                      mFile);
+  NS_DispatchToMainThread(event);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::Allow()
+{
+  mAllowed = true;
+  if (!isSafePath(mFile->mPath)) {
+    nsCOMPtr<nsIRunnable> r = new PostErrorEvent(this,
+                                                 POST_ERROR_EVENT_ILLEGAL_FILE_NAME,
+                                                 mFile);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+  mFile->mFile->AppendRelativePath(mFile->mPath);
+
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  NS_ASSERTION(target, "Must have stream transport service");
+
+  nsCOMPtr<InitCursorEvent> event = new InitCursorEvent(this, mFile);
+  target->Dispatch(event, NS_DISPATCH_NORMAL);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::Continue()
+{
+  if (!mOkToCallContinue) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (!mAllowed) {
+    nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+    if (prompt) {
+      prompt->Prompt(this);
+    }
+    return NS_OK;
+  }
+
+  if (mRooted) {
+    // We call onsuccess multiple times. clear the last
+    // rooted result.
+    NS_DROP_JS_OBJECTS(this, nsDOMDeviceStorageCursor);
+    mResult = JSVAL_VOID;
+    mDone = false;
+    mRooted = false;
+  }
+
+  nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(this);
+  NS_DispatchToMainThread(event);
+
+  mOkToCallContinue = false;
+  return NS_OK;
+}
+
+
+class PostResultEvent : public nsRunnable
+{
+public:
+  PostResultEvent(nsRefPtr<DOMRequest>& aRequest, bool aEditable, DeviceStorageFile* aFile)
+    : mEditable(aEditable)
+    , mFile(aFile)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  PostResultEvent(nsRefPtr<DOMRequest>& aRequest, const nsAString & aPath)
+    : mPath(aPath)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~PostResultEvent() {}
+
+  NS_IMETHOD Run() 
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    jsval result = JSVAL_NULL;
+    if (mFile) {
+      result = nsIFileToJsval(mRequest->GetOwner(), mFile, mEditable);
+    } else {
+      result = StringToJsval(mRequest->GetOwner(), mPath);
+    }
+
+    mRequest->FireSuccess(result);
+    mRequest = nsnull;
+    return NS_OK;
+  }
+
+private:
+  bool mEditable;
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsString mPath;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+class WriteFileEvent : public nsRunnable
+{
+public:
+  WriteFileEvent(nsIDOMBlob *aBlob,
+                 DeviceStorageFile *aFile,
+                 nsRefPtr<DOMRequest>& aRequest)
+  : mBlob(aBlob)
+  , mFile(aFile)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~WriteFileEvent() {}
+
+  void CleanupOnFail(const char* error)
+  {
+    if (mFile) {
+      mFile->mFile->Remove(false);
+      mFile = nsnull;
+    }
+
+    nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
+                                                        error,
+                                                        mFile);
+    NS_DispatchToMainThread(event);
+  }
+
+  NS_IMETHOD Run() 
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    //TODO - this might be faster if we check to see if
+    //these are backed by OS-files, and if so, then just do
+    //a copy()
+
+    nsCOMPtr<nsIFile> f = mFile->mFile;
+
+    // This also creates all ancestors
+    nsresult rv = f->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+    if (NS_FAILED(rv)) {
+      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN);
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIInputStream> stream;
+    mBlob->GetInternalStream(getter_AddRefs(stream));
+
+    if (!stream) {
+      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN);
+      return NS_OK;
+    }
+
+    PRUint32 bufSize;
+    stream->Available(&bufSize);
+
+    nsCOMPtr<nsIOutputStream> outputStream;
+    NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), f);
+
+    if (!outputStream) {
+      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN);
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIOutputStream> bufferedOutputStream;
+    NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
+                               outputStream,
+                               4096*4);
+
+    if (!bufferedOutputStream) {
+      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN);
+      return NS_OK;
+    }
+
+    PRUint32 wrote;
+    bufferedOutputStream->WriteFrom(stream, bufSize, &wrote);
+    bufferedOutputStream->Close();
+    outputStream->Close();
+
+    if (bufSize != wrote) {
+      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN);
+      return NS_OK;
+    }
+
+    nsCOMPtr<PostResultEvent> event = new PostResultEvent(mRequest, mFile->mPath);
+    NS_DispatchToMainThread(event);
+
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIDOMBlob> mBlob;
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+
+class ReadFileEvent : public nsRunnable
+{
+public:
+    ReadFileEvent(DeviceStorageFile* aFile,
+                  bool aEditable,
+                  nsRefPtr<DOMRequest>& aRequest)
+  : mFile(aFile)
+  , mEditable(aEditable)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~ReadFileEvent() {}
+
+  NS_IMETHOD Run() 
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    nsRefPtr<nsRunnable> r;
+
+    if (!mEditable) {
+      bool check = false;
+      mFile->mFile->Exists(&check);
+      if (!check) {
+        r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST, mFile);
+      }
+    }
+
+    if (!r) {
+      r = new PostResultEvent(mRequest, mEditable, mFile);
+    }
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  bool mEditable;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+class DeleteFileEvent : public nsRunnable
+{
+public:
+  DeleteFileEvent(DeviceStorageFile* aFile,
+                  nsRefPtr<DOMRequest>& aRequest)
+  : mFile(aFile)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~DeleteFileEvent() {}
+    
+  NS_IMETHOD Run() 
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    mFile->mFile->Remove(true);
+    nsCOMPtr<PostResultEvent> event = new PostResultEvent(mRequest, mFile->mPath);
+    NS_DispatchToMainThread(event);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+class DeviceStorageRequest : public nsIContentPermissionRequest, public nsIRunnable
+{
+public:
+
+    enum {
+        DEVICE_STORAGE_REQUEST_READ,
+        DEVICE_STORAGE_REQUEST_WRITE,
+        DEVICE_STORAGE_REQUEST_DELETE
+    };
+    DeviceStorageRequest(const PRInt32 aRequestType,
+                         nsPIDOMWindow *aWindow,
+                         nsIURI *aURI,
+                         DeviceStorageFile *aFile,
+                         DOMRequest* aRequest,
+                         bool aEditable,
+                         nsIDOMBlob *aBlob = nsnull)
+        : mRequestType(aRequestType)
+        , mWindow(aWindow)
+        , mURI(aURI)
+        , mFile(aFile)
+        , mRequest(aRequest)
+        , mEditable(aEditable)
+        , mBlob(aBlob) {}
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest, nsIContentPermissionRequest)
+
+  NS_IMETHOD Run() {
+
+    if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
+      Allow();
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+    if (prompt) {
+      prompt->Prompt(this);
+    }
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetType(nsACString & aType)
+  {
+    aType = "device-storage";
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetUri(nsIURI * *aRequestingURI)
+  {
+    NS_ADDREF(*aRequestingURI = mURI);
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow)
+  {
+    NS_IF_ADDREF(*aRequestingWindow = mWindow);
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement)
+  {
+    *aRequestingElement = nsnull;
+    return NS_OK;
+  }
+
+  NS_IMETHOD Cancel()
+  {
+    nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
+                                                        POST_ERROR_EVENT_PERMISSION_DENIED,
+                                                        mFile);
+    NS_DispatchToMainThread(event);
+    return NS_OK;
+  }
+
+  NS_IMETHOD Allow()
+  {
+    nsCOMPtr<nsIRunnable> r;
+
+    if (!mRequest) {
+      return NS_ERROR_FAILURE;
+    }
+
+    switch(mRequestType) {
+      case DEVICE_STORAGE_REQUEST_WRITE:
+      {
+        if (!mBlob) {
+          return NS_ERROR_FAILURE;
+        }
+
+        r = new WriteFileEvent(mBlob, mFile, mRequest);
+        break;
+      }
+      case DEVICE_STORAGE_REQUEST_READ:
+      {
+        r = new ReadFileEvent(mFile, mEditable, mRequest);
+        break;
+      }
+      case DEVICE_STORAGE_REQUEST_DELETE:
+      {
+        r = new DeleteFileEvent(mFile, mRequest);
+        break;
+      }
+    }
+    
+    if (r) {
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+    }
+    return NS_OK;
+  }
+
+private:
+  PRInt32 mRequestType;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsIURI> mURI;
+  nsRefPtr<DeviceStorageFile> mFile;
+
+  nsRefPtr<DOMRequest> mRequest;
+  bool mEditable;
+  nsCOMPtr<nsIDOMBlob> mBlob;
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
+  NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
+  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageRequest)
+NS_IMPL_CYCLE_COLLECTION_CLASS(DeviceStorageRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DeviceStorageRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mBlob)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DeviceStorageRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRequest, nsIDOMDOMRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mWindow, nsPIDOMWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mBlob, nsIDOMBlob)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+DOMCI_DATA(DeviceStorage, nsDOMDeviceStorage)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorage)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorage)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDeviceStorage)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_THREADSAFE_ADDREF(nsDOMDeviceStorage)
+NS_IMPL_THREADSAFE_RELEASE(nsDOMDeviceStorage)
+
+nsDOMDeviceStorage::nsDOMDeviceStorage()
+ : mStorageType(DEVICE_STORAGE_TYPE_DEFAULT)
+{
+}
+
+nsresult
+nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType, const PRInt32 aIndex)
+{
+  NS_ASSERTION(aWindow, "Must have a content dom");
+
+  mStorageType = SetRootFileForType(aType, aIndex);
+  if (!mFile) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mOwner = do_GetWeakReference(aWindow);
+  if (!mOwner) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Grab the uri of the document
+  nsCOMPtr<nsIDOMDocument> domdoc;
+  aWindow->GetDocument(getter_AddRefs(domdoc));
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
+  if (!doc) {
+    return NS_ERROR_FAILURE;
+  }
+  doc->NodePrincipal()->GetURI(getter_AddRefs(mURI));
+
+  return NS_OK;
+}
+
+nsDOMDeviceStorage::~nsDOMDeviceStorage()
+{
+}
+
+void
+nsDOMDeviceStorage::CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
+                                            const nsAString &aType,
+                                            nsIVariant** _retval)
+{
+  nsTArray<nsRefPtr<nsIDOMDeviceStorage> > stores;
+
+  PRInt32 index = 0;
+  while (1) {
+    nsresult rv;
+    nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage();
+    rv = storage->Init(aWin, aType, index++);
+    if (NS_FAILED(rv))
+      break;
+    stores.AppendElement(storage);
+  }
+
+  nsCOMPtr<nsIWritableVariant> result = do_CreateInstance("@mozilla.org/variant;1");
+  if (!result) {
+    return;
+  }
+
+  result->SetAsArray(nsIDataType::VTYPE_INTERFACE,
+                     &NS_GET_IID(nsIDOMDeviceStorage),
+                     stores.Length(),
+                     const_cast<void*>(static_cast<const void*>(stores.Elements())));
+  NS_ADDREF(*_retval = result);
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::GetType(nsAString & aType)
+{
+  switch(mStorageType) {
+    case DEVICE_STORAGE_TYPE_EXTERNAL:
+      aType.AssignLiteral("external");
+      break;
+    case DEVICE_STORAGE_TYPE_SHARED:
+      aType.AssignLiteral("shared");
+      break;
+    case DEVICE_STORAGE_TYPE_DEFAULT:
+    default:
+      aType.AssignLiteral("default");
+      break;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval NS_OUTPARAM)
+{
+  char buffer[128];
+  NS_MakeRandomString(buffer, 128);
+
+  nsString path;
+  path.AssignWithConversion(nsDependentCString(buffer));
+
+  return AddNamed(aBlob, path, _retval);
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::AddNamed(nsIDOMBlob *aBlob,
+                             const nsAString & aPath,
+                             nsIDOMDOMRequest * *_retval NS_OUTPARAM)
+{
+  // if the blob is null here, bail
+  if (aBlob == nsnull)
+    return NS_OK;
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mOwner);
+  if (!win) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  
+  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+  NS_ADDREF(*_retval = request);
+
+  nsCOMPtr<nsIRunnable> r;
+
+  nsCOMPtr<nsIFile> file;
+  mFile->Clone(getter_AddRefs(file));
+  file->AppendRelativePath(aPath);
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(file, aPath);
+
+  if (!isSafePath(aPath)) {
+    r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
+  }
+  else {
+    r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_WRITE,
+                                 win, mURI, dsf, request, true, aBlob);
+  }
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::Get(const JS::Value & aPath,
+                        JSContext* aCx,
+                        nsIDOMDOMRequest * *_retval NS_OUTPARAM)
+{
+  return GetInternal(aPath, aCx, _retval, false);
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::GetEditable(const JS::Value & aPath,
+                                JSContext* aCx,
+                                nsIDOMDOMRequest * *_retval NS_OUTPARAM)
+{
+  return GetInternal(aPath, aCx, _retval, true);
+}
+
+nsresult
+nsDOMDeviceStorage::GetInternal(const JS::Value & aPath,
+                                JSContext* aCx,
+                                nsIDOMDOMRequest * *_retval NS_OUTPARAM,
+                                bool aEditable)
+{
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mOwner);
+  if (!win) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+  NS_ADDREF(*_retval = request);
+
+  nsCOMPtr<nsIRunnable> r;
+
+  JSString* jsstr = JS_ValueToString(aCx, aPath);
+  nsDependentJSString path;
+  if (!path.init(aCx, jsstr)) {
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
+    r = new PostErrorEvent(request,
+                           POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED,
+                           dsf);
+  } else {
+
+    nsCOMPtr<nsIFile> file;
+    mFile->Clone(getter_AddRefs(file));
+    file->AppendRelativePath(path);
+
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(file, path);
+
+    r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_READ,
+                                 win, mURI, dsf, request, aEditable);
+  }
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::Delete(const JS::Value & aPath, JSContext* aCx, nsIDOMDOMRequest * *_retval NS_OUTPARAM)
+{
+  nsCOMPtr<nsIRunnable> r;
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mOwner);
+  if (!win) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+  NS_ADDREF(*_retval = request);
+
+  JSString* jsstr = JS_ValueToString(aCx, aPath);
+  nsDependentJSString path;
+  if (!path.init(aCx, jsstr)) {
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
+    r = new PostErrorEvent(request, POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED, dsf);
+  } else if (!isSafePath(path)) {
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
+    r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
+  }
+  else {
+    nsCOMPtr<nsIFile> file;
+    mFile->Clone(getter_AddRefs(file));
+    file->AppendRelativePath(path);
+
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(file, path);
+    r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_DELETE,
+                                 win, mURI, dsf, request, true);
+  }
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::Enumerate(const nsAString & aPath,
+                              nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM)
+{
+  return EnumerateInternal(aPath, _retval, false);
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorage::EnumerateEditable(const nsAString & aPath,
+                                      nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM)
+{
+  return EnumerateInternal(aPath, _retval, true);
+}
+
+nsresult
+nsDOMDeviceStorage::EnumerateInternal(const nsAString & aPath,
+                                      nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM,
+                                      bool aEditable)
+{
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mOwner);
+  if (!win)
+    return NS_ERROR_UNEXPECTED;
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, aPath);
+
+  nsDOMDeviceStorageCursor* cursor = new nsDOMDeviceStorageCursor(win, mURI, dsf, aEditable);
+  NS_ADDREF(*_retval = cursor);
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -0,0 +1,58 @@
+/* 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 nsDeviceStorage_h
+#define nsDeviceStorage_h
+
+class nsPIDOMWindow;
+
+#include "nsIClassInfo.h"
+#include "nsIDOMDeviceStorage.h"
+#include "nsIDOMDeviceStorageCursor.h"
+#include "nsIDOMWindow.h"
+#include "nsIURI.h"
+
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMClassInfoID.h"
+#include "nsString.h"
+#include "nsWeakPtr.h"
+#include "nsInterfaceHashtable.h"
+
+class nsDOMDeviceStorage : public nsIDOMDeviceStorage
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMDEVICESTORAGE
+
+  nsDOMDeviceStorage();
+
+  nsresult Init(nsPIDOMWindow* aWindow, const nsAString &aType, const PRInt32 aIndex);
+
+  PRInt32 SetRootFileForType(const nsAString& aType, const PRInt32 aIndex);
+
+  static void CreateDeviceStoragesFor(nsPIDOMWindow* aWin, const nsAString &aType, nsIVariant** _retval);
+
+private:
+  ~nsDOMDeviceStorage();
+
+
+  nsresult GetInternal(const JS::Value & aName, JSContext* aCx, nsIDOMDOMRequest * *_retval NS_OUTPARAM, bool aEditable);
+  nsresult EnumerateInternal(const nsAString & aName, nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM, bool aEditable);
+
+  PRInt32 mStorageType;
+  nsCOMPtr<nsIFile> mFile;
+
+  nsWeakPtr mOwner;
+  nsCOMPtr<nsIURI> mURI;
+
+  // nsIDOMDeviceStorage.type
+  enum {
+      DEVICE_STORAGE_TYPE_DEFAULT = 0,
+      DEVICE_STORAGE_TYPE_SHARED,
+      DEVICE_STORAGE_TYPE_EXTERNAL,
+  };
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/Makefile.in
@@ -0,0 +1,26 @@
+# 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/.
+
+DEPTH            = ../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE           = dom
+LIBRARY_NAME     = domdevicestorage_s
+XPIDL_MODULE     = dom_devicestorage
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+XPIDLSRCS = \
+	nsIDOMDeviceStorage.idl \
+	nsIDOMDeviceStorageCursor.idl \
+	nsIDOMNavigatorDeviceStorage.idl
+
+include $(topsrcdir)/config/rules.mk
+
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
@@ -0,0 +1,35 @@
+/* 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 "domstubs.idl"
+interface nsIDOMBlob;
+interface nsIDOMDOMRequest;
+interface nsIDOMDeviceStorageCursor;
+
+[scriptable, uuid(05C0D0C8-D698-4CCD-899C-7198A33BD7EC)]
+interface nsIDOMDeviceStorage : nsISupports
+{
+    /*
+     * Hint as to what kind of storage this object is.
+     * May be "external", "shared", or "default".
+     */
+    readonly attribute DOMString type;
+
+    nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
+    nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
+
+    [implicit_jscontext]
+    nsIDOMDOMRequest get(in jsval aName);
+
+    [implicit_jscontext]
+    nsIDOMDOMRequest getEditable(in jsval aName);
+    
+    [implicit_jscontext]
+    nsIDOMDOMRequest delete(in jsval aName);
+
+    nsIDOMDeviceStorageCursor enumerate([optional] in DOMString directory);
+
+    nsIDOMDeviceStorageCursor enumerateEditable([optional] in DOMString directory);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorageCursor.idl
@@ -0,0 +1,11 @@
+/* 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 "domstubs.idl"
+
+[scriptable, uuid(995DFF99-ED70-4780-AC9A-4B58CD491186)]
+interface nsIDOMDeviceStorageCursor : nsISupports
+{
+    void continue();
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/nsIDOMNavigatorDeviceStorage.idl
@@ -0,0 +1,17 @@
+/* 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 "domstubs.idl"
+interface nsIVariant;
+
+/**
+ * Property that extends the navigator object.
+ */
+[scriptable, uuid(A4B2831D-6065-472F-8A6D-2C9085C74C15)]
+interface nsIDOMNavigatorDeviceStorage : nsISupports
+{
+    // returns an array of nsIDOMDeviceStorage
+    nsIVariant getDeviceStorage(in DOMString type);
+};
+
--- a/dom/tests/mochitest/Makefile.in
+++ b/dom/tests/mochitest/Makefile.in
@@ -13,16 +13,17 @@ include $(DEPTH)/config/autoconf.mk
 DIRS	+= \
 	dom-level0 \
 	dom-level1-core \
 	dom-level2-core \
 	dom-level2-html \
 	ajax \
 	bugs \
 	chrome \
+	devicestorage \
 	general \
 	whatwg \
 	geolocation \
 	localstorage \
 	orientation \
 	sessionstorage \
 	storageevent \
 	pointerlock \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/Makefile.in
@@ -0,0 +1,27 @@
+# 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/.
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= dom/tests/mochitest/devicestorage
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES	= \
+		test_sanity.html \
+		test_basic.html \
+		test_enumerate.html \
+		test_enumerateMultipleContinue.html \
+		test_overwrite.html \
+		test_dotdot.html \
+		devicestorage_common.js \
+		$(NULL)
+
+libs:: 	$(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/devicestorage_common.js
@@ -0,0 +1,48 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var oldVal = false;
+  
+function devicestorage_setup() {
+  SimpleTest.waitForExplicitFinish();
+  try {
+    oldVal = SpecialPowers.getBoolPref("device.storage.enabled");
+  } catch(e) {}
+  SpecialPowers.setBoolPref("device.storage.enabled", true);
+  SpecialPowers.setBoolPref("device.storage.testing", true);
+  SpecialPowers.setBoolPref("device.storage.prompt.testing", true);
+}
+
+function devicestorage_cleanup() {
+  SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
+  SpecialPowers.setBoolPref("device.storage.testing", false);
+  SpecialPowers.setBoolPref("device.storage.prompt.testing", false);
+  SimpleTest.finish();
+}
+
+function getRandomBuffer() {
+  var size = 1024;
+  var buffer = new ArrayBuffer(size);
+  var view = new Uint8Array(buffer);
+  for (var i = 0; i < size; i++) {
+    view[i] = parseInt(Math.random() * 255);
+  }
+  return buffer;
+}
+
+function createRandomBlob() {
+ return blob = new Blob([getRandomBuffer()], {type: 'binary/random'});
+}
+
+function randomFilename(l) {
+    var set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ";
+    var result = "";
+    for (var i=0; i<l; i++) {
+	var r = Math.floor(set.length * Math.random());
+	result += set.substring(r, r + 1);
+    }
+    return result;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_basic.html
@@ -0,0 +1,102 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for the device storage API </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=717103">Mozilla Bug 717103</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 = "devicestorage/hi";
+
+function getAfterDeleteSuccess(e) {
+  ok(false, "file was deleted not successfully");
+  devicestorage_cleanup();
+}
+
+function getAfterDeleteError(e) {
+  ok(true, "file was deleted successfully");
+  devicestorage_cleanup();
+}
+
+function deleteSuccess(e) {
+
+  ok(e.target.result == gFileName, "File name should match");
+
+  var storage = navigator.getDeviceStorage("profile");
+  request = storage[0].get(e.target.result);
+  request.onsuccess = getAfterDeleteSuccess;
+  request.onerror = getAfterDeleteError;
+
+}
+
+function deleteError(e) {
+  ok(false, "deleteError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+function getSuccess(e) {
+  var storage = navigator.getDeviceStorage("profile");
+  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+  ok(e.target.result.name == gFileName, "File name should match");
+
+  request = storage[0].delete(e.target.result.name)
+  request.onsuccess = deleteSuccess;
+  request.onerror = deleteError;
+}
+
+function getError(e) {
+  ok(false, "getError was called : " + e.target.error.name);
+  SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
+  SimpleTest.finish();
+}
+
+function addSuccess(e) {
+
+  ok(e.target.result == gFileName, "File name should match");
+
+  var storage = navigator.getDeviceStorage("profile");
+  request = storage[0].get(gFileName);
+  request.onsuccess = getSuccess;
+  request.onerror = getError;
+
+  ok(true, "addSuccess was called");
+}
+
+function addError(e) {
+  ok(false, "addError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+var storage = navigator.getDeviceStorage("profile");
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+request = storage[0].addNamed(createRandomBlob(), "devicestorage/hi");
+ok(request, "Should have a non-null request");
+
+request.onsuccess = addSuccess;
+request.onerror = addError;
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_dotdot.html
@@ -0,0 +1,72 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+function profileStorage() {
+  return navigator.getDeviceStorage("profile")[0];
+}
+
+var tests = [
+  function () { return profileStorage().addNamed(createRandomBlob(), gFileName); },
+  function () { return profileStorage().delete(gFileName); },
+  function () { return profileStorage().get(gFileName); },
+  function () { var r = profileStorage().enumerate("../"); return r; }
+];
+
+var gFileName = "../owned";
+
+function fail(e) {
+  ok(false, "addSuccess was called");
+  devicestorage_cleanup();
+}
+
+function next(e) {
+
+  if (e != undefined)
+    ok(true, "addError was called");
+  
+  var f = tests.pop();
+
+  if (f == undefined) {
+    devicestorage_cleanup();
+    return;
+  }
+
+  var request = f();
+  request.onsuccess = fail;
+  request.onerror = next;
+}
+
+next();
+
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_enumerate.html
@@ -0,0 +1,101 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// Array Remove - By John Resig (MIT Licensed)
+Array.prototype.remove = function(from, to) {
+  var rest = this.slice((to || from) + 1 || this.length);
+  this.length = from < 0 ? this.length + from : from;
+  return this.push.apply(this, rest);
+};
+
+devicestorage_setup();
+
+function enumerateSuccess(e) {
+
+  if (e.target.result == null) {
+    ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
+    dump("We still have length = " + files.length);
+    devicestorage_cleanup();
+    return;
+  }
+  
+  dump("asdfasdf"+ e.target.result + "\n");
+  dump("asdfasdf"+ e.target.result.name + "\n");
+
+  var filename = e.target.result.name;
+
+  var index = files.indexOf(filename);
+  files.remove(index);
+
+  ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
+
+  // clean up
+  var cleanup = storage[0].delete(prefix + "/" + filename);
+  cleanup.onsuccess = function(e) {}  // todo - can i remove this?
+
+  e.target.continue();
+}
+
+function handleError(e) {
+  ok(false, "handleError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+function addSuccess(e) {
+  addedSoFar = addedSoFar + 1;
+  if (addedSoFar == files.length) {
+
+    var cursor = storage[0].enumerate(prefix);
+    cursor.onsuccess = enumerateSuccess;
+    cursor.onerror = handleError;
+  }
+}
+
+function addError(e) {
+  ok(false, "addError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+var storage = navigator.getDeviceStorage("profile");
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+var prefix = "devicestorage/" + randomFilename(12)
+
+var files = [ "a", "b", "c", "d/a", "d/b", "d/c", "d/d", "The/quick/brown/fox/jumps/over/the/lazy/dog"]
+var addedSoFar = 0;
+
+
+for (var i=0; i<files.length; i++) {
+
+ request = storage[0].addNamed(createRandomBlob(), prefix + '/' + files[i]);
+
+ ok(request, "Should have a non-null request");
+ request.onsuccess = addSuccess;
+ request.onerror = addError;
+}
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_enumerateMultipleContinue.html
@@ -0,0 +1,50 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+function enumerateSuccess(e) {
+}
+
+function enumerateFailure(e) {
+}
+
+var cursor = navigator.getDeviceStorage("profile")[0].enumerate();
+cursor.onsuccess = enumerateSuccess;
+cursor.onerror = enumerateFailure;
+
+try {
+ cursor.continue();
+}
+catch (e) {
+  ok(true, "Calling continue before enumerateSuccess fires should throw");
+  devicestorage_cleanup();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_enumerateNoParam.html
@@ -0,0 +1,95 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// Array Remove - By John Resig (MIT Licensed)
+Array.prototype.remove = function(from, to) {
+  var rest = this.slice((to || from) + 1 || this.length);
+  this.length = from < 0 ? this.length + from : from;
+  return this.push.apply(this, rest);
+};
+
+devicestorage_setup();
+
+function enumerateSuccess(e) {
+
+  if (e.target.result == null) {
+    ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
+    devicestorage_cleanup();
+  }
+  
+  var filename = e.target.result.name;
+  var index = files.indexOf(filename);
+  files.remove(index);
+
+  ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
+
+  // clean up
+  var cleanup = storage[0].delete(prefix + "/" + filename);
+  cleanup.onsuccess = function(e) {}  // todo - can i remove this?
+
+  e.target.continue();
+}
+
+function handleError(e) {
+  ok(false, "handleError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+function addSuccess(e) {
+  addedSoFar = addedSoFar + 1;
+  if (addedSoFar == files.length) {
+
+    var cursor = storage[0].enumerate();
+    cursor.onsuccess = enumerateSuccess;
+    cursor.onerror = handleError;
+  }
+}
+
+function addError(e) {
+  ok(false, "addError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+var storage = navigator.getDeviceStorage("profile");
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+var prefix = "devicestorage/" + randomFilename(12)
+
+var files = [ "a", "b", "c" ]
+var addedSoFar = 0;
+
+
+for (var i=0; i<files.length; i++) {
+
+ request = storage[0].addNamed(createRandomBlob(), prefix + '/' + files[i]);
+
+ ok(request, "Should have a non-null request");
+ request.onsuccess = addSuccess;
+ request.onerror = addError;
+}
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_overwrite.html
@@ -0,0 +1,90 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for basic sanity of the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var filename = "devicestorage/aaaa"
+
+devicestorage_setup();
+
+
+function deleteSuccess(e) {
+  devicestorage_cleanup();
+}
+
+function deleteError(e) {
+  ok(false, "deleteError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+function addOverwritingSuccess(e) {
+  ok(false, "addOverwritingSuccess was called.");
+}
+
+function addOverwritingError(e) {
+  ok(true, "Adding to the same location should fail");
+
+  var storage = navigator.getDeviceStorage("profile");
+  request = storage[0].delete(filename)
+  request.onsuccess = deleteSuccess;
+  request.onerror = deleteError;
+}
+
+function addSuccess(e) {
+  ok(true, "addSuccess was called");
+
+  var storage = navigator.getDeviceStorage("profile");
+  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+  request = storage[0].addNamed(createRandomBlob(), filename);
+  ok(request, "Should have a non-null request");
+
+  request.onsuccess = addOverwritingSuccess;
+  request.onerror = addOverwritingError;
+}
+
+function addError(e) {
+  // test file is already exists.  clean it up and try again..
+  var storage = navigator.getDeviceStorage("profile");
+  request = storage[0].delete(filename)
+  request.onsuccess = runtest;
+}
+
+function runtest() {
+  var storage = navigator.getDeviceStorage("profile");
+  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+  request = storage[0].addNamed(createRandomBlob(), filename);
+  ok(request, "Should have a non-null request");
+
+  request.onsuccess = addSuccess;
+  request.onerror = addError;
+}
+
+runtest();
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/devicestorage/test_sanity.html
@@ -0,0 +1,72 @@
+<!--
+  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=717103
+-->
+<head>
+  <title>Test for basic sanity of the device storage API </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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup()
+
+ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+var storage;
+
+var throws = false;
+try {
+ storage = navigator.getDeviceStorage();
+} catch(e) {throws = true}
+ok(throws, "getDeviceStorage takes one arg");
+
+storage = navigator.getDeviceStorage("kilimanjaro");
+ok(!storage, "kilimanjaro - Should not have this type of storage");
+
+storage = navigator.getDeviceStorage("temp");
+ok(storage, "temp - Should have getDeviceStorage");
+
+storage = navigator.getDeviceStorage("profile");
+ok(storage, "profile - Should have getDeviceStorage");
+
+var cursor = storage[0].enumerate();
+ok(cursor, "Should have a non-null cursor");
+
+var i = 4;
+cursor.onsuccess = function(e) {
+  i = i - 1;
+  if (i > 0) {
+    ok(true, "onsuccess was called");
+    e.target.continue();
+  }
+  else {
+    ok(true, "onsuccess was called 4 times");
+    devicestorage_cleanup();
+  }
+}
+
+cursor.onerror = function(e) {
+  ok(false, "onerror was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -57,16 +57,17 @@ SHARED_LIBRARY_LIBS = \
 	$(DEPTH)/content/xslt/src/xpath/$(LIB_PREFIX)txxpath_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/xslt/$(LIB_PREFIX)txxslt_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xbl/src/$(LIB_PREFIX)gkconxbl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xul/document/src/$(LIB_PREFIX)gkconxuldoc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/view/src/$(LIB_PREFIX)gkview_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/base/$(LIB_PREFIX)jsdombase_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/battery/$(LIB_PREFIX)dom_battery_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/contacts/$(LIB_PREFIX)jsdomcontacts_s.$(LIB_SUFFIX) \
+	$(DEPTH)/dom/devicestorage/$(LIB_PREFIX)domdevicestorage_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/power/$(LIB_PREFIX)dom_power_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/settings/$(LIB_PREFIX)jsdomsettings_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/network/src/$(LIB_PREFIX)dom_network_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/sms/src/$(LIB_PREFIX)dom_sms_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/events/$(LIB_PREFIX)jsdomevents_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/json/$(LIB_PREFIX)json_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/jsurl/$(LIB_PREFIX)jsurl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/storage/$(LIB_PREFIX)jsdomstorage_s.$(LIB_SUFFIX) \
@@ -209,17 +210,17 @@ include $(topsrcdir)/config/rules.mk
 LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(srcdir)/../generic \
 		   -I$(srcdir)/../forms \
 		   -I$(srcdir)/../tables \
 		   -I$(srcdir)/../style \
 		   -I$(srcdir)/../xul/base/src \
 		   -I$(srcdir)/../mathml \
 		   -I$(topsrcdir)/content/base/src \
- 		   -I$(topsrcdir)/content/canvas/src \
+		   -I$(topsrcdir)/content/canvas/src \
 		   -I$(topsrcdir)/content/html/content/src \
 		   -I$(topsrcdir)/content/html/document/src \
 		   -I$(topsrcdir)/content/xslt/src/base \
 		   -I$(topsrcdir)/content/xslt/src/xml \
 		   -I$(topsrcdir)/content/xslt/src/xpath \
 		   -I$(topsrcdir)/content/xslt/src/xslt \
 		   -I$(topsrcdir)/content/xul/content/src \
 		   -I$(topsrcdir)/content/xul/document/src \
--- a/mobile/xul/installer/package-manifest.in
+++ b/mobile/xul/installer/package-manifest.in
@@ -163,16 +163,17 @@
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
+@BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3458,16 +3458,19 @@ pref("layers.prefer-d3d9", false);
 #endif
 
 // Enable/Disable the geolocation API for content
 pref("geo.enabled", true);
 
 // Enable/Disable the orientation API for content
 pref("device.motion.enabled", true);
 
+// Enable/Disable the device storage API for content
+pref("device.storage.enabled", false);
+
 // Toggle which thread the HTML5 parser uses for stream parsing
 pref("html5.offmainthread", true);
 // Time in milliseconds between the time a network buffer is seen and the 
 // timer firing when the timer hasn't fired previously in this parse in the 
 // off-the-main-thread HTML5 parser.
 pref("html5.flushtimer.initialdelay", 120);
 // Time in milliseconds between the time a network buffer is seen and the 
 // timer firing when the timer has already fired previously in this parse.
--- a/toolkit/toolkit-makefiles.sh
+++ b/toolkit/toolkit-makefiles.sh
@@ -13,16 +13,17 @@ MAKEFILES_dom="
   ipc/ipdl/Makefile
   ipc/testshell/Makefile
   dom/Makefile
   dom/interfaces/apps/Makefile
   dom/interfaces/base/Makefile
   dom/interfaces/canvas/Makefile
   dom/interfaces/core/Makefile
   dom/interfaces/css/Makefile
+  dom/interfaces/devicestorage/Makefile
   dom/interfaces/events/Makefile
   dom/interfaces/geolocation/Makefile
   dom/interfaces/html/Makefile
   dom/interfaces/json/Makefile
   dom/interfaces/load-save/Makefile
   dom/interfaces/offline/Makefile
   dom/interfaces/notification/Makefile
   dom/interfaces/range/Makefile