Bug 661877 - Enable storing files in IndexedDB. r=bent
authorJan Varga <jan.varga@gmail.com>
Fri, 16 Dec 2011 08:34:24 +0100
changeset 82698 5efcb9c3b375200062ee7e9c2db2f0906547e539
parent 82697 c8b8b310f27e69af12083f12e812b1e590417b5a
child 82767 dcf6e5163e631a470678cd35d1b6733687d9f034
push id21694
push userJan.Varga@gmail.com
push dateFri, 16 Dec 2011 07:35:48 +0000
treeherdermozilla-central@5efcb9c3b375 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs661877
milestone11.0a1
first release with
nightly linux32
5efcb9c3b375 / 11.0a1 / 20111216031140 / files
nightly linux64
5efcb9c3b375 / 11.0a1 / 20111216031140 / files
nightly mac
5efcb9c3b375 / 11.0a1 / 20111216031140 / files
nightly win32
5efcb9c3b375 / 11.0a1 / 20111216031140 / files
nightly win64
5efcb9c3b375 / 11.0a1 / 20111216031140 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 661877 - Enable storing files in IndexedDB. r=bent
browser/base/content/pageinfo/permissions.js
content/base/public/nsDOMFile.h
content/base/public/nsIDOMFile.idl
content/base/src/nsDOMFile.cpp
db/sqlite3/README.MOZILLA
db/sqlite3/src/sqlite.def
db/sqlite3/src/test_quota.c
db/sqlite3/src/test_quota.h
dom/base/StructuredCloneTags.h
dom/base/nsDOMWindowUtils.cpp
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/AsyncConnectionHelper.h
dom/indexedDB/CheckPermissionsHelper.cpp
dom/indexedDB/FileInfo.cpp
dom/indexedDB/FileInfo.h
dom/indexedDB/FileManager.cpp
dom/indexedDB/FileManager.h
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBCursor.h
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBDatabase.h
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/IDBTransaction.cpp
dom/indexedDB/IDBTransaction.h
dom/indexedDB/IndexedDatabase.h
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
dom/indexedDB/Makefile.in
dom/indexedDB/OpenDatabaseHelper.cpp
dom/indexedDB/OpenDatabaseHelper.h
dom/indexedDB/TransactionThreadPool.h
dom/indexedDB/nsIIndexedDatabaseManager.idl
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/file.js
dom/indexedDB/test/helpers.js
dom/indexedDB/test/test_file_array.html
dom/indexedDB/test/test_file_cross_database_copying.html
dom/indexedDB/test/test_file_delete.html
dom/indexedDB/test/test_file_os_delete.html
dom/indexedDB/test/test_file_put_get_values.html
dom/indexedDB/test/test_file_quota.html
dom/indexedDB/test/test_file_resurrection_delete.html
dom/indexedDB/test/test_file_resurrection_transaction_abort.html
dom/indexedDB/test/test_file_sharing.html
dom/indexedDB/test/test_file_transaction_abort.html
dom/interfaces/base/nsIDOMWindowUtils.idl
storage/public/mozIStorageConnection.idl
storage/public/mozIStorageServiceQuotaManagement.idl
storage/src/FileSystemModule.cpp
storage/src/FileSystemModule.h
storage/src/Makefile.in
storage/src/mozStorageConnection.cpp
storage/src/mozStorageService.cpp
testing/mochitest/tests/SimpleTest/specialpowersAPI.js
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -221,17 +221,17 @@ function onIndexedDBClear()
 
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
   permissionManager.remove(gPermURI.host, "indexedDB");
   permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
   initIndexedDBRow();
 }
 
-function onIndexedDBUsageCallback(uri, usage)
+function onIndexedDBUsageCallback(uri, usage, fileUsage)
 {
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
   }
 
   if (usage) {
     if (!("DownloadUtils" in window)) {
       Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -48,36 +48,40 @@
 #include "nsIJSNativeInitializer.h"
 #include "nsIMutable.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsIXMLHttpRequest.h"
 #include "prmem.h"
 #include "nsAutoPtr.h"
+#include "mozilla/dom/indexedDB/FileInfo.h"
+#include "mozilla/dom/indexedDB/FileManager.h"
+#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 
 #include "mozilla/GuardObjects.h"
 
 #ifndef PR_UINT64_MAX
 #define PR_UINT64_MAX (~(PRUint64)(0))
 #endif
 
 class nsIFile;
 class nsIInputStream;
 class nsIClassInfo;
 class nsIBlobBuilder;
 
 nsresult NS_NewBlobBuilder(nsISupports* *aSupports);
 
+using namespace mozilla::dom;
+
 class nsDOMFileBase : public nsIDOMFile,
                       public nsIXHRSendable,
                       public nsIMutable
 {
 public:
-
   nsDOMFileBase(const nsAString& aName, const nsAString& aContentType,
                 PRUint64 aLength)
     : mIsFile(true), mImmutable(false), mContentType(aContentType),
       mName(aName), mStart(0), mLength(aLength)
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
@@ -114,54 +118,93 @@ public:
   NS_DECL_NSIMUTABLE
 
 protected:
   bool IsSizeUnknown()
   {
     return mLength == PR_UINT64_MAX;
   }
 
+  virtual bool IsStoredFile()
+  {
+    return false;
+  }
+
+  virtual bool IsWholeFile()
+  {
+    NS_NOTREACHED("Should only be called on dom blobs backed by files!");
+    return false;
+  }
+
+  indexedDB::FileInfo*
+  GetFileInfoInternal(indexedDB::FileManager* aFileManager,
+                      PRUint32 aStartIndex);
+
   bool mIsFile;
   bool mImmutable;
   nsString mContentType;
   nsString mName;
 
   PRUint64 mStart;
   PRUint64 mLength;
+
+  // Protected by IndexedDatabaseManager::FileMutex()
+  nsTArray<nsRefPtr<indexedDB::FileInfo> > mFileInfos;
 };
 
 class nsDOMFileFile : public nsDOMFileBase,
                       public nsIJSNativeInitializer
 {
 public:
   // Create as a file
   nsDOMFileFile(nsIFile *aFile)
     : nsDOMFileBase(EmptyString(), EmptyString(), PR_UINT64_MAX),
-      mFile(aFile), mWholeFile(true)
+      mFile(aFile), mWholeFile(true), mStoredFile(false)
   {
     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)
     : nsDOMFileBase(aContentType, PR_UINT64_MAX),
-      mFile(aFile), mWholeFile(true),
+      mFile(aFile), mWholeFile(true), mStoredFile(false),
       mCacheToken(aCacheToken)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
+  // Create as a stored file
+  nsDOMFileFile(const nsAString& aName, const nsAString& aContentType,
+                PRUint64 aLength, nsIFile* aFile,
+                indexedDB::FileInfo* aFileInfo)
+    : nsDOMFileBase(aName, aContentType, aLength),
+      mFile(aFile), mWholeFile(true), mStoredFile(true)
+  {
+    NS_ASSERTION(mFile, "must have file");
+    mFileInfos.AppendElement(aFileInfo);
+  }
+
+  // Create as a stored blob
+  nsDOMFileFile(const nsAString& aContentType, PRUint64 aLength,
+                nsIFile* aFile, indexedDB::FileInfo* aFileInfo)
+    : nsDOMFileBase(aContentType, aLength),
+      mFile(aFile), mWholeFile(true), mStoredFile(true)
+  {
+    NS_ASSERTION(mFile, "must have file");
+    mFileInfos.AppendElement(aFileInfo);
+  }
+
   // Create as a file to be later initialized
   nsDOMFileFile()
     : nsDOMFileBase(EmptyString(), EmptyString(), PR_UINT64_MAX),
-      mWholeFile(true)
+      mWholeFile(true), mStoredFile(false)
   {
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mName.SetIsVoid(true);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
@@ -183,27 +226,57 @@ public:
   NewFile(nsISupports* *aNewObject);
 
 protected:
   // Create slice
   nsDOMFileFile(const nsDOMFileFile* aOther, PRUint64 aStart, PRUint64 aLength,
                 const nsAString& aContentType)
     : nsDOMFileBase(aContentType, aOther->mStart + aStart, aLength),
       mFile(aOther->mFile), mWholeFile(false),
-      mCacheToken(aOther->mCacheToken)
+      mStoredFile(aOther->mStoredFile), mCacheToken(aOther->mCacheToken)
   {
     NS_ASSERTION(mFile, "must have file");
     mImmutable = aOther->mImmutable;
+
+    if (mStoredFile) {
+      indexedDB::FileInfo* fileInfo;
+
+      if (!indexedDB::IndexedDatabaseManager::IsClosed()) {
+        indexedDB::IndexedDatabaseManager::FileMutex().Lock();
+      }
+
+      NS_ASSERTION(!aOther->mFileInfos.IsEmpty(),
+                   "A stored file must have at least one file info!");
+
+      fileInfo = aOther->mFileInfos.ElementAt(0);
+
+      if (!indexedDB::IndexedDatabaseManager::IsClosed()) {
+        indexedDB::IndexedDatabaseManager::FileMutex().Unlock();
+      }
+
+      mFileInfos.AppendElement(fileInfo);
+    }
   }
   virtual already_AddRefed<nsIDOMBlob>
   CreateSlice(PRUint64 aStart, PRUint64 aLength,
               const nsAString& aContentType);
 
+  virtual bool IsStoredFile()
+  {
+    return mStoredFile;
+  }
+
+  virtual bool IsWholeFile()
+  {
+    return mWholeFile;
+  }
+
   nsCOMPtr<nsIFile> mFile;
   bool mWholeFile;
+  bool mStoredFile;
   nsCOMPtr<nsISupports> mCacheToken;
 };
 
 class nsDOMMemoryFile : public nsDOMFileBase
 {
 public:
   // Create as file
   nsDOMMemoryFile(void *aMemoryBuffer,
--- a/content/base/public/nsIDOMFile.idl
+++ b/content/base/public/nsIDOMFile.idl
@@ -34,38 +34,63 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
 %{C++
 #include "jsapi.h"
+
+namespace mozilla {
+namespace dom {
+namespace indexedDB {
+class FileInfo;
+class FileManager;
+}
+}
+}
+
 %}
 
+[ptr] native FileInfo(mozilla::dom::indexedDB::FileInfo);
+[ptr] native FileManager(mozilla::dom::indexedDB::FileManager);
+
 interface nsIDOMFileError;
 interface nsIInputStream;
 interface nsIURI;
 interface nsIPrincipal;
 interface nsIDOMBlob;
 
-[scriptable, builtinclass, uuid(d5237f31-443a-460b-9e42-449a135346f0)]
+[scriptable, builtinclass, uuid(f62c6887-e3bc-495a-802c-287e12e969a0)]
 interface nsIDOMBlob : nsISupports
 {
   readonly attribute unsigned long long size;
   readonly attribute DOMString type;
 
   [noscript] readonly attribute nsIInputStream internalStream;
   // The caller is responsible for releasing the internalUrl from the
   // moz-filedata: protocol handler
   [noscript] DOMString getInternalUrl(in nsIPrincipal principal);
 
   [optional_argc] nsIDOMBlob mozSlice([optional] in long long start,
                                       [optional] in long long end,
                                       [optional] in DOMString contentType);
+
+  // Get internal id of stored file. Returns -1 if it is not a stored file.
+  // Intended only for testing. It can be called on any thread.
+  [notxpcom] long long getFileId();
+
+  // Called when the blob was successfully stored in a database or when
+  // the blob is initialized from a database. It can be called on any thread.
+  [notxpcom] void addFileInfo(in FileInfo aFileInfo);
+
+  // Called before the blob is stored in a database to decide if it can be
+  // shared or needs to be copied. It can be called on any thread.
+  [notxpcom] FileInfo getFileInfo(in FileManager aFileManager);
 };
 
 [scriptable, builtinclass, uuid(b096ef67-7b77-47f8-8e70-5d8ee36416bf)]
 interface nsIDOMFile : nsIDOMBlob
 {
   readonly attribute DOMString name;
   readonly attribute DOMString mozFullPath;
 
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -293,16 +293,86 @@ nsDOMFileBase::GetInternalUrl(nsIPrincip
   nsFileDataProtocolHandler::AddFileDataEntry(url, this,
                                               aPrincipal);
 
   CopyASCIItoUTF16(url, aURL);
   
   return NS_OK;
 }
 
+NS_IMETHODIMP_(PRInt64)
+nsDOMFileBase::GetFileId()
+{
+  PRInt64 id = -1;
+
+  if (IsStoredFile() && IsWholeFile()) {
+    if (!indexedDB::IndexedDatabaseManager::IsClosed()) {
+      indexedDB::IndexedDatabaseManager::FileMutex().Lock();
+    }
+
+    NS_ASSERTION(!mFileInfos.IsEmpty(),
+                 "A stored file must have at least one file info!");
+
+    nsRefPtr<indexedDB::FileInfo>& fileInfo = mFileInfos.ElementAt(0);
+    if (fileInfo) {
+      id =  fileInfo->Id();
+    }
+
+    if (!indexedDB::IndexedDatabaseManager::IsClosed()) {
+      indexedDB::IndexedDatabaseManager::FileMutex().Unlock();
+    }
+  }
+
+  return id;
+}
+
+NS_IMETHODIMP_(void)
+nsDOMFileBase::AddFileInfo(indexedDB::FileInfo* aFileInfo)
+{
+  if (indexedDB::IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return;
+  }
+
+  nsRefPtr<indexedDB::FileInfo> fileInfo = aFileInfo;
+
+  MutexAutoLock lock(indexedDB::IndexedDatabaseManager::FileMutex());
+
+  NS_ASSERTION(!mFileInfos.Contains(aFileInfo),
+               "Adding the same file info agan?!");
+
+  nsRefPtr<indexedDB::FileInfo>* element = mFileInfos.AppendElement();
+  element->swap(fileInfo);
+}
+
+NS_IMETHODIMP_(indexedDB::FileInfo*)
+nsDOMFileBase::GetFileInfo(indexedDB::FileManager* aFileManager)
+{
+  if (indexedDB::IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return nsnull;
+  }
+
+  // A slice created from a stored file must keep the file info alive.
+  // However, we don't support sharing of slices yet, so the slice must be
+  // copied again. That's why we have to ignore the first file info.
+  PRUint32 startIndex = IsStoredFile() && !IsWholeFile() ? 1 : 0;
+
+  MutexAutoLock lock(indexedDB::IndexedDatabaseManager::FileMutex());
+
+  for (PRUint32 i = startIndex; i < mFileInfos.Length(); i++) {
+    nsRefPtr<indexedDB::FileInfo>& fileInfo = mFileInfos.ElementAt(i);
+    if (fileInfo->Manager() == aFileManager) {
+      return fileInfo;
+    }
+  }
+
+  return nsnull;
+}
+
 NS_IMETHODIMP
 nsDOMFileBase::GetSendInfo(nsIInputStream** aBody,
                            nsACString& aContentType,
                            nsACString& aCharset)
 {
   nsresult rv;
 
   nsCOMPtr<nsIInputStream> stream;
--- a/db/sqlite3/README.MOZILLA
+++ b/db/sqlite3/README.MOZILLA
@@ -6,17 +6,17 @@ See http://www.sqlite.org/ for more info
 
 We have a mozilla-specific Makefile.in in src/ (normally no
 Makefile.in there) that we use to build.
 
 To move to a new version:
 
 Simply copy the sqlite3.h and sqlite3.c files from the amalgamation of sqlite.
 
-Also copy test_quota.c from the full source package.
+Also copy test_quota.h and test_quota.c from the full source package.
 
 Be sure to update SQLITE_VERSION accordingly in $(topsrcdir)/configure.in as
 well as the version number at the top of this file.
 
 -- Paul O’Shannessy <paul@oshannessy.com>, 01/2011
 
 We are using an experimental quota management feature included in test_quota.c.
 This file is not compiled into mozsqlite, but instead included directly into
--- a/db/sqlite3/src/sqlite.def
+++ b/db/sqlite3/src/sqlite.def
@@ -176,12 +176,13 @@ EXPORTS
         sqlite3_value_text16be
         sqlite3_value_text16le
         sqlite3_value_type
         sqlite3_version
         sqlite3_vfs_find
         sqlite3_vfs_unregister
         sqlite3_vfs_register
         sqlite3_vmprintf
+        sqlite3_win32_utf8_to_mbcs
 #ifdef SQLITE_DEBUG
         sqlite3_mutex_held
         sqlite3_mutex_notheld
 #endif
--- a/db/sqlite3/src/test_quota.c
+++ b/db/sqlite3/src/test_quota.c
@@ -22,17 +22,17 @@
 **
 ** However, before returning SQLITE_FULL, the write requests invoke
 ** a callback function that is configurable for each quota group.
 ** This callback has the opportunity to enlarge the quota.  If the
 ** callback does enlarge the quota such that the total size of all
 ** files within the group is less than the new quota, then the write
 ** continues as if nothing had happened.
 */
-#include "sqlite3.h"
+#include "test_quota.h"
 #include <string.h>
 #include <assert.h>
 
 /*
 ** For an build without mutexes, no-op the mutex calls.
 */
 #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
 #define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
@@ -40,30 +40,16 @@
 #define sqlite3_mutex_enter(X)
 #define sqlite3_mutex_try(X)      SQLITE_OK
 #define sqlite3_mutex_leave(X)
 #define sqlite3_mutex_held(X)     ((void)(X),1)
 #define sqlite3_mutex_notheld(X)  ((void)(X),1)
 #endif /* SQLITE_THREADSAFE==0 */
 
 
-/*
-** For an build without mutexes, no-op the mutex calls.
-*/
-#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
-#define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
-#define sqlite3_mutex_free(X)
-#define sqlite3_mutex_enter(X)
-#define sqlite3_mutex_try(X)      SQLITE_OK
-#define sqlite3_mutex_leave(X)
-#define sqlite3_mutex_held(X)     ((void)(X),1)
-#define sqlite3_mutex_notheld(X)  ((void)(X),1)
-#endif /* SQLITE_THREADSAFE==0 */
-
-
 /************************ Object Definitions ******************************/
 
 /* Forward declaration of all object types */
 typedef struct quotaGroup quotaGroup;
 typedef struct quotaConn quotaConn;
 typedef struct quotaFile quotaFile;
 
 /*
@@ -120,16 +106,28 @@ struct quotaFile {
 ** VFS is appended to this structure.
 */
 struct quotaConn {
   sqlite3_file base;              /* Base class - must be first */
   quotaFile *pFile;               /* The underlying file */
   /* The underlying VFS sqlite3_file is appended to this object */
 };
 
+/*
+** An instance of the following object records the state of an
+** open file.  This object is opaque to all users - the internal
+** structure is only visible to the functions below.
+*/
+struct quota_FILE {
+  FILE *f;                /* Open stdio file pointer */
+  sqlite3_int64 iOfst;    /* Current offset into the file */
+  quotaFile *pFile;       /* The file record in the quota system */
+};
+
+
 /************************* Global Variables **********************************/
 /*
 ** All global variables used by this file are containing within the following
 ** gQuota structure.
 */
 static struct {
   /* The pOrigVfs is the real, original underlying VFS implementation.
   ** Most operations pass-through to the real VFS.  This value is read-only
@@ -234,37 +232,40 @@ static void quotaGroupDeref(quotaGroup *
 **
 **      '?'       Matches exactly one character.
 **
 **     [...]      Matches one character from the enclosed list of
 **                characters.
 **
 **     [^...]     Matches one character not in the enclosed list.
 **
+**     /          Matches "/" or "\\"
+**
 */
 static int quotaStrglob(const char *zGlob, const char *z){
-  int c, c2;
+  int c, c2, cx;
   int invert;
   int seen;
 
   while( (c = (*(zGlob++)))!=0 ){
     if( c=='*' ){
       while( (c=(*(zGlob++))) == '*' || c=='?' ){
         if( c=='?' && (*(z++))==0 ) return 0;
       }
       if( c==0 ){
         return 1;
       }else if( c=='[' ){
         while( *z && quotaStrglob(zGlob-1,z)==0 ){
           z++;
         }
         return (*z)!=0;
       }
+      cx = (c=='/') ? '\\' : c;
       while( (c2 = (*(z++)))!=0 ){
-        while( c2!=c ){
+        while( c2!=c && c2!=cx ){
           c2 = *(z++);
           if( c2==0 ) return 0;
         }
         if( quotaStrglob(zGlob,z) ) return 1;
       }
       return 0;
     }else if( c=='?' ){
       if( (*(z++))==0 ) return 0;
@@ -292,16 +293,19 @@ static int quotaStrglob(const char *zGlo
           if( c==c2 ){
             seen = 1;
           }
           prior_c = c2;
         }
         c2 = *(zGlob++);
       }
       if( c2==0 || (seen ^ invert)==0 ) return 0;
+    }else if( c=='/' ){
+      if( z[0]!='/' && z[0]!='\\' ) return 0;
+      z++;
     }else{
       if( c!=(*(z++)) ) return 0;
     }
   }
   return *z==0;
 }
 
 
@@ -322,24 +326,141 @@ static quotaGroup *quotaGroupFind(const 
 static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
   quotaConn *p = (quotaConn*)pConn;
   return (sqlite3_file*)&p[1];
 }
 
 /* Find a file in a quota group and return a pointer to that file.
 ** Return NULL if the file is not in the group.
 */
-static quotaFile *quotaFindFile(quotaGroup *pGroup, const char *zName){
+static quotaFile *quotaFindFile(
+  quotaGroup *pGroup,     /* Group in which to look for the file */
+  const char *zName,      /* Full pathname of the file */
+  int createFlag          /* Try to create the file if not found */
+){
   quotaFile *pFile = pGroup->pFiles;
   while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
     pFile = pFile->pNext;
   }
+  if( pFile==0 && createFlag ){
+    int nName = strlen(zName);
+    pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
+    if( pFile ){
+      memset(pFile, 0, sizeof(*pFile));
+      pFile->zFilename = (char*)&pFile[1];
+      memcpy(pFile->zFilename, zName, nName+1);
+      pFile->pNext = pGroup->pFiles;
+      if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
+      pFile->ppPrev = &pGroup->pFiles;
+      pGroup->pFiles = pFile;
+      pFile->pGroup = pGroup;
+    }
+  }
   return pFile;
 }
 
+/*
+** Figure out if we are dealing with Unix, Windows, or some other
+** operating system.  After the following block of preprocess macros,
+** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER 
+** will defined to either 1 or 0.  One of the four will be 1.  The other 
+** three will be 0.
+*/
+#if defined(SQLITE_OS_OTHER)
+# if SQLITE_OS_OTHER==1
+#   undef SQLITE_OS_UNIX
+#   define SQLITE_OS_UNIX 0
+#   undef SQLITE_OS_WIN
+#   define SQLITE_OS_WIN 0
+#   undef SQLITE_OS_OS2
+#   define SQLITE_OS_OS2 0
+# else
+#   undef SQLITE_OS_OTHER
+# endif
+#endif
+#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER)
+# define SQLITE_OS_OTHER 0
+# ifndef SQLITE_OS_WIN
+#   if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \
+                       || defined(__MINGW32__) || defined(__BORLANDC__)
+#     define SQLITE_OS_WIN 1
+#     define SQLITE_OS_UNIX 0
+#     define SQLITE_OS_OS2 0
+#   elif defined(__EMX__) || defined(_OS2) || defined(OS2) \
+                          || defined(_OS2_) || defined(__OS2__)
+#     define SQLITE_OS_WIN 0
+#     define SQLITE_OS_UNIX 0
+#     define SQLITE_OS_OS2 1
+#   else
+#     define SQLITE_OS_WIN 0
+#     define SQLITE_OS_UNIX 1
+#     define SQLITE_OS_OS2 0
+#  endif
+# else
+#  define SQLITE_OS_UNIX 0
+#  define SQLITE_OS_OS2 0
+# endif
+#else
+# ifndef SQLITE_OS_WIN
+#  define SQLITE_OS_WIN 0
+# endif
+#endif
+
+#if SQLITE_OS_UNIX
+# include <unistd.h>
+#endif
+#if SQLITE_OS_WIN
+# include <windows.h>
+# include <io.h>
+#endif
+
+/*
+** Translate UTF8 to MBCS for use in fopen() calls.  Return a pointer to the
+** translated text..  Call quota_mbcs_free() to deallocate any memory
+** used to store the returned pointer when done.
+*/
+static char *quota_utf8_to_mbcs(const char *zUtf8){
+#if SQLITE_OS_WIN
+  int n;             /* Bytes in zUtf8 */
+  int nWide;         /* number of UTF-16 characters */
+  int nMbcs;         /* Bytes of MBCS */
+  LPWSTR zTmpWide;   /* The UTF16 text */
+  char *zMbcs;       /* The MBCS text */
+  int codepage;      /* Code page used by fopen() */
+
+  n = strlen(zUtf8);
+  nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0);
+  if( nWide==0 ) return 0;
+  zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) );
+  if( zTmpWide==0 ) return 0;
+  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide);
+  codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
+  nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0);
+  zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0;
+  if( zMbcs ){
+    WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0);
+  }
+  sqlite3_free(zTmpWide);
+  return zMbcs;
+#else
+  return (char*)zUtf8;  /* No-op on unix */
+#endif  
+}
+
+/*
+** Deallocate any memory allocated by quota_utf8_to_mbcs().
+*/
+static void quota_mbcs_free(char *zOld){
+#if SQLITE_OS_WIN
+  sqlite3_free(zOld);
+#else
+  /* No-op on unix */
+#endif  
+}
+
 /************************* VFS Method Wrappers *****************************/
 /*
 ** This is the xOpen method used for the "quota" VFS.
 **
 ** Most of the work is done by the underlying original VFS.  This method
 ** simply links the new file into the appropriate quota group if it is a
 ** file that needs to be tracked.
 */
@@ -373,35 +494,23 @@ static int quotaOpen(
     rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
   }else{
     /* If we get to this point, it means the file needs to be quota tracked.
     */
     pQuotaOpen = (quotaConn*)pConn;
     pSubOpen = quotaSubOpen(pConn);
     rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
     if( rc==SQLITE_OK ){
-      pFile = quotaFindFile(pGroup, zName);
+      pFile = quotaFindFile(pGroup, zName, 1);
       if( pFile==0 ){
-        int nName = strlen(zName);
-        pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
-        if( pFile==0 ){
-          quotaLeave();
-          pSubOpen->pMethods->xClose(pSubOpen);
-          return SQLITE_NOMEM;
-        }
-        memset(pFile, 0, sizeof(*pFile));
-        pFile->zFilename = (char*)&pFile[1];
-        memcpy(pFile->zFilename, zName, nName+1);
-        pFile->pNext = pGroup->pFiles;
-        if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
-        pFile->ppPrev = &pGroup->pFiles;
-        pGroup->pFiles = pFile;
-        pFile->pGroup = pGroup;
-        pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
+        quotaLeave();
+        pSubOpen->pMethods->xClose(pSubOpen);
+        return SQLITE_NOMEM;
       }
+      pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
       pFile->nRef++;
       pQuotaOpen->pFile = pFile;
       if( pSubOpen->pMethods->iVersion==1 ){
         pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
       }else{
         pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
       }
     }
@@ -432,17 +541,17 @@ static int quotaDelete(
 
   /* If the file just deleted is a member of a quota group, then remove
   ** it from that quota group.
   */
   if( rc==SQLITE_OK ){
     quotaEnter();
     pGroup = quotaGroupFind(zName);
     if( pGroup ){
-      pFile = quotaFindFile(pGroup, zName);
+      pFile = quotaFindFile(pGroup, zName, 0);
       if( pFile ){
         if( pFile->nRef ){
           pFile->deleteOnClose = 1;
         }else{
           quotaRemoveFile(pFile);
           quotaGroupDeref(pGroup);
         }
       }
@@ -464,17 +573,20 @@ static int quotaClose(sqlite3_file *pCon
   quotaFile *pFile = p->pFile;
   sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   int rc;
   rc = pSubOpen->pMethods->xClose(pSubOpen);
   quotaEnter();
   pFile->nRef--;
   if( pFile->nRef==0 ){
     quotaGroup *pGroup = pFile->pGroup;
-    if( pFile->deleteOnClose ) quotaRemoveFile(pFile);
+    if( pFile->deleteOnClose ){
+      gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+      quotaRemoveFile(pFile);
+    }
     quotaGroupDeref(pGroup);
   }
   quotaLeave();
   return rc;
 }
 
 /* Pass xRead requests directory thru to the original VFS without
 ** further processing.
@@ -598,17 +710,23 @@ static int quotaCheckReservedLock(sqlite
   sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
 }
 
 /* Pass xFileControl requests through to the original VFS unchanged.
 */
 static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
   sqlite3_file *pSubOpen = quotaSubOpen(pConn);
-  return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
+  int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
+#if defined(SQLITE_FCNTL_VFSNAME)
+  if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
+    *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg);
+  }
+#endif
+  return rc;
 }
 
 /* Pass xSectorSize requests through to the original VFS unchanged.
 */
 static int quotaSectorSize(sqlite3_file *pConn){
   sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   return pSubOpen->pMethods->xSectorSize(pSubOpen);
 }
@@ -814,18 +932,18 @@ int sqlite3_quota_set(
 ** management, update its size.
 */
 int sqlite3_quota_file(const char *zFilename){
   char *zFull;
   sqlite3_file *fd;
   int rc;
   int outFlags = 0;
   sqlite3_int64 iSize;
-  fd = (sqlite3_file*)
-    sqlite3_malloc(gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+1);
+  fd = (sqlite3_file*)sqlite3_malloc(gQuota.sThisVfs.szOsFile +
+                                     gQuota.sThisVfs.mxPathname+1);
   if( fd==0 ) return SQLITE_NOMEM;
   zFull = gQuota.sThisVfs.szOsFile + (char*)fd;
   rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
                                       gQuota.sThisVfs.mxPathname+1, zFull);
   if( rc==SQLITE_OK ){
     rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 
                    SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
   }
@@ -833,25 +951,239 @@ int sqlite3_quota_file(const char *zFile
     fd->pMethods->xFileSize(fd, &iSize);
     fd->pMethods->xClose(fd);
   }else if( rc==SQLITE_CANTOPEN ){
     quotaGroup *pGroup;
     quotaFile *pFile;
     quotaEnter();
     pGroup = quotaGroupFind(zFull);
     if( pGroup ){
-      pFile = quotaFindFile(pGroup, zFull);
+      pFile = quotaFindFile(pGroup, zFull, 0);
       if( pFile ) quotaRemoveFile(pFile);
     }
     quotaLeave();
   }
   sqlite3_free(fd);
   return rc;
 }
 
+/*
+** Open a potentially quotaed file for I/O.
+*/
+quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){
+  quota_FILE *p = 0;
+  char *zFull = 0;
+  char *zFullTranslated;
+  int rc;
+  quotaGroup *pGroup;
+  quotaFile *pFile;
+
+  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
+  if( zFull==0 ) return 0;
+  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+                                      gQuota.sThisVfs.mxPathname+1, zFull);
+  if( rc ) goto quota_fopen_error;
+  p = (quota_FILE*)sqlite3_malloc(sizeof(*p));
+  if( p==0 ) goto quota_fopen_error;
+  memset(p, 0, sizeof(*p));
+  zFullTranslated = quota_utf8_to_mbcs(zFull);
+  if( zFullTranslated==0 ) goto quota_fopen_error;
+  p->f = fopen(zFullTranslated, zMode);
+  quota_mbcs_free(zFullTranslated);
+  if( p->f==0 ) goto quota_fopen_error;
+  quotaEnter();
+  pGroup = quotaGroupFind(zFull);
+  if( pGroup ){
+    pFile = quotaFindFile(pGroup, zFull, 1);
+    if( pFile==0 ){
+      quotaLeave();
+      goto quota_fopen_error;
+    }
+    pFile->nRef++;
+    p->pFile = pFile;
+  }
+  quotaLeave();
+  sqlite3_free(zFull);
+  return p;
+
+quota_fopen_error:
+  sqlite3_free(zFull);
+  if( p && p->f ) fclose(p->f);
+  sqlite3_free(p);
+  return 0;
+}
+
+/*
+** Read content from a quota_FILE
+*/
+size_t sqlite3_quota_fread(
+  void *pBuf,            /* Store the content here */
+  size_t size,           /* Size of each element */
+  size_t nmemb,          /* Number of elements to read */
+  quota_FILE *p          /* Read from this quota_FILE object */
+){
+  return fread(pBuf, size, nmemb, p->f);
+}
+
+/*
+** Write content into a quota_FILE.  Invoke the quota callback and block
+** the write if we exceed quota.
+*/
+size_t sqlite3_quota_fwrite(
+  void *pBuf,            /* Take content to write from here */
+  size_t size,           /* Size of each element */
+  size_t nmemb,          /* Number of elements */
+  quota_FILE *p          /* Write to this quota_FILE objecct */
+){
+  sqlite3_int64 iOfst;
+  sqlite3_int64 iEnd;
+  sqlite3_int64 szNew;
+  quotaFile *pFile;
+  
+  iOfst = ftell(p->f);
+  iEnd = iOfst + size*nmemb;
+  pFile = p->pFile;
+  if( pFile && pFile->iSize<iEnd ){
+    quotaGroup *pGroup = pFile->pGroup;
+    quotaEnter();
+    szNew = pGroup->iSize - pFile->iSize + iEnd;
+    if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+      if( pGroup->xCallback ){
+        pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 
+                          pGroup->pArg);
+      }
+      if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+        iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize;
+        nmemb = (iEnd - iOfst)/size;
+        iEnd = iOfst + size*nmemb;
+        szNew = pGroup->iSize - pFile->iSize + iEnd;
+      }
+    }
+    pGroup->iSize = szNew;
+    pFile->iSize = iEnd;
+    quotaLeave();
+  }
+  return fwrite(pBuf, size, nmemb, p->f);
+}
+
+/*
+** Close an open quota_FILE stream.
+*/
+int sqlite3_quota_fclose(quota_FILE *p){
+  int rc;
+  quotaFile *pFile;
+  rc = fclose(p->f);
+  pFile = p->pFile;
+  if( pFile ){
+    quotaEnter();
+    pFile->nRef--;
+    if( pFile->nRef==0 ){
+      quotaGroup *pGroup = pFile->pGroup;
+      if( pFile->deleteOnClose ){
+        gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+        quotaRemoveFile(pFile);
+      }
+      quotaGroupDeref(pGroup);
+    }
+    quotaLeave();
+  }
+  sqlite3_free(p);
+  return rc;
+}
+
+/*
+** Flush memory buffers for a quota_FILE to disk.
+*/
+int sqlite3_quota_fflush(quota_FILE *p, int doFsync){
+  int rc;
+  rc = fflush(p->f);
+  if( rc==0 && doFsync ){
+#if SQLITE_OS_UNIX
+    rc = fsync(fileno(p->f));
+#endif
+#if SQLITE_OS_WIN
+    rc = _commit(_fileno(p->f));
+#endif
+  }
+  return rc!=0;
+}
+
+/*
+** Seek on a quota_FILE stream.
+*/
+int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){
+  return fseek(p->f, offset, whence);
+}
+
+/*
+** rewind a quota_FILE stream.
+*/
+void sqlite3_quota_rewind(quota_FILE *p){
+  rewind(p->f);
+}
+
+/*
+** Tell the current location of a quota_FILE stream.
+*/
+long sqlite3_quota_ftell(quota_FILE *p){
+  return ftell(p->f);
+}
+
+/*
+** Remove a managed file.  Update quotas accordingly.
+*/
+int sqlite3_quota_remove(const char *zFilename){
+  char *zFull;            /* Full pathname for zFilename */
+  int nFull;              /* Number of bytes in zFilename */
+  int rc;                 /* Result code */
+  quotaGroup *pGroup;     /* Group containing zFilename */
+  quotaFile *pFile;       /* A file in the group */
+  quotaFile *pNextFile;   /* next file in the group */
+  int diff;               /* Difference between filenames */
+  char c;                 /* First character past end of pattern */
+
+  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
+  if( zFull==0 ) return SQLITE_NOMEM;
+  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+                                      gQuota.sThisVfs.mxPathname+1, zFull);
+  if( rc ){
+    sqlite3_free(zFull);
+    return rc;
+  }
+
+  /* Figure out the length of the full pathname.  If the name ends with
+  ** / (or \ on windows) then remove the trailing /.
+  */
+  nFull = strlen(zFull);
+  if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){
+    nFull--;
+    zFull[nFull] = 0;
+  }
+
+  quotaEnter();
+  pGroup = quotaGroupFind(zFull);
+  if( pGroup ){
+    for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){
+      pNextFile = pFile->pNext;
+      diff = memcmp(zFull, pFile->zFilename, nFull);
+      if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){
+        if( pFile->nRef ){
+          pFile->deleteOnClose = 1;
+        }else{
+          rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+          quotaRemoveFile(pFile);
+          quotaGroupDeref(pGroup);
+        }
+      }
+    }
+  }
+  quotaLeave();
+  sqlite3_free(zFull);
+  return rc;
+}
   
 /***************************** Test Code ***********************************/
 #ifdef SQLITE_TEST
 #include <tcl.h>
 
 /*
 ** Argument passed to a TCL quota-over-limit callback.
 */
@@ -1070,49 +1402,329 @@ static int test_quota_dump(
     pGroupTerm = Tcl_NewObj();
     Tcl_ListObjAppendElement(interp, pGroupTerm,
           Tcl_NewStringObj(pGroup->zPattern, -1));
     Tcl_ListObjAppendElement(interp, pGroupTerm,
           Tcl_NewWideIntObj(pGroup->iLimit));
     Tcl_ListObjAppendElement(interp, pGroupTerm,
           Tcl_NewWideIntObj(pGroup->iSize));
     for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
+      int i;
+      char zTemp[1000];
       pFileTerm = Tcl_NewObj();
+      sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename);
+      for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; }
       Tcl_ListObjAppendElement(interp, pFileTerm,
-            Tcl_NewStringObj(pFile->zFilename, -1));
+            Tcl_NewStringObj(zTemp, -1));
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewWideIntObj(pFile->iSize));
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewWideIntObj(pFile->nRef));
       Tcl_ListObjAppendElement(interp, pFileTerm,
             Tcl_NewWideIntObj(pFile->deleteOnClose));
       Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
     }
     Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
   }
   quotaLeave();
   Tcl_SetObjResult(interp, pResult);
   return TCL_OK;
 }
 
 /*
+** tclcmd: sqlite3_quota_fopen FILENAME MODE
+*/
+static int test_quota_fopen(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  const char *zMode;              /* Mode string */
+  quota_FILE *p;                  /* Open string object */
+  char zReturn[50];               /* Name of pointer to return */
+
+  /* Process arguments */
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+  zMode = Tcl_GetString(objv[2]);
+  p = sqlite3_quota_fopen(zFilename, zMode);
+  sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p);
+  Tcl_SetResult(interp, zReturn, TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/* Defined in test1.c */
+extern void *sqlite3TestTextToPtr(const char*);
+
+/*
+** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM
+*/
+static int test_quota_fread(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  char *zBuf;
+  int sz;
+  int nElem;
+  int got;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
+  zBuf = (char*)sqlite3_malloc( sz*nElem + 1 );
+  if( zBuf==0 ){
+    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
+    return TCL_ERROR;
+  }
+  got = sqlite3_quota_fread(zBuf, sz, nElem, p);
+  if( got<0 ) got = 0;
+  zBuf[got*sz] = 0;
+  Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
+  sqlite3_free(zBuf);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT
+*/
+static int test_quota_fwrite(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  char *zBuf;
+  int sz;
+  int nElem;
+  int got;
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
+  zBuf = Tcl_GetString(objv[4]);
+  got = sqlite3_quota_fwrite(zBuf, sz, nElem, p);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(got));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fclose HANDLE
+*/
+static int test_quota_fclose(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  rc = sqlite3_quota_fclose(p);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC?
+*/
+static int test_quota_fflush(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int rc;
+  int doSync = 0;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( objc==3 ){
+    if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR;
+  }
+  rc = sqlite3_quota_fflush(p, doSync);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE
+*/
+static int test_quota_fseek(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int ofst;
+  const char *zWhence;
+  int whence;
+  int rc;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR;
+  zWhence = Tcl_GetString(objv[3]);
+  if( strcmp(zWhence, "SEEK_SET")==0 ){
+    whence = SEEK_SET;
+  }else if( strcmp(zWhence, "SEEK_CUR")==0 ){
+    whence = SEEK_CUR;
+  }else if( strcmp(zWhence, "SEEK_END")==0 ){
+    whence = SEEK_END;
+  }else{
+    Tcl_AppendResult(interp,
+           "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0);
+    return TCL_ERROR;
+  }
+  rc = sqlite3_quota_fseek(p, ofst, whence);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_rewind HANDLE
+*/
+static int test_quota_rewind(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  sqlite3_quota_rewind(p);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_ftell HANDLE
+*/
+static int test_quota_ftell(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_ftell(p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_remove FILENAME
+*/
+static int test_quota_remove(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  int rc;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+  rc = sqlite3_quota_remove(zFilename);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_glob PATTERN TEXT
+**
+** Test the glob pattern matching.  Return 1 if TEXT matches PATTERN
+** and return 0 if it does not.
+*/
+static int test_quota_glob(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zPattern;          /* The glob pattern */
+  const char *zText;             /* Text to compare agains the pattern */
+  int rc;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT");
+    return TCL_ERROR;
+  }
+  zPattern = Tcl_GetString(objv[1]);
+  zText = Tcl_GetString(objv[2]);
+  rc = quotaStrglob(zPattern, zText);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
 ** This routine registers the custom TCL commands defined in this
 ** module.  This should be the only procedure visible from outside
 ** of this module.
 */
 int Sqlitequota_Init(Tcl_Interp *interp){
   static struct {
      char *zName;
      Tcl_ObjCmdProc *xProc;
   } aCmd[] = {
     { "sqlite3_quota_initialize", test_quota_initialize },
-    { "sqlite3_quota_shutdown", test_quota_shutdown },
-    { "sqlite3_quota_set", test_quota_set },
-    { "sqlite3_quota_file", test_quota_file },
-    { "sqlite3_quota_dump", test_quota_dump },
+    { "sqlite3_quota_shutdown",   test_quota_shutdown },
+    { "sqlite3_quota_set",        test_quota_set },
+    { "sqlite3_quota_file",       test_quota_file },
+    { "sqlite3_quota_dump",       test_quota_dump },
+    { "sqlite3_quota_fopen",      test_quota_fopen },
+    { "sqlite3_quota_fread",      test_quota_fread },
+    { "sqlite3_quota_fwrite",     test_quota_fwrite },
+    { "sqlite3_quota_fclose",     test_quota_fclose },
+    { "sqlite3_quota_fflush",     test_quota_fflush },
+    { "sqlite3_quota_fseek",      test_quota_fseek },
+    { "sqlite3_quota_rewind",     test_quota_rewind },
+    { "sqlite3_quota_ftell",      test_quota_ftell },
+    { "sqlite3_quota_remove",     test_quota_remove },
+    { "sqlite3_quota_glob",       test_quota_glob },
   };
   int i;
 
   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
   }
 
   return TCL_OK;
new file mode 100644
--- /dev/null
+++ b/db/sqlite3/src/test_quota.h
@@ -0,0 +1,209 @@
+/*
+** 2011 December 1
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains the interface definition for the quota a VFS shim.
+**
+** This particular shim enforces a quota system on files.  One or more
+** database files are in a "quota group" that is defined by a GLOB
+** pattern.  A quota is set for the combined size of all files in the
+** the group.  A quota of zero means "no limit".  If the total size
+** of all files in the quota group is greater than the limit, then
+** write requests that attempt to enlarge a file fail with SQLITE_FULL.
+**
+** However, before returning SQLITE_FULL, the write requests invoke
+** a callback function that is configurable for each quota group.
+** This callback has the opportunity to enlarge the quota.  If the
+** callback does enlarge the quota such that the total size of all
+** files within the group is less than the new quota, then the write
+** continues as if nothing had happened.
+*/
+#ifndef _QUOTA_H_
+#include "sqlite3.h"
+#include <stdio.h>
+
+/* Make this callable from C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** Initialize the quota VFS shim.  Use the VFS named zOrigVfsName
+** as the VFS that does the actual work.  Use the default if
+** zOrigVfsName==NULL.  
+**
+** The quota VFS shim is named "quota".  It will become the default
+** VFS if makeDefault is non-zero.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
+** during start-up.
+*/
+int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault);
+
+/*
+** Shutdown the quota system.
+**
+** All SQLite database connections must be closed before calling this
+** routine.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
+** shutting down in order to free all remaining quota groups.
+*/
+int sqlite3_quota_shutdown(void);
+
+/*
+** Create or destroy a quota group.
+**
+** The quota group is defined by the zPattern.  When calling this routine
+** with a zPattern for a quota group that already exists, this routine
+** merely updates the iLimit, xCallback, and pArg values for that quota
+** group.  If zPattern is new, then a new quota group is created.
+**
+** The zPattern is always compared against the full pathname of the file.
+** Even if APIs are called with relative pathnames, SQLite converts the
+** name to a full pathname before comparing it against zPattern.  zPattern
+** is a glob pattern with the following matching rules:
+**
+**      '*'       Matches any sequence of zero or more characters.
+**
+**      '?'       Matches exactly one character.
+**
+**     [...]      Matches one character from the enclosed list of
+**                characters.  "]" can be part of the list if it is
+**                the first character.  Within the list "X-Y" matches
+**                characters X or Y or any character in between the
+**                two.  Ex:  "[0-9]" matches any digit.
+**
+**     [^...]     Matches one character not in the enclosed list.
+**
+**     /          Matches either / or \.  This allows glob patterns
+**                containing / to work on both unix and windows.
+**
+** Note that, unlike unix shell globbing, the directory separator "/"
+** can match a wildcard.  So, for example, the pattern "/abc/xyz/" "*"
+** matches any files anywhere in the directory hierarchy beneath
+** /abc/xyz.
+**
+** The glob algorithm works on bytes.  Multi-byte UTF8 characters are
+** matched as if each byte were a separate character.
+**
+** If the iLimit for a quota group is set to zero, then the quota group
+** is disabled and will be deleted when the last database connection using
+** the quota group is closed.
+**
+** Calling this routine on a zPattern that does not exist and with a
+** zero iLimit is a no-op.
+**
+** A quota group must exist with a non-zero iLimit prior to opening
+** database connections if those connections are to participate in the
+** quota group.  Creating a quota group does not affect database connections
+** that are already open.
+**
+** The patterns that define the various quota groups should be distinct.
+** If the same filename matches more than one quota group pattern, then
+** the behavior of this package is undefined.
+*/
+int sqlite3_quota_set(
+  const char *zPattern,           /* The filename pattern */
+  sqlite3_int64 iLimit,           /* New quota to set for this quota group */
+  void (*xCallback)(              /* Callback invoked when going over quota */
+     const char *zFilename,         /* Name of file whose size increases */
+     sqlite3_int64 *piLimit,        /* IN/OUT: The current limit */
+     sqlite3_int64 iSize,           /* Total size of all files in the group */
+     void *pArg                     /* Client data */
+  ),
+  void *pArg,                     /* client data passed thru to callback */
+  void (*xDestroy)(void*)         /* Optional destructor for pArg */
+);
+
+/*
+** Bring the named file under quota management, assuming its name matches
+** the glob pattern of some quota group.  Or if it is already under
+** management, update its size.  If zFilename does not match the glob
+** pattern of any quota group, this routine is a no-op.
+*/
+int sqlite3_quota_file(const char *zFilename);
+
+/*
+** The following object serves the same role as FILE in the standard C
+** library.  It represents an open connection to a file on disk for I/O.
+**
+** A single quota_FILE should not be used by two or more threads at the
+** same time.  Multiple threads can be using different quota_FILE objects
+** simultaneously, but not the same quota_FILE object.
+*/
+typedef struct quota_FILE quota_FILE;
+
+/*
+** Create a new quota_FILE object used to read and/or write to the
+** file zFilename.  The zMode parameter is as with standard library zMode.
+*/
+quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode);
+
+/*
+** Perform I/O against a quota_FILE object.  When doing writes, the
+** quota mechanism may result in a short write, in order to prevent
+** the sum of sizes of all files from going over quota.
+*/
+size_t sqlite3_quota_fread(void*, size_t, size_t, quota_FILE*);
+size_t sqlite3_quota_fwrite(void*, size_t, size_t, quota_FILE*);
+
+/*
+** Flush all written content held in memory buffers out to disk.
+** This is the equivalent of fflush() in the standard library.
+**
+** If the hardSync parameter is true (non-zero) then this routine
+** also forces OS buffers to disk - the equivalent of fsync().
+**
+** This routine return zero on success and non-zero if something goes
+** wrong.
+*/
+int sqlite3_quota_fflush(quota_FILE*, int hardSync);
+
+/*
+** Close a quota_FILE object and free all associated resources.  The
+** file remains under quota management.
+*/
+int sqlite3_quota_fclose(quota_FILE*);
+
+/*
+** Move the read/write pointer for a quota_FILE object.  Or tell the
+** current location of the read/write pointer.
+*/
+int sqlite3_quota_fseek(quota_FILE*, long, int);
+void sqlite3_quota_rewind(quota_FILE*);
+long sqlite3_quota_ftell(quota_FILE*);
+
+/*
+** Delete a file from the disk, if that file is under quota management.
+** Adjust quotas accordingly.
+**
+** If zFilename is the name of a directory that matches one of the
+** quota glob patterns, then all files under quota management that
+** are contained within that directory are deleted.
+**
+** A standard SQLite result code is returned (SQLITE_OK, SQLITE_NOMEM, etc.)
+** When deleting a directory of files, if the deletion of any one
+** file fails (for example due to an I/O error), then this routine
+** returns immediately, with the error code, and does not try to 
+** delete any of the other files in the specified directory.
+**
+** All files are removed from quota management and deleted from disk.
+** However, no attempt is made to remove empty directories.
+**
+** This routine is a no-op for files that are not under quota management.
+*/
+int sqlite3_quota_remove(const char *zFilename);
+
+#ifdef __cplusplus
+}  /* end of the 'extern "C"' block */
+#endif
+#endif /* _QUOTA_H_ */
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -40,16 +40,17 @@
 #include "jsapi.h"
 
 namespace mozilla {
 namespace dom {
 
 enum StructuredCloneTags {
   SCTAG_BASE = JS_SCTAG_USER_MIN,
   SCTAG_DOM_BLOB,
+  SCTAG_DOM_FILE,
   SCTAG_DOM_FILELIST,
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -78,16 +78,18 @@
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #endif
 
 #include "Layers.h"
 #include "nsIIOService.h"
 
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/indexedDB/FileInfo.h"
+#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "sampler.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 
 static bool IsUniversalXPConnectCapable()
 {
@@ -1917,8 +1919,56 @@ nsDOMWindowUtils::CheckAndClearPaintedSt
     *aResult = false;
     return NS_OK;
   }
 
   *aResult = frame->CheckAndClearPaintedState();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFileId(nsIDOMBlob* aBlob, PRInt64* aResult)
+{
+  if (!IsUniversalXPConnectCapable()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  *aResult = aBlob->GetFileId();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFileReferences(const nsAString& aDatabaseName,
+                                    PRInt64 aId, PRInt32* aRefCnt,
+                                    PRInt32* aDBRefCnt, PRInt32* aSliceRefCnt,
+                                    bool* aResult)
+{
+  if (!IsUniversalXPConnectCapable()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
+
+  nsCString origin;
+  nsresult rv = indexedDB::IndexedDatabaseManager::GetASCIIOriginFromWindow(
+    mWindow, origin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<indexedDB::IndexedDatabaseManager> mgr =
+    indexedDB::IndexedDatabaseManager::GetOrCreate();
+  NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
+
+  nsRefPtr<indexedDB::FileManager> fileManager =
+    mgr->GetOrCreateFileManager(origin, aDatabaseName);
+  NS_ENSURE_TRUE(fileManager, NS_ERROR_FAILURE);
+
+  nsRefPtr<indexedDB::FileInfo> fileInfo = fileManager->GetFileInfo(aId);
+  if (fileInfo) {
+    fileInfo->GetReferences(aRefCnt, aDBRefCnt, aSliceRefCnt);
+    *aRefCnt--;
+    *aResult = true;
+    return NS_OK;
+  }
+
+  *aRefCnt = *aDBRefCnt = *aSliceRefCnt = -1;
+  *aResult = false;
+  return NS_OK;
+}
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -72,38 +72,39 @@ public:
 private:
   IDBTransaction* mTransaction;
 };
 
 // This inline is just so that we always clear aBuffers appropriately even if
 // something fails.
 inline
 nsresult
-ConvertCloneBuffersToArrayInternal(
+ConvertCloneReadInfosToArrayInternal(
                                 JSContext* aCx,
-                                nsTArray<JSAutoStructuredCloneBuffer>& aBuffers,
+                                nsTArray<StructuredCloneReadInfo>& aReadInfos,
                                 jsval* aResult)
 {
   JSObject* array = JS_NewArrayObject(aCx, 0, nsnull);
   if (!array) {
     NS_WARNING("Failed to make array!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  if (!aBuffers.IsEmpty()) {
-    if (!JS_SetArrayLength(aCx, array, jsuint(aBuffers.Length()))) {
+  if (!aReadInfos.IsEmpty()) {
+    if (!JS_SetArrayLength(aCx, array, jsuint(aReadInfos.Length()))) {
       NS_WARNING("Failed to set array length!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
-    for (uint32 index = 0, count = aBuffers.Length(); index < count; index++) {
-      JSAutoStructuredCloneBuffer& buffer = aBuffers[index];
+    for (uint32 index = 0, count = aReadInfos.Length(); index < count;
+         index++) {
+      StructuredCloneReadInfo& readInfo = aReadInfos[index];
 
       jsval val;
-      if (!IDBObjectStore::DeserializeValue(aCx, buffer, &val)) {
+      if (!IDBObjectStore::DeserializeValue(aCx, readInfo, &val)) {
         NS_WARNING("Failed to decode!");
         return NS_ERROR_DOM_DATA_CLONE_ERR;
       }
 
       if (!JS_SetElement(aCx, array, index, &val)) {
         NS_WARNING("Failed to set array element!");
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
@@ -499,32 +500,32 @@ AsyncConnectionHelper::ReleaseMainThread
   mDatabase = nsnull;
   mTransaction = nsnull;
 
   HelperBase::ReleaseMainThreadObjects();
 }
 
 // static
 nsresult
-AsyncConnectionHelper::ConvertCloneBuffersToArray(
+AsyncConnectionHelper::ConvertCloneReadInfosToArray(
                                 JSContext* aCx,
-                                nsTArray<JSAutoStructuredCloneBuffer>& aBuffers,
+                                nsTArray<StructuredCloneReadInfo>& aReadInfos,
                                 jsval* aResult)
 {
   NS_ASSERTION(aCx, "Null context!");
   NS_ASSERTION(aResult, "Null pointer!");
 
   JSAutoRequest ar(aCx);
 
-  nsresult rv = ConvertCloneBuffersToArrayInternal(aCx, aBuffers, aResult);
+  nsresult rv = ConvertCloneReadInfosToArrayInternal(aCx, aReadInfos, aResult);
 
-  for (PRUint32 index = 0; index < aBuffers.Length(); index++) {
-    aBuffers[index].clear();
+  for (PRUint32 index = 0; index < aReadInfos.Length(); index++) {
+    aReadInfos[index].mCloneBuffer.clear();
   }
-  aBuffers.Clear();
+  aReadInfos.Clear();
 
   return rv;
 }
 
 NS_IMETHODIMP_(nsrefcnt)
 TransactionPoolEventTarget::AddRef()
 {
   NS_NOTREACHED("Don't call me!");
--- a/dom/indexedDB/AsyncConnectionHelper.h
+++ b/dom/indexedDB/AsyncConnectionHelper.h
@@ -36,16 +36,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_dom_indexeddb_asyncconnectionhelper_h__
 #define mozilla_dom_indexeddb_asyncconnectionhelper_h__
 
 // Only meant to be included in IndexedDB source files, not exported.
+#include "DatabaseInfo.h"
 #include "IndexedDatabase.h"
 #include "IDBDatabase.h"
 #include "IDBRequest.h"
 
 #include "mozIStorageProgressHandler.h"
 #include "nsIRunnable.h"
 
 #include "nsDOMEvent.h"
@@ -193,19 +194,19 @@ protected:
    * on the main thread, regardless of success or failure. Subclasses that
    * implement this method *MUST* call the base class implementation as well.
    */
   virtual void ReleaseMainThreadObjects();
 
   /**
    * Helper to make a JS array object out of an array of clone buffers.
    */
-  static nsresult ConvertCloneBuffersToArray(
+  static nsresult ConvertCloneReadInfosToArray(
                                 JSContext* aCx,
-                                nsTArray<JSAutoStructuredCloneBuffer>& aBuffers,
+                                nsTArray<StructuredCloneReadInfo>& aReadInfos,
                                 jsval* aResult);
 
 protected:
   nsRefPtr<IDBDatabase> mDatabase;
   nsRefPtr<IDBTransaction> mTransaction;
 
 private:
   nsCOMPtr<mozIStorageProgressHandler> mOldProgressHandler;
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -200,16 +200,13 @@ CheckPermissionsHelper::Observe(nsISuppo
   NS_ASSERTION(mPromptAllowed, "How did we get here?");
 
   mHasPrompted = true;
 
   nsresult rv;
   mPromptResult = nsDependentString(aData).ToInteger(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-  NS_ASSERTION(mgr, "This should never be null!");
-
   rv = NS_DispatchToCurrentThread(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/FileInfo.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Indexed Database.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "IndexedDatabaseManager.h"
+#include "FileInfo.h"
+
+USING_INDEXEDDB_NAMESPACE
+
+// static
+FileInfo*
+FileInfo::Create(FileManager* aFileManager, PRInt64 aId)
+{
+  NS_ASSERTION(aId > 0, "Wrong id!");
+
+  if (aId <= PR_INT16_MAX) {
+    return new FileInfo16(aFileManager, aId);
+  }
+
+  if (aId <= PR_INT32_MAX) {
+    return new FileInfo32(aFileManager, aId);
+  }
+
+  return new FileInfo64(aFileManager, aId);
+}
+
+void
+FileInfo::GetReferences(PRInt32* aRefCnt, PRInt32* aDBRefCnt,
+                        PRInt32* aSliceRefCnt)
+{
+  if (IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+
+    if (aRefCnt) {
+      *aRefCnt = -1;
+    }
+
+    if (aDBRefCnt) {
+      *aDBRefCnt = -1;
+    }
+
+    if (aSliceRefCnt) {
+      *aSliceRefCnt = -1;
+    }
+
+    return;
+  }
+
+  MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+  if (aRefCnt) {
+    *aRefCnt = mRefCnt;
+  }
+
+  if (aDBRefCnt) {
+    *aDBRefCnt = mDBRefCnt;
+  }
+
+  if (aSliceRefCnt) {
+    *aSliceRefCnt = mSliceRefCnt;
+  }
+}
+
+void
+FileInfo::UpdateReferences(nsAutoRefCnt& aRefCount, PRInt32 aDelta,
+                           bool aClear)
+{
+  if (IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return;
+  }
+
+  {
+    MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+    aRefCount = aClear ? 0 : aRefCount + aDelta;
+
+    if (mRefCnt + mDBRefCnt + mSliceRefCnt > 0) {
+      return;
+    }
+
+    mFileManager->mFileInfos.Remove(Id());
+  }
+
+  Cleanup();
+
+  delete this;
+}
+
+void
+FileInfo::Cleanup()
+{
+  if (IndexedDatabaseManager::IsShuttingDown() ||
+      mFileManager->Invalidated()) {
+    return;
+  }
+
+  nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+  NS_ASSERTION(mgr, "Shouldn't be null!");
+
+  if (NS_FAILED(mgr->AsyncDeleteFile(mFileManager, Id()))) {
+    NS_WARNING("Failed to delete file asynchronously!");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/FileInfo.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Indexed Database.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_indexeddb_fileinfo_h__
+#define mozilla_dom_indexeddb_fileinfo_h__
+
+#include "IndexedDatabase.h"
+#include "nsAtomicRefcnt.h"
+#include "nsThreadUtils.h"
+#include "FileManager.h"
+#include "IndexedDatabaseManager.h"
+
+BEGIN_INDEXEDDB_NAMESPACE
+
+class FileInfo
+{
+  friend class FileManager;
+
+public:
+  FileInfo(FileManager* aFileManager)
+  : mFileManager(aFileManager)
+  { }
+
+  virtual ~FileInfo()
+  {
+#ifdef DEBUG
+    NS_ASSERTION(NS_IsMainThread(), "File info destroyed on wrong thread!");
+#endif
+  }
+
+  static
+  FileInfo* Create(FileManager* aFileManager, PRInt64 aId);
+
+  void AddRef()
+  {
+    if (IndexedDatabaseManager::IsClosed()) {
+      NS_AtomicIncrementRefcnt(mRefCnt);
+    }
+    else {
+      UpdateReferences(mRefCnt, 1);
+    }
+  }
+
+  void Release()
+  {
+    if (IndexedDatabaseManager::IsClosed()) {
+      nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt);
+      if (count == 0) {
+        mRefCnt = 1;
+        delete this;
+      }
+    }
+    else {
+      UpdateReferences(mRefCnt, -1);
+    }
+  }
+
+  void UpdateDBRefs(PRInt32 aDelta)
+  {
+    UpdateReferences(mDBRefCnt, aDelta);
+  }
+
+  void ClearDBRefs()
+  {
+    UpdateReferences(mDBRefCnt, 0, true);
+  }
+
+  void UpdateSliceRefs(PRInt32 aDelta)
+  {
+    UpdateReferences(mSliceRefCnt, aDelta);
+  }
+
+  void GetReferences(PRInt32* aRefCnt, PRInt32* aDBRefCnt,
+                     PRInt32* aSliceRefCnt);
+
+  FileManager* Manager() const
+  {
+    return mFileManager;
+  }
+
+  virtual PRInt64 Id() const = 0;
+
+private:
+  void UpdateReferences(nsAutoRefCnt& aRefCount, PRInt32 aDelta,
+                        bool aClear = false);
+  void Cleanup();
+
+  nsAutoRefCnt mRefCnt;
+  nsAutoRefCnt mDBRefCnt;
+  nsAutoRefCnt mSliceRefCnt;
+
+  nsRefPtr<FileManager> mFileManager;
+};
+
+#define FILEINFO_SUBCLASS(_bits)                                              \
+class FileInfo##_bits : public FileInfo                                       \
+{                                                                             \
+public:                                                                       \
+  FileInfo##_bits(FileManager* aFileManager, PRInt64 aId)                     \
+  : FileInfo(aFileManager), mId(aId)                                          \
+  { }                                                                         \
+                                                                              \
+  virtual PRInt64 Id() const                                                  \
+  {                                                                           \
+    return mId;                                                               \
+  }                                                                           \
+                                                                              \
+private:                                                                      \
+  PRInt##_bits mId;                                                           \
+};
+
+FILEINFO_SUBCLASS(16);
+FILEINFO_SUBCLASS(32);
+FILEINFO_SUBCLASS(64);
+
+#undef FILEINFO_SUBCLASS
+
+END_INDEXEDDB_NAMESPACE
+
+#endif // mozilla_dom_indexeddb_fileinfo_h__
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/FileManager.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Indexed Database.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "FileManager.h"
+
+#include "mozIStorageConnection.h"
+#include "mozIStorageServiceQuotaManagement.h"
+#include "mozIStorageStatement.h"
+#include "nsISimpleEnumerator.h"
+#include "mozStorageCID.h"
+#include "nsContentUtils.h"
+
+#include "FileInfo.h"
+#include "IndexedDatabaseManager.h"
+
+USING_INDEXEDDB_NAMESPACE
+
+namespace {
+
+PLDHashOperator
+EnumerateToTArray(const PRUint64& aKey,
+                  FileInfo* aValue,
+                  void* aUserArg)
+{
+  NS_ASSERTION(aValue, "Null pointer!");
+  NS_ASSERTION(aUserArg, "Null pointer!");
+
+  nsTArray<FileInfo*>* array =
+    static_cast<nsTArray<FileInfo*>*>(aUserArg);
+
+  array->AppendElement(aValue);
+
+  return PL_DHASH_NEXT;
+}
+
+} // anonymous namespace
+
+nsresult
+FileManager::Init()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  NS_ENSURE_TRUE(mFileInfos.Init(), NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+nsresult
+FileManager::InitDirectory(nsIFile* aDirectory,
+                           mozIStorageConnection* aConnection)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  bool exists;
+  nsresult rv = aDirectory->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    bool isDirectory;
+    rv = aDirectory->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE);
+  }
+  else {
+    rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE VIRTUAL TABLE fs USING filesystem;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT name, (name IN (SELECT id FROM file)) FROM fs "
+    "WHERE path = :path"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsString path;
+  rv = aDirectory->GetPath(path);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
+
+  bool hasResult;
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+    nsString name;
+    rv = stmt->GetString(0, name);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRInt32 flag = stmt->AsInt32(1);
+
+    nsCOMPtr<nsIFile> file;
+    rv = aDirectory->Clone(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = file->Append(name);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (flag) {
+      rv = ss->UpdateQuotaInformationForFile(file);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      rv = file->Remove(false);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to remove orphaned file!");
+      }
+    }
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE fs;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDirectory->GetPath(mDirectoryPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDirectory->GetLeafName(mDirectoryName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+FileManager::Load(mozIStorageConnection* aConnection)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, refcount "
+    "FROM file"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool hasResult;
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+    PRInt64 id;
+    rv = stmt->GetInt64(0, &id);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRInt32 refcount;
+    rv = stmt->GetInt32(1, &refcount);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    NS_ASSERTION(refcount, "This shouldn't happen!");
+
+    nsRefPtr<FileInfo> fileInfo = FileInfo::Create(this, id);
+    fileInfo->mDBRefCnt = refcount;
+
+    if (!mFileInfos.Put(id, fileInfo)) {
+      NS_WARNING("Out of memory?");
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    mLastFileId = NS_MAX(id, mLastFileId);
+  }
+
+  mLoaded = true;
+
+  return NS_OK;
+}
+
+nsresult
+FileManager::Invalidate()
+{
+  if (IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsTArray<FileInfo*> fileInfos;
+  {
+    MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+    NS_ASSERTION(!mInvalidated, "Invalidate more than once?!");
+    mInvalidated = true;
+
+    fileInfos.SetCapacity(mFileInfos.Count());
+    mFileInfos.EnumerateRead(EnumerateToTArray, &fileInfos);
+  }
+
+  for (PRUint32 i = 0; i < fileInfos.Length(); i++) {
+    FileInfo* fileInfo = fileInfos.ElementAt(i);
+    fileInfo->ClearDBRefs();
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+FileManager::GetDirectory()
+{
+  nsCOMPtr<nsILocalFile> directory =
+    do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  NS_ENSURE_TRUE(directory, nsnull);
+
+  nsresult rv = directory->InitWithPath(mDirectoryPath);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  return directory.forget();
+}
+
+already_AddRefed<FileInfo>
+FileManager::GetFileInfo(PRInt64 aId)
+{
+  if (IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return nsnull;
+  }
+
+  FileInfo* fileInfo = nsnull;
+  {
+    MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+    fileInfo = mFileInfos.Get(aId);
+  }
+  nsRefPtr<FileInfo> result = fileInfo;
+  return result.forget();
+}
+
+already_AddRefed<FileInfo>
+FileManager::GetNewFileInfo()
+{
+  if (IndexedDatabaseManager::IsClosed()) {
+    NS_ERROR("Shouldn't be called after shutdown!");
+    return nsnull;
+  }
+
+  nsAutoPtr<FileInfo> fileInfo;
+
+  {
+    MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+    PRInt64 id = mLastFileId + 1;
+
+    fileInfo = FileInfo::Create(this, id);
+
+    if (!mFileInfos.Put(id, fileInfo)) {
+      NS_WARNING("Out of memory?");
+      return nsnull;
+    }
+
+    mLastFileId = id;
+  }
+
+  nsRefPtr<FileInfo> result = fileInfo.forget();
+  return result.forget();
+}
+
+already_AddRefed<nsIFile>
+FileManager::GetFileForId(nsIFile* aDirectory, PRInt64 aId)
+{
+  NS_ASSERTION(aDirectory, "Null pointer!");
+
+  nsAutoString id;
+  id.AppendInt(aId);
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  rv = file->Append(id);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  return file.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/FileManager.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Indexed Database.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_indexeddb_filemanager_h__
+#define mozilla_dom_indexeddb_filemanager_h__
+
+#include "IndexedDatabase.h"
+#include "nsIFile.h"
+#include "nsILocalFile.h"
+#include "nsIDOMFile.h"
+#include "nsDataHashtable.h"
+
+class mozIStorageConnection;
+
+BEGIN_INDEXEDDB_NAMESPACE
+
+class FileInfo;
+
+class FileManager
+{
+  friend class FileInfo;
+
+public:
+  FileManager(const nsACString& aOrigin,
+              const nsAString& aDatabaseName)
+  : mOrigin(aOrigin), mDatabaseName(aDatabaseName), mLastFileId(0),
+    mLoaded(false), mInvalidated(false)
+  { }
+
+  ~FileManager()
+  { }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileManager)
+
+  const nsACString& Origin() const
+  {
+    return mOrigin;
+  }
+
+  const nsAString& DatabaseName() const
+  {
+    return mDatabaseName;
+  }
+
+  const nsAString& DirectoryName() const
+  {
+    return mDirectoryName;
+  }
+
+  bool IsDirectoryInited() const
+  {
+    return !mDirectoryPath.IsEmpty();
+  }
+
+  bool Loaded() const
+  {
+    return mLoaded;
+  }
+
+  bool Invalidated() const
+  {
+    return mInvalidated;
+  }
+
+  nsresult Init();
+
+  nsresult InitDirectory(nsIFile* aDirectory,
+                         mozIStorageConnection* aConnection);
+
+  nsresult Load(mozIStorageConnection* aConnection);
+
+  nsresult Invalidate();
+
+  already_AddRefed<nsIFile> GetDirectory();
+
+  already_AddRefed<FileInfo> GetFileInfo(PRInt64 aId);
+
+  already_AddRefed<FileInfo> GetNewFileInfo();
+
+  static already_AddRefed<nsIFile> GetFileForId(nsIFile* aDirectory,
+                                                PRInt64 aId);
+
+private:
+  nsCString mOrigin;
+  nsString mDatabaseName;
+
+  nsString mDirectoryPath;
+  nsString mDirectoryName;
+
+  PRInt64 mLastFileId;
+
+  // Protected by IndexedDatabaseManager::FileMutex()
+  nsDataHashtable<nsUint64HashKey, FileInfo*> mFileInfos;
+
+  bool mLoaded;
+  bool mInvalidated;
+};
+
+END_INDEXEDDB_NAMESPACE
+
+#endif // mozilla_dom_indexeddb_filemanager_h__
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -44,17 +44,16 @@
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfoID.h"
 #include "nsEventDispatcher.h"
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 
 #include "AsyncConnectionHelper.h"
-#include "DatabaseInfo.h"
 #include "IDBEvents.h"
 #include "IDBIndex.h"
 #include "IDBObjectStore.h"
 #include "IDBTransaction.h"
 #include "TransactionThreadPool.h"
 
 USING_INDEXEDDB_NAMESPACE
 
@@ -82,17 +81,17 @@ public:
   : AsyncConnectionHelper(aCursor->mTransaction, aCursor->mRequest),
     mCursor(aCursor), mCount(aCount)
   {
     NS_ASSERTION(aCount > 0, "Must have a count!");
   }
 
   ~ContinueHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
@@ -107,17 +106,17 @@ protected:
   virtual nsresult
   GatherResultsFromStatement(mozIStorageStatement* aStatement) = 0;
 
 protected:
   nsRefPtr<IDBCursor> mCursor;
   PRInt32 mCount;
   Key mKey;
   Key mObjectKey;
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
 };
 
 class ContinueObjectStoreHelper : public ContinueHelper
 {
 public:
   ContinueObjectStoreHelper(IDBCursor* aCursor,
                             PRUint32 aCount)
   : ContinueHelper(aCursor, aCount)
@@ -160,30 +159,30 @@ already_AddRefed<IDBCursor>
 IDBCursor::Create(IDBRequest* aRequest,
                   IDBTransaction* aTransaction,
                   IDBObjectStore* aObjectStore,
                   PRUint16 aDirection,
                   const Key& aRangeKey,
                   const nsACString& aContinueQuery,
                   const nsACString& aContinueToQuery,
                   const Key& aKey,
-                  JSAutoStructuredCloneBuffer& aCloneBuffer)
+                  StructuredCloneReadInfo& aCloneReadInfo)
 {
   NS_ASSERTION(aObjectStore, "Null pointer!");
   NS_ASSERTION(!aKey.IsUnset(), "Bad key!");
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::CreateCommon(aRequest, aTransaction, aObjectStore, aDirection,
                             aRangeKey, aContinueQuery, aContinueToQuery);
   NS_ASSERTION(cursor, "This shouldn't fail!");
 
   cursor->mObjectStore = aObjectStore;
   cursor->mType = OBJECTSTORE;
   cursor->mKey = aKey;
-  cursor->mCloneBuffer.swap(aCloneBuffer);
+  cursor->mCloneReadInfo.Swap(aCloneReadInfo);
 
   return cursor.forget();
 }
 
 // static
 already_AddRefed<IDBCursor>
 IDBCursor::Create(IDBRequest* aRequest,
                   IDBTransaction* aTransaction,
@@ -219,33 +218,33 @@ IDBCursor::Create(IDBRequest* aRequest,
                   IDBTransaction* aTransaction,
                   IDBIndex* aIndex,
                   PRUint16 aDirection,
                   const Key& aRangeKey,
                   const nsACString& aContinueQuery,
                   const nsACString& aContinueToQuery,
                   const Key& aKey,
                   const Key& aObjectKey,
-                  JSAutoStructuredCloneBuffer& aCloneBuffer)
+                  StructuredCloneReadInfo& aCloneReadInfo)
 {
   NS_ASSERTION(aIndex, "Null pointer!");
   NS_ASSERTION(!aKey.IsUnset(), "Bad key!");
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(),
                             aDirection, aRangeKey, aContinueQuery,
                             aContinueToQuery);
   NS_ASSERTION(cursor, "This shouldn't fail!");
 
   cursor->mObjectStore = aIndex->ObjectStore();
   cursor->mIndex = aIndex;
   cursor->mType = INDEXOBJECT;
   cursor->mKey = aKey;
   cursor->mObjectKey = aObjectKey;
-  cursor->mCloneBuffer.swap(aCloneBuffer);
+  cursor->mCloneReadInfo.Swap(aCloneReadInfo);
 
   return cursor.forget();
 }
 
 // static
 already_AddRefed<IDBCursor>
 IDBCursor::CreateCommon(IDBRequest* aRequest,
                         IDBTransaction* aTransaction,
@@ -295,17 +294,17 @@ IDBCursor::IDBCursor()
 
 IDBCursor::~IDBCursor()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (mRooted) {
     NS_DROP_JS_OBJECTS(this, IDBCursor);
   }
-  IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
 }
 
 nsresult
 IDBCursor::ContinueInternal(const Key& aKey,
                             PRInt32 aCount)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aCount > 0, "Must have a count!");
@@ -524,22 +523,22 @@ IDBCursor::GetValue(JSContext* aCx,
   }
 
   if (!mHaveCachedValue) {
     if (!mRooted) {
       NS_HOLD_JS_OBJECTS(this, IDBCursor);
       mRooted = true;
     }
 
-    if (!IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, &mCachedValue)) {
+    if (!IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, &mCachedValue)) {
       mCachedValue = JSVAL_VOID;
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
 
-    mCloneBuffer.clear();
+    mCloneReadInfo.mCloneBuffer.clear();
     mHaveCachedValue = true;
   }
 
   *aValue = mCachedValue;
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -757,18 +756,18 @@ ContinueHelper::GetSuccessResult(JSConte
     NS_ASSERTION(mCursor->mType == IDBCursor::OBJECTSTORE ||
                  !mObjectKey.IsUnset(), "Bad key!");
 
     // Set new values.
     mCursor->mKey = mKey;
     mCursor->mObjectKey = mObjectKey;
     mCursor->mContinueToKey.Unset();
 
-    mCursor->mCloneBuffer.swap(mCloneBuffer);
-    mCloneBuffer.clear();
+    mCursor->mCloneReadInfo.Swap(mCloneReadInfo);
+    mCloneReadInfo.mCloneBuffer.clear();
 
     nsresult rv = WrapNative(aCx, mCursor, aVal);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
@@ -806,18 +805,18 @@ ContinueObjectStoreHelper::BindArguments
 nsresult
 ContinueObjectStoreHelper::GatherResultsFromStatement(
                                                mozIStorageStatement* aStatement)
 {
   // Figure out what kind of key we have next.
   nsresult rv = mKey.SetFromStatement(aStatement, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = IDBObjectStore::GetStructuredCloneDataFromStatement(aStatement, 1,
-                                                           mCloneBuffer);
+  rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 1, 2,
+    mDatabase->Manager(), mCloneReadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 ContinueIndexHelper::BindArgumentsToStatement(mozIStorageStatement* aStatement)
 {
@@ -876,14 +875,14 @@ ContinueIndexObjectHelper::GatherResults
                                                mozIStorageStatement* aStatement)
 {
   nsresult rv = mKey.SetFromStatement(aStatement, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mObjectKey.SetFromStatement(aStatement, 1);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = IDBObjectStore::GetStructuredCloneDataFromStatement(aStatement, 2,
-                                                           mCloneBuffer);
+  rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 2, 3,
+    mDatabase->Manager(), mCloneReadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
--- a/dom/indexedDB/IDBCursor.h
+++ b/dom/indexedDB/IDBCursor.h
@@ -83,17 +83,17 @@ public:
   Create(IDBRequest* aRequest,
          IDBTransaction* aTransaction,
          IDBObjectStore* aObjectStore,
          PRUint16 aDirection,
          const Key& aRangeKey,
          const nsACString& aContinueQuery,
          const nsACString& aContinueToQuery,
          const Key& aKey,
-         JSAutoStructuredCloneBuffer& aCloneBuffer);
+         StructuredCloneReadInfo& aCloneReadInfo);
 
   // For INDEXKEY cursors.
   static
   already_AddRefed<IDBCursor>
   Create(IDBRequest* aRequest,
          IDBTransaction* aTransaction,
          IDBIndex* aIndex,
          PRUint16 aDirection,
@@ -110,17 +110,17 @@ public:
          IDBTransaction* aTransaction,
          IDBIndex* aIndex,
          PRUint16 aDirection,
          const Key& aRangeKey,
          const nsACString& aContinueQuery,
          const nsACString& aContinueToQuery,
          const Key& aKey,
          const Key& aObjectKey,
-         JSAutoStructuredCloneBuffer& aCloneBuffer);
+         StructuredCloneReadInfo& aCloneReadInfo);
 
   enum Type
   {
     OBJECTSTORE = 0,
     INDEXKEY,
     INDEXOBJECT
   };
 
@@ -164,17 +164,17 @@ protected:
   jsval mCachedKey;
   jsval mCachedPrimaryKey;
   jsval mCachedValue;
 
   Key mRangeKey;
 
   Key mKey;
   Key mObjectKey;
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
   Key mContinueToKey;
 
   bool mHaveCachedKey;
   bool mHaveCachedPrimaryKey;
   bool mHaveCachedValue;
   bool mRooted;
   bool mContinueCalled;
   bool mHaveValue;
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -38,16 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "IDBDatabase.h"
 
 #include "jscntxt.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/storage.h"
 #include "nsDOMClassInfo.h"
+#include "nsDOMLists.h"
 #include "nsEventDispatcher.h"
 #include "nsJSUtils.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 #include "AsyncConnectionHelper.h"
 #include "CheckQuotaHelper.h"
 #include "DatabaseInfo.h"
@@ -146,17 +147,18 @@ private:
 
 } // anonymous namespace
 
 // static
 already_AddRefed<IDBDatabase>
 IDBDatabase::Create(nsIScriptContext* aScriptContext,
                     nsPIDOMWindow* aOwner,
                     already_AddRefed<DatabaseInfo> aDatabaseInfo,
-                    const nsACString& aASCIIOrigin)
+                    const nsACString& aASCIIOrigin,
+                    FileManager* aFileManager)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
 
   nsRefPtr<DatabaseInfo> databaseInfo(aDatabaseInfo);
   NS_ASSERTION(databaseInfo, "Null pointer!");
 
   nsRefPtr<IDBDatabase> db(new IDBDatabase());
@@ -164,16 +166,17 @@ IDBDatabase::Create(nsIScriptContext* aS
   db->mScriptContext = aScriptContext;
   db->mOwner = aOwner;
 
   db->mDatabaseId = databaseInfo->id;
   db->mName = databaseInfo->name;
   db->mFilePath = databaseInfo->filePath;
   databaseInfo.swap(db->mDatabaseInfo);
   db->mASCIIOrigin = aASCIIOrigin;
+  db->mFileManager = aFileManager;
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   NS_ASSERTION(mgr, "This should never be null!");
 
   if (!mgr->RegisterDatabase(db)) {
     // Either out of memory or shutting down.
     return nsnull;
   }
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -36,22 +36,22 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_dom_indexeddb_idbdatabase_h__
 #define mozilla_dom_indexeddb_idbdatabase_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
+#include "mozilla/dom/indexedDB/FileManager.h"
 
 #include "nsIIDBDatabase.h"
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMEventTargetHelper.h"
-#include "nsDOMLists.h"
 #include "nsIDocument.h"
 
 class nsIScriptContext;
 class nsPIDOMWindow;
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class AsyncConnectionHelper;
@@ -73,17 +73,18 @@ public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBDatabase,
                                            nsDOMEventTargetHelper)
 
   static already_AddRefed<IDBDatabase>
   Create(nsIScriptContext* aScriptContext,
          nsPIDOMWindow* aOwner,
          already_AddRefed<DatabaseInfo> aDatabaseInfo,
-         const nsACString& aASCIIOrigin);
+         const nsACString& aASCIIOrigin,
+         FileManager* aFileManager);
 
   // nsIDOMEventTarget
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
   nsIAtom* Id() const
   {
     return mDatabaseId;
   }
@@ -136,16 +137,21 @@ public:
   void CloseInternal(bool aIsDead);
 
   // Whether or not the database has had Close called on it.
   bool IsClosed();
 
   void EnterSetVersionTransaction();
   void ExitSetVersionTransaction();
 
+  FileManager* Manager() const
+  {
+    return mFileManager;
+  }
+
 private:
   IDBDatabase();
   ~IDBDatabase();
 
   void OnUnlink();
 
   nsRefPtr<DatabaseInfo> mDatabaseInfo;
   nsCOMPtr<nsIAtom> mDatabaseId;
@@ -153,16 +159,18 @@ private:
   nsString mFilePath;
   nsCString mASCIIOrigin;
 
   PRInt32 mInvalidated;
   bool mRegistered;
   bool mClosed;
   bool mRunningVersionChange;
 
+  nsRefPtr<FileManager> mFileManager;
+
   // Only touched on the main thread.
   nsRefPtr<nsDOMEventListenerWrapper> mOnAbortListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnVersionChangeListener;
 };
 
 END_INDEXEDDB_NAMESPACE
 
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -99,31 +99,31 @@ public:
             IDBRequest* aRequest,
             IDBIndex* aIndex,
             IDBKeyRange* aKeyRange)
   : GetKeyHelper(aTransaction, aRequest, aIndex, aKeyRange)
   { }
 
   ~GetHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
     GetKeyHelper::ReleaseMainThreadObjects();
   }
 
 protected:
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
 };
 
 class GetAllKeysHelper : public GetKeyHelper
 {
 public:
   GetAllKeysHelper(IDBTransaction* aTransaction,
                    IDBRequest* aRequest,
                    IDBIndex* aIndex,
@@ -149,36 +149,38 @@ public:
                IDBIndex* aIndex,
                IDBKeyRange* aKeyRange,
                const PRUint32 aLimit)
   : GetKeyHelper(aTransaction, aRequest, aIndex, aKeyRange), mLimit(aLimit)
   { }
 
   ~GetAllHelper()
   {
-    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(
+        mCloneReadInfos[index].mCloneBuffer);
     }
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
-    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(
+        mCloneReadInfos[index].mCloneBuffer);
     }
     GetKeyHelper::ReleaseMainThreadObjects();
   }
 
 protected:
   const PRUint32 mLimit;
-  nsTArray<JSAutoStructuredCloneBuffer> mCloneBuffers;
+  nsTArray<StructuredCloneReadInfo> mCloneReadInfos;
 };
 
 class OpenKeyCursorHelper : public AsyncConnectionHelper
 {
 public:
   OpenKeyCursorHelper(IDBTransaction* aTransaction,
                       IDBRequest* aRequest,
                       IDBIndex* aIndex,
@@ -222,17 +224,17 @@ public:
                    IDBKeyRange* aKeyRange,
                    PRUint16 aDirection)
   : AsyncConnectionHelper(aTransaction, aRequest), mIndex(aIndex),
     mKeyRange(aKeyRange), mDirection(aDirection)
   { }
 
   ~OpenCursorHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
@@ -245,17 +247,17 @@ private:
   // In-params.
   nsRefPtr<IDBIndex> mIndex;
   nsRefPtr<IDBKeyRange> mKeyRange;
   const PRUint16 mDirection;
 
   // Out-params.
   Key mKey;
   Key mObjectKey;
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
   nsCString mContinueQuery;
   nsCString mContinueToQuery;
   Key mRangeKey;
 };
 
 class CountHelper : public AsyncConnectionHelper
 {
 public:
@@ -788,17 +790,17 @@ GetHelper::DoDatabaseWork(mozIStorageCon
 
   nsCString keyRangeClause;
   mKeyRange->GetBindingClause(NS_LITERAL_CSTRING("value"), keyRangeClause);
 
   NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!");
 
   NS_NAMED_LITERAL_CSTRING(indexId, "index_id");
 
-  nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + objectTable +
+  nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + objectTable +
                     NS_LITERAL_CSTRING(" INNER JOIN ") + joinTable +
                     NS_LITERAL_CSTRING(" ON ") + objectTable +
                     NS_LITERAL_CSTRING(".id = ") + joinTable +
                     NS_LITERAL_CSTRING(".") + objectColumn +
                     NS_LITERAL_CSTRING(" WHERE ") + indexId +
                     NS_LITERAL_CSTRING(" = :") + indexId + keyRangeClause +
                     NS_LITERAL_CSTRING(" LIMIT 1");
 
@@ -813,31 +815,31 @@ GetHelper::DoDatabaseWork(mozIStorageCon
   rv = mKeyRange->BindToStatement(stmt);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasResult;
   rv = stmt->ExecuteStep(&hasResult);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   if (hasResult) {
-    rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0,
-                                                             mCloneBuffer);
+    rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1,
+      mDatabase->Manager(), mCloneReadInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
 GetHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
-  bool result = IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, aVal);
+  bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal);
 
-  mCloneBuffer.clear();
+  mCloneReadInfo.mCloneBuffer.clear();
 
   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 nsresult
 GetAllKeysHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */)
 {
@@ -998,17 +1000,17 @@ GetAllHelper::DoDatabaseWork(mozIStorage
   }
 
   nsCString limitClause;
   if (mLimit != PR_UINT32_MAX) {
     limitClause = NS_LITERAL_CSTRING(" LIMIT ");
     limitClause.AppendInt(mLimit);
   }
 
-  nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + dataTableName +
+  nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + dataTableName +
                     NS_LITERAL_CSTRING(" INNER JOIN ") + indexTableName  +
                     NS_LITERAL_CSTRING(" ON ") + dataTableName +
                     NS_LITERAL_CSTRING(".id = ") + indexTableName +
                     NS_LITERAL_CSTRING(".") + objectDataId +
                     NS_LITERAL_CSTRING(" WHERE ") + indexId  +
                     NS_LITERAL_CSTRING(" = :") + indexId + keyRangeClause +
                     limitClause;
 
@@ -1020,45 +1022,46 @@ GetAllHelper::DoDatabaseWork(mozIStorage
   nsresult rv = stmt->BindInt64ByName(indexId, mIndex->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   if (mKeyRange) {
     rv = mKeyRange->BindToStatement(stmt);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  mCloneBuffers.SetCapacity(50);
+  mCloneReadInfos.SetCapacity(50);
 
   bool hasResult;
   while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
-    if (mCloneBuffers.Capacity() == mCloneBuffers.Length()) {
-      mCloneBuffers.SetCapacity(mCloneBuffers.Capacity() * 2);
+    if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) {
+      mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2);
     }
 
-    JSAutoStructuredCloneBuffer* buffer = mCloneBuffers.AppendElement();
-    NS_ASSERTION(buffer, "This shouldn't fail!");
+    StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement();
+    NS_ASSERTION(readInfo, "This shouldn't fail!");
 
-    rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, *buffer);
+    rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1,
+      mDatabase->Manager(), *readInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 nsresult
 GetAllHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
-  NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!");
+  NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!");
 
-  nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
+  nsresult rv = ConvertCloneReadInfosToArray(aCx, mCloneReadInfos, aVal);
 
-  for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-    mCloneBuffers[index].clear();
+  for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+    mCloneReadInfos[index].mCloneBuffer.clear();
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
@@ -1312,19 +1315,20 @@ OpenCursorHelper::DoDatabaseWork(mozISto
       NS_NOTREACHED("Unknown direction!");
   }
 
   NS_NAMED_LITERAL_CSTRING(id, "id");
   NS_NAMED_LITERAL_CSTRING(dot, ".");
   NS_NAMED_LITERAL_CSTRING(commaspace, ", ");
 
   nsCString data = objectTable + NS_LITERAL_CSTRING(".data");
+  nsCString fileIds = objectTable + NS_LITERAL_CSTRING(".file_ids");
 
   nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + value + commaspace +
-                         keyValue + commaspace + data +
+                         keyValue + commaspace + data + commaspace + fileIds +
                          NS_LITERAL_CSTRING(" FROM ") + indexTable +
                          NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable +
                          NS_LITERAL_CSTRING(" ON ") + indexTable + dot +
                          objectDataIdColumn + NS_LITERAL_CSTRING(" = ") +
                          objectTable + dot + id +
                          NS_LITERAL_CSTRING(" WHERE ") + indexTable +
                          NS_LITERAL_CSTRING(".index_id = :") + id +
                          keyRangeClause + directionClause +
@@ -1354,23 +1358,24 @@ OpenCursorHelper::DoDatabaseWork(mozISto
   }
 
   rv = mKey.SetFromStatement(stmt, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mObjectKey.SetFromStatement(stmt, 1);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 2,
-                                                           mCloneBuffer);
+  rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 2, 3,
+    mDatabase->Manager(), mCloneReadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Now we need to make the query to get the next match.
   nsCAutoString queryStart = NS_LITERAL_CSTRING("SELECT ") + value +
                              commaspace + keyValue + commaspace + data +
+                             commaspace + fileIds +
                              NS_LITERAL_CSTRING(" FROM ") + indexTable +
                              NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable +
                              NS_LITERAL_CSTRING(" ON ") + indexTable + dot +
                              objectDataIdColumn + NS_LITERAL_CSTRING(" = ") +
                              objectTable + dot + id +
                              NS_LITERAL_CSTRING(" WHERE ") + indexTable +
                              NS_LITERAL_CSTRING(".index_id = :") + id;
 
@@ -1453,20 +1458,20 @@ OpenCursorHelper::GetSuccessResult(JSCon
   if (mKey.IsUnset()) {
     *aVal = JSVAL_VOID;
     return NS_OK;
   }
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::Create(mRequest, mTransaction, mIndex, mDirection, mRangeKey,
                       mContinueQuery, mContinueToQuery, mKey, mObjectKey,
-                      mCloneBuffer);
+                      mCloneReadInfo);
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!");
+  NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!");
 
   return WrapNative(aCx, cursor, aVal);
 }
 
 nsresult
 CountHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   nsCString table;
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -37,120 +37,124 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "IDBObjectStore.h"
 
 #include "nsIJSContextStack.h"
 
 #include "jsclone.h"
+#include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/storage.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfo.h"
+#include "nsDOMFile.h"
+#include "nsDOMLists.h"
 #include "nsEventDispatcher.h"
 #include "nsJSUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "snappy/snappy.h"
+#include "test_quota.h"
 
 #include "AsyncConnectionHelper.h"
 #include "IDBCursor.h"
 #include "IDBEvents.h"
 #include "IDBIndex.h"
 #include "IDBKeyRange.h"
 #include "IDBTransaction.h"
 #include "DatabaseInfo.h"
 
+#define FILE_COPY_BUFFER_SIZE 32768
+
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
 class AddHelper : public AsyncConnectionHelper
 {
 public:
   AddHelper(IDBTransaction* aTransaction,
             IDBRequest* aRequest,
             IDBObjectStore* aObjectStore,
-            JSAutoStructuredCloneBuffer& aCloneBuffer,
+            StructuredCloneWriteInfo& aCloneWriteInfo,
             const Key& aKey,
             bool aOverwrite,
-            nsTArray<IndexUpdateInfo>& aIndexUpdateInfo,
-            PRUint64 aOffsetToKeyProp)
+            nsTArray<IndexUpdateInfo>& aIndexUpdateInfo)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
-    mKey(aKey), mOverwrite(aOverwrite), mOffsetToKeyProp(aOffsetToKeyProp)
+    mKey(aKey), mOverwrite(aOverwrite)
   {
-    mCloneBuffer.swap(aCloneBuffer);
+    mCloneWriteInfo.Swap(aCloneWriteInfo);
     mIndexUpdateInfo.SwapElements(aIndexUpdateInfo);
   }
 
   ~AddHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 private:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
 
   // These may change in the autoincrement case.
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneWriteInfo mCloneWriteInfo;
   Key mKey;
   const bool mOverwrite;
   nsTArray<IndexUpdateInfo> mIndexUpdateInfo;
-  PRUint64 mOffsetToKeyProp;
 };
 
 class GetHelper : public AsyncConnectionHelper
 {
 public:
   GetHelper(IDBTransaction* aTransaction,
             IDBRequest* aRequest,
             IDBObjectStore* aObjectStore,
             IDBKeyRange* aKeyRange)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mKeyRange(aKeyRange)
   { }
 
   ~GetHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     mKeyRange = nsnull;
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 protected:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
   nsRefPtr<IDBKeyRange> mKeyRange;
 
 private:
   // Out-params.
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
 };
 
 class DeleteHelper : public GetHelper
 {
 public:
   DeleteHelper(IDBTransaction* aTransaction,
                IDBRequest* aRequest,
                IDBObjectStore* aObjectStore,
@@ -194,40 +198,40 @@ public:
                    IDBKeyRange* aKeyRange,
                    PRUint16 aDirection)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mKeyRange(aKeyRange), mDirection(aDirection)
   { }
 
   ~OpenCursorHelper()
   {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     mKeyRange = nsnull;
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 private:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
   nsRefPtr<IDBKeyRange> mKeyRange;
   const PRUint16 mDirection;
 
   // Out-params.
   Key mKey;
-  JSAutoStructuredCloneBuffer mCloneBuffer;
+  StructuredCloneReadInfo mCloneReadInfo;
   nsCString mContinueQuery;
   nsCString mContinueToQuery;
   Key mRangeKey;
 };
 
 class CreateIndexHelper : public AsyncConnectionHelper
 {
 public:
@@ -307,44 +311,46 @@ public:
                IDBKeyRange* aKeyRange,
                const PRUint32 aLimit)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mKeyRange(aKeyRange), mLimit(aLimit)
   { }
 
   ~GetAllHelper()
   {
-    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(
+        mCloneReadInfos[index].mCloneBuffer);
     }
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     mKeyRange = nsnull;
-    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(
+        mCloneReadInfos[index].mCloneBuffer);
     }
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 protected:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
   nsRefPtr<IDBKeyRange> mKeyRange;
   const PRUint32 mLimit;
 
 private:
   // Out-params.
-  nsTArray<JSAutoStructuredCloneBuffer> mCloneBuffers;
+  nsTArray<StructuredCloneReadInfo> mCloneReadInfos;
 };
 
 class CountHelper : public AsyncConnectionHelper
 {
 public:
   CountHelper(IDBTransaction* aTransaction,
               IDBRequest* aRequest,
               IDBObjectStore* aObjectStore,
@@ -487,42 +493,16 @@ JSClass gDummyPropClass = {
   "dummy", 0,
   JS_PropertyStub,  JS_PropertyStub,
   JS_PropertyStub,  JS_StrictPropertyStub,
   JS_EnumerateStub, JS_ResolveStub,
   JS_ConvertStub, JS_FinalizeStub,
   JSCLASS_NO_OPTIONAL_MEMBERS
 };
 
-JSBool
-StructuredCloneWriteDummyProp(JSContext* aCx,
-                              JSStructuredCloneWriter* aWriter,
-                              JSObject* aObj,
-                              void* aClosure)
-{
-  if (JS_GET_CLASS(aCx, aObj) == &gDummyPropClass) {
-    PRUint64* closure = reinterpret_cast<PRUint64*>(aClosure);
-
-    NS_ASSERTION(*closure == 0, "We should not have been here before!");
-    *closure = js_GetSCOffset(aWriter);
-
-    PRUint64 value = 0;
-    return JS_WriteBytes(aWriter, &value, sizeof(value));
-  }
-
-  // try using the runtime callbacks
-  const JSStructuredCloneCallbacks* runtimeCallbacks =
-    aCx->runtime->structuredCloneCallbacks;
-  if (runtimeCallbacks) {
-    return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull);
-  }
-
-  return JS_FALSE;
-}
-
 } // anonymous namespace
 
 // static
 already_AddRefed<IDBObjectStore>
 IDBObjectStore::Create(IDBTransaction* aTransaction,
                        const ObjectStoreInfo* aStoreInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -766,33 +746,36 @@ IDBObjectStore::UpdateIndexes(IDBTransac
     }
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
-IDBObjectStore::GetStructuredCloneDataFromStatement(
+IDBObjectStore::GetStructuredCloneReadInfoFromStatement(
                                            mozIStorageStatement* aStatement,
-                                           PRUint32 aIndex,
-                                           JSAutoStructuredCloneBuffer& aBuffer)
+                                           PRUint32 aDataIndex,
+                                           PRUint32 aFileIdsIndex,
+                                           FileManager* aFileManager,
+                                           StructuredCloneReadInfo& aInfo)
 {
 #ifdef DEBUG
   {
-    PRInt32 valueType;
-    NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aIndex, &valueType)) &&
-                 valueType == mozIStorageStatement::VALUE_TYPE_BLOB,
+    PRInt32 type;
+    NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aDataIndex, &type)) &&
+                 type == mozIStorageStatement::VALUE_TYPE_BLOB,
                  "Bad value type!");
   }
 #endif
 
   const PRUint8* blobData;
   PRUint32 blobDataLength;
-  nsresult rv = aStatement->GetSharedBlob(aIndex, &blobDataLength, &blobData);
+  nsresult rv = aStatement->GetSharedBlob(aDataIndex, &blobDataLength,
+                                          &blobData);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   const char* compressed = reinterpret_cast<const char*>(blobData);
   size_t compressedLength = size_t(blobDataLength);
 
   size_t uncompressedLength;
   if (!snappy::GetUncompressedLength(compressed, compressedLength,
                                      &uncompressedLength)) {
@@ -803,104 +786,373 @@ IDBObjectStore::GetStructuredCloneDataFr
   nsAutoArrayPtr<char> uncompressed(new char[uncompressedLength]);
 
   if (!snappy::RawUncompress(compressed, compressedLength,
                              uncompressed.get())) {
     NS_WARNING("Snappy can't determine uncompressed length!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  return aBuffer.copy(reinterpret_cast<const uint64_t *>(uncompressed.get()),
-                      uncompressedLength) ?
-         NS_OK :
-         NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  JSAutoStructuredCloneBuffer& buffer = aInfo.mCloneBuffer;
+  if (!buffer.copy(reinterpret_cast<const uint64_t *>(uncompressed.get()),
+                   uncompressedLength)) {
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  bool isNull;
+  rv = aStatement->GetIsNull(aFileIdsIndex, &isNull);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  if (isNull) {
+    return NS_OK;
+  }
+
+  nsString ids;
+  rv = aStatement->GetString(aFileIdsIndex, ids);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsAutoTArray<PRInt64, 10> array;
+  rv = ConvertFileIdsToArray(ids, array);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  for (PRUint32 i = 0; i < array.Length(); i++) {
+    const PRInt64& id = array.ElementAt(i);
+
+    nsRefPtr<FileInfo> fileInfo = aFileManager->GetFileInfo(id);
+    NS_ASSERTION(fileInfo, "Null file info!");
+
+    aInfo.mFileInfos.AppendElement(fileInfo);
+  }
+
+  return NS_OK;
 }
 
 // static
 void
 IDBObjectStore::ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer)
 {
   if (aBuffer.data()) {
     aBuffer.clear();
   }
 }
 
 // static
 bool
 IDBObjectStore::DeserializeValue(JSContext* aCx,
-                                 JSAutoStructuredCloneBuffer& aBuffer,
-                                 jsval* aValue,
-                                 JSStructuredCloneCallbacks* aCallbacks,
-                                 void* aClosure)
+                                 StructuredCloneReadInfo& aCloneReadInfo,
+                                 jsval* aValue)
 {
   NS_ASSERTION(NS_IsMainThread(),
                "Should only be deserializing on the main thread!");
   NS_ASSERTION(aCx, "A JSContext is required!");
 
-  if (!aBuffer.data()) {
+  JSAutoStructuredCloneBuffer& buffer = aCloneReadInfo.mCloneBuffer;
+
+  if (!buffer.data()) {
     *aValue = JSVAL_VOID;
     return true;
   }
 
   JSAutoRequest ar(aCx);
 
-  return aBuffer.read(aCx, aValue, aCallbacks, aClosure);
+  JSStructuredCloneCallbacks callbacks = {
+    IDBObjectStore::StructuredCloneReadCallback,
+    nsnull,
+    nsnull
+  };
+
+  return buffer.read(aCx, aValue, &callbacks, &aCloneReadInfo);
 }
 
 // static
 bool
 IDBObjectStore::SerializeValue(JSContext* aCx,
-                               JSAutoStructuredCloneBuffer& aBuffer,
-                               jsval aValue,
-                               JSStructuredCloneCallbacks* aCallbacks,
-                               void* aClosure)
+                               StructuredCloneWriteInfo& aCloneWriteInfo,
+                               jsval aValue)
 {
   NS_ASSERTION(NS_IsMainThread(),
                "Should only be serializing on the main thread!");
   NS_ASSERTION(aCx, "A JSContext is required!");
 
   JSAutoRequest ar(aCx);
 
-  return aBuffer.write(aCx, aValue, aCallbacks, aClosure);
+  JSStructuredCloneCallbacks callbacks = {
+    nsnull,
+    StructuredCloneWriteCallback,
+    nsnull
+  };
+
+  JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer;
+
+  return buffer.write(aCx, aValue, &callbacks, &aCloneWriteInfo);
+}
+
+static inline PRUint32
+SwapBytes(PRUint32 u)
+{
+#ifdef IS_BIG_ENDIAN
+  return ((u & 0x000000ffU) << 24) |                                          
+         ((u & 0x0000ff00U) << 8) |                                           
+         ((u & 0x00ff0000U) >> 8) |                                           
+         ((u & 0xff000000U) >> 24);
+#else
+  return u;
+#endif
 }
 
 static inline jsdouble
 SwapBytes(PRUint64 u)
 {
 #ifdef IS_BIG_ENDIAN
-    return ((u & 0x00000000000000ffLLU) << 56) |
-           ((u & 0x000000000000ff00LLU) << 40) |
-           ((u & 0x0000000000ff0000LLU) << 24) |
-            ((u & 0x00000000ff000000LLU) << 8) |
-            ((u & 0x000000ff00000000LLU) >> 8) |
-           ((u & 0x0000ff0000000000LLU) >> 24) |
-           ((u & 0x00ff000000000000LLU) >> 40) |
-           ((u & 0xff00000000000000LLU) >> 56);
+  return ((u & 0x00000000000000ffLLU) << 56) |
+         ((u & 0x000000000000ff00LLU) << 40) |
+         ((u & 0x0000000000ff0000LLU) << 24) |
+         ((u & 0x00000000ff000000LLU) << 8) |
+         ((u & 0x000000ff00000000LLU) >> 8) |
+         ((u & 0x0000ff0000000000LLU) >> 24) |
+         ((u & 0x00ff000000000000LLU) >> 40) |
+         ((u & 0xff00000000000000LLU) >> 56);
 #else
-     return jsdouble(u);
+  return jsdouble(u);
 #endif
 }
 
+static inline bool
+StructuredCloneReadString(JSStructuredCloneReader* aReader,
+                          nsCString& aString)
+{
+  PRUint32 length;
+  if (!JS_ReadBytes(aReader, &length, sizeof(PRUint32))) {
+    NS_WARNING("Failed to read length!");
+    return false;
+  }
+  length = SwapBytes(length);
+
+  if (!EnsureStringLength(aString, length)) {
+    NS_WARNING("Out of memory?");
+    return false;
+  }
+  char* buffer = aString.BeginWriting();
+
+  if (!JS_ReadBytes(aReader, buffer, length)) {
+    NS_WARNING("Failed to read type!");
+    return false;
+  }
+
+  return true;
+}
+
+JSObject*
+IDBObjectStore::StructuredCloneReadCallback(JSContext* aCx,
+                                            JSStructuredCloneReader* aReader,
+                                            uint32 aTag,
+                                            uint32 aData,
+                                            void* aClosure)
+{
+  if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE) {
+    StructuredCloneReadInfo* cloneReadInfo =
+      reinterpret_cast<StructuredCloneReadInfo*>(aClosure);
+
+    NS_ASSERTION(aData < cloneReadInfo->mFileInfos.Length(),
+                 "Bad blob index!");
+
+    nsRefPtr<FileInfo> fileInfo = cloneReadInfo->mFileInfos[aData];
+    nsRefPtr<FileManager> fileManager = fileInfo->Manager();
+    nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
+    if (!directory) {
+      return nsnull;
+    }
+
+    nsCOMPtr<nsIFile> nativeFile =
+      fileManager->GetFileForId(directory, fileInfo->Id());
+    if (!nativeFile) {
+      return nsnull;
+    }
+
+    PRUint64 size;
+    if (!JS_ReadBytes(aReader, &size, sizeof(PRUint64))) {
+      NS_WARNING("Failed to read size!");
+      return nsnull;
+    }
+    size = SwapBytes(size);
+
+    nsCString type;
+    if (!StructuredCloneReadString(aReader, type)) {
+      return nsnull;
+    }
+    NS_ConvertUTF8toUTF16 convType(type);
+
+    if (aTag == SCTAG_DOM_BLOB) {
+      nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(convType, size,
+                                                    nativeFile, fileInfo);
+
+      jsval wrappedBlob;
+      nsresult rv =
+        nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), blob,
+                                   &NS_GET_IID(nsIDOMBlob), &wrappedBlob);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to wrap native!");
+        return nsnull;
+      }
+
+      return JSVAL_TO_OBJECT(wrappedBlob);
+    }
+
+    nsCString name;
+    if (!StructuredCloneReadString(aReader, name)) {
+      return nsnull;
+    }
+    NS_ConvertUTF8toUTF16 convName(name);
+
+    nsCOMPtr<nsIDOMFile> file = new nsDOMFileFile(convName, convType, size,
+                                                  nativeFile, fileInfo);
+
+    jsval wrappedFile;
+    nsresult rv =
+      nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), file,
+                                 &NS_GET_IID(nsIDOMFile), &wrappedFile);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to wrap native!");
+      return nsnull;
+    }
+
+    return JSVAL_TO_OBJECT(wrappedFile);
+  }
+
+  const JSStructuredCloneCallbacks* runtimeCallbacks =
+    aCx->runtime->structuredCloneCallbacks;
+
+  if (runtimeCallbacks) {
+    return runtimeCallbacks->read(aCx, aReader, aTag, aData, nsnull);
+  }
+
+  return nsnull;
+}
+
+JSBool
+IDBObjectStore::StructuredCloneWriteCallback(JSContext* aCx,
+                                             JSStructuredCloneWriter* aWriter,
+                                             JSObject* aObj,
+                                             void* aClosure)
+{
+  StructuredCloneWriteInfo* cloneWriteInfo =
+    reinterpret_cast<StructuredCloneWriteInfo*>(aClosure);
+
+  if (JS_GET_CLASS(aCx, aObj) == &gDummyPropClass) {
+    NS_ASSERTION(cloneWriteInfo->mOffsetToKeyProp == 0,
+                 "We should not have been here before!");
+    cloneWriteInfo->mOffsetToKeyProp = js_GetSCOffset(aWriter);
+
+    PRUint64 value = 0;
+    return JS_WriteBytes(aWriter, &value, sizeof(value));
+  }
+
+  nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative;
+  nsContentUtils::XPConnect()->
+    GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative));
+
+  if (wrappedNative) {
+    nsISupports* supports = wrappedNative->Native();
+
+    nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(supports);
+    if (blob) {
+      nsCOMPtr<nsIDOMFile> file = do_QueryInterface(blob);
+
+      PRUint64 size;
+      if (NS_FAILED(blob->GetSize(&size))) {
+        return false;
+      }
+      size = SwapBytes(size);
+
+      nsString type;
+      if (NS_FAILED(blob->GetType(type))) {
+        return false;
+      }
+      NS_ConvertUTF16toUTF8 convType(type);
+      PRUint32 convTypeLength = SwapBytes(convType.Length());
+
+      if (!JS_WriteUint32Pair(aWriter, file ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
+                              cloneWriteInfo->mBlobs.Length()) ||
+          !JS_WriteBytes(aWriter, &size, sizeof(PRUint64)) ||
+          !JS_WriteBytes(aWriter, &convTypeLength, sizeof(PRUint32)) ||
+          !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
+        return false;
+      }
+
+      if (file) {
+        nsString name;
+        if (NS_FAILED(file->GetName(name))) {
+          return false;
+        }
+        NS_ConvertUTF16toUTF8 convName(name);
+        PRUint32 convNameLength = SwapBytes(convName.Length());
+
+        if (!JS_WriteBytes(aWriter, &convNameLength, sizeof(PRUint32)) ||
+            !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
+          return false;
+        }
+      }
+
+      cloneWriteInfo->mBlobs.AppendElement(blob);
+
+      return true;
+    }
+  }
+
+  // try using the runtime callbacks
+  const JSStructuredCloneCallbacks* runtimeCallbacks =
+    aCx->runtime->structuredCloneCallbacks;
+  if (runtimeCallbacks) {
+    return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull);
+  }
+
+  return false;
+}
+
 nsresult
-IDBObjectStore::ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer,
-                                     Key& aKey,
-                                     PRUint64 aOffsetToKeyProp)
+IDBObjectStore::ConvertFileIdsToArray(const nsAString& aFileIds,
+                                      nsTArray<PRInt64>& aResult)
+{
+  nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> tokenizer(aFileIds, ' ');
+
+  while (tokenizer.hasMoreTokens()) {
+    nsString token(tokenizer.nextToken());
+
+    NS_ASSERTION(!token.IsEmpty(), "Should be a valid id!");
+
+    nsresult rv;
+    PRInt32 id = token.ToInteger(&rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    
+    PRInt64* element = aResult.AppendElement();
+    *element = id;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+IDBObjectStore::ModifyValueForNewKey(StructuredCloneWriteInfo& aCloneWriteInfo,
+                                     Key& aKey)
 {
   NS_ASSERTION(IsAutoIncrement() && aKey.IsInteger(), "Don't call me!");
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread");
 
   // This is a duplicate of the js engine's byte munging here
   union {
     jsdouble d;
     PRUint64 u;
   } pun;
 
-  pun.d = SwapBytes(aKey.ToInteger());
-
-  memcpy((char*)aBuffer.data() + aOffsetToKeyProp, &pun.u, sizeof(PRUint64));
+  pun.d = SwapBytes(static_cast<PRUint64>(aKey.ToInteger()));
+
+  JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer;
+  PRUint64 offsetToKeyProp = aCloneWriteInfo.mOffsetToKeyProp;
+
+  memcpy((char*)buffer.data() + offsetToKeyProp, &pun.u, sizeof(PRUint64));
   return NS_OK;
 }
 
 IDBObjectStore::IDBObjectStore()
 : mId(LL_MININT),
   mAutoIncrement(false)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -910,20 +1162,19 @@ IDBObjectStore::~IDBObjectStore()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 nsresult
 IDBObjectStore::GetAddInfo(JSContext* aCx,
                            jsval aValue,
                            jsval aKeyVal,
-                           JSAutoStructuredCloneBuffer& aCloneBuffer,
+                           StructuredCloneWriteInfo& aCloneWriteInfo,
                            Key& aKey,
-                           nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
-                           PRUint64* aOffsetToKeyProp)
+                           nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
   nsresult rv;
 
   // Return DATA_ERR if a key was passed in and this objectStore uses inline
   // keys.
   if (!JSVAL_IS_VOID(aKeyVal) && HasKeyPath()) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
@@ -1061,28 +1312,22 @@ IDBObjectStore::GetAddInfo(JSContext* aC
             rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
             break;
           }
         }
       }
     }
   }
 
-  JSStructuredCloneCallbacks callbacks = {
-    nsnull,
-    StructuredCloneWriteDummyProp,
-    nsnull
-  };
-  *aOffsetToKeyProp = 0;
+  aCloneWriteInfo.mOffsetToKeyProp = 0;
 
   // We guard on rv being a success because we need to run the property
   // deletion code below even if we should not be serializing the value
   if (NS_SUCCEEDED(rv) && 
-      !IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue, &callbacks,
-                                      aOffsetToKeyProp)) {
+      !IDBObjectStore::SerializeValue(aCx, aCloneWriteInfo, aValue)) {
     rv = NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
   if (targetObject) {
     // If this fails, we lose, and the web page sees a magical property
     // appear on the object :-(
     jsval succeeded;
     if (!JS_DeleteUCProperty2(aCx, targetObject,
@@ -1113,38 +1358,37 @@ IDBObjectStore::AddOrPut(const jsval& aV
   }
 
   if (!IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   jsval keyval = (aOptionalArgCount >= 1) ? aKey : JSVAL_VOID;
 
-  JSAutoStructuredCloneBuffer cloneBuffer;
+  StructuredCloneWriteInfo cloneWriteInfo;
   Key key;
   nsTArray<IndexUpdateInfo> updateInfo;
-  PRUint64 offset;
-
-  nsresult rv =
-    GetAddInfo(aCx, aValue, keyval, cloneBuffer, key, updateInfo, &offset);
+
+  nsresult rv = GetAddInfo(aCx, aValue, keyval, cloneWriteInfo, key,
+                           updateInfo);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Put requires a key, unless this is an autoIncrementing objectStore.
   if (aOverwrite && !mAutoIncrement && key.IsUnset()) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   nsRefPtr<IDBRequest> request = GenerateRequest(this);
   NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<AddHelper> helper =
-    new AddHelper(mTransaction, request, this, cloneBuffer, key, aOverwrite,
-                  updateInfo, offset);
+    new AddHelper(mTransaction, request, this, cloneWriteInfo, key, aOverwrite,
+                  updateInfo);
 
   rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
 
@@ -1683,16 +1927,41 @@ IDBObjectStore::Count(const jsval& aKey,
     new CountHelper(mTransaction, request, this, keyRange);
   rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
 
+inline nsresult
+CopyData(nsIInputStream* aStream, quota_FILE* aFile)
+{
+  do {
+    char copyBuffer[FILE_COPY_BUFFER_SIZE];
+
+    PRUint32 numRead;
+    nsresult rv = aStream->Read(copyBuffer, FILE_COPY_BUFFER_SIZE, &numRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (numRead <= 0) {
+      break;
+    }
+
+    size_t numWrite = sqlite3_quota_fwrite(copyBuffer, 1, numRead, aFile);
+    NS_ENSURE_TRUE(numWrite == numRead, NS_ERROR_FAILURE);
+  } while (true);
+
+  // Flush and sync
+  NS_ENSURE_TRUE(sqlite3_quota_fflush(aFile, 1) == 0,
+                 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  return NS_OK;
+}
+
 nsresult
 AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(aConnection, "Passed a null connection!");
 
   nsresult rv;
   bool mayOverwrite = mOverwrite;
   bool unsetKey = mKey.IsUnset();
@@ -1750,16 +2019,74 @@ AddHelper::DoDatabaseWork(mozIStorageCon
     rv = stmt->ExecuteStep(&hasResult);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
     if (hasResult) {
       return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
     }
   }
 
+  nsRefPtr<FileManager> fileManager = mDatabase->Manager();
+  nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
+  NS_ENSURE_TRUE(directory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsAutoString fileIds;
+
+  for (PRUint32 index = 0; index < mCloneWriteInfo.mBlobs.Length(); index++) {
+    nsCOMPtr<nsIDOMBlob>& domBlob = mCloneWriteInfo.mBlobs[index];
+
+    PRInt64 id = -1;
+
+    // Check if it is a blob created from this db or the blob was already
+    // stored in this db
+    nsRefPtr<FileInfo> fileInfo = domBlob->GetFileInfo(fileManager);
+    if (fileInfo) {
+      id = fileInfo->Id();
+    }
+
+    if (id == -1) {
+      fileInfo = fileManager->GetNewFileInfo();
+      NS_ENSURE_TRUE(fileInfo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      id = fileInfo->Id();
+
+      mTransaction->OnNewFileInfo(fileInfo);
+
+      // Copy it
+      nsCOMPtr<nsIInputStream> inputStream;
+      rv = domBlob->GetInternalStream(getter_AddRefs(inputStream));
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      nsCOMPtr<nsIFile> nativeFile = fileManager->GetFileForId(directory, id);
+      NS_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      nsString nativeFilePath;
+      rv = nativeFile->GetPath(nativeFilePath);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      quota_FILE* file =
+        sqlite3_quota_fopen(NS_ConvertUTF16toUTF8(nativeFilePath).get(), "wb");
+      NS_ENSURE_TRUE(file, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      rv = CopyData(inputStream, file);
+
+      NS_ENSURE_TRUE(sqlite3_quota_fclose(file) == 0,
+                     NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      domBlob->AddFileInfo(fileInfo);
+    }
+
+    if (index) {
+      fileIds.Append(NS_LITERAL_STRING(" "));
+    }
+    fileIds.AppendInt(id);
+  }
+
   // Now we add it to the database (or update, depending on our variables).
   stmt = mTransaction->AddStatement(true, mayOverwrite, autoIncrement);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   NS_NAMED_LITERAL_CSTRING(keyValue, "key_value");
 
@@ -1788,32 +2115,39 @@ AddHelper::DoDatabaseWork(mozIStorageCon
   // have the appropriate key value.
   if (autoIncrement && !mOverwrite && !keyPath.IsEmpty() && unsetKey) {
     rv = stmt->BindInt32ByName(data, 0);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
   else {
     // Compress the bytes before adding into the database.
     const char* uncompressed =
-      reinterpret_cast<const char*>(mCloneBuffer.data());
-    size_t uncompressedLength = mCloneBuffer.nbytes();
+      reinterpret_cast<const char*>(mCloneWriteInfo.mCloneBuffer.data());
+    size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes();
 
     size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
     compressed = new char[compressedLength];
 
     snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
                         &compressedLength);
 
     dataBuffer = reinterpret_cast<const PRUint8*>(compressed.get());
     dataBufferLength = compressedLength;
 
     rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
 
+  if (fileIds.IsEmpty()) {
+    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids"));
+  } else {
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds);
+  }
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   rv = stmt->Execute();
   if (NS_FAILED(rv)) {
     if (mayOverwrite && rv == NS_ERROR_STORAGE_CONSTRAINT) {
       scoper.Abandon();
 
       rv = stmt->Reset();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
@@ -1830,16 +2164,23 @@ AddHelper::DoDatabaseWork(mozIStorageCon
       rv = mKey.BindToStatement(stmt, keyValue);
       NS_ENSURE_SUCCESS(rv, rv);
 
       NS_ASSERTION(dataBuffer && dataBufferLength, "These should be set!");
 
       rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+      if (fileIds.IsEmpty()) {
+        rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids"));
+      } else {
+        rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds);
+      }
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
       rv = stmt->Execute();
     }
 
     if (NS_FAILED(rv)) {
       return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
     }
   }
 
@@ -1862,18 +2203,17 @@ AddHelper::DoDatabaseWork(mozIStorageCon
       NS_ASSERTION(mKey.ToInteger() == oldKey, "Something went haywire!");
     }
 #endif
 
     if (!keyPath.IsEmpty() && unsetKey) {
       // Special case where someone put an object into an autoIncrement'ing
       // objectStore with no key in its keyPath set. We needed to figure out
       // which row id we would get above before we could set that properly.
-      rv = mObjectStore->ModifyValueForNewKey(mCloneBuffer, mKey,
-                                              mOffsetToKeyProp);
+      rv = mObjectStore->ModifyValueForNewKey(mCloneWriteInfo, mKey);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       scoper.Abandon();
       rv = stmt->Reset();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       stmt = mTransaction->AddStatement(false, true, true);
       NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -1884,32 +2224,39 @@ AddHelper::DoDatabaseWork(mozIStorageCon
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       rv = mKey.BindToStatement(stmt, keyValue);
       NS_ENSURE_SUCCESS(rv, rv);
 
       NS_ASSERTION(!dataBuffer && !dataBufferLength, "These should be unset!");
 
       const char* uncompressed =
-        reinterpret_cast<const char*>(mCloneBuffer.data());
-      size_t uncompressedLength = mCloneBuffer.nbytes();
+        reinterpret_cast<const char*>(mCloneWriteInfo.mCloneBuffer.data());
+      size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes();
 
       size_t compressedLength =
         snappy::MaxCompressedLength(uncompressedLength);
       compressed = new char[compressedLength];
 
       snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
                           &compressedLength);
 
       dataBuffer = reinterpret_cast<const PRUint8*>(compressed.get());
       dataBufferLength = compressedLength;
 
       rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+      if (fileIds.IsEmpty()) {
+        rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids"));
+      } else {
+        rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds);
+      }
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     }
   }
 
   // Update our indexes if needed.
   if (mOverwrite || !mIndexUpdateInfo.IsEmpty()) {
     PRInt64 objectDataId = autoIncrement ? mKey.ToInteger() : LL_MININT;
@@ -1926,17 +2273,17 @@ AddHelper::DoDatabaseWork(mozIStorageCon
 }
 
 nsresult
 AddHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
   NS_ASSERTION(!mKey.IsUnset(), "Badness!");
 
-  mCloneBuffer.clear();
+  mCloneWriteInfo.mCloneBuffer.clear();
 
   return mKey.ToJSVal(aCx, aVal);
 }
 
 nsresult
 GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */)
 {
   NS_ASSERTION(mKeyRange, "Must have a key range here!");
@@ -1954,17 +2301,17 @@ GetHelper::DoDatabaseWork(mozIStorageCon
 
   nsCString keyRangeClause;
   mKeyRange->GetBindingClause(value, keyRangeClause);
 
   NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!");
 
   NS_NAMED_LITERAL_CSTRING(osid, "osid");
 
-  nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + table +
+  nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table +
                     NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid +
                     keyRangeClause + NS_LITERAL_CSTRING(" LIMIT 1");
 
   nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
@@ -1974,31 +2321,31 @@ GetHelper::DoDatabaseWork(mozIStorageCon
   rv = mKeyRange->BindToStatement(stmt);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasResult;
   rv = stmt->ExecuteStep(&hasResult);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   if (hasResult) {
-    rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0,
-                                                             mCloneBuffer);
+    rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1,
+      mDatabase->Manager(), mCloneReadInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
 GetHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
-  bool result = IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, aVal);
-
-  mCloneBuffer.clear();
+  bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal);
+
+  mCloneReadInfo.mCloneBuffer.clear();
 
   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 nsresult
 DeleteHelper::DoDatabaseWork(mozIStorageConnection* /*aConnection */)
 {
@@ -2116,17 +2463,17 @@ OpenCursorHelper::DoDatabaseWork(mozISto
       directionClause += NS_LITERAL_CSTRING(" DESC");
       break;
 
     default:
       NS_NOTREACHED("Unknown direction type!");
   }
 
   nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn +
-                         NS_LITERAL_CSTRING(", data FROM ") + table +
+                         NS_LITERAL_CSTRING(", data, file_ids FROM ") + table +
                          NS_LITERAL_CSTRING(" WHERE object_store_id = :") +
                          id + keyRangeClause + directionClause +
                          NS_LITERAL_CSTRING(" LIMIT 1");
 
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(firstQuery);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
@@ -2147,18 +2494,18 @@ OpenCursorHelper::DoDatabaseWork(mozISto
   if (!hasResult) {
     mKey.Unset();
     return NS_OK;
   }
 
   rv = mKey.SetFromStatement(stmt, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 1,
-                                                           mCloneBuffer);
+  rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2,
+    mDatabase->Manager(), mCloneReadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Now we need to make the query to get the next match.
   keyRangeClause.Truncate();
   nsCAutoString continueToKeyRangeClause;
 
   NS_NAMED_LITERAL_CSTRING(currentKey, "current_key");
   NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
@@ -2195,23 +2542,23 @@ OpenCursorHelper::DoDatabaseWork(mozISto
       }
       break;
 
     default:
       NS_NOTREACHED("Unknown direction type!");
   }
 
   mContinueQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn +
-                   NS_LITERAL_CSTRING(", data FROM ") + table +
+                   NS_LITERAL_CSTRING(", data, file_ids FROM ") + table +
                    NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                    keyRangeClause + directionClause +
                    NS_LITERAL_CSTRING(" LIMIT ");
 
   mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn +
-                     NS_LITERAL_CSTRING(", data FROM ") + table +
+                     NS_LITERAL_CSTRING(", data, file_ids FROM ") + table +
                      NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                      continueToKeyRangeClause + directionClause +
                      NS_LITERAL_CSTRING(" LIMIT ");
 
   return NS_OK;
 }
 
 nsresult
@@ -2221,20 +2568,20 @@ OpenCursorHelper::GetSuccessResult(JSCon
   if (mKey.IsUnset()) {
     *aVal = JSVAL_VOID;
     return NS_OK;
   }
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection,
                       mRangeKey, mContinueQuery, mContinueToQuery, mKey,
-                      mCloneBuffer);
+                      mCloneReadInfo);
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!");
+  NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!");
 
   return WrapNative(aCx, cursor, aVal);
 }
 
 class ThreadLocalJSRuntime
 {
   JSRuntime* mRuntime;
   JSContext* mContext;
@@ -2386,21 +2733,21 @@ CreateIndexHelper::DoDatabaseWork(mozISt
 
 nsresult
 CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection)
 {
   nsCAutoString table;
   nsCAutoString columns;
   if (mIndex->IsAutoIncrement()) {
     table.AssignLiteral("ai_object_data");
-    columns.AssignLiteral("id, data");
+    columns.AssignLiteral("id, data, file_ids");
   }
   else {
     table.AssignLiteral("object_data");
-    columns.AssignLiteral("id, data, key_value");
+    columns.AssignLiteral("id, data, file_ids, key_value");
   }
 
   nsCString query = NS_LITERAL_CSTRING("SELECT ") + columns +
                     NS_LITERAL_CSTRING(" FROM ") + table +
                     NS_LITERAL_CSTRING(" WHERE object_store_id = :osid");
 
   nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -2430,22 +2777,31 @@ CreateIndexHelper::InsertDataFromObjectS
 
     PR_SetThreadPrivate(sTLSIndex, tlsEntry);
   }
 
   JSContext* cx = tlsEntry->Context();
   JSAutoRequest ar(cx);
 
   do {
-    JSAutoStructuredCloneBuffer buffer;
-    rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 1, buffer);
+    StructuredCloneReadInfo cloneReadInfo;
+    rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2,
+      mDatabase->Manager(), cloneReadInfo);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    JSAutoStructuredCloneBuffer& buffer = cloneReadInfo.mCloneBuffer;
+
+    JSStructuredCloneCallbacks callbacks = {
+      IDBObjectStore::StructuredCloneReadCallback,
+      nsnull,
+      nsnull
+    };
+
     jsval clone;
-    if (!buffer.read(cx, &clone)) {
+    if (!buffer.read(cx, &clone, &callbacks, &cloneReadInfo)) {
       NS_WARNING("Failed to deserialize structured clone data!");
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
 
     nsTArray<IndexUpdateInfo> updateInfo;
     rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(),
                                                mIndex->KeyPath(),
                                                mIndex->IsUnique(),
@@ -2453,17 +2809,17 @@ CreateIndexHelper::InsertDataFromObjectS
                                                tlsEntry->Context(),
                                                clone, updateInfo);
     NS_ENSURE_SUCCESS(rv, rv);
 
     PRInt64 objectDataID = stmt->AsInt64(0);
 
     Key key;
     if (!mIndex->IsAutoIncrement()) {
-      rv = key.SetFromStatement(stmt, 2);
+      rv = key.SetFromStatement(stmt, 3);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
       key.SetFromInteger(objectDataID);
     }
 
     rv = IDBObjectStore::UpdateIndexes(mTransaction, mIndex->Id(),
                                        key, mIndex->IsAutoIncrement(),
@@ -2545,22 +2901,22 @@ GetAllHelper::DoDatabaseWork(mozIStorage
   }
 
   nsCAutoString limitClause;
   if (mLimit != PR_UINT32_MAX) {
     limitClause.AssignLiteral(" LIMIT ");
     limitClause.AppendInt(mLimit);
   }
 
-  nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + table +
+  nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table +
                     NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid +
                     keyRangeClause + NS_LITERAL_CSTRING(" ORDER BY ") +
                     keyColumn + NS_LITERAL_CSTRING(" ASC") + limitClause;
 
-  mCloneBuffers.SetCapacity(50);
+  mCloneReadInfos.SetCapacity(50);
 
   nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -2573,44 +2929,45 @@ GetAllHelper::DoDatabaseWork(mozIStorage
     if (!mKeyRange->Upper().IsUnset()) {
       rv = mKeyRange->Upper().BindToStatement(stmt, upperKeyName);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   bool hasResult;
   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
-    if (mCloneBuffers.Capacity() == mCloneBuffers.Length()) {
-      if (!mCloneBuffers.SetCapacity(mCloneBuffers.Capacity() * 2)) {
+    if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) {
+      if (!mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2)) {
         NS_ERROR("Out of memory!");
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
     }
 
-    JSAutoStructuredCloneBuffer* buffer = mCloneBuffers.AppendElement();
-    NS_ASSERTION(buffer, "Shouldn't fail if SetCapacity succeeded!");
-
-    rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, *buffer);
+    StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement();
+    NS_ASSERTION(readInfo, "Shouldn't fail if SetCapacity succeeded!");
+
+    rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1,
+      mDatabase->Manager(), *readInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 nsresult
 GetAllHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
-  NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!");
-
-  nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
-
-  for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-    mCloneBuffers[index].clear();
+  NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!");
+
+  nsresult rv = ConvertCloneReadInfosToArray(aCx, mCloneReadInfos, aVal);
+
+  for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) {
+    mCloneReadInfos[index].mCloneBuffer.clear();
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 CountHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -54,16 +54,18 @@ class nsPIDOMWindow;
 BEGIN_INDEXEDDB_NAMESPACE
 
 class AsyncConnectionHelper;
 class Key;
 
 struct ObjectStoreInfo;
 struct IndexInfo;
 struct IndexUpdateInfo;
+struct StructuredCloneReadInfo;
+struct StructuredCloneWriteInfo;
 
 class IDBObjectStore : public nsIIDBObjectStore
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIIDBOBJECTSTORE
 
   NS_DECL_CYCLE_COLLECTION_CLASS(IDBObjectStore)
@@ -89,36 +91,50 @@ public:
                 PRInt64 aObjectStoreId,
                 const Key& aObjectStoreKey,
                 bool aAutoIncrement,
                 bool aOverwrite,
                 PRInt64 aObjectDataId,
                 const nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   static nsresult
-  GetStructuredCloneDataFromStatement(mozIStorageStatement* aStatement,
-                                      PRUint32 aIndex,
-                                      JSAutoStructuredCloneBuffer& aBuffer);
+  GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
+                                          PRUint32 aDataIndex,
+                                          PRUint32 aFileIdsIndex,
+                                          FileManager* aFileManager,
+                                          StructuredCloneReadInfo& aInfo);
 
   static void
   ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer);
 
   static bool
   DeserializeValue(JSContext* aCx,
-                   JSAutoStructuredCloneBuffer& aBuffer,
-                   jsval* aValue,
-                   JSStructuredCloneCallbacks* aCallbacks = nsnull,
-                   void* aClosure = nsnull);
+                   StructuredCloneReadInfo& aCloneReadInfo,
+                   jsval* aValue);
 
   static bool
   SerializeValue(JSContext* aCx,
-                 JSAutoStructuredCloneBuffer& aBuffer,
-                 jsval aValue,
-                 JSStructuredCloneCallbacks* aCallbacks = nsnull,
-                 void* aClosure = nsnull);
+                 StructuredCloneWriteInfo& aCloneWriteInfo,
+                 jsval aValue);
+
+  static JSObject*
+  StructuredCloneReadCallback(JSContext* aCx,
+                              JSStructuredCloneReader* aReader,
+                              uint32 aTag,
+                              uint32 aData,
+                              void* aClosure);
+  static JSBool
+  StructuredCloneWriteCallback(JSContext* aCx,
+                               JSStructuredCloneWriter* aWriter,
+                               JSObject* aObj,
+                               void* aClosure);
+
+  static nsresult
+  ConvertFileIdsToArray(const nsAString& aFileIds,
+                        nsTArray<PRInt64>& aResult);
 
   const nsString& Name() const
   {
     return mName;
   }
 
   bool IsAutoIncrement() const
   {
@@ -146,31 +162,29 @@ public:
     return !mKeyPath.IsVoid();
   }
 
   IDBTransaction* Transaction()
   {
     return mTransaction;
   }
 
-  nsresult ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer,
-                                Key& aKey,
-                                PRUint64 aOffsetToKeyProp);
+  nsresult ModifyValueForNewKey(StructuredCloneWriteInfo& aCloneWriteInfo,
+                                Key& aKey);
 
 protected:
   IDBObjectStore();
   ~IDBObjectStore();
 
   nsresult GetAddInfo(JSContext* aCx,
                       jsval aValue,
                       jsval aKeyVal,
-                      JSAutoStructuredCloneBuffer& aCloneBuffer,
+                      StructuredCloneWriteInfo& aCloneWriteInfo,
                       Key& aKey,
-                      nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
-                      PRUint64* aOffsetToKeyProp);
+                      nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   nsresult AddOrPut(const jsval& aValue,
                     const jsval& aKey,
                     JSContext* aCx,
                     PRUint8 aOptionalArgCount,
                     nsIIDBRequest** _retval,
                     bool aOverwrite);
 
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -38,16 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "IDBTransaction.h"
 
 #include "nsIScriptContext.h"
 
 #include "mozilla/storage.h"
 #include "nsDOMClassInfoID.h"
+#include "nsDOMLists.h"
 #include "nsEventDispatcher.h"
 #include "nsPIDOMWindow.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 #include "AsyncConnectionHelper.h"
 #include "DatabaseInfo.h"
 #include "IDBCursor.h"
@@ -320,32 +321,45 @@ IDBTransaction::GetOrCreateConnection(mo
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!mConnection) {
     nsCOMPtr<mozIStorageConnection> connection =
       IDBFactory::GetConnection(mDatabase->FilePath());
     NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE);
 
+    nsresult rv;
+
+    nsRefPtr<UpdateRefcountFunction> function;
     nsCString beginTransaction;
     if (mMode != nsIIDBTransaction::READ_ONLY) {
+      function = new UpdateRefcountFunction(Database()->Manager());
+      NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY);
+
+      rv = function->Init();
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = connection->CreateFunction(
+        NS_LITERAL_CSTRING("update_refcount"), 2, function);
+      NS_ENSURE_SUCCESS(rv, rv);
+
       beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
     }
     else {
       beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
     }
 
     nsCOMPtr<mozIStorageStatement> stmt;
-    nsresult rv = connection->CreateStatement(beginTransaction,
-                                              getter_AddRefs(stmt));
-    NS_ENSURE_SUCCESS(rv, false);
+    rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
 
     rv = stmt->Execute();
-    NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_SUCCESS(rv, rv);
 
+    function.swap(mUpdateFileRefcountFunction);
     connection.swap(mConnection);
   }
 
   nsCOMPtr<mozIStorageConnection> result(mConnection);
   result.forget(aResult);
   return NS_OK;
 }
 
@@ -359,47 +373,49 @@ IDBTransaction::AddStatement(bool aCreat
     NS_ASSERTION(aOverwrite, "Bad param combo!");
   }
 #endif
 
   if (aAutoIncrement) {
     if (aCreate) {
       if (aOverwrite) {
         return GetCachedStatement(
-          "INSERT OR FAIL INTO ai_object_data (object_store_id, id, data) "
-          "VALUES (:osid, :key_value, :data)"
+          "INSERT OR FAIL INTO ai_object_data (object_store_id, id, data, "
+          "file_ids) "
+          "VALUES (:osid, :key_value, :data, :file_ids)"
         );
       }
       return GetCachedStatement(
-        "INSERT INTO ai_object_data (object_store_id, data) "
-        "VALUES (:osid, :data)"
+        "INSERT INTO ai_object_data (object_store_id, data, file_ids) "
+        "VALUES (:osid, :data, :file_ids)"
       );
     }
     return GetCachedStatement(
       "UPDATE ai_object_data "
-      "SET data = :data "
+      "SET data = :data, file_ids = :file_ids "
       "WHERE object_store_id = :osid "
       "AND id = :key_value"
     );
   }
   if (aCreate) {
     if (aOverwrite) {
       return GetCachedStatement(
-        "INSERT OR FAIL INTO object_data (object_store_id, key_value, data) "
-        "VALUES (:osid, :key_value, :data)"
+        "INSERT OR FAIL INTO object_data (object_store_id, key_value, data, "
+        "file_ids) "
+        "VALUES (:osid, :key_value, :data, :file_ids)"
       );
     }
     return GetCachedStatement(
-      "INSERT INTO object_data (object_store_id, key_value, data) "
-      "VALUES (:osid, :key_value, :data)"
+      "INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
+      "VALUES (:osid, :key_value, :data, :file_ids)"
     );
   }
   return GetCachedStatement(
     "UPDATE object_data "
-    "SET data = :data "
+    "SET data = :data, file_ids = :file_ids "
     "WHERE object_store_id = :osid "
     "AND key_value = :key_value"
   );
 }
 
 already_AddRefed<mozIStorageStatement>
 IDBTransaction::IndexDataInsertStatement(bool aAutoIncrement,
                                          bool aUnique)
@@ -547,16 +563,28 @@ IDBTransaction::GetOrCreateObjectStore(c
   if (!mCreatedObjectStores.AppendElement(retval)) {
     NS_WARNING("Out of memory!");
     return nsnull;
   }
 
   return retval.forget();
 }
 
+void
+IDBTransaction::OnNewFileInfo(FileInfo* aFileInfo)
+{
+  mCreatedFileInfos.AppendElement(aFileInfo);
+}
+
+void
+IDBTransaction::ClearCreatedFileInfos()
+{
+  mCreatedFileInfos.Clear();
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
                                                   nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDatabase,
                                                        nsIDOMEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCompleteListener)
@@ -822,32 +850,41 @@ IDBTransaction::AfterProcessNextEvent(ns
 CommitHelper::CommitHelper(IDBTransaction* aTransaction,
                            IDBTransactionListener* aListener)
 : mTransaction(aTransaction),
   mListener(aListener),
   mAborted(!!aTransaction->mAborted),
   mHaveMetadata(false)
 {
   mConnection.swap(aTransaction->mConnection);
+  mUpdateFileRefcountFunction.swap(aTransaction->mUpdateFileRefcountFunction);
 }
 
 CommitHelper::~CommitHelper()
 {
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(CommitHelper, nsIRunnable)
 
 NS_IMETHODIMP
 CommitHelper::Run()
 {
   if (NS_IsMainThread()) {
     NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!");
 
     mTransaction->mReadyState = nsIIDBTransaction::DONE;
 
+    // Release file infos on the main thread, so they will eventually get
+    // destroyed on correct thread.
+    mTransaction->ClearCreatedFileInfos();
+    if (mUpdateFileRefcountFunction) {
+      mUpdateFileRefcountFunction->ClearFileInfoEntries();
+      mUpdateFileRefcountFunction = nsnull;
+    }
+
     nsCOMPtr<nsIDOMEvent> event;
     if (mAborted) {
       if (mHaveMetadata) {
         NS_ASSERTION(mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE,
                      "Bad transaction type!");
 
         DatabaseInfo* dbInfo = mTransaction->Database()->Info();
 
@@ -891,19 +928,29 @@ CommitHelper::Run()
   IDBDatabase* database = mTransaction->Database();
   if (database->IsInvalidated()) {
     mAborted = true;
   }
 
   if (mConnection) {
     IndexedDatabaseManager::SetCurrentWindow(database->Owner());
 
+    if (!mAborted && mUpdateFileRefcountFunction &&
+        NS_FAILED(mUpdateFileRefcountFunction->UpdateDatabase(mConnection))) {
+      mAborted = true;
+    }
+
     if (!mAborted) {
       NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION");
-      if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
+      if (NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(release))) {
+        if (mUpdateFileRefcountFunction) {
+          mUpdateFileRefcountFunction->UpdateFileInfos();
+        }
+      }
+      else {
         mAborted = true;
       }
     }
 
     if (mAborted) {
       NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
       if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
         NS_WARNING("Failed to rollback transaction!");
@@ -922,16 +969,212 @@ CommitHelper::Run()
         }
       }
     }
   }
 
   mDoomedObjects.Clear();
 
   if (mConnection) {
+    if (mUpdateFileRefcountFunction) {
+      nsresult rv = mConnection->RemoveFunction(
+        NS_LITERAL_CSTRING("update_refcount"));
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to remove function!");
+      }
+    }
+
     mConnection->Close();
     mConnection = nsnull;
 
     IndexedDatabaseManager::SetCurrentWindow(nsnull);
   }
 
   return NS_OK;
 }
+
+nsresult
+UpdateRefcountFunction::Init()
+{
+  NS_ENSURE_TRUE(mFileInfoEntries.Init(), NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(UpdateRefcountFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+                                       nsIVariant** _retval)
+{
+  *_retval = nsnull;
+
+  PRUint32 numEntries;
+  nsresult rv = aValues->GetNumEntries(&numEntries);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
+
+#ifdef DEBUG
+  PRInt32 type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
+  aValues->GetTypeOfIndex(0, &type1);
+
+  PRInt32 type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
+  aValues->GetTypeOfIndex(1, &type2);
+
+  NS_ASSERTION(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
+                 type2 == mozIStorageValueArray::VALUE_TYPE_NULL),
+               "Shouldn't be called!");
+#endif
+
+  rv = ProcessValue(aValues, 0, eDecrement);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = ProcessValue(aValues, 1, eIncrement);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
+                                     PRInt32 aIndex,
+                                     UpdateType aUpdateType)
+{
+  PRInt32 type;
+  aValues->GetTypeOfIndex(aIndex, &type);
+  if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
+    return NS_OK;
+  }
+
+  nsString ids;
+  aValues->GetString(aIndex, ids);
+
+  nsTArray<PRInt64> fileIds;
+  nsresult rv = IDBObjectStore::ConvertFileIdsToArray(ids, fileIds);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (PRUint32 i = 0; i < fileIds.Length(); i++) {
+    PRInt64 id = fileIds.ElementAt(i);
+
+    FileInfoEntry* entry;
+    if (!mFileInfoEntries.Get(id, &entry)) {
+      nsRefPtr<FileInfo> fileInfo = mFileManager->GetFileInfo(id);
+      NS_ASSERTION(fileInfo, "Shouldn't be null!");
+
+      nsAutoPtr<FileInfoEntry> newEntry(new FileInfoEntry(fileInfo));
+      if (!mFileInfoEntries.Put(id, newEntry)) {
+        NS_WARNING("Out of memory?");
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+      entry = newEntry.forget();
+    }
+
+    switch (aUpdateType) {
+      case eIncrement:
+        entry->mDelta++;
+        break;
+      case eDecrement:
+        entry->mDelta--;
+        break;
+      default:
+        NS_NOTREACHED("Unknown update type!");
+    }
+  }
+
+  return NS_OK;
+}
+
+PLDHashOperator
+UpdateRefcountFunction::DatabaseUpdateCallback(const PRUint64& aKey,
+                                               FileInfoEntry* aValue,
+                                               void* aUserArg)
+{
+  if (!aValue->mDelta) {
+    return PL_DHASH_NEXT;
+  }
+
+  DatabaseUpdateFunction* function =
+    static_cast<DatabaseUpdateFunction*>(aUserArg);
+
+  if (!function->Update(aKey, aValue->mDelta)) {
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+PLDHashOperator
+UpdateRefcountFunction::FileInfoUpdateCallback(const PRUint64& aKey,
+                                               FileInfoEntry* aValue,
+                                               void* aUserArg)
+{
+  if (aValue->mDelta) {
+    aValue->mFileInfo->UpdateDBRefs(aValue->mDelta);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+bool
+UpdateRefcountFunction::DatabaseUpdateFunction::Update(PRInt64 aId,
+                                                       PRInt32 aDelta)
+{
+  nsresult rv = UpdateInternal(aId, aDelta);
+  if (NS_FAILED(rv)) {
+    mErrorCode = rv;
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+UpdateRefcountFunction::DatabaseUpdateFunction::UpdateInternal(PRInt64 aId,
+                                                               PRInt32 aDelta)
+{
+  nsresult rv;
+
+  if (!mUpdateStatement) {
+    rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+      "UPDATE file SET refcount = refcount + :delta WHERE id = :id"
+    ), getter_AddRefs(mUpdateStatement));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mozStorageStatementScoper updateScoper(mUpdateStatement);
+
+  rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mUpdateStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRInt32 rows;
+  rv = mConnection->GetAffectedRows(&rows);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (rows > 0) {
+    return NS_OK;
+  }
+
+  if (!mInsertStatement) {
+    rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+      "INSERT INTO file (id, refcount) VALUES(:id, :delta)"
+    ), getter_AddRefs(mInsertStatement));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mozStorageStatementScoper insertScoper(mInsertStatement);
+
+  rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mInsertStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -37,38 +37,42 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_dom_indexeddb_idbtransaction_h__
 #define mozilla_dom_indexeddb_idbtransaction_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 #include "mozilla/dom/indexedDB/IDBDatabase.h"
+#include "mozilla/dom/indexedDB/FileInfo.h"
 
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageFunction.h"
 #include "nsIIDBTransaction.h"
 #include "nsIRunnable.h"
 #include "nsIThreadInternal.h"
 
 #include "nsDOMEventTargetHelper.h"
 #include "nsCycleCollectionParticipant.h"
 
 #include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsInterfaceHashtable.h"
 
-class mozIStorageConnection;
-class mozIStorageStatement;
 class nsIThread;
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class AsyncConnectionHelper;
 class CommitHelper;
 struct ObjectStoreInfo;
 class TransactionThreadPool;
+class UpdateRefcountFunction;
 
 class IDBTransactionListener
 {
 public:
   NS_IMETHOD_(nsrefcnt) AddRef() = 0;
   NS_IMETHOD_(nsrefcnt) Release() = 0;
 
   virtual nsresult NotifyTransactionComplete(IDBTransaction* aTransaction) = 0;
@@ -162,16 +166,20 @@ public:
     NS_ASSERTION(mDatabase, "This should never be null!");
     return mDatabase;
   }
 
   already_AddRefed<IDBObjectStore>
   GetOrCreateObjectStore(const nsAString& aName,
                          ObjectStoreInfo* aObjectStoreInfo);
 
+  void OnNewFileInfo(FileInfo* aFileInfo);
+
+  void ClearCreatedFileInfos();
+
 private:
   IDBTransaction();
   ~IDBTransaction();
 
   nsresult CommitOrRollback();
 
   nsRefPtr<IDBDatabase> mDatabase;
   nsTArray<nsString> mObjectStoreNames;
@@ -199,16 +207,19 @@ private:
   nsTArray<nsRefPtr<IDBObjectStore> > mCreatedObjectStores;
 
   bool mAborted;
   bool mCreating;
 
 #ifdef DEBUG
   bool mFiredCompleteOrAbort;
 #endif
+
+  nsRefPtr<UpdateRefcountFunction> mUpdateFileRefcountFunction;
+  nsTArray<nsRefPtr<FileInfo> > mCreatedFileInfos;
 };
 
 class CommitHelper : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
@@ -228,20 +239,116 @@ public:
     }
     return true;
   }
 
 private:
   nsRefPtr<IDBTransaction> mTransaction;
   nsRefPtr<IDBTransactionListener> mListener;
   nsCOMPtr<mozIStorageConnection> mConnection;
+  nsRefPtr<UpdateRefcountFunction> mUpdateFileRefcountFunction;
   nsAutoTArray<nsCOMPtr<nsISupports>, 10> mDoomedObjects;
 
   PRUint64 mOldVersion;
   nsTArray<nsAutoPtr<ObjectStoreInfo> > mOldObjectStores;
 
   bool mAborted;
   bool mHaveMetadata;
 };
 
+class UpdateRefcountFunction : public mozIStorageFunction
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+
+  UpdateRefcountFunction(FileManager* aFileManager)
+  : mFileManager(aFileManager)
+  { }
+
+  ~UpdateRefcountFunction()
+  { }
+
+  nsresult Init();
+
+  void ClearFileInfoEntries()
+  {
+    mFileInfoEntries.Clear();
+  }
+
+  nsresult UpdateDatabase(mozIStorageConnection* aConnection)
+  {
+    DatabaseUpdateFunction function(aConnection);
+
+    mFileInfoEntries.EnumerateRead(DatabaseUpdateCallback, &function);
+
+    return function.ErrorCode();
+  }
+
+  void UpdateFileInfos()
+  {
+    mFileInfoEntries.EnumerateRead(FileInfoUpdateCallback, nsnull);
+  }
+
+private:
+  class FileInfoEntry
+  {
+  public:
+    FileInfoEntry(FileInfo* aFileInfo)
+    : mFileInfo(aFileInfo), mDelta(0)
+    { }
+
+    ~FileInfoEntry()
+    { }
+
+    nsRefPtr<FileInfo> mFileInfo;
+    PRInt32 mDelta;
+  };
+
+  enum UpdateType {
+    eIncrement,
+    eDecrement
+  };
+
+  class DatabaseUpdateFunction
+  {
+  public:
+    DatabaseUpdateFunction(mozIStorageConnection* aConnection)
+    : mConnection(aConnection), mErrorCode(NS_OK)
+    { }
+
+    bool Update(PRInt64 aId, PRInt32 aDelta);
+    nsresult ErrorCode()
+    {
+      return mErrorCode;
+    }
+
+  private:
+    nsresult UpdateInternal(PRInt64 aId, PRInt32 aDelta);
+
+    nsCOMPtr<mozIStorageConnection> mConnection;
+    nsCOMPtr<mozIStorageStatement> mUpdateStatement;
+    nsCOMPtr<mozIStorageStatement> mInsertStatement;
+
+    nsresult mErrorCode;
+  };
+
+  nsresult ProcessValue(mozIStorageValueArray* aValues,
+                        PRInt32 aIndex,
+                        UpdateType aUpdateType);
+
+  static PLDHashOperator
+  DatabaseUpdateCallback(const PRUint64& aKey,
+                         FileInfoEntry* aValue,
+                         void* aUserArg);
+
+  static PLDHashOperator
+  FileInfoUpdateCallback(const PRUint64& aKey,
+                         FileInfoEntry* aValue,
+                         void* aUserArg);
+
+  FileManager* mFileManager;
+  nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
+};
+
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbtransaction_h__
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -55,18 +55,44 @@
   namespace mozilla { namespace dom { namespace indexedDB {
 
 #define END_INDEXEDDB_NAMESPACE \
   } /* namespace indexedDB */ } /* namepsace dom */ } /* namespace mozilla */
 
 #define USING_INDEXEDDB_NAMESPACE \
   using namespace mozilla::dom::indexedDB;
 
+class nsIDOMBlob;
+
 BEGIN_INDEXEDDB_NAMESPACE
 
+class FileInfo;
+
+struct StructuredCloneReadInfo {
+  void Swap(StructuredCloneReadInfo& aCloneReadInfo) {
+    mCloneBuffer.swap(aCloneReadInfo.mCloneBuffer);
+    mFileInfos.SwapElements(aCloneReadInfo.mFileInfos);
+  }
+
+  JSAutoStructuredCloneBuffer mCloneBuffer;
+  nsTArray<nsRefPtr<FileInfo> > mFileInfos;
+};
+
+struct StructuredCloneWriteInfo {
+  void Swap(StructuredCloneWriteInfo& aCloneWriteInfo) {
+    mCloneBuffer.swap(aCloneWriteInfo.mCloneBuffer);
+    mBlobs.SwapElements(aCloneWriteInfo.mBlobs);
+    mOffsetToKeyProp = aCloneWriteInfo.mOffsetToKeyProp;
+  }
+
+  JSAutoStructuredCloneBuffer mCloneBuffer;
+  nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
+  PRUint64 mOffsetToKeyProp;
+};
+
 inline
 void
 AppendConditionClause(const nsACString& aColumnName,
                       const nsACString& aArgName,
                       bool aLessThan,
                       bool aEquals,
                       nsACString& aResult)
 {
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -49,23 +49,25 @@
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/storage.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
+#include "test_quota.h"
 
 #include "AsyncConnectionHelper.h"
 #include "CheckQuotaHelper.h"
 #include "IDBDatabase.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "LazyIdleThread.h"
+#include "OpenDatabaseHelper.h"
 #include "TransactionThreadPool.h"
 
 // The amount of time, in milliseconds, that our IO thread will stay alive
 // after the last event it processes.
 #define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 // The amount of time, in milliseconds, that we will wait for active database
 // transactions on shutdown before aborting them.
@@ -82,22 +84,43 @@
 
 USING_INDEXEDDB_NAMESPACE
 using namespace mozilla::services;
 using mozilla::Preferences;
 
 namespace {
 
 PRInt32 gShutdown = 0;
+PRInt32 gClosed = 0;
 
 // Does not hold a reference.
 IndexedDatabaseManager* gInstance = nsnull;
 
 PRInt32 gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
 
+bool
+GetBaseFilename(const nsAString& aFilename,
+                nsAString& aBaseFilename)
+{
+  NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!");
+
+  NS_NAMED_LITERAL_STRING(sqlite, ".sqlite");
+  nsAString::size_type filenameLen = aFilename.Length();
+  nsAString::size_type sqliteLen = sqlite.Length();
+
+  if (sqliteLen > filenameLen ||
+      Substring(aFilename, filenameLen - sqliteLen, sqliteLen) != sqlite) {
+    return false;
+  }
+
+  aBaseFilename = Substring(aFilename, 0, filenameLen - sqliteLen);
+
+  return true;
+}
+
 class QuotaCallback : public mozIStorageQuotaCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD
   QuotaExceeded(const nsACString& aFilename,
                 PRInt64 aCurrentSizeLimit,
@@ -133,21 +156,39 @@ EnumerateToTArray(const nsACString& aKey
   if (!array->AppendElements(*aValue)) {
     NS_WARNING("Out of memory!");
     return PL_DHASH_STOP;
   }
 
   return PL_DHASH_NEXT;
 }
 
+PLDHashOperator
+InvalidateAllFileManagers(const nsACString& aKey,
+                          nsTArray<nsRefPtr<FileManager> >* aValue,
+                          void* aUserArg)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
+  NS_ASSERTION(aValue, "Null pointer!");
+
+  for (PRUint32 i = 0; i < aValue->Length(); i++) {
+    nsRefPtr<FileManager> fileManager = aValue->ElementAt(i);
+    fileManager->Invalidate();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 } // anonymous namespace
 
 IndexedDatabaseManager::IndexedDatabaseManager()
 : mCurrentWindowIndex(BAD_TLS_INDEX),
-  mQuotaHelperMutex("IndexedDatabaseManager.mQuotaHelperMutex")
+  mQuotaHelperMutex("IndexedDatabaseManager.mQuotaHelperMutex"),
+  mFileMutex("IndexedDatabaseManager.mFileMutex")
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!gInstance, "More than one instance!");
 }
 
 IndexedDatabaseManager::~IndexedDatabaseManager()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -174,17 +215,18 @@ IndexedDatabaseManager::GetOrCreate()
                                               DEFAULT_QUOTA_MB))) {
       NS_WARNING("Unable to respond to quota pref changes!");
       gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
     }
 
     instance = new IndexedDatabaseManager();
 
     if (!instance->mLiveDatabases.Init() ||
-        !instance->mQuotaHelperHash.Init()) {
+        !instance->mQuotaHelperHash.Init() ||
+        !instance->mFileManagers.Init()) {
       NS_WARNING("Out of memory!");
       return nsnull;
     }
 
     // We need a thread-local to hold the current window.
     NS_ASSERTION(instance->mCurrentWindowIndex == BAD_TLS_INDEX, "Huh?");
 
     if (PR_NewThreadPrivateIndex(&instance->mCurrentWindowIndex, nsnull) !=
@@ -442,16 +484,23 @@ IndexedDatabaseManager::AcquireExclusive
 
 // static
 bool
 IndexedDatabaseManager::IsShuttingDown()
 {
   return !!gShutdown;
 }
 
+// static
+bool
+IndexedDatabaseManager::IsClosed()
+{
+  return !!gClosed;
+}
+
 void
 IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null pointer!");
 
   nsAutoTArray<IDBDatabase*, 50> liveDatabases;
   mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
@@ -568,39 +617,56 @@ IndexedDatabaseManager::SetCurrentWindow
 // static
 PRUint32
 IndexedDatabaseManager::GetIndexedDBQuotaMB()
 {
   return PRUint32(NS_MAX(gIndexedDBQuotaMB, 0));
 }
 
 nsresult
-IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory)
+IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin,
+                                                  nsIFile** aDirectory)
 {
 #ifdef DEBUG
   {
     bool correctThread;
     NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) &&
                  correctThread,
                  "Running on the wrong thread!");
   }
 #endif
-  NS_ASSERTION(aDirectory, "Null pointer!");
 
-  nsCString path;
-  nsresult rv = aDirectory->GetNativePath(path);
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = IDBFactory::GetDirectoryForOrigin(aOrigin,
+                                                  getter_AddRefs(directory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = directory->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mTrackedQuotaPaths.Contains(path)) {
-    return true;
+  if (exists) {
+    bool isDirectory;
+    rv = directory->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
+  }
+  else {
+    rv = directory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (mFileManagers.Get(aOrigin)) {
+    NS_ADDREF(*aDirectory = directory);
+    return NS_OK;
   }
 
   // First figure out the filename pattern we'll use.
   nsCOMPtr<nsIFile> patternFile;
-  rv = aDirectory->Clone(getter_AddRefs(patternFile));
+  rv = directory->Clone(getter_AddRefs(patternFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = patternFile->Append(NS_LITERAL_STRING("*"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString pattern;
   rv = patternFile->GetNativePath(pattern);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -610,52 +676,151 @@ IndexedDatabaseManager::EnsureQuotaManag
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
 
   rv = ss->SetQuotaForFilenamePattern(pattern,
                                       GetIndexedDBQuotaMB() * 1024 * 1024,
                                       mQuotaCallbackSingleton, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // If the directory exists then we need to see if there are any files in it
-  // already. We need to tell SQLite about all of them.
-  bool exists;
-  rv = aDirectory->Exists(&exists);
+  // We need to see if there are any files in the directory already. If they
+  // are database files then we need to create file managers for them and also
+  // tell SQLite about all of them.
+
+  nsAutoTArray<nsString, 20> subdirectories;
+  nsAutoTArray<nsCOMPtr<nsIFile> , 20> unknownFiles;
+
+  nsAutoPtr<nsTArray<nsRefPtr<FileManager> > > fileManagers(
+    new nsTArray<nsRefPtr<FileManager> >());
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (exists) {
-    // Make sure this really is a directory.
+  bool hasMore;
+  while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
+    nsCOMPtr<nsISupports> entry;
+    rv = entries->GetNext(getter_AddRefs(entry));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+    NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+
+    nsString leafName;
+    rv = file->GetLeafName(leafName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     bool isDirectory;
-    rv = aDirectory->IsDirectory(&isDirectory);
+    rv = file->IsDirectory(&isDirectory);
     NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
+
+    if (isDirectory) {
+      subdirectories.AppendElement(leafName);
+      continue;
+    }
+
+    nsString dbBaseFilename;
+    if (!GetBaseFilename(leafName, dbBaseFilename)) {
+      unknownFiles.AppendElement(file);
+      continue;
+    }
+
+    nsCOMPtr<nsIFile> fileManagerDirectory;
+    rv = directory->Clone(getter_AddRefs(fileManagerDirectory));
+    NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsISimpleEnumerator> entries;
-    rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+    rv = fileManagerDirectory->Append(dbBaseFilename);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsString voidString;
+    voidString.SetIsVoid(true);
+
+    nsCOMPtr<mozIStorageConnection> connection;
+    rv = OpenDatabaseHelper::CreateDatabaseConnection(
+      voidString, file, fileManagerDirectory, getter_AddRefs(connection));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT name "
+      "FROM database"
+    ), getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool hasResult;
+    rv = stmt->ExecuteStep(&hasResult);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    bool hasMore;
-    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
-      nsCOMPtr<nsISupports> entry;
-      rv = entries->GetNext(getter_AddRefs(entry));
-      NS_ENSURE_SUCCESS(rv, rv);
+    if (!hasResult) {
+      NS_ERROR("Database has no name!");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    nsString databaseName;
+    rv = stmt->GetString(0, databaseName);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
-      NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+    nsRefPtr<FileManager> fileManager = new FileManager(aOrigin, databaseName);
+
+    rv = fileManager->Init();
+    NS_ENSURE_SUCCESS(rv, rv);
 
-      rv = ss->UpdateQutoaInformationForFile(file);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+    rv = fileManager->InitDirectory(fileManagerDirectory, connection);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    fileManagers->AppendElement(fileManager);
+
+    rv = ss->UpdateQuotaInformationForFile(file);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  NS_ASSERTION(!mTrackedQuotaPaths.Contains(path), "What?!");
+  for (PRUint32 i = 0; i < subdirectories.Length(); i++) {
+    const nsString& subdirectory = subdirectories[i];
+    bool unknown = true;
+    for (PRUint32 j = 0; j < fileManagers->Length(); j++) {
+      nsRefPtr<FileManager>& fileManager = fileManagers->ElementAt(j);
+      if (fileManager->DirectoryName().Equals(subdirectory)) {
+        unknown = false;
+        break;
+      }
+    }
+    if (unknown) {
+      NS_WARNING("Unknown subdirectory found!");
+      return NS_ERROR_UNEXPECTED;
+    }
+  }
+
+  for (PRUint32 i = 0; i < unknownFiles.Length(); i++) {
+    nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
 
-  mTrackedQuotaPaths.AppendElement(path);
-  return rv;
+    bool exists;
+    rv = unknownFile->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (exists) {
+      nsString leafName;
+      unknownFile->GetLeafName(leafName);
+
+      if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) {
+        NS_WARNING("Unknown file found!");
+        return NS_ERROR_UNEXPECTED;
+      }
+    }
+  }
+
+  if (!mFileManagers.Put(aOrigin, fileManagers)) {
+    NS_WARNING("Out of memory?");
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  fileManagers.forget();
+
+  NS_ADDREF(*aDirectory = directory);
+  return NS_OK;
 }
 
 bool
 IndexedDatabaseManager::QuotaIsLiftedInternal()
 {
   nsPIDOMWindow* window = nsnull;
   nsRefPtr<CheckQuotaHelper> helper = nsnull;
   bool createdHelper = false;
@@ -744,16 +909,126 @@ IndexedDatabaseManager::GetASCIIOriginFr
       NS_WARNING("IndexedDB databases not allowed for this principal!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
 
   return NS_OK;
 }
 
+already_AddRefed<FileManager>
+IndexedDatabaseManager::GetOrCreateFileManager(const nsACString& aOrigin,
+                                               const nsAString& aDatabaseName)
+{
+  nsTArray<nsRefPtr<FileManager> >* array;
+  if (!mFileManagers.Get(aOrigin, &array)) {
+    nsAutoPtr<nsTArray<nsRefPtr<FileManager> > > newArray(
+      new nsTArray<nsRefPtr<FileManager> >());
+    if (!mFileManagers.Put(aOrigin, newArray)) {
+      NS_WARNING("Out of memory?");
+      return nsnull;
+    }
+    array = newArray.forget();
+  }
+
+  nsRefPtr<FileManager> fileManager;
+  for (PRUint32 i = 0; i < array->Length(); i++) {
+    nsRefPtr<FileManager> fm = array->ElementAt(i);
+
+    if (fm->DatabaseName().Equals(aDatabaseName)) {
+      fileManager = fm.forget();
+      break;
+    }
+  }
+  
+  if (!fileManager) {
+    fileManager = new FileManager(aOrigin, aDatabaseName);
+
+    if (NS_FAILED(fileManager->Init())) {
+      NS_WARNING("Failed to initialize file manager!");
+      return nsnull;
+    }
+
+    array->AppendElement(fileManager);
+  }
+
+  return fileManager.forget();
+}
+
+void
+IndexedDatabaseManager::InvalidateFileManagersForOrigin(
+                                                     const nsACString& aOrigin)
+{
+  nsTArray<nsRefPtr<FileManager> >* array;
+  if (mFileManagers.Get(aOrigin, &array)) {
+    for (PRUint32 i = 0; i < array->Length(); i++) {
+      nsRefPtr<FileManager> fileManager = array->ElementAt(i);
+      fileManager->Invalidate();
+    }
+    mFileManagers.Remove(aOrigin);
+  }
+}
+
+void
+IndexedDatabaseManager::InvalidateFileManager(const nsACString& aOrigin,
+                                              const nsAString& aDatabaseName)
+{
+  nsTArray<nsRefPtr<FileManager> >* array;
+  if (!mFileManagers.Get(aOrigin, &array)) {
+    return;
+  }
+
+  for (PRUint32 i = 0; i < array->Length(); i++) {
+    nsRefPtr<FileManager> fileManager = array->ElementAt(i);
+    if (fileManager->DatabaseName().Equals(aDatabaseName)) {
+      fileManager->Invalidate();
+      array->RemoveElementAt(i);
+
+      if (array->IsEmpty()) {
+        mFileManagers.Remove(aOrigin);
+      }
+
+      break;
+    }
+  }
+}
+
+nsresult
+IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
+                                        PRInt64 aFileId)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  NS_ENSURE_ARG_POINTER(aFileManager);
+
+  // See if we're currently clearing the databases for this origin. If so then
+  // we pretend that we've already deleted everything.
+  if (IsClearOriginPending(aFileManager->Origin())) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIFile> directory = aFileManager->GetDirectory();
+  NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIFile> file = aFileManager->GetFileForId(directory, aFileId);
+  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+  nsString filePath;
+  nsresult rv = file->GetPath(filePath);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<AsyncDeleteFileRunnable> runnable =
+    new AsyncDeleteFileRunnable(filePath);
+
+  rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 // static
 nsresult
 IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
 {
   nsresult rv = NS_OK;
 
   // If the helper has a transaction, dispatch it to the transaction
   // threadpool.
@@ -944,16 +1219,22 @@ IndexedDatabaseManager::Observe(nsISuppo
     // to close. Our timer may fire during that loop.
     TransactionThreadPool::Shutdown();
 
     // Cancel the timer regardless of whether it actually fired.
     if (NS_FAILED(mShutdownTimer->Cancel())) {
       NS_WARNING("Failed to cancel shutdown timer!");
     }
 
+    mFileManagers.EnumerateRead(InvalidateAllFileManagers, nsnull);
+
+    if (PR_ATOMIC_SET(&gClosed, 1)) {
+      NS_ERROR("Close more than once?!");
+    }
+
     return NS_OK;
   }
 
   if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
     NS_WARNING("Some database operations are taking longer than expected "
                "during shutdown and will be aborted!");
 
     // Grab all live databases, for all origins.
@@ -1002,20 +1283,22 @@ IndexedDatabaseManager::OriginClearRunna
         return NS_ERROR_FAILURE;
       }
 
       return NS_OK;
     }
 
     NS_ASSERTION(!mThread, "Should have been cleared already!");
 
-    // Tell the IndexedDatabaseManager that we're done.
     IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
     NS_ASSERTION(mgr, "This should never fail!");
 
+    mgr->InvalidateFileManagersForOrigin(mOrigin);
+
+    // Tell the IndexedDatabaseManager that we're done.
     mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
 
     return NS_OK;
   }
 
   NS_ASSERTION(!mThread, "Should have been cleared already!");
 
   // Remove the directory that contains all our databases.
@@ -1041,39 +1324,54 @@ IndexedDatabaseManager::OriginClearRunna
 IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable(
                                      nsIURI* aURI,
                                      const nsACString& aOrigin,
                                      nsIIndexedDatabaseUsageCallback* aCallback)
 : mURI(aURI),
   mOrigin(aOrigin),
   mCallback(aCallback),
   mUsage(0),
+  mFileUsage(0),
   mCanceled(0)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aURI, "Null pointer!");
   NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
   NS_ASSERTION(aCallback, "Null pointer!");
 }
 
 void
 IndexedDatabaseManager::AsyncUsageRunnable::Cancel()
 {
   if (PR_ATOMIC_SET(&mCanceled, 1)) {
     NS_ERROR("Canceled more than once?!");
   }
 }
 
+inline void
+IncrementUsage(PRUint64* aUsage, PRUint64 aDelta)
+{
+  // Watch for overflow!
+  if ((LL_MAXINT - *aUsage) <= aDelta) {
+    NS_WARNING("Database sizes exceed max we can report!");
+    *aUsage = LL_MAXINT;
+  } else {
+    *aUsage += aDelta;
+  }
+}
+
 nsresult
 IndexedDatabaseManager::AsyncUsageRunnable::RunInternal()
 {
   if (NS_IsMainThread()) {
     // Call the callback unless we were canceled.
     if (!mCanceled) {
-      mCallback->OnUsageResult(mURI, mUsage);
+      PRUint64 usage = mUsage;
+      IncrementUsage(&usage, mFileUsage);
+      mCallback->OnUsageResult(mURI, usage, mFileUsage);
     }
 
     // Clean up.
     mURI = nsnull;
     mCallback = nsnull;
 
     // And tell the IndexedDatabaseManager that we're done.
     IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
@@ -1096,49 +1394,73 @@ IndexedDatabaseManager::AsyncUsageRunnab
 
   bool exists;
   rv = directory->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If the directory exists then enumerate all the files inside, adding up the
   // sizes to get the final usage statistic.
   if (exists && !mCanceled) {
-    nsCOMPtr<nsISimpleEnumerator> entries;
-    rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+    rv = GetUsageForDirectory(directory, &mUsage);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  return NS_OK;
+}
+
+nsresult
+IndexedDatabaseManager::AsyncUsageRunnable::GetUsageForDirectory(
+                                     nsIFile* aDirectory,
+                                     PRUint64* aUsage)
+{
+  NS_ASSERTION(aDirectory, "Null pointer!");
+  NS_ASSERTION(aUsage, "Null pointer!");
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!entries) {
+    return NS_OK;
+  }
+
+  bool hasMore;
+  while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+         hasMore && !mCanceled) {
+    nsCOMPtr<nsISupports> entry;
+    rv = entries->GetNext(getter_AddRefs(entry));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (entries) {
-      bool hasMore;
-      while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
-             hasMore && !mCanceled) {
-        nsCOMPtr<nsISupports> entry;
-        rv = entries->GetNext(getter_AddRefs(entry));
-        NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIFile> file(do_QueryInterface(entry));
+    NS_ASSERTION(file, "Don't know what this is!");
 
-        nsCOMPtr<nsIFile> file(do_QueryInterface(entry));
-        NS_ASSERTION(file, "Don't know what this is!");
-
-        PRInt64 fileSize;
-        rv = file->GetFileSize(&fileSize);
-        NS_ENSURE_SUCCESS(rv, rv);
+    bool isDirectory;
+    rv = file->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-        NS_ASSERTION(fileSize > 0, "Negative size?!");
+    if (isDirectory) {
+      if (aUsage == &mFileUsage) {
+        NS_WARNING("Unknown directory found!");
+      } else {
+        rv = GetUsageForDirectory(file, &mFileUsage);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      continue;
+    }
 
-        // Watch for overflow!
-        if (NS_UNLIKELY((LL_MAXINT - mUsage) <= PRUint64(fileSize))) {
-          NS_WARNING("Database sizes exceed max we can report!");
-          mUsage = LL_MAXINT;
-        }
-        else {
-          mUsage += fileSize;
-        }
-      }
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+    PRInt64 fileSize;
+    rv = file->GetFileSize(&fileSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    NS_ASSERTION(fileSize > 0, "Negative size?!");
+
+    IncrementUsage(aUsage, PRUint64(fileSize));
   }
+  NS_ENSURE_SUCCESS(rv, rv);
+ 
   return NS_OK;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncUsageRunnable,
                               nsIRunnable)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::AsyncUsageRunnable::Run()
@@ -1242,8 +1564,25 @@ IndexedDatabaseManager::SynchronizedOp::
 
   PRUint32 count = mDelayedRunnables.Length();
   for (PRUint32 index = 0; index < count; index++) {
     NS_DispatchToCurrentThread(mDelayedRunnables[index]);
   }
 
   mDelayedRunnables.Clear();
 }
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncDeleteFileRunnable,
+                              nsIRunnable)
+
+NS_IMETHODIMP
+IndexedDatabaseManager::AsyncDeleteFileRunnable::Run()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  int rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(mFilePath).get());
+  if (rc != SQLITE_OK) {
+    NS_WARNING("Failed to delete stored file!");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -35,16 +35,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_dom_indexeddb_indexeddatabasemanager_h__
 #define mozilla_dom_indexeddb_indexeddatabasemanager_h__
 
+#include "mozilla/dom/indexedDB/FileManager.h"
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 #include "mozilla/dom/indexedDB/IDBDatabase.h"
 #include "mozilla/dom/indexedDB/IDBRequest.h"
 
 #include "mozilla/Mutex.h"
 
 #include "nsIIndexedDatabaseManager.h"
 #include "nsIObserver.h"
@@ -98,16 +99,18 @@ public:
   {
     NS_ASSERTION(mIOThread, "This should never be null!");
     return mIOThread;
   }
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
+  static bool IsClosed();
+
   typedef void (*WaitingOnDatabasesCallback)(nsTArray<nsRefPtr<IDBDatabase> >&, void*);
 
   // Acquire exclusive access to the database given (waits for all others to
   // close).  If databases need to close first, the callback will be invoked
   // with an array of said databases.
   nsresult AcquireExclusiveAccess(IDBDatabase* aDatabase,
                                   AsyncConnectionHelper* aHelper,
                                   WaitingOnDatabasesCallback aCallback,
@@ -143,17 +146,18 @@ public:
     NS_ASSERTION(mgr, "Must have a manager here!");
 
     return mgr->SetCurrentWindowInternal(aWindow);
   }
 
   static PRUint32
   GetIndexedDBQuotaMB();
 
-  nsresult EnsureQuotaManagementForDirectory(nsIFile* aDirectory);
+  nsresult EnsureOriginIsInitialized(const nsACString& aOrigin,
+                                     nsIFile** aDirectory);
 
   // Determine if the quota is lifted for the Window the current thread is
   // using.
   static inline bool
   QuotaIsLifted()
   {
     IndexedDatabaseManager* mgr = Get();
     NS_ASSERTION(mgr, "Must have a manager here!");
@@ -168,16 +172,36 @@ public:
     NS_ASSERTION(mgr, "Must have a manager here!");
 
     mgr->CancelPromptsForWindowInternal(aWindow);
   }
 
   static nsresult
   GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, nsCString& aASCIIOrigin);
 
+  already_AddRefed<FileManager>
+  GetOrCreateFileManager(const nsACString& aOrigin,
+                         const nsAString& aDatabaseName);
+
+  void InvalidateFileManagersForOrigin(const nsACString& aOrigin);
+
+  void InvalidateFileManager(const nsACString& aOrigin,
+                             const nsAString& aDatabaseName);
+
+  nsresult AsyncDeleteFile(FileManager* aFileManager,
+                           PRInt64 aFileId);
+
+  static mozilla::Mutex& FileMutex()
+  {
+    IndexedDatabaseManager* mgr = Get();
+    NS_ASSERTION(mgr, "Must have a manager here!");
+
+    return mgr->mFileMutex;
+  }
+
 private:
   IndexedDatabaseManager();
   ~IndexedDatabaseManager();
 
   nsresult AcquireExclusiveAccess(const nsACString& aOrigin, 
                                   IDBDatabase* aDatabase,
                                   AsyncConnectionHelper* aHelper,
                                   WaitingOnDatabasesCallback aCallback,
@@ -245,20 +269,24 @@ private:
 
     // Sets the canceled flag so that the callback is never called.
     void Cancel();
 
     // Run calls the RunInternal method and makes sure that we always dispatch
     // to the main thread in case of an error.
     inline nsresult RunInternal();
 
+    nsresult GetUsageForDirectory(nsIFile* aDirectory,
+                                  PRUint64* aUsage);
+
     nsCOMPtr<nsIURI> mURI;
     nsCString mOrigin;
     nsCOMPtr<nsIIndexedDatabaseUsageCallback> mCallback;
     PRUint64 mUsage;
+    PRUint64 mFileUsage;
     PRInt32 mCanceled;
   };
 
   // Called when AsyncUsageRunnable has finished its Run() method.
   inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable);
 
   // A struct that contains the information corresponding to a pending or
   // running operation that requires synchronization (e.g. opening a db,
@@ -297,30 +325,49 @@ private:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
 
   private:
     // The IndexedDatabaseManager holds this alive.
     SynchronizedOp* mOp;
   };
 
+  class AsyncDeleteFileRunnable : public nsIRunnable
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIRUNNABLE
+    AsyncDeleteFileRunnable(const nsAString& aFilePath)
+    : mFilePath(aFilePath)
+    { }
+
+  private:
+    nsString mFilePath;
+  };
+
   static nsresult DispatchHelper(AsyncConnectionHelper* aHelper);
 
   // Maintains a list of live databases per origin.
   nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;
 
   // TLS storage index for the current thread's window
   PRUintn mCurrentWindowIndex;
 
   // Lock protecting mQuotaHelperHash
   mozilla::Mutex mQuotaHelperMutex;
 
   // A map of Windows to the corresponding quota helper.
   nsRefPtrHashtable<nsPtrHashKey<nsPIDOMWindow>, CheckQuotaHelper> mQuotaHelperHash;
 
+  // Maintains a list of all file managers per origin. The list is actually also
+  // a list of all origins that were successfully initialized. This list
+  // isn't protected by any mutex but it is only ever touched on the IO thread.
+  nsClassHashtable<nsCStringHashKey,
+                   nsTArray<nsRefPtr<FileManager> > > mFileManagers;
+
   // Maintains a list of origins that we're currently enumerating to gather
   // usage statistics.
   nsAutoTArray<nsRefPtr<AsyncUsageRunnable>, 1> mUsageRunnables;
 
   // Maintains a list of synchronized operatons that are in progress or queued.
   nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
 
   // Thread on which IO is performed.
@@ -328,20 +375,20 @@ private:
 
   // A timer that gets activated at shutdown to ensure we close all databases.
   nsCOMPtr<nsITimer> mShutdownTimer;
 
   // A single threadsafe instance of our quota callback. Created on the main
   // thread during GetOrCreate().
   nsCOMPtr<mozIStorageQuotaCallback> mQuotaCallbackSingleton;
 
-  // A list of all paths that are under SQLite's quota tracking system. This
-  // list isn't protected by any mutex but it is only ever touched on the IO
-  // thread.
-  nsTArray<nsCString> mTrackedQuotaPaths;
+  // Lock protecting FileManager.mFileInfos and nsDOMFileBase.mFileInfos
+  // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt
+  // and FileInfo.mSliceRefCnt
+  mozilla::Mutex mFileMutex;
 };
 
 class AutoEnterWindow
 {
 public:
   AutoEnterWindow(nsPIDOMWindow* aWindow)
   {
     NS_ASSERTION(aWindow, "This should never be null!");
--- a/dom/indexedDB/Makefile.in
+++ b/dom/indexedDB/Makefile.in
@@ -51,16 +51,18 @@ FORCE_STATIC_LIB = 1
 
 EXPORTS_NAMESPACES = mozilla/dom/indexedDB
 
 CPPSRCS = \
   AsyncConnectionHelper.cpp \
   CheckPermissionsHelper.cpp \
   CheckQuotaHelper.cpp \
   DatabaseInfo.cpp \
+  FileInfo.cpp \
+  FileManager.cpp \
   IDBCursor.cpp \
   IDBDatabase.cpp \
   IDBEvents.cpp \
   IDBIndex.cpp \
   IDBKeyRange.cpp \
   IDBObjectStore.cpp \
   IDBRequest.cpp \
   IDBTransaction.cpp \
@@ -80,19 +82,22 @@ EXPORTS_mozilla/dom/indexedDB = \
   IDBObjectStore.h \
   IDBRequest.h \
   IDBTransaction.h \
   IndexedDatabase.h \
   IndexedDatabaseManager.h \
   IDBFactory.h \
   Key.h \
   LazyIdleThread.h \
+  FileManager.h \
+  FileInfo.h \
   $(NULL)
 
 LOCAL_INCLUDES = \
+  -I$(topsrcdir)/db/sqlite3/src \
   -I$(topsrcdir)/xpcom/build \
   -I$(topsrcdir)/dom/base \
   -I$(topsrcdir)/dom/src/storage \
   -I$(topsrcdir)/content/base/src \
   -I$(topsrcdir)/content/events/src \
   -I$(topsrcdir)/js/xpconnect/src \
   $(NULL)
 
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -40,31 +40,32 @@
 
 #include "nsIFile.h"
 
 #include "mozilla/storage.h"
 #include "nsContentUtils.h"
 #include "nsEscape.h"
 #include "nsThreadUtils.h"
 #include "snappy/snappy.h"
+#include "test_quota.h"
 
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IndexedDatabaseManager.h"
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 PR_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 1);
 
 // Major schema version. Bump for almost everything.
-const PRUint32 kMajorSchemaVersion = 9;
+const PRUint32 kMajorSchemaVersion = 10;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const PRUint32 kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -90,34 +91,20 @@ PRUint32 GetMajorSchemaVersion(PRInt32 a
 
 inline
 PRUint32 GetMinorSchemaVersion(PRInt32 aSchemaVersion)
 {
   return PRUint32(aSchemaVersion) & 0xF;
 }
 
 nsresult
-GetDatabaseFile(const nsACString& aASCIIOrigin,
-                const nsAString& aName,
-                nsIFile** aDatabaseFile)
+GetDatabaseFilename(const nsAString& aName,
+                    nsAString& aDatabaseFilename)
 {
-  NS_ASSERTION(!aASCIIOrigin.IsEmpty() && !aName.IsEmpty(), "Bad arguments!");
-
-  nsCOMPtr<nsIFile> dbFile;
-  nsresult rv = IDBFactory::GetDirectory(getter_AddRefs(dbFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  NS_ConvertASCIItoUTF16 originSanitized(aASCIIOrigin);
-  originSanitized.ReplaceChar(":/", '+');
-
-  rv = dbFile->Append(originSanitized);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoString filename;
-  filename.AppendInt(HashString(aName));
+  aDatabaseFilename.AppendInt(HashString(aName));
 
   nsCString escapedName;
   if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) {
     NS_WARNING("Can't escape database name!");
     return NS_ERROR_UNEXPECTED;
   }
 
   const char* forwardIter = escapedName.BeginReading();
@@ -128,23 +115,107 @@ GetDatabaseFile(const nsACString& aASCII
     if (substring.Length() % 2) {
       substring.Append(*backwardIter--);
     }
     else {
       substring.Append(*forwardIter++);
     }
   }
 
-  filename.Append(NS_ConvertASCIItoUTF16(substring));
-  filename.AppendLiteral(".sqlite");
+  aDatabaseFilename.Append(NS_ConvertASCIItoUTF16(substring));
+
+  return NS_OK;
+}
+
+nsresult
+CreateFileTables(mozIStorageConnection* aDBConn)
+{
+  // Table `file`
+  nsresult rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE file ("
+      "id INTEGER PRIMARY KEY, "
+      "refcount INTEGER NOT NULL"
+    ");"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = dbFile->Append(filename);
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_insert_trigger "
+    "AFTER INSERT ON object_data "
+    "FOR EACH ROW "
+    "WHEN NEW.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(NULL, NEW.file_ids); "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_update_trigger "
+    "AFTER UPDATE OF file_ids ON object_data "
+    "FOR EACH ROW "
+    "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+    "END;"
+  ));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  dbFile.forget(aDatabaseFile);
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_delete_trigger "
+    "AFTER DELETE ON object_data "
+    "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(OLD.file_ids, NULL); "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER ai_object_data_insert_trigger "
+    "AFTER INSERT ON ai_object_data "
+    "FOR EACH ROW "
+    "WHEN NEW.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(NULL, NEW.file_ids); "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER ai_object_data_update_trigger "
+    "AFTER UPDATE OF file_ids ON ai_object_data "
+    "FOR EACH ROW "
+    "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER ai_object_data_delete_trigger "
+    "AFTER DELETE ON ai_object_data "
+    "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(OLD.file_ids, NULL); "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER file_update_trigger "
+    "AFTER UPDATE ON file "
+    "FOR EACH ROW WHEN NEW.refcount = 0 "
+    "BEGIN "
+      "DELETE FROM file WHERE id = OLD.id; "
+    "END;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
 CreateTables(mozIStorageConnection* aDBConn)
 {
   NS_PRECONDITION(!NS_IsMainThread(),
                   "Creating tables on the main thread!");
@@ -173,29 +244,31 @@ CreateTables(mozIStorageConnection* aDBC
 
   // Table `object_data`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_data ("
       "id INTEGER PRIMARY KEY, "
       "object_store_id INTEGER NOT NULL, "
       "key_value DEFAULT NULL, "
       "data BLOB NOT NULL, "
+      "file_ids TEXT, "
       "UNIQUE (object_store_id, key_value), "
       "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `ai_object_data`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE ai_object_data ("
       "id INTEGER PRIMARY KEY AUTOINCREMENT, "
       "object_store_id INTEGER NOT NULL, "
       "data BLOB NOT NULL, "
+      "file_ids TEXT, "
       "UNIQUE (object_store_id, id), "
       "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `index`
@@ -305,16 +378,19 @@ CreateTables(mozIStorageConnection* aDBC
   // Need this to make cascading deletes from ai_object_data and object_store
   // fast.
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
     "ON ai_unique_index_data (ai_object_data_id);"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = CreateFileTables(aDBConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   rv = aDBConn->SetSchemaVersion(kSQLiteSchemaVersion);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection)
@@ -956,137 +1032,34 @@ UpgradeSchemaFrom8To9_0(mozIStorageConne
 
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(9, 0));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-CreateDatabaseConnection(const nsAString& aName,
-                         nsIFile* aDBFile,
-                         mozIStorageConnection** aConnection)
+UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection)
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota");
-
-  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
-    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
-
-  nsCOMPtr<mozIStorageConnection> connection;
-  nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
-                                        getter_AddRefs(connection));
-  if (rv == NS_ERROR_FILE_CORRUPTED) {
-    // Nuke the database file.  The web services can recreate their data.
-    rv = aDBFile->Remove(false);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
-                                 getter_AddRefs(connection));
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Check to make sure that the database schema is correct.
-  PRInt32 schemaVersion;
-  rv = connection->GetSchemaVersion(&schemaVersion);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (schemaVersion > kSQLiteSchemaVersion) {
-    NS_WARNING("Unable to open IndexedDB database, schema is too high!");
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  bool vacuumNeeded = false;
-
-  if (schemaVersion != kSQLiteSchemaVersion) {
-    mozStorageTransaction transaction(connection, false,
-                                  mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
-    if (!schemaVersion) {
-      // Brand new file, initialize our tables.
-      rv = CreateTables(connection);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) &&
-                   schemaVersion == kSQLiteSchemaVersion,
-                   "CreateTables set a bad schema version!");
-
-      nsCOMPtr<mozIStorageStatement> stmt;
-      nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-        "INSERT INTO database (name) "
-        "VALUES (:name)"
-      ), getter_AddRefs(stmt));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-      rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-      rv = stmt->Execute();
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-    }
-    else  {
-      // This logic needs to change next time we change the schema!
-      PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((9 << 4) + 0));
-
-      while (schemaVersion != kSQLiteSchemaVersion) {
-        if (schemaVersion == 4) {
-          rv = UpgradeSchemaFrom4To5(connection);
-        }
-        else if (schemaVersion == 5) {
-          rv = UpgradeSchemaFrom5To6(connection);
-        }
-        else if (schemaVersion == 6) {
-          rv = UpgradeSchemaFrom6To7(connection);
-        }
-        else if (schemaVersion == 7) {
-          rv = UpgradeSchemaFrom7To8(connection);
-        }
-        else if (schemaVersion == 8) {
-          rv = UpgradeSchemaFrom8To9_0(connection);
-          vacuumNeeded = true;
-        }
-#if 0
-        else if (schemaVersion == MakeSchemaVersion(9, 0)) {
-          // Upgrade.
-        }
-#endif
-        else {
-          NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
-                     "available!");
-          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-        }
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = connection->GetSchemaVersion(&schemaVersion);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-
-      NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!");
-    }
-
-    rv = transaction.Commit();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (vacuumNeeded) {
-    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "VACUUM;"
-    ));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  // Turn on foreign key constraints.
-  rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "PRAGMA foreign_keys = ON;"
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE object_data ADD COLUMN file_ids TEXT;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  connection.forget(aConnection);
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = CreateFileTables(aConnection);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 class VersionChangeEventsRunnable;
 
 class SetVersionHelper : public AsyncConnectionHelper,
                          public IDBTransactionListener
 {
@@ -1125,18 +1098,18 @@ protected:
 
   PRUint64 RequestedVersion() const
   {
     return mRequestedVersion;
   }
 
 private:
   // In-params
+  nsRefPtr<IDBOpenDBRequest> mOpenRequest;
   nsRefPtr<OpenDatabaseHelper> mOpenHelper;
-  nsRefPtr<IDBOpenDBRequest> mOpenRequest;
   PRUint64 mRequestedVersion;
   PRUint64 mCurrentVersion;
 };
 
 class DeleteDatabaseHelper : public AsyncConnectionHelper
 {
   friend class VersionChangeEventsRunnable;
 public:
@@ -1340,50 +1313,49 @@ OpenDatabaseHelper::DoDatabaseWork()
   NS_ASSERTION(mOpenDBRequest, "This should never be null!");
 
   // Once we support IDB outside of Windows this assertion will no longer hold.
   nsPIDOMWindow* window = mOpenDBRequest->Owner();
   NS_ASSERTION(window, "This should never be null");
 
   AutoEnterWindow autoWindow(window);
 
+  nsCOMPtr<nsIFile> dbDirectory;
+
+  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+  NS_ASSERTION(mgr, "This should never be null!");
+
+  nsresult rv = mgr->EnsureOriginIsInitialized(mASCIIOrigin,
+                                               getter_AddRefs(dbDirectory));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsAutoString filename;
+  rv = GetDatabaseFilename(mName, filename);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  
   nsCOMPtr<nsIFile> dbFile;
-  nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile));
+  rv = dbDirectory->Clone(getter_AddRefs(dbFile));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = dbFile->GetPath(mDatabaseFilePath);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  nsCOMPtr<nsIFile> dbDirectory;
-  rv = dbFile->GetParent(getter_AddRefs(dbDirectory));
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  bool exists;
-  rv = dbDirectory->Exists(&exists);
+  nsCOMPtr<nsIFile> fileManagerDirectory;
+  rv = dbDirectory->Clone(getter_AddRefs(fileManagerDirectory));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  if (exists) {
-    bool isDirectory;
-    rv = dbDirectory->IsDirectory(&isDirectory);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-    NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-  }
-  else {
-    rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-  }
-
-  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-  NS_ASSERTION(mgr, "This should never be null!");
-
-  rv = mgr->EnsureQuotaManagementForDirectory(dbDirectory);
+  rv = fileManagerDirectory->Append(filename);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsCOMPtr<mozIStorageConnection> connection;
-  rv = CreateDatabaseConnection(mName, dbFile, getter_AddRefs(connection));
+  rv = CreateDatabaseConnection(mName, dbFile, fileManagerDirectory,
+                                getter_AddRefs(connection));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = IDBFactory::LoadDatabaseInformation(connection, mDatabaseId,
                                            &mCurrentVersion, mObjectStores);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   if (mForDeletion) {
     mState = eDeletePending;
@@ -1417,16 +1389,185 @@ OpenDatabaseHelper::DoDatabaseWork()
   if (mCurrentVersion > mRequestedVersion) {
     return NS_ERROR_DOM_INDEXEDDB_VERSION_ERR;
   }
 
   if (mCurrentVersion != mRequestedVersion) {
     mState = eSetVersionPending;
   }
 
+  mFileManager = mgr->GetOrCreateFileManager(mASCIIOrigin, mName);
+  NS_ENSURE_TRUE(mFileManager, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  if (!mFileManager->IsDirectoryInited()) {
+    rv = mFileManager->InitDirectory(fileManagerDirectory, connection);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
+  if (!mFileManager->Loaded()) {
+    rv = mFileManager->Load(connection);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult
+OpenDatabaseHelper::CreateDatabaseConnection(
+                                        const nsAString& aName,
+                                        nsIFile* aDBFile,
+                                        nsIFile* aFileManagerDirectory,
+                                        mozIStorageConnection** aConnection)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota");
+
+  nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
+                                        getter_AddRefs(connection));
+  if (rv == NS_ERROR_FILE_CORRUPTED) {
+    // If we're just opening the database during origin initialization, then
+    // we don't want to erase any files. The failure here will fail origin
+    // initialization too.
+    if (aName.IsVoid()) {
+      return rv;
+    }
+
+    // Nuke the database file.  The web services can recreate their data.
+    rv = aDBFile->Remove(false);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    rv = aFileManagerDirectory->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (exists) {
+      bool isDirectory;
+      rv = aFileManagerDirectory->IsDirectory(&isDirectory);
+      NS_ENSURE_SUCCESS(rv, rv);
+      NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      rv = aFileManagerDirectory->Remove(true);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName,
+                                 getter_AddRefs(connection));
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Check to make sure that the database schema is correct.
+  PRInt32 schemaVersion;
+  rv = connection->GetSchemaVersion(&schemaVersion);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Unknown schema will fail origin initialization too
+  if (!schemaVersion && aName.IsVoid()) {
+    NS_WARNING("Unable to open IndexedDB database, schema is not set!");
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  if (schemaVersion > kSQLiteSchemaVersion) {
+    NS_WARNING("Unable to open IndexedDB database, schema is too high!");
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  bool vacuumNeeded = false;
+
+  if (schemaVersion != kSQLiteSchemaVersion) {
+    mozStorageTransaction transaction(connection, false,
+                                  mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    if (!schemaVersion) {
+      // Brand new file, initialize our tables.
+      rv = CreateTables(connection);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) &&
+                   schemaVersion == kSQLiteSchemaVersion,
+                   "CreateTables set a bad schema version!");
+
+      nsCOMPtr<mozIStorageStatement> stmt;
+      nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+        "INSERT INTO database (name) "
+        "VALUES (:name)"
+      ), getter_AddRefs(stmt));
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      rv = stmt->Execute();
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+    }
+    else  {
+      // This logic needs to change next time we change the schema!
+      PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((10 << 4) + 0));
+
+      while (schemaVersion != kSQLiteSchemaVersion) {
+        if (schemaVersion == 4) {
+          rv = UpgradeSchemaFrom4To5(connection);
+        }
+        else if (schemaVersion == 5) {
+          rv = UpgradeSchemaFrom5To6(connection);
+        }
+        else if (schemaVersion == 6) {
+          rv = UpgradeSchemaFrom6To7(connection);
+        }
+        else if (schemaVersion == 7) {
+          rv = UpgradeSchemaFrom7To8(connection);
+        }
+        else if (schemaVersion == 8) {
+          rv = UpgradeSchemaFrom8To9_0(connection);
+          vacuumNeeded = true;
+        }
+        else if (schemaVersion == MakeSchemaVersion(9, 0)) {
+          rv = UpgradeSchemaFrom9_0To10_0(connection);
+        }
+        else {
+          NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
+                     "available!");
+          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = connection->GetSchemaVersion(&schemaVersion);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!");
+    }
+
+    rv = transaction.Commit();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (vacuumNeeded) {
+    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "VACUUM;"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Turn on foreign key constraints.
+  rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA foreign_keys = ON;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  connection.forget(aConnection);
   return NS_OK;
 }
 
 nsresult
 OpenDatabaseHelper::StartSetVersion()
 {
   NS_ASSERTION(mState == eSetVersionPending, "Why are we here?");
 
@@ -1535,16 +1676,21 @@ OpenDatabaseHelper::Run()
         mState = eFiringEvents;
         break;
       }
 
       case eDeleteCompleted: {
         // Destroy the database now (we should have the only ref).
         mDatabase = nsnull;
 
+        IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+        NS_ASSERTION(mgr, "This should never fail!");
+
+        mgr->InvalidateFileManager(mASCIIOrigin, mName);
+
         mState = eFiringEvents;
         break;
       }
 
       case eFiringEvents: {
         // Notify the request that we're done, but only if we didn't just
         // finish a [SetVersion/DeleteDatabase]Helper.  In that case, the
         // helper tells the request that it is done, and we avoid calling
@@ -1664,17 +1810,18 @@ OpenDatabaseHelper::EnsureSuccessResult(
 
   dbInfo->nextObjectStoreId = mLastObjectStoreId + 1;
   dbInfo->nextIndexId = mLastIndexId + 1;
 
   nsRefPtr<IDBDatabase> database =
     IDBDatabase::Create(mOpenDBRequest->ScriptContext(),
                         mOpenDBRequest->Owner(),
                         dbInfo.forget(),
-                        mASCIIOrigin);
+                        mASCIIOrigin,
+                        mFileManager);
   if (!database) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   NS_ASSERTION(!mDatabase, "Shouldn't have a database yet!");
   mDatabase.swap(database);
 
   return NS_OK;
@@ -1883,29 +2030,78 @@ SetVersionHelper::NotifyTransactionCompl
   return rv;
 }
 
 nsresult
 DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(!aConnection, "How did we get a connection here?");
 
-  nsCOMPtr<nsIFile> dbFile;
-  nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile));
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = IDBFactory::GetDirectoryForOrigin(mASCIIOrigin,
+                                                  getter_AddRefs(directory));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  NS_ASSERTION(dbFile, "What?");
+  NS_ASSERTION(directory, "What?");
+
+  nsAutoString filename;
+  rv = GetDatabaseFilename(mName, filename);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsCOMPtr<nsIFile> dbFile;
+  rv = directory->Clone(getter_AddRefs(dbFile));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   bool exists = false;
   rv = dbFile->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  int rc;
+
   if (exists) {
-    rv = dbFile->Remove(false);
+    nsString dbFilePath;
+    rv = dbFile->GetPath(dbFilePath);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(dbFilePath).get());
+    if (rc != SQLITE_OK) {
+      NS_WARNING("Failed to delete db file!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+  }
+
+  nsCOMPtr<nsIFile> fileManagerDirectory;
+  rv = directory->Clone(getter_AddRefs(fileManagerDirectory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = fileManagerDirectory->Append(filename);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = fileManagerDirectory->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  if (exists) {
+    bool isDirectory;
+    rv = fileManagerDirectory->IsDirectory(&isDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    nsString fileManagerDirectoryPath;
+    rv = fileManagerDirectory->GetPath(fileManagerDirectoryPath);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    rc = sqlite3_quota_remove(
+      NS_ConvertUTF16toUTF8(fileManagerDirectoryPath).get());
+    if (rc != SQLITE_OK) {
+      NS_WARNING("Failed to delete file directory!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 DeleteDatabaseHelper::GetSuccessResult(JSContext* aCx, jsval* aVal)
 {
--- a/dom/indexedDB/OpenDatabaseHelper.h
+++ b/dom/indexedDB/OpenDatabaseHelper.h
@@ -97,16 +97,22 @@ public:
   }
 
   IDBDatabase* Database() const
   {
     NS_ASSERTION(mDatabase, "Calling at the wrong time!");
     return mDatabase;
   }
 
+  static
+  nsresult CreateDatabaseConnection(const nsAString& aName,
+                                    nsIFile* aDBFile,
+                                    nsIFile* aFileManagerDirectory,
+                                    mozIStorageConnection** aConnection);
+
 protected:
   // Methods only called on the main thread
   nsresult EnsureSuccessResult();
   nsresult StartSetVersion();
   nsresult StartDelete();
   nsresult GetSuccessResult(JSContext* aCx,
                           jsval* aVal);
   void DispatchSuccessEvent();
@@ -140,13 +146,15 @@ private:
     eFiringEvents, // Waiting to fire/firing events on the main thread
     eSetVersionPending, // Waiting on a SetVersionHelper
     eSetVersionCompleted, // SetVersionHelper is done
     eDeletePending, // Waiting on a DeleteDatabaseHelper
     eDeleteCompleted, // DeleteDatabaseHelper is done
   };
   OpenDatabaseState mState;
   nsresult mResultCode;
+
+  nsRefPtr<FileManager> mFileManager;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_opendatabasehelper_h__
--- a/dom/indexedDB/TransactionThreadPool.h
+++ b/dom/indexedDB/TransactionThreadPool.h
@@ -82,17 +82,17 @@ public:
   bool WaitForAllDatabasesToComplete(
                                    nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
                                    nsIRunnable* aCallback);
 
   // Abort all transactions, unless they are already in the process of being
   // committed, for aDatabase.
   void AbortTransactionsForDatabase(IDBDatabase* aDatabase);
 
-  // Returns true iff there are running or pending transactions for aDatabase.
+  // Returns true if there are running or pending transactions for aDatabase.
   bool HasTransactionsForDatabase(IDBDatabase* aDatabase);
 
 protected:
   class TransactionQueue : public nsIRunnable
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
--- a/dom/indexedDB/nsIIndexedDatabaseManager.idl
+++ b/dom/indexedDB/nsIIndexedDatabaseManager.idl
@@ -36,24 +36,25 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 
-[scriptable, function, uuid(17675af5-0569-4f5b-987f-ff4bb60f73ee)]
+[scriptable, function, uuid(ef1795ec-7050-4658-b80f-0e48cbe1d64b)]
 interface nsIIndexedDatabaseUsageCallback : nsISupports
 {
   /**
    *
    */
   void onUsageResult(in nsIURI aURI,
-                     in unsigned long long aUsage);
+                     in unsigned long long aUsage,
+                     in unsigned long long aFileUsage);
 };
 
 [scriptable, builtinclass, uuid(415f5684-6c84-4a8b-b777-d01f5df778f2)]
 interface nsIIndexedDatabaseManager : nsISupports
 {
   /**
    * Schedules an asynchronous callback that will return the total amount of
    * disk space being used by databases for the given origin.
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -45,16 +45,17 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 TEST_FILES = \
   bfcache_iframe1.html \
   bfcache_iframe2.html \
   error_events_abort_transactions_iframe.html \
   event_propagation_iframe.html \
   exceptions_in_success_events_iframe.html \
+  file.js \
   helpers.js \
   leaving_page_iframe.html \
   test_add_put.html \
   test_add_twice_failure.html \
   test_advance.html \
   test_autoIncrement_indexes.html \
   test_bfcache.html \
   test_clear.html \
@@ -68,16 +69,25 @@ TEST_FILES = \
   test_cursor_mutation.html \
   test_cursor_update_updates_indexes.html \
   test_deleteDatabase.html \
   test_deleteDatabase_interactions.html \
   test_error_events_abort_transactions.html \
   test_event_propagation.html \
   test_event_source.html \
   test_exceptions_in_success_events.html \
+  test_file_array.html \
+  test_file_cross_database_copying.html \
+  test_file_delete.html \
+  test_file_os_delete.html \
+  test_file_put_get_values.html \
+  test_file_resurrection_delete.html \
+  test_file_resurrection_transaction_abort.html \
+  test_file_sharing.html \
+  test_file_transaction_abort.html \
   test_getAll.html \
   test_global_data.html \
   test_index_empty_keyPath.html \
   test_index_getAll.html \
   test_index_getAllObjects.html \
   test_index_object_cursors.html \
   test_index_update_delete.html \
   test_indexes.html \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/file.js
@@ -0,0 +1,225 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var builder = new MozBlobBuilder();
+var manager = null;
+var bufferCache = [];
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function getBuffer(size)
+{
+  let buffer = new ArrayBuffer(size);
+  is(buffer.byteLength, size, "Correct byte length");
+  return buffer;
+}
+
+function getRandomBuffer(size)
+{
+  let buffer = getBuffer(size);
+  let view = new Uint8Array(buffer);
+  for (let i = 0; i < size; i++) {
+    view[i] = parseInt(Math.random() * 255)
+  }
+  return buffer;
+}
+
+function compareBuffers(buffer1, buffer2)
+{
+  if (buffer1.byteLength != buffer2.byteLength) {
+    return false;
+  }
+  let view1 = new Uint8Array(buffer1);
+  let view2 = new Uint8Array(buffer2);
+  for (let i = 0; i < buffer1.byteLength; i++) {
+    if (view1[i] != view2[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function getBlob(type, buffer)
+{
+  builder.append(buffer);
+  return builder.getBlob(type);
+}
+
+function getFile(name, type, buffer)
+{
+  builder.append(buffer);
+  return builder.getFile(name, type);
+}
+
+function getRandomBlob(size)
+{
+  return getBlob("binary/random", getRandomBuffer(size));
+}
+
+function getRandomFile(name, size)
+{
+  return getFile(name, "binary/random", getRandomBuffer(size));
+}
+
+function getNullBlob(size)
+{
+  return getBlob("binary/null", getBuffer(size));
+}
+
+function getNullFile(name, size)
+{
+  return getFile(name, "binary/null", getBuffer(size));
+}
+
+function verifyBuffers(buffer1, buffer2)
+{
+  ok(compareBuffers(buffer1, buffer2), "Correct blob data");
+}
+
+function verifyBlob(blob1, blob2, fileId, blobReadHandler)
+{
+  is(blob1 instanceof Components.interfaces.nsIDOMBlob, true,
+     "Instance of nsIDOMBlob");
+  is(blob1 instanceof Components.interfaces.nsIDOMFile,
+     blob2 instanceof Components.interfaces.nsIDOMFile,
+     "Instance of nsIDOMFile");
+  is(blob1.size, blob2.size, "Correct size");
+  is(blob1.type, blob2.type, "Correct type");
+  if (blob2 instanceof Components.interfaces.nsIDOMFile) {
+    is(blob1.name, blob2.name, "Correct name");
+  }
+  is(utils.getFileId(blob1), fileId, "Correct file id");
+
+  let buffer1;
+  let buffer2;
+
+  for (let i = 0; i < bufferCache.length; i++) {
+    if (bufferCache[i].blob == blob2) {
+      buffer2 = bufferCache[i].buffer;
+      break;
+    }
+  }
+
+  if (!buffer2) {
+    let reader = new FileReader();
+    reader.readAsArrayBuffer(blob2);
+    reader.onload = function(event) {
+      buffer2 = event.target.result;
+      bufferCache.push({ blob: blob2, buffer: buffer2 });
+      if (buffer1) {
+        verifyBuffers(buffer1, buffer2);
+        if (blobReadHandler) {
+          blobReadHandler();
+        } else {
+          testGenerator.next();
+        }
+      }
+    }
+  }
+
+  let reader = new FileReader();
+  reader.readAsArrayBuffer(blob1);
+  reader.onload = function(event) {
+    buffer1 = event.target.result;
+    if (buffer2) {
+      verifyBuffers(buffer1, buffer2);
+      if (blobReadHandler) {
+        blobReadHandler();
+      } else {
+        testGenerator.next();
+      }
+    }
+  }
+}
+
+function verifyBlobArray(blobs1, blobs2, expectedFileIds)
+{
+  is(blobs1 instanceof Array, true, "Got an array object");
+  is(blobs1.length, blobs2.length, "Correct length");
+
+  if (!blobs1.length) {
+    return;
+  }
+
+  let verifiedCount = 0;
+
+  function blobReadHandler() {
+    if (++verifiedCount == blobs1.length) {
+      testGenerator.next();
+    } else {
+      verifyBlob(blobs1[verifiedCount], blobs2[verifiedCount],
+                 expectedFileIds[verifiedCount], blobReadHandler);
+    }
+  }
+
+  verifyBlob(blobs1[verifiedCount], blobs2[verifiedCount],
+             expectedFileIds[verifiedCount], blobReadHandler);
+}
+
+function grabFileUsageAndContinueHandler(usage, fileUsage)
+{
+  testGenerator.send(fileUsage);
+}
+
+function getUsage(usageHandler)
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  if (!manager) {
+    manager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"]
+              .getService(Components.interfaces.nsIIndexedDatabaseManager);
+  }
+
+  let uri = SpecialPowers.getDocumentURIObject(window.document);
+  let callback = {
+    onUsageResult: function(uri, usage, fileUsage) {
+      usageHandler(usage, fileUsage);
+    }
+  };
+
+  manager.getUsageForURI(uri, callback);
+}
+
+function getUsageSync()
+{
+  let usage;
+
+  getUsage(function(aUsage, aFileUsage) {
+    usage = aUsage;
+  });
+
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  let thread = Components.classes["@mozilla.org/thread-manager;1"]
+                         .getService(Components.interfaces.nsIThreadManager)
+                         .currentThread;
+  while (!usage) {
+    thread.processNextEvent(true);
+  }
+
+  return usage;
+}
+
+function scheduleGC()
+{
+  SpecialPowers.exactGC(window, continueToNextStep);
+}
+
+function hasFileInfo(name, id)
+{
+  return utils.getFileReferences(name, id);
+}
+
+function getFileRefCount(name, id)
+{
+  let count = {};
+  utils.getFileReferences(name, id, count);
+  return count.value;
+}
+
+function getFileDBRefCount(name, id)
+{
+  let count = {};
+  utils.getFileReferences(name, id, {}, count);
+  return count.value;
+}
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -3,24 +3,26 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var testGenerator = testSteps();
 
 function runTest()
 {
   allowIndexedDB();
+  allowUnlimitedQuota();
 
   SimpleTest.waitForExplicitFinish();
   testGenerator.next();
 }
 
 function finishTest()
 {
-  disallowIndexedDB();
+  resetUnlimitedQuota();
+  resetIndexedDB();
 
   SimpleTest.executeSoon(function() {
     testGenerator.close();
     SimpleTest.finish();
   });
 }
 
 function browserRunTest()
@@ -72,34 +74,39 @@ ExpectError.prototype = {
   {
     is(event.type, "error", "Got an error event");
     is(this._code, event.target.errorCode, "Expected error was thrown.");
     event.preventDefault();
     grabEventAndContinueHandler(event);
   }
 };
 
-function addPermission(permission, url)
+function addPermission(type, allow, url)
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   let uri;
   if (url) {
     uri = Components.classes["@mozilla.org/network/io-service;1"]
                     .getService(Components.interfaces.nsIIOService)
                     .newURI(url, null, null);
+  } else {
+    uri = SpecialPowers.getDocumentURIObject(window.document);
   }
-  else {
-    uri = SpecialPowers.getDocumentURIObject(window.document);
+
+  let permission;
+  if (allow) {
+    permission = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
+  } else {
+    permission = Components.interfaces.nsIPermissionManager.DENY_ACTION;
   }
 
   Components.classes["@mozilla.org/permissionmanager;1"]
             .getService(Components.interfaces.nsIPermissionManager)
-            .add(uri, permission,
-                 Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+            .add(uri, type, permission);
 }
 
 function removePermission(permission, url)
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   let uri;
   if (url) {
@@ -123,25 +130,30 @@ function setQuota(quota)
   let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
 
   prefs.setIntPref("dom.indexedDB.warningQuota", quota);
 }
 
 function allowIndexedDB(url)
 {
-  addPermission("indexedDB", url);
+  addPermission("indexedDB", true, url);
 }
 
-function disallowIndexedDB(url)
+function resetIndexedDB(url)
 {
   removePermission("indexedDB", url);
 }
 
 function allowUnlimitedQuota(url)
 {
-  addPermission("indexedDB-unlimited", url);
+  addPermission("indexedDB-unlimited", true, url);
 }
 
-function disallowUnlimitedQuota(url)
+function denyUnlimitedQuota(url)
+{
+  addPermission("indexedDB-unlimited", false, url);
+}
+
+function resetUnlimitedQuota(url)
 {
   removePermission("indexedDB-unlimited", url);
 }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_array.html
@@ -0,0 +1,84 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const b1 = getRandomBlob(10000);
+
+    const b2 = [ getRandomBlob(5000), getRandomBlob(3000), getRandomBlob(12000),
+      getRandomBlob(17000), getRandomBlob(16000), getRandomBlob(16000),
+      getRandomBlob(8000)
+    ];
+
+    const b3 = [ getRandomBlob(5000), getRandomBlob(3000), getRandomBlob(9000)];
+
+    const objectStoreData = [
+      { key: 1, blobs: [ b1, b1, b1, b1, b1, b1, b1, b1, b1, b1 ],
+        expectedFileIds: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] },
+      { key: 2, blobs: [ b2[0], b2[1], b2[2], b2[3], b2[4], b2[5], b2[6] ],
+        expectedFileIds: [2, 3, 4, 5, 6, 7, 8] },
+      { key: 3, blobs: [ b3[0], b3[0], b3[1], b3[2], b3[2], b3[0], b3[0] ],
+        expectedFileIds: [9, 9, 10, 11, 11, 9, 9] }
+    ];
+
+    let request = mozIndexedDB.open(name, 1, description);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Got correct event type");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let objectStore = db.createObjectStore(objectStoreName, { });
+
+    for each (let data in objectStoreData) {
+      objectStore.add(data.blobs, data.key);
+    }
+
+    event = yield;
+
+    is(event.type, "success", "Got correct event type");
+
+    for each (let data in objectStoreData) {
+      objectStore = db.transaction([objectStoreName])
+                      .objectStore(objectStoreName);
+
+      request = objectStore.get(data.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      verifyBlobArray(event.target.result, data.blobs, data.expectedFileIds);
+      yield;
+    }
+
+    is(bufferCache.length, 11, "Correct length");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_cross_database_copying.html
@@ -0,0 +1,106 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const databaseInfo = [
+      { name: window.location.pathname + "1", description: "Test Database 1" },
+      { name: window.location.pathname + "2", description: "Test Database 2" }
+    ];
+
+    const objectStoreName = "Blobs";
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    let databases = [];
+    for each (let info in databaseInfo) {
+      let request = mozIndexedDB.open(info.name, 1, info.description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+      objectStore.add(fileData.file, fileData.key);
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      databases.push(db);
+    }
+
+    let refResult;
+    for each (let db in databases) {
+      let request = db.transaction([objectStoreName])
+                      .objectStore(objectStoreName).get(fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      verifyBlob(result, fileData.file, 1);
+      yield;
+
+      if (!refResult) {
+        refResult = result;
+        continue;
+      }
+
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      isnot(result.mozFullPath, refResult.mozFullPath, "Different os files");
+    }
+
+    for (let i = 1; i < databases.length; i++) {
+      let db = databases[i];
+
+      let objectStore = db.transaction([objectStoreName], READ_WRITE)
+                          .objectStore(objectStoreName);
+
+      request = objectStore.add(refResult, 2);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 2, "Got correct key");
+
+      request = objectStore.get(2);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      verifyBlob(result, refResult, 2);
+      yield;
+
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      isnot(result.mozFullPath, refResult.mozFullPath, "Different os files");
+    }
+
+    is(bufferCache.length, 2, "Correct length");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_delete.html
@@ -0,0 +1,134 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const fileData1 = { key: 1, file: getRandomFile("random1.bin", 110000) };
+    const fileData2 = { key: 2, file: getRandomFile("random2.bin", 120000) };
+    const fileData3 = { key: 3, file: getRandomFile("random3.bin", 130000) };
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+
+      objectStore.add(fileData1.file, fileData1.key);
+      objectStore.add(fileData2.file, fileData2.key);
+      objectStore.add(fileData3.file, fileData3.key);
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      trans.objectStore(objectStoreName).delete(fileData1.key);
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.type, "complete", "Got correct event type");
+
+      is(getFileDBRefCount(name, 1), 0, "Correct db ref count");
+
+      fileData1.file = null;
+      fileData2.file = null;
+      fileData3.file = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    ok(!hasFileInfo(name, 1), "Correct ref count");
+    ok(hasFileInfo(name, 2), "Correct ref count");
+    ok(hasFileInfo(name, 3), "Correct ref count");
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.get(fileData2.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      ok(result, "Got result");
+
+      objectStore.delete(fileData2.key);
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.type, "complete", "Got correct event type");
+
+      is(getFileDBRefCount(name, 2), 0, "Correct db ref count");
+
+
+      trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      objectStore.delete(fileData3.key);
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.type, "complete", "Got correct event type");
+
+      is(getFileDBRefCount(name, 3), -1, "Correct db ref count");
+
+      event = null;
+      result = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    ok(!hasFileInfo(name, 1), "Correct ref count");
+    ok(!hasFileInfo(name, 2), "Correct ref count");
+    ok(!hasFileInfo(name, 3), "Correct ref count");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_os_delete.html
@@ -0,0 +1,86 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    getUsage(grabFileUsageAndContinueHandler);
+    let startUsage = yield;
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+
+      objectStore.add(fileData.file, fileData.key);
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      getUsage(grabFileUsageAndContinueHandler);
+      let usage = yield;
+
+      is(usage, startUsage + fileData.file.size, "Correct file usage");
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      trans.objectStore(objectStoreName).delete(fileData.key);
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.type, "complete", "Got correct event type");
+
+      getUsage(grabFileUsageAndContinueHandler);
+      usage = yield;
+
+      is(usage, startUsage + fileData.file.size, "OS file exists");
+
+      fileData.file = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    getUsage(grabFileUsageAndContinueHandler);
+    let endUsage = yield;
+
+    is(endUsage, startUsage, "OS file deleted");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_put_get_values.html
@@ -0,0 +1,101 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const blobData = { key: 1, blob: getRandomBlob(10000) };
+    const fileData = { key: 2, file: getRandomFile("random.bin", 100000) };
+
+    let request = mozIndexedDB.open(name, 1, description);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Got correct event type");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    db.createObjectStore(objectStoreName, { });
+
+    event = yield;
+
+    is(event.type, "success", "Got correct event type");
+
+    let objectStore = db.transaction([objectStoreName], READ_WRITE)
+                        .objectStore(objectStoreName);
+    request = objectStore.add(blobData.blob, blobData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.target.result, blobData.key, "Got correct key");
+
+    request = objectStore.get(blobData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    verifyBlob(event.target.result, blobData.blob, 1);
+    yield;
+
+    request = db.transaction([objectStoreName])
+                .objectStore(objectStoreName).get(blobData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    verifyBlob(event.target.result, blobData.blob, 1);
+    yield;
+
+    objectStore = db.transaction([objectStoreName], READ_WRITE)
+                    .objectStore(objectStoreName);
+    request = objectStore.add(fileData.file, fileData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.target.result, fileData.key, "Got correct key");
+
+    request = objectStore.get(fileData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    verifyBlob(event.target.result, fileData.file, 2);
+    yield;
+
+    request = db.transaction([objectStoreName])
+                .objectStore(objectStoreName).get(fileData.key);
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    verifyBlob(event.target.result, fileData.file, 2);
+    yield;
+
+    is(bufferCache.length, 2, "Correct length");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_quota.html
@@ -0,0 +1,75 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    denyUnlimitedQuota();
+
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+    const DEFAULT_QUOTA_MB = 50;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const testData = { key: 0, value: {} };
+    const fileData = { key: 1, file: null };
+
+    let request = mozIndexedDB.open(name, 1, description);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Got correct event type");
+     
+    let db = event.target.result;
+
+    let objectStore = db.createObjectStore(objectStoreName, { });
+    objectStore.add(testData.value, testData.key);
+
+    let usage = getUsageSync();
+
+    let size = (DEFAULT_QUOTA_MB + 1) * 1024 * 1024 - usage;
+
+    fileData.file = getNullFile("random.bin", size);
+
+    event = yield;
+
+    is(event.type, "success", "Got correct event type");
+
+    trans = db.transaction([objectStoreName], READ_WRITE);
+    objectStore = trans.objectStore(objectStoreName);
+
+    request = objectStore.add(fileData.file, fileData.key);
+    request.onerror = new ExpectError(IDBDatabaseException.UNKNOWN_ERR);
+    request.onsuccess = unexpectedSuccessHandler;
+    event = yield;
+
+    trans.oncomplete = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "complete", "Got correct event type");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_resurrection_delete.html
@@ -0,0 +1,136 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    {
+      let file = getRandomFile("random1.bin", 100000);
+
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+
+      objectStore.add(fileData.file, fileData.key);
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      objectStore.delete(fileData.key);
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 0, "Correct db ref count");
+
+      trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.add(fileData.file, fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 1, "Correct db ref count");
+
+      fileData.file = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    is(getFileRefCount(name, 1), 1, "Correct ref count");
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.get(fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      ok(result, "Got result");
+
+      objectStore.delete(fileData.key);
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 0, "Correct db ref count");
+
+      trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.add(result, fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 1, "Correct db ref count");
+
+      event = null;
+      result = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    is(getFileRefCount(name, 1), 1, "Correct ref count");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_resurrection_transaction_abort.html
@@ -0,0 +1,93 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.add(fileData.file, fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      request = objectStore.get(fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      ok(result, "Got result");
+
+      trans.onabort = grabEventAndContinueHandler;
+      trans.abort();
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 0, "Correct db ref count");
+
+      trans = db.transaction([objectStoreName], READ_WRITE);
+      objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.add(result, fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      trans.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+
+      is(getFileDBRefCount(name, 1), 1, "Correct db ref count");
+
+      fileData.file = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    is(getFileRefCount(name, 1), 1, "Correct ref count");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_sharing.html
@@ -0,0 +1,106 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreInfo = [
+      { name: "Blobs", options: { } },
+      { name: "Other Blobs", options: { } }
+    ];
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    let request = mozIndexedDB.open(name, 1, description);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Got correct event type");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    for each (let info in objectStoreInfo) {
+      let objectStore = db.createObjectStore(info.name, info.options);
+      objectStore.add(fileData.file, fileData.key);
+    }
+
+    event = yield;
+
+    is(event.type, "success", "Got correct event type");
+
+    let refResult;
+    for each (let info in objectStoreInfo) {
+      let objectStore = db.transaction([info.name])
+                          .objectStore(info.name);
+
+      request = objectStore.get(fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      verifyBlob(result, fileData.file, 1);
+      yield;
+
+      if (!refResult) {
+        refResult = result;
+        continue;
+      }
+
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      is(result.mozFullPath, refResult.mozFullPath, "The same os file");
+    }
+
+    for (let i = 1; i < objectStoreInfo.length; i++) {
+      let info = objectStoreInfo[i];
+
+      let objectStore = db.transaction([info.name], READ_WRITE)
+                          .objectStore(info.name);
+
+      request = objectStore.add(refResult, 2);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 2, "Got correct key");
+
+      request = objectStore.get(2);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let result = event.target.result;
+      verifyBlob(result, refResult, 1);
+      yield;
+
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      is(result.mozFullPath, refResult.mozFullPath, "The same os file");
+    }
+
+    is(bufferCache.length, 2, "Correct length");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_file_transaction_abort.html
@@ -0,0 +1,78 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const READ_WRITE = IDBTransaction.READ_WRITE;
+
+    const name = window.location.pathname;
+    const description = "My Test Database";
+
+    const objectStoreName = "Blobs";
+
+    const fileData = { key: 1, file: getRandomFile("random.bin", 100000) };
+
+    {
+      let request = mozIndexedDB.open(name, 1, description);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      objectStore = db.createObjectStore(objectStoreName, { });
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      let objectStore = trans.objectStore(objectStoreName);
+
+      request = objectStore.add(fileData.file, fileData.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, fileData.key, "Got correct key");
+
+      trans.onabort = grabEventAndContinueHandler;
+      trans.abort();
+      event = yield;
+
+      is(event.type, "abort", "Got correct event type");
+
+      is(getFileDBRefCount(name, 1), 0, "Correct db ref count");
+
+      fileData.file = null;
+    }
+
+    scheduleGC();
+    yield;
+
+    ok(!hasFileInfo(name, 1), "Correct ref count");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="file.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -60,20 +60,21 @@ interface nsICycleCollectorListener;
 interface nsIDOMNode;
 interface nsIDOMNodeList;
 interface nsIDOMElement;
 interface nsIDOMHTMLCanvasElement;
 interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
+interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 
-[scriptable, uuid(bf868921-0288-4799-a806-2fa642590197)]
+[scriptable, uuid(36adf309-e5c4-4912-9152-7fb151dc754a)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -921,9 +922,23 @@ interface nsIDOMWindowUtils : nsISupport
   readonly attribute boolean mayHaveTouchEventListeners;
  
   /**
    * Check if any ThebesLayer painting has been done for this element,
    * clears the painted flags if they have.
    */
   boolean checkAndClearPaintedState(in nsIDOMElement aElement);
 
+  /*
+   * Get internal id of the stored blob.
+   */
+  long long getFileId(in nsIDOMBlob aBlob);
+
+  /*
+   * Get file ref count info for given database and file id.
+   *
+   */
+  boolean getFileReferences(in AString aDatabaseName, in long long aId,
+                            [optional] out long aRefCnt,
+                            [optional] out long aDBRefCnt,
+                            [optional] out long aSliceRefCnt);
+
 };
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -56,17 +56,17 @@ interface nsIFile;
  * mozIStorageConnection represents a database connection attached to
  * a specific file or to the in-memory data storage.  It is the
  * primary interface for interacting with a database, including
  * creating prepared statements, executing SQL, and examining database
  * errors.
  *
  * @threadsafe
  */
-[scriptable, uuid(ad035628-4ffb-42ff-a256-0ed9e410b859)]
+[scriptable, uuid(b2a4b534-f92e-4387-9bd9-d10408173925)]
 interface mozIStorageConnection : nsISupports {
   /**
    * The default size for SQLite database pages used by mozStorage for new
    * databases.
    * This value must stay in sync with the SQLITE_DEFAULT_PAGE_SIZE define in
    * /db/sqlite3/src/Makefile.in
    */
   const long DEFAULT_PAGE_SIZE = 32768;
@@ -139,16 +139,22 @@ interface mozIStorageConnection : nsISup
 
   /**
    * lastInsertRowID returns the row ID from the last INSERT
    * operation.
    */
   readonly attribute long long lastInsertRowID;
 
   /**
+   * affectedRows returns the number of database rows that were changed or
+   * inserted or deleted by last operation.
+   */
+  readonly attribute long affectedRows;
+
+  /**
    * The last error SQLite error code.
    */
   readonly attribute long lastError;
 
   /**
    * The last SQLite error as a string (in english, straight from the
    * sqlite library).
    */
@@ -379,9 +385,20 @@ interface mozIStorageConnection : nsISup
    *        The database file will grow in multiples of chunkSize.
    * @param aDatabaseName
    *        Sqlite database name. "" means pass NULL for zDbName to sqlite3_file_control.
    *        See http://sqlite.org/c3ref/file_control.html for more details.
    * @throws NS_ERROR_FILE_TOO_BIG
    *         If the system is short on storage space.
    */
   void setGrowthIncrement(in PRInt32 aIncrement, in AUTF8String aDatabaseName);
+
+  /**
+   * Enable a predefined virtual table implementation.
+   *
+   * @param aModuleName
+   *        The module to enable. Only "filesystem" is currently supported.
+   *
+   * @throws NS_ERROR_FAILURE
+   *         For unknown module names.
+   */
+  [noscript] void enableModule(in ACString aModuleName);
 };
--- a/storage/public/mozIStorageServiceQuotaManagement.idl
+++ b/storage/public/mozIStorageServiceQuotaManagement.idl
@@ -70,17 +70,17 @@ interface mozIStorageQuotaCallback : nsI
                           in long long aCurrentTotalSize,
                           in nsISupports aUserData);
 };
 
 /**
  * This is a temporary interface that should eventually merge with
  * mozIStorageService.
  */
-[scriptable, uuid(11def472-446f-4635-a1d8-8856e85aac50)]
+[scriptable, uuid(4d81faf5-fe01-428b-99b8-c94cba12fd72)]
 interface mozIStorageServiceQuotaManagement : nsISupports
 {
   /**
    * See mozIStorageService.openDatabase. Exactly the same only with a custom
    * SQLite VFS.
    */
   mozIStorageConnection openDatabaseWithVFS(in nsIFile aDatabaseFile,
                                             in ACString aVFSName);
@@ -123,10 +123,10 @@ interface mozIStorageServiceQuotaManagem
    * quota pattern (set previously by setQuotaForFilenamePattern()).
    * 
    * @param aFile
    *        The file for which quota information should be updated. If the file
    *        exists then its size information will be added or refreshed. If the
    *        file does not exist then the file will be removed from tracking
    *        under the quota system.
    */
-  void updateQutoaInformationForFile(in nsIFile aFile);
+  void updateQuotaInformationForFile(in nsIFile aFile);
 };
new file mode 100644
--- /dev/null
+++ b/storage/src/FileSystemModule.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "FileSystemModule.h"
+
+#include "sqlite3.h"
+#include "nsString.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIFile.h"
+#include "nsILocalFile.h"
+
+namespace {
+
+struct VirtualTableCursorBase
+{
+  VirtualTableCursorBase()
+  {
+    memset(&mBase, 0, sizeof(mBase));
+  }
+
+  sqlite3_vtab_cursor mBase;
+};
+
+struct VirtualTableCursor : public VirtualTableCursorBase
+{
+public:
+  VirtualTableCursor()
+  : mRowId(-1)
+  {
+    mCurrentFileName.SetIsVoid(true);
+  }
+
+  const nsString& DirectoryPath() const
+  {
+    return mDirectoryPath;
+  }
+
+  const nsString& CurrentFileName() const
+  {
+    return mCurrentFileName;
+  }
+
+  PRInt64 RowId() const
+  {
+    return mRowId;
+  }
+
+  nsresult Init(const nsAString& aPath);
+  nsresult NextFile();
+
+private:
+  nsCOMPtr<nsISimpleEnumerator> mEntries;
+
+  nsString mDirectoryPath;
+  nsString mCurrentFileName;
+
+  PRInt64 mRowId;
+};
+
+nsresult
+VirtualTableCursor::Init(const nsAString& aPath)
+{
+  nsCOMPtr<nsILocalFile> directory =
+    do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
+
+  nsresult rv = directory->InitWithPath(aPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = directory->GetPath(mDirectoryPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = directory->GetDirectoryEntries(getter_AddRefs(mEntries));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NextFile();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+VirtualTableCursor::NextFile()
+{
+  bool hasMore;
+  nsresult rv = mEntries->HasMoreElements(&hasMore);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!hasMore) {
+    mCurrentFileName.SetIsVoid(true);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISupports> entry;
+  rv = mEntries->GetNext(getter_AddRefs(entry));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+  rv = file->GetLeafName(mCurrentFileName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mRowId++;
+
+  return NS_OK;
+}
+
+int Connect(sqlite3* aDB, void* aAux, int aArgc, const char* const* aArgv,
+            sqlite3_vtab** aVtab, char** aErr)
+{
+  static const char virtualTableSchema[] =
+    "CREATE TABLE fs ("
+      "name TEXT, "
+      "path TEXT"
+    ")";
+
+  int rc = sqlite3_declare_vtab(aDB, virtualTableSchema);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+
+  sqlite3_vtab* vt = new sqlite3_vtab();
+  memset(vt, 0, sizeof(*vt));
+
+  *aVtab = vt;
+
+  return SQLITE_OK;
+}
+
+int Disconnect(sqlite3_vtab* aVtab )
+{
+  delete aVtab;
+
+  return SQLITE_OK;
+}
+
+int BestIndex(sqlite3_vtab* aVtab, sqlite3_index_info* aInfo)
+{
+  // Here we specify what index constraints we want to handle. That is, there
+  // might be some columns with particular constraints in which we can help
+  // SQLite narrow down the result set.
+  //
+  // For example, take the "path = x" where x is a directory. In this case,
+  // we can narrow our search to just this directory instead of the entire file
+  // system. This can be a significant optimization. So, we want to handle that
+  // constraint. To do so, we would look for two specific input conditions:
+  //
+  // 1. aInfo->aConstraint[i].iColumn == 1
+  // 2. aInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ
+  //
+  // The first states that the path column is being used in one of the input
+  // constraints and the second states that the constraint involves the equal
+  // operator.
+  //
+  // An even more specific search would be for name='xxx', in which case we
+  // can limit the search to a single file, if it exists.
+  //
+  // What we have to do here is look for all of our index searches and select
+  // the narrowest. We can only pick one, so obviously we want the one that
+  // is the most specific, which leads to the smallest result set.
+
+  for(int i = 0; i < aInfo->nConstraint; i++) {
+    if (aInfo->aConstraint[i].iColumn == 1 && aInfo->aConstraint[i].usable) {
+      if (aInfo->aConstraint[i].op & SQLITE_INDEX_CONSTRAINT_EQ) {
+        aInfo->aConstraintUsage[i].argvIndex = 1;
+      }
+      break;
+    }
+
+    // TODO: handle single files (constrained also by the name column)
+  }
+
+  return SQLITE_OK;
+}
+
+int Open(sqlite3_vtab* aVtab, sqlite3_vtab_cursor** aCursor)
+{
+  VirtualTableCursor* cursor = new VirtualTableCursor();
+
+  *aCursor = reinterpret_cast<sqlite3_vtab_cursor*>(cursor);
+
+  return SQLITE_OK;
+}
+
+int Close(sqlite3_vtab_cursor* aCursor)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+  delete cursor;
+
+  return SQLITE_OK;
+}
+
+int Filter(sqlite3_vtab_cursor* aCursor, int aIdxNum, const char* aIdxStr,
+           int aArgc, sqlite3_value** aArgv)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+  if(aArgc <= 0) {
+    return SQLITE_OK;
+  }
+
+  nsDependentString path(
+    reinterpret_cast<const PRUnichar*>(::sqlite3_value_text16(aArgv[0])));
+
+  nsresult rv = cursor->Init(path);
+  NS_ENSURE_SUCCESS(rv, SQLITE_ERROR);
+
+  return SQLITE_OK;
+}
+
+int Next(sqlite3_vtab_cursor* aCursor)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+  nsresult rv = cursor->NextFile();
+  NS_ENSURE_SUCCESS(rv, SQLITE_ERROR);
+
+  return SQLITE_OK;
+}
+
+int Eof(sqlite3_vtab_cursor* aCursor)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+  return cursor->CurrentFileName().IsVoid() ? 1 : 0;
+}
+
+int Column(sqlite3_vtab_cursor* aCursor, sqlite3_context* aContext,
+           int aColumnIndex)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+  switch (aColumnIndex) {
+    // name
+    case 0: {
+      const nsString& name = cursor->CurrentFileName();
+      sqlite3_result_text16(aContext, name.get(),
+                            name.Length() * sizeof(PRUnichar),
+                            SQLITE_TRANSIENT);
+      break;
+    }
+
+    // path
+    case 1: {
+      const nsString& path = cursor->DirectoryPath();
+      sqlite3_result_text16(aContext, path.get(),
+                            path.Length() * sizeof(PRUnichar),
+                            SQLITE_TRANSIENT);
+      break;
+    }
+    default:
+      NS_NOTREACHED("Unsupported column!");
+  }
+
+  return SQLITE_OK;
+}
+
+int RowId(sqlite3_vtab_cursor* aCursor, sqlite3_int64* aRowid)
+{
+  VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+  *aRowid = cursor->RowId();
+
+  return SQLITE_OK;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace storage {
+
+int RegisterFileSystemModule(sqlite3* aDB, const char* aName)
+{
+  static sqlite3_module module = {
+    1,
+    Connect,
+    Connect,
+    BestIndex,
+    Disconnect,
+    Disconnect,
+    Open,
+    Close,
+    Filter,
+    Next,
+    Eof,
+    Column,
+    RowId,
+    nsnull,
+    nsnull,
+    nsnull,
+    nsnull,
+    nsnull,
+    nsnull,
+    nsnull
+  };
+
+  return sqlite3_create_module(aDB, aName, &module, nsnull);
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/FileSystemModule.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_storage_FileSystemModule_h
+#define mozilla_storage_FileSystemModule_h
+
+#include "nscore.h"
+
+struct sqlite3;
+
+namespace mozilla {
+namespace storage {
+
+NS_HIDDEN_(int) RegisterFileSystemModule(sqlite3* aDB, const char* aName);
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_FileSystemModule_h
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -81,16 +81,17 @@ CPPSRCS = \
   mozStorageBindingParams.cpp \
   mozStorageAsyncStatement.cpp \
   mozStorageAsyncStatementJSHelper.cpp \
   mozStorageAsyncStatementParams.cpp \
   StorageBaseStatementInternal.cpp \
   SQLCollations.cpp \
   VacuumManager.cpp \
   TelemetryVFS.cpp \
+  FileSystemModule.cpp \
   $(NULL)
 
 # For nsDependentJSString
 LOCAL_INCLUDES = \
   $(SQLITE_CFLAGS) \
   -I$(topsrcdir)/db/sqlite3/src \
   -I$(topsrcdir)/dom/base \
   $(NULL)
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -64,16 +64,17 @@
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageAsyncStatement.h"
 #include "mozStorageArgValueArray.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozStorageStatementData.h"
 #include "StorageBaseStatementInternal.h"
 #include "SQLCollations.h"
+#include "FileSystemModule.h"
 
 #include "prlog.h"
 #include "prprf.h"
 
 #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
 
 // Maximum size of the pages cache per connection.  If the default cache_size
 // value evaluates to a larger size, it will be reduced to save memory.
@@ -154,16 +155,29 @@ sqlite3_T_blob(sqlite3_context *aCtx,
 {
   ::sqlite3_result_blob(aCtx, aData, aSize, NS_Free);
   return SQLITE_OK;
 }
 
 #include "variantToSQLiteT_impl.h"
 
 ////////////////////////////////////////////////////////////////////////////////
+//// Modules
+
+struct Module
+{
+  const char* name;
+  int (*registerFunc)(sqlite3*, const char*);
+};
+
+Module gModules[] = {
+  { "filesystem", RegisterFileSystemModule }
+};
+
+////////////////////////////////////////////////////////////////////////////////
 //// Local Functions
 
 #ifdef PR_LOGGING
 void tracefunc (void *aClosure, const char *aStmt)
 {
   PR_LOG(gStorageLog, PR_LOG_DEBUG, ("sqlite3_trace on %p for '%s'", aClosure,
                                      aStmt));
 }
@@ -1125,16 +1139,26 @@ Connection::GetLastInsertRowID(PRInt64 *
 
   sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
   *_id = id;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+Connection::GetAffectedRows(PRInt32 *_rows)
+{
+  if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+  *_rows = ::sqlite3_changes(mDBConn);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 Connection::GetLastError(PRInt32 *_error)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   *_error = ::sqlite3_errcode(mDBConn);
 
   return NS_OK;
 }
@@ -1507,10 +1531,29 @@ Connection::SetGrowthIncrement(PRInt32 a
   (void)::sqlite3_file_control(mDBConn,
                                aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get() : NULL,
                                SQLITE_FCNTL_CHUNK_SIZE,
                                &aChunkSize);
 #endif
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Connection::EnableModule(const nsACString& aModuleName)
+{
+  if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+  for (size_t i = 0; i < ArrayLength(gModules); i++) {
+    struct Module* m = &gModules[i];
+    if (aModuleName.Equals(m->name)) {
+      int srv = m->registerFunc(mDBConn, m->name);
+      if (srv != SQLITE_OK)
+        return convertResultCode(srv);
+
+      return NS_OK;
+    }
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
 } // namespace storage
 } // namespace mozilla
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -52,18 +52,24 @@
 #include "nsILocale.h"
 #include "nsILocaleService.h"
 #include "nsIXPConnect.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 
 #include "sqlite3.h"
+#include "test_quota.h"
 #include "test_quota.c"
 
+#ifdef SQLITE_OS_WIN
+// "windows.h" was included and it can #define lots of things we care about...
+#undef CompareString
+#endif
+
 #include "nsIPromptService.h"
 #include "nsIMemoryReporter.h"
 
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/Util.h"
 
 namespace {
 
@@ -740,17 +746,17 @@ Service::SetQuotaForFilenamePattern(cons
                                data, QuotaCallbackData::Destroy);
   NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
 
   data.forget();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-Service::UpdateQutoaInformationForFile(nsIFile *aFile)
+Service::UpdateQuotaInformationForFile(nsIFile *aFile)
 {
   NS_ENSURE_ARG_POINTER(aFile);
 
   nsCString path;
   nsresult rv = aFile->GetNativePath(path);
   NS_ENSURE_SUCCESS(rv, rv);
 
   int rc = ::sqlite3_quota_file(PromiseFlatCString(path).get());
--- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
+++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
@@ -522,16 +522,37 @@ SpecialPowersAPI.prototype = {
   gc: function() {
     this.DOMWindowUtils.garbageCollect();
   },
 
   forceGC: function() {
     Components.utils.forceGC();
   },
 
+  exactGC: function(win, callback) {
+    var self = this;
+    let count = 0;
+
+    function doPreciseGCandCC() {
+      function scheduledGCCallback() {
+        self.getDOMWindowUtils(win).cycleCollect();
+
+        if (++count < 2) {
+          doPreciseGCandCC();
+        } else {
+          callback();
+        }
+      }
+
+      Components.utils.schedulePreciseGC(scheduledGCCallback);
+    }
+
+    doPreciseGCandCC();
+  },
+
   hasContentProcesses: function() {
     try {
       var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
       return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
     } catch (e) {
       return true;
     }
   },