Merge mozilla-central and mozilla-inbound
authorEd Morley <bmo@edmorley.co.uk>
Fri, 16 Dec 2011 10:19:52 +0000
changeset 84406 dcf6e5163e631a470678cd35d1b6733687d9f034
parent 84405 4950a29c499f7b97d3bede4a985d6405e4a76cb8 (current diff)
parent 84337 5efcb9c3b375200062ee7e9c2db2f0906547e539 (diff)
child 84407 d6d732ef5650562f1f1593df4bd446614e3f2dfa
child 84425 30f9367dfa68f936d54a2e358fe1df3022102ae6
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central and mozilla-inbound
--- 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;
     }
   },