Bug 753990 - Allow appcache to work with a custom cache (profile) folder within a single application, r=michal.novotny
authorHonza Bambas <honzab.moz@firemni.cz>
Mon, 04 Jun 2012 16:12:24 +0200
changeset 100212 59e7730d780ba305dbee82fa4cf6260dec010414
parent 100211 f2b089df69b30de2bfb24e88ba84ac87a8bd514e
child 100213 b4a1b04851884215aeb09edc398e0660b9bbbe3b
push id173
push userlsblakk@mozilla.com
push dateFri, 24 Aug 2012 15:39:16 +0000
treeherdermozilla-release@bcc45eb1fb41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs753990
milestone15.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
Bug 753990 - Allow appcache to work with a custom cache (profile) folder within a single application, r=michal.novotny
netwerk/base/public/nsIApplicationCache.idl
netwerk/base/public/nsIApplicationCacheService.idl
netwerk/base/public/nsICachingChannel.idl
netwerk/cache/nsApplicationCacheService.cpp
netwerk/cache/nsCacheEntry.cpp
netwerk/cache/nsCacheEntry.h
netwerk/cache/nsCacheRequest.h
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsCacheSession.cpp
netwerk/cache/nsCacheSession.h
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
netwerk/cache/nsICacheSession.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/unit/test_offlinecache_custom-directory.js
netwerk/test/unit/xpcshell.ini
uriloader/prefetch/OfflineCacheUpdateChild.cpp
uriloader/prefetch/OfflineCacheUpdateGlue.cpp
uriloader/prefetch/OfflineCacheUpdateGlue.h
uriloader/prefetch/OfflineCacheUpdateParent.cpp
uriloader/prefetch/nsIOfflineCacheUpdate.idl
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
uriloader/prefetch/nsOfflineCacheUpdateService.cpp
--- a/netwerk/base/public/nsIApplicationCache.idl
+++ b/netwerk/base/public/nsIApplicationCache.idl
@@ -2,16 +2,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIArray;
+interface nsILocalFile;
 
 /**
  * Application caches can store a set of namespace entries that affect
  * loads from the application cache.  If a load from the cache fails
  * to match an exact cache entry, namespaces entries will be searched
  * for a substring match, and should be applied appropriately.
  */
 [scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)]
@@ -73,17 +74,17 @@ interface nsIApplicationCacheNamespace :
  * types, as discussed in the WHAT-WG offline applications
  * specification.
  *
  * All application caches with the same group ID belong to a cache
  * group.  Each group has one "active" cache that will service future
  * loads.  Inactive caches will be removed from the cache when they are
  * no longer referenced.
  */
-[scriptable, uuid(32f83e3f-470c-4423-a86a-d35d1c215ccb)]
+[scriptable, uuid(231e1e53-05c1-41b6-b9de-dbbcce9385c9)]
 interface nsIApplicationCache : nsISupports
 {
     /**
      * Init this application cache instance to just hold the group ID and
      * the client ID to work just as a handle to the real cache. Used on
      * content process to simplify the application cache code.
      */
     void initAsHandle(in ACString groupId, in ACString clientId);
@@ -184,9 +185,15 @@ interface nsIApplicationCache : nsISuppo
      *        An nsIArray of nsIApplicationCacheNamespace entries.
      */
     void addNamespaces(in nsIArray namespaces);
 
     /**
      * Get the most specific namespace matching a given key.
      */
     nsIApplicationCacheNamespace getMatchingNamespace(in ACString key);
+
+    /**
+     * If set, this offline cache is placed in a different directory
+     * than the current application profile.
+     */
+    readonly attribute nsILocalFile cacheDirectory;
 };
--- a/netwerk/base/public/nsIApplicationCacheService.idl
+++ b/netwerk/base/public/nsIApplicationCacheService.idl
@@ -2,31 +2,48 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIApplicationCache;
+interface nsILocalFile;
 
 /**
  * The application cache service manages the set of application cache
  * groups.
  */
-[scriptable, uuid(10fdea21-1224-4c29-8507-8f3205a121d5)]
+[scriptable, uuid(28adfdc7-6718-4b3e-bdb2-ecfefa3c8910)]
 interface nsIApplicationCacheService : nsISupports
 {
     /**
      * Create a new, empty application cache for the given cache
      * group.
      */
     nsIApplicationCache createApplicationCache(in ACString group);
 
     /**
+     * Create a new, empty application cache for the given cache
+     * group residing in a custom directory with a custom quota.
+     *
+     * @param group
+     *    URL of the manifest
+     * @param directory
+     *    Actually a reference to a profile directory where to
+     *    create the OfflineCache sub-dir.
+     * @param quota
+     *    Optional override of the default quota.
+     */
+    nsIApplicationCache createCustomApplicationCache(in ACString group,
+                                                     in nsILocalFile profileDir,
+                                                     in PRInt32 quota);
+
+    /**
      * Get an application cache object for the given client ID.
      */
     nsIApplicationCache getApplicationCache(in ACString clientID);
 
     /**
      * Get the currently active cache object for a cache group.
      */
     nsIApplicationCache getActiveCache(in ACString group);
--- a/netwerk/base/public/nsICachingChannel.idl
+++ b/netwerk/base/public/nsICachingChannel.idl
@@ -1,28 +1,29 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsICacheInfoChannel.idl"
 
 interface nsIFile;
+interface nsILocalFile;
 
 /**
  * A channel may optionally implement this interface to allow clients
  * to affect its behavior with respect to how it uses the cache service.
  *
  * This interface provides:
  *   1) Support for "stream as file" semantics (for JAR and plugins).
  *   2) Support for "pinning" cached data in the cache (for printing and save-as).
  *   3) Support for uniquely identifying cached data in cases when the URL
  *      is insufficient (e.g., HTTP form submission).
  */
-[scriptable, uuid(830D4BCB-3E46-4011-9BDA-51A5D1AF891F)]
+[scriptable, uuid(E2143B61-62FE-4da5-BE2E-E31981095889)]
 interface nsICachingChannel : nsICacheInfoChannel
 {
     /**
      * Set/get the cache token... uniquely identifies the data in the cache.
      * Holding a reference to this token prevents the cached data from being
      * removed.
      * 
      * A cache token retrieved from a particular instance of nsICachingChannel
@@ -83,16 +84,22 @@ interface nsICachingChannel : nsICacheIn
 
     /**
      * The session into which to cache offline data.  If not specified,
      * data will be placed in "HTTP-offline"
      */
     attribute ACString offlineCacheClientID;
 
     /**
+     * Override base (profile) directory to work with when accessing the cache.
+     * When not specified, the current process' profile directory will be used.
+     */
+    attribute nsILocalFile profileDirectory;
+
+    /**
      * Get the "file" where the cached data can be found.  This is valid for
      * as long as a reference to the cache token is held.  This may return
      * an error if cacheAsFile is false.
      */
     readonly attribute nsIFile cacheFile;
 
     /**************************************************************************
      * Caching channel specific load flags:
--- a/netwerk/cache/nsApplicationCacheService.cpp
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -30,16 +30,33 @@ nsApplicationCacheService::CreateApplica
 
     nsRefPtr<nsOfflineCacheDevice> device;
     nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
     NS_ENSURE_SUCCESS(rv, rv);
     return device->CreateApplicationCache(group, out);
 }
 
 NS_IMETHODIMP
+nsApplicationCacheService::CreateCustomApplicationCache(const nsACString & group,
+                                                        nsILocalFile *profileDir,
+                                                        PRInt32 quota,
+                                                        nsIApplicationCache **out)
+{
+    if (!mCacheService)
+        return NS_ERROR_UNEXPECTED;
+
+    nsRefPtr<nsOfflineCacheDevice> device;
+    nsresult rv = mCacheService->GetCustomOfflineDevice(profileDir,
+                                                        quota,
+                                                        getter_AddRefs(device));
+    NS_ENSURE_SUCCESS(rv, rv);
+    return device->CreateApplicationCache(group, out);
+}
+
+NS_IMETHODIMP
 nsApplicationCacheService::GetApplicationCache(const nsACString &clientID,
                                                nsIApplicationCache **out)
 {
     if (!mCacheService)
         return NS_ERROR_UNEXPECTED;
 
     nsRefPtr<nsOfflineCacheDevice> device;
     nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
--- a/netwerk/cache/nsCacheEntry.cpp
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -27,16 +27,17 @@ nsCacheEntry::nsCacheEntry(nsCString *  
       mFetchCount(0),
       mLastFetched(0),
       mLastModified(0),
       mExpirationTime(nsICache::NO_EXPIRATION_TIME),
       mFlags(0),
       mPredictedDataSize(-1),
       mDataSize(0),
       mCacheDevice(nsnull),
+      mCustomDevice(nsnull),
       mData(nsnull)
 {
     MOZ_COUNT_CTOR(nsCacheEntry);
     PR_INIT_CLIST(this);
     PR_INIT_CLIST(&mRequestQ);
     PR_INIT_CLIST(&mDescriptorQ);
 
     if (streamBased) MarkStreamBased();
--- a/netwerk/cache/nsCacheEntry.h
+++ b/netwerk/cache/nsCacheEntry.h
@@ -58,16 +58,19 @@ public:
     PRUint32 ExpirationTime()                     { return mExpirationTime; }
     void     SetExpirationTime( PRUint32 expires) { mExpirationTime = expires; }
 
     PRUint32 Size()                               
         { return mDataSize + mMetaData.Size() + (mKey ? mKey->Length() : 0); }
 
     nsCacheDevice * CacheDevice()                            { return mCacheDevice; }
     void            SetCacheDevice( nsCacheDevice * device)  { mCacheDevice = device; }
+    void            SetCustomCacheDevice( nsCacheDevice * device )
+                                                             { mCustomDevice = device; }
+    nsCacheDevice * CustomCacheDevice()                      { return mCustomDevice; }
     const char *    GetDeviceID();
 
     /**
      * Data accessors
      */
     nsISupports *Data()                           { return mData; }
     void         SetData( nsISupports * data);
 
@@ -211,16 +214,17 @@ private:
     PRUint32                mLastFetched;    // 4
     PRUint32                mLastModified;   // 4
     PRUint32                mLastValidated;  // 4
     PRUint32                mExpirationTime; // 4
     PRUint32                mFlags;          // 4
     PRInt64                 mPredictedDataSize;  // Size given by ContentLength.
     PRUint32                mDataSize;       // 4
     nsCacheDevice *         mCacheDevice;    // 4
+    nsCacheDevice *         mCustomDevice;   // 4
     nsCOMPtr<nsISupports>   mSecurityInfo;   // 
     nsISupports *           mData;           // strong ref
     nsCOMPtr<nsIThread>     mThread;
     nsCacheMetaData         mMetaData;       // 4
     PRCList                 mRequestQ;       // 8
     PRCList                 mDescriptorQ;    // 8
 };
 
--- a/netwerk/cache/nsCacheRequest.h
+++ b/netwerk/cache/nsCacheRequest.h
@@ -32,17 +32,18 @@ private:
                     nsICacheListener *    listener,
                     nsCacheAccessMode     accessRequested,
                     bool                  blockingMode,
                     nsCacheSession *      session)
         : mKey(key),
           mInfo(0),
           mListener(listener),
           mLock("nsCacheRequest.mLock"),
-          mCondVar(mLock, "nsCacheRequest.mCondVar")
+          mCondVar(mLock, "nsCacheRequest.mCondVar"),
+          mProfileDir(session->ProfileDir())
     {
         MOZ_COUNT_CTOR(nsCacheRequest);
         PR_INIT_CLIST(this);
         SetAccessRequested(accessRequested);
         SetStoragePolicy(session->StoragePolicy());
         if (session->IsStreamBased())             MarkStreamBased();
         if (session->WillDoomEntriesIfExpired())  MarkDoomEntriesIfExpired();
         if (session->IsPrivate())                 MarkPrivate();
@@ -147,11 +148,12 @@ private:
      * Data members
      */
     nsCString *                mKey;
     PRUint32                   mInfo;
     nsICacheListener *         mListener;  // strong ref
     nsCOMPtr<nsIThread>        mThread;
     Mutex                      mLock;
     CondVar                    mCondVar;
+    nsCOMPtr<nsILocalFile>     mProfileDir;
 };
 
 #endif // _nsCacheRequest_h_
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -1084,16 +1084,17 @@ nsCacheService::nsCacheService()
       mDeactivateFailures(0),
       mDeactivatedUnboundEntries(0)
 {
     NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!");
     gService = this;
 
     // create list of cache devices
     PR_INIT_CLIST(&mDoomedEntries);
+    mCustomOfflineDevices.Init();
 }
 
 nsCacheService::~nsCacheService()
 {
     if (mInitialized) // Shutdown hasn't been called yet.
         (void) Shutdown();
 
     gService = nsnull;
@@ -1138,16 +1139,25 @@ nsCacheService::Init()
     mEnableDiskDevice    = mObserver->DiskCacheEnabled();
     mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
     mEnableMemoryDevice  = mObserver->MemoryCacheEnabled();
 
     mInitialized = true;
     return NS_OK;
 }
 
+// static
+PLDHashOperator
+nsCacheService::ShutdownCustomCacheDeviceEnum(const nsAString& aProfileDir,
+                                              nsRefPtr<nsOfflineCacheDevice>& aDevice,
+                                              void* aUserArg)
+{
+    aDevice->Shutdown();
+    return PL_DHASH_REMOVE;
+}
 
 void
 nsCacheService::Shutdown()
 {
     nsCOMPtr<nsIThread> cacheIOThread;
     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
 
     bool shouldSanitize = false;
@@ -1193,16 +1203,18 @@ nsCacheService::Shutdown()
         delete mDiskDevice;
         mDiskDevice = nsnull;
 
         if (mOfflineDevice)
             mOfflineDevice->Shutdown();
 
         NS_IF_RELEASE(mOfflineDevice);
 
+        mCustomOfflineDevices.Enumerate(&nsCacheService::ShutdownCustomCacheDeviceEnum, nsnull);
+
 #ifdef PR_LOGGING
         LogCacheStatistics();
 #endif
 
         mCacheIOThread.swap(cacheIOThread);
     }
     } // lock
 
@@ -1528,41 +1540,84 @@ nsCacheService::GetOfflineDevice(nsOffli
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     NS_ADDREF(*aDevice = mOfflineDevice);
     return NS_OK;
 }
 
 nsresult
+nsCacheService::GetCustomOfflineDevice(nsILocalFile *aProfileDir,
+                                       PRInt32 aQuota,
+                                       nsOfflineCacheDevice **aDevice)
+{
+    nsresult rv;
+
+    nsAutoString profilePath;
+    rv = aProfileDir->GetPath(profilePath);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
+        rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        mCustomOfflineDevices.Put(profilePath, *aDevice);
+    }
+
+    return NS_OK;
+}
+
+nsresult
 nsCacheService::CreateOfflineDevice()
 {
-    CACHE_LOG_ALWAYS(("Creating offline device"));
+    CACHE_LOG_ALWAYS(("Creating default offline device"));
+
+    if (mOfflineDevice)        return NS_OK;
+    if (!mObserver)            return NS_ERROR_NOT_AVAILABLE;
+
+    nsresult rv = CreateCustomOfflineDevice(
+        mObserver->OfflineCacheParentDirectory(),
+        mObserver->OfflineCacheCapacity(),
+        &mOfflineDevice);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateCustomOfflineDevice(nsILocalFile *aProfileDir,
+                                          PRInt32 aQuota,
+                                          nsOfflineCacheDevice **aDevice)
+{
+    NS_ENSURE_ARG(aProfileDir);
+
+#if defined(PR_LOGGING)
+    nsCAutoString profilePath;
+    aProfileDir->GetNativePath(profilePath);
+    CACHE_LOG_ALWAYS(("Creating custom offline device, %s, %d",
+                      profilePath.BeginReading(), aQuota));
+#endif
 
     if (!mInitialized)         return NS_ERROR_NOT_AVAILABLE;
     if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
-    if (mOfflineDevice)        return NS_OK;
-
-    mOfflineDevice = new nsOfflineCacheDevice;
-    if (!mOfflineDevice)       return NS_ERROR_OUT_OF_MEMORY;
-
-    NS_ADDREF(mOfflineDevice);
+
+    *aDevice = new nsOfflineCacheDevice;
+
+    NS_ADDREF(*aDevice);
 
     // set the preferences
-    mOfflineDevice->SetCacheParentDirectory(
-        mObserver->OfflineCacheParentDirectory());
-    mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity());
-
-    nsresult rv = mOfflineDevice->Init();
+    (*aDevice)->SetCacheParentDirectory(aProfileDir);
+    (*aDevice)->SetCapacity(aQuota);
+
+    nsresult rv = (*aDevice)->Init();
     if (NS_FAILED(rv)) {
-        CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv));
+        CACHE_LOG_DEBUG(("OfflineDevice->Init() failed (0x%.8x)\n", rv));
         CACHE_LOG_DEBUG(("    - disabling offline cache for this session.\n"));
 
-        mEnableOfflineDevice = false;
-        NS_RELEASE(mOfflineDevice);
+        NS_RELEASE(*aDevice);
     }
     return rv;
 }
 
 nsresult
 nsCacheService::CreateMemoryDevice()
 {
     if (!mInitialized)        return NS_ERROR_NOT_AVAILABLE;
@@ -1729,16 +1784,30 @@ nsCacheService::ProcessRequest(nsCacheRe
 
         if (entry->IsNotInUse()) {
             // this request was the last one keeping it around, so get rid of it
             DeactivateEntry(entry);
         }
         // loop back around to look for another entry
     }
 
+    if (NS_SUCCEEDED(rv) && request->mProfileDir) {
+        // Custom cache directory has been demanded.  Preset the cache device.
+        if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
+            // Failsafe check: this is implemented only for offline cache atm.
+            rv = NS_ERROR_FAILURE;
+        } else {
+            nsRefPtr<nsOfflineCacheDevice> customCacheDevice;
+            rv = GetCustomOfflineDevice(request->mProfileDir, -1,
+                                        getter_AddRefs(customCacheDevice));
+            if (NS_SUCCEEDED(rv))
+                entry->SetCustomCacheDevice(customCacheDevice);
+        }
+    }
+
     nsICacheEntryDescriptor *descriptor = nsnull;
     
     if (NS_SUCCEEDED(rv))
         rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
 
     // If doomedEntry is set, ActivatEntry() doomed an existing entry and
     // created a new one for that cache-key. However, any pending requests
     // on the doomed entry were not processed and we need to do that here.
@@ -2041,22 +2110,26 @@ nsCacheService::EnsureEntryHasDevice(nsC
     }
 
     if (!device && entry->IsStreamData() &&
         entry->IsAllowedOffline() && mEnableOfflineDevice) {
         if (!mOfflineDevice) {
             (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
         }
 
-        if (mOfflineDevice) {
+        device = entry->CustomCacheDevice()
+               ? entry->CustomCacheDevice()
+               : mOfflineDevice;
+
+        if (device) {
             entry->MarkBinding();
-            nsresult rv = mOfflineDevice->BindEntry(entry);
+            nsresult rv = device->BindEntry(entry);
             entry->ClearBinding();
-            if (NS_SUCCEEDED(rv))
-                device = mOfflineDevice;
+            if (NS_FAILED(rv))
+                device = nsnull;
         }
     }
 
     if (device) 
         entry->SetCacheDevice(device);
     return device;
 }
 
@@ -2141,16 +2214,19 @@ nsCacheService::OnProfileShutdown(bool c
     gService->mEnableDiskDevice = false;
 
     if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
         if (cleanse)
             gService->mOfflineDevice->EvictEntries(nsnull);
 
         gService->mOfflineDevice->Shutdown();
     }
+    gService->mCustomOfflineDevices.Enumerate(
+        &nsCacheService::ShutdownCustomCacheDeviceEnum, nsnull);
+
     gService->mEnableOfflineDevice = false;
 
     if (gService->mMemoryDevice) {
         // clear memory cache
         gService->mMemoryDevice->EvictEntries(nsnull);
     }
 
     gService->mClearingEntries = false;
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -12,16 +12,17 @@
 #include "nsCacheSession.h"
 #include "nsCacheDevice.h"
 #include "nsCacheEntry.h"
 
 #include "prthread.h"
 #include "nsIObserver.h"
 #include "nsString.h"
 #include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Mutex.h"
 
 class nsCacheRequest;
 class nsCacheProfilePrefObserver;
 class nsDiskCacheDevice;
 class nsMemoryCacheDevice;
 class nsOfflineCacheDevice;
@@ -107,16 +108,25 @@ public:
     static bool      IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy policy);
 
     /**
      * Methods called by nsApplicationCacheService
      */
 
     nsresult GetOfflineDevice(nsOfflineCacheDevice ** aDevice);
 
+    /**
+     * Creates an offline cache device that works over a specific profile directory.
+     * A tool to preload offline cache for profiles different from the current
+     * application's profile directory.
+     */
+    nsresult GetCustomOfflineDevice(nsILocalFile *aProfileDir,
+                                    PRInt32 aQuota,
+                                    nsOfflineCacheDevice **aDevice);
+
     // This method may be called to release an object while the cache service
     // lock is being held.  If a non-null target is specified and the target
     // does not correspond to the current thread, then the release will be
     // proxied to the specified target.  Otherwise, the object will be added to
     // the list of objects to be released when the cache service is unlocked.
     static void      ReleaseObject_Locked(nsISupports *    object,
                                           nsIEventTarget * target = nsnull);
 
@@ -178,16 +188,19 @@ private:
      * Internal Methods
      */
 
     static void      Lock();
     static void      Unlock();
 
     nsresult         CreateDiskDevice();
     nsresult         CreateOfflineDevice();
+    nsresult         CreateCustomOfflineDevice(nsILocalFile *aProfileDir,
+                                               PRInt32 aQuota,
+                                               nsOfflineCacheDevice **aDevice);
     nsresult         CreateMemoryDevice();
 
     nsresult         CreateRequest(nsCacheSession *   session,
                                    const nsACString & clientKey,
                                    nsCacheAccessMode  accessRequested,
                                    bool               blockingMode,
                                    nsICacheListener * listener,
                                    nsCacheRequest **  request);
@@ -232,16 +245,21 @@ private:
                                              PLDHashEntryHdr * hdr,
                                              PRUint32          number,
                                              void *            arg);
     static
     PLDHashOperator  RemoveActiveEntry(PLDHashTable *    table,
                                        PLDHashEntryHdr * hdr,
                                        PRUint32          number,
                                        void *            arg);
+
+    static
+    PLDHashOperator  ShutdownCustomCacheDeviceEnum(const nsAString& aProfileDir,
+                                                   nsRefPtr<nsOfflineCacheDevice>& aDevice,
+                                                   void* aUserArg);
 #if defined(PR_LOGGING)
     void LogCacheStatistics();
 #endif
 
     nsresult         SetDiskSmartSize_Locked();
 
     /**
      *  Data Members
@@ -265,16 +283,18 @@ private:
     bool                            mEnableMemoryDevice;
     bool                            mEnableDiskDevice;
     bool                            mEnableOfflineDevice;
 
     nsMemoryCacheDevice *           mMemoryDevice;
     nsDiskCacheDevice *             mDiskDevice;
     nsOfflineCacheDevice *          mOfflineDevice;
 
+    nsRefPtrHashtable<nsStringHashKey, nsOfflineCacheDevice> mCustomOfflineDevices;
+
     nsCacheEntryHashTable           mActiveEntries;
     PRCList                         mDoomedEntries;
 
     // stats
     
     PRUint32                        mTotalEntries;
     PRUint32                        mCacheHits;
     PRUint32                        mCacheMisses;
--- a/netwerk/cache/nsCacheSession.cpp
+++ b/netwerk/cache/nsCacheSession.cpp
@@ -36,16 +36,41 @@ nsCacheSession::~nsCacheSession()
 NS_IMETHODIMP nsCacheSession::GetDoomEntriesIfExpired(bool *result)
 {
     NS_ENSURE_ARG_POINTER(result);
     *result = WillDoomEntriesIfExpired();
     return NS_OK;
 }
 
 
+NS_IMETHODIMP nsCacheSession::SetProfileDirectory(nsILocalFile *profileDir)
+{
+  if (StoragePolicy() != nsICache::STORE_OFFLINE && profileDir) {
+        // Profile directory override is currently implemented only for
+        // offline cache.  This is an early failure to prevent the request
+        // being processed before it would fail later because of inability
+        // to assign a cache base dir.
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    mProfileDir = profileDir;
+    return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::GetProfileDirectory(nsILocalFile **profileDir)
+{
+    if (mProfileDir)
+        NS_ADDREF(*profileDir = mProfileDir);
+    else
+        *profileDir = nsnull;
+
+    return NS_OK;
+}
+
+
 NS_IMETHODIMP nsCacheSession::SetDoomEntriesIfExpired(bool doomEntriesIfExpired)
 {
     if (doomEntriesIfExpired)  MarkDoomEntriesIfExpired();
     else                       ClearDoomEntriesIfExpired();
     return NS_OK;
 }
 
 
--- a/netwerk/cache/nsCacheSession.h
+++ b/netwerk/cache/nsCacheSession.h
@@ -4,17 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _nsCacheSession_h_
 #define _nsCacheSession_h_
 
 #include "nspr.h"
 #include "nsError.h"
+#include "nsCOMPtr.h"
 #include "nsICacheSession.h"
+#include "nsILocalFile.h"
 #include "nsString.h"
 
 class nsCacheSession : public nsICacheSession
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICACHESESSION
     
@@ -48,14 +50,17 @@ public:
 
     void SetStoragePolicy(nsCacheStoragePolicy policy)
     {
         NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
         mInfo &= ~eStoragePolicyMask; // clear storage policy bits
         mInfo |= policy;
     }
 
+    nsILocalFile* ProfileDir() { return mProfileDir; }
+
 private:
     nsCString               mClientID;
     PRUint32                mInfo;
+    nsCOMPtr<nsILocalFile>  mProfileDir;
 };
 
 #endif // _nsCacheSession_h_
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -625,16 +625,27 @@ nsApplicationCache::GetGroupID(nsACStrin
 NS_IMETHODIMP
 nsApplicationCache::GetClientID(nsACString &out)
 {
   out = mClientID;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsApplicationCache::GetCacheDirectory(nsILocalFile **out)
+{
+  if (mDevice->BaseDirectory())
+      NS_ADDREF(*out = mDevice->BaseDirectory());
+  else
+      *out = nsnull;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsApplicationCache::GetActive(bool *out)
 {
   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
 
   *out = mDevice->IsActiveCache(mGroup, mClientID);
   return NS_OK;
 }
 
@@ -2356,16 +2367,18 @@ nsOfflineCacheDevice::SetCacheParentDire
   // ensure parent directory exists
   nsresult rv = EnsureDir(parentDir);
   if (NS_FAILED(rv))
   {
     NS_WARNING("unable to create parent directory");
     return;
   }
 
+  mBaseDirectory = parentDir;
+
   // cache dir may not exist, but that's ok
   nsCOMPtr<nsIFile> dir;
   rv = parentDir->Clone(getter_AddRefs(dir));
   if (NS_FAILED(rv))
     return;
   rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
   if (NS_FAILED(rv))
     return;
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -163,16 +163,17 @@ public:
 
   /**
    * Preference accessors
    */
 
   void                    SetCacheParentDirectory(nsILocalFile * parentDir);
   void                    SetCapacity(PRUint32  capacity);
 
+  nsILocalFile *          BaseDirectory() { return mBaseDirectory; }
   nsILocalFile *          CacheDirectory() { return mCacheDirectory; }
   PRUint32                CacheCapacity() { return mCacheCapacity; }
   PRUint32                CacheSize();
   PRUint32                EntryCount();
   
 private:
   friend class nsApplicationCache;
 
@@ -247,16 +248,17 @@ private:
   nsCOMPtr<mozIStorageStatement>  mStatement_GatherEntries;
   nsCOMPtr<mozIStorageStatement>  mStatement_ActivateClient;
   nsCOMPtr<mozIStorageStatement>  mStatement_DeactivateGroup;
   nsCOMPtr<mozIStorageStatement>  mStatement_FindClient;
   nsCOMPtr<mozIStorageStatement>  mStatement_FindClientByNamespace;
   nsCOMPtr<mozIStorageStatement>  mStatement_EnumerateGroups;
   nsCOMPtr<mozIStorageStatement>  mStatement_EnumerateGroupsTimeOrder;
 
+  nsCOMPtr<nsILocalFile>          mBaseDirectory;
   nsCOMPtr<nsILocalFile>          mCacheDirectory;
   PRUint32                        mCacheCapacity; // in bytes
   PRInt32                         mDeltaCounter;
 
   nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
   nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
   nsTHashtable<nsCStringHashKey> mActiveCaches;
 
--- a/netwerk/cache/nsICacheSession.idl
+++ b/netwerk/cache/nsICacheSession.idl
@@ -4,29 +4,38 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsICache.idl"
 
 interface nsICacheEntryDescriptor;
 interface nsICacheListener;
+interface nsILocalFile;
 
 [scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)]
 interface nsICacheSession : nsISupports
 {
     /**
      * Expired entries will be doomed or evicted if this attribute is set to
      * true.  If false, expired entries will be returned (useful for offline-
      * mode and clients, such as HTTP, that can update the valid lifetime of
      * cached content).  This attribute defaults to true.
      */
     attribute boolean doomEntriesIfExpired;
 
     /**
+     * When set, entries created with this session will be placed to a cache
+     * based at this directory.  Use when storing entries to a different
+     * profile than the active profile of the the current running application
+     * process.
+     */
+    attribute nsILocalFile profileDirectory;
+
+    /**
      * A cache session can only give out one descriptor with WRITE access
      * to a given cache entry at a time.  Until the client calls MarkValid on
      * its descriptor, other attempts to open the same cache entry will block.
      */
 
     /**
      * Synchronous cache access.  This returns a unique descriptor each
      * time it is called, even if the same key is specified.  When
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2612,16 +2612,21 @@ nsHttpChannel::OpenOfflineCacheEntryForW
     if (NS_FAILED(rv)) return rv;
 
     rv = serv->CreateSession(mOfflineCacheClientID.get(),
                              nsICache::STORE_OFFLINE,
                              nsICache::STREAM_BASED,
                              getter_AddRefs(session));
     if (NS_FAILED(rv)) return rv;
 
+    if (mProfileDirectory) {
+        rv = session->SetProfileDirectory(mProfileDirectory);
+        if (NS_FAILED(rv)) return rv;
+    }
+
     mOnCacheEntryAvailableCallback =
         &nsHttpChannel::OnOfflineCacheEntryForWritingAvailable;
     rv = session->AsyncOpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE,
                                       this, true);
     if (NS_SUCCEEDED(rv))
         return NS_OK;
 
     mOnCacheEntryAvailableCallback = nsnull;
@@ -3929,16 +3934,17 @@ nsHttpChannel::SetupReplacementChannel(n
                 if (cacheKey) {
                     cachingChannel->SetCacheKey(cacheKey);
                 }
             }
 
             // cacheClientID, cacheForOfflineUse
             cachingChannel->SetOfflineCacheClientID(mOfflineCacheClientID);
             cachingChannel->SetCacheForOfflineUse(mCacheForOfflineUse);
+            cachingChannel->SetProfileDirectory(mProfileDirectory);
         }
     }
 
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::AsyncProcessRedirection(PRUint32 redirectType)
@@ -5357,16 +5363,32 @@ nsHttpChannel::SetOfflineCacheClientID(c
     ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
     mOfflineCacheClientID = value;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsHttpChannel::GetProfileDirectory(nsILocalFile **_result)
+{
+    NS_ENSURE_ARG(_result);
+
+    NS_ADDREF(*_result = mProfileDirectory);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetProfileDirectory(nsILocalFile *value)
+{
+    mProfileDirectory = value;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
 {
     if (!mCacheEntry)
         return NS_ERROR_NOT_AVAILABLE;
     return mCacheEntry->GetFile(cacheFile);
 }
 
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -22,16 +22,17 @@
 #include "nsIPrompt.h"
 #include "nsIResumableChannel.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsICancelable.h"
 #include "nsIHttpAuthenticableChannel.h"
 #include "nsIHttpChannelAuthProvider.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsITimedChannel.h"
+#include "nsILocalFile.h"
 #include "nsDNSPrefetch.h"
 #include "TimingStruct.h"
 #include "AutoClose.h"
 #include "mozilla/Telemetry.h"
 
 class nsAHttpConnection;
 
 namespace mozilla { namespace net {
@@ -299,16 +300,18 @@ private:
     typedef nsresult (nsHttpChannel:: *nsOnCacheEntryAvailableCallback)(
         nsICacheEntryDescriptor *, nsCacheAccessMode, nsresult);
     nsOnCacheEntryAvailableCallback   mOnCacheEntryAvailableCallback;
 
     nsCOMPtr<nsICacheEntryDescriptor> mOfflineCacheEntry;
     nsCacheAccessMode                 mOfflineCacheAccess;
     nsCString                         mOfflineCacheClientID;
 
+    nsCOMPtr<nsILocalFile>            mProfileDirectory;
+
     // auth specific data
     nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
 
     // Proxy info to replace with
     nsCOMPtr<nsIProxyInfo>            mTargetProxyInfo;
 
     // If the channel is associated with a cache, and the URI matched
     // a fallback namespace, this will hold the key for the fallback
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_offlinecache_custom-directory.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test executes nsIOfflineCacheUpdateService.scheduleCustomProfileUpdate API
+ * 1. preloads an app with a manifest to a custom sudir in the profile (for simplicity)
+ * 2. observes progress and completion of the update
+ * 3. checks presence of index.sql and files in the expected location
+ */
+
+do_load_httpd_js();
+
+var httpServer = null;
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+            getService(Ci.nsIIOService);
+  return ios.newChannel(url, "", null);
+}
+
+function make_uri(url) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+            getService(Ci.nsIIOService);
+  return ios.newURI(url, null, null);
+}
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+  var masterEntryContent = "<html manifest='/manifest'></html>";
+  response.setHeader("Content-Type", "text/html");
+  response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+  var manifestContent = "CACHE MANIFEST\n";
+  response.setHeader("Content-Type", "text/cache-manifest");
+  response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// finally check we got fallback content
+function finish_test(customDir)
+{
+  var offlineCacheDir = customDir.clone();
+  offlineCacheDir.append("OfflineCache");
+
+  var indexSqlFile = offlineCacheDir.clone();
+  indexSqlFile.append('index.sqlite');
+  do_check_eq(indexSqlFile.exists(), true);
+
+  var file1 = offlineCacheDir.clone();
+  file1.append("2");
+  file1.append("E");
+  file1.append("2C99DE6E7289A5-0");
+  do_check_eq(file1.exists(), true);
+
+  var file2 = offlineCacheDir.clone();
+  file2.append("8");
+  file2.append("6");
+  file2.append("0B457F75198B29-0");
+  do_check_eq(file2.exists(), true);
+
+  httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+  httpServer = new nsHttpServer();
+  httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+  httpServer.registerPathHandler("/manifest", manifestHandler);
+  httpServer.start(4444);
+
+  var profileDir = do_get_profile();
+  var customDir = profileDir.clone();
+  customDir.append("customOfflineCacheDir" + Math.random());
+
+  var pm = Cc["@mozilla.org/permissionmanager;1"]
+    .getService(Ci.nsIPermissionManager);
+  var uri = make_uri("http://localhost:4444");
+  if (pm.testPermission(uri, "offline-app") != 0) {
+    dump("Previous test failed to clear offline-app permission!  Expect failures.\n");
+  }
+  pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  var ps = Cc["@mozilla.org/preferences-service;1"]
+    .getService(Ci.nsIPrefBranch);
+  ps.setBoolPref("browser.cache.offline.enable", true);
+  // Set this pref to mimic the default browser behavior.
+  ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, profileDir);
+
+  var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+           getService(Ci.nsIOfflineCacheUpdateService);
+  var update = us.scheduleCustomProfileUpdate(
+      make_uri("http://localhost:4444/manifest"),
+      make_uri("http://localhost:4444/masterEntry"),
+      customDir);
+
+  var expectedStates = [
+      Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING,
+      Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED,
+      Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS,
+      Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMCOMPLETED,
+      Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED
+  ];
+
+  update.addObserver({
+    updateStateChanged: function(update, state)
+    {
+      do_check_eq(state, expectedStates.shift());
+
+      if (state == Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED)
+          finish_test(customDir);
+    },
+
+    applicationCacheAvailable: function(appCache)
+    {
+    }
+  }, false);
+
+  do_test_pending();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -182,8 +182,9 @@ run-if = hasNode
 [test_standardurl.js]
 [test_standardurl_port.js]
 [test_streamcopier.js]
 [test_traceable_channel.js]
 [test_unescapestring.js]
 [test_xmlhttprequest.js]
 [test_XHR_redirects.js]
 [test_pinned_app_cache.js]
+[test_offlinecache_custom-directory.js]
--- a/uriloader/prefetch/OfflineCacheUpdateChild.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
@@ -172,27 +172,33 @@ OfflineCacheUpdateChild::AssociateDocume
 }
 
 //-----------------------------------------------------------------------------
 // OfflineCacheUpdateChild::nsIOfflineCacheUpdate
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 OfflineCacheUpdateChild::Init(nsIURI *aManifestURI,
-                           nsIURI *aDocumentURI,
-                           nsIDOMDocument *aDocument)
+                              nsIURI *aDocumentURI,
+                              nsIDOMDocument *aDocument,
+                              nsILocalFile *aCustomProfileDir)
 {
     nsresult rv;
 
     // Make sure the service has been initialized
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
     if (!service)
         return NS_ERROR_FAILURE;
 
+    if (aCustomProfileDir) {
+        NS_ERROR("Custom Offline Cache Update not supported on child process");
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
+
     LOG(("OfflineCacheUpdateChild::Init [%p]", this));
 
     // Only http and https applications are supported.
     bool match;
     rv = aManifestURI->SchemeIs("http", &match);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!match) {
--- a/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
@@ -86,27 +86,28 @@ OfflineCacheUpdateGlue::Schedule()
     mUpdate->AddObserver(this, false);
 
     return mUpdate->Schedule();
 }
 
 NS_IMETHODIMP
 OfflineCacheUpdateGlue::Init(nsIURI *aManifestURI, 
                              nsIURI *aDocumentURI,
-                             nsIDOMDocument *aDocument)
+                             nsIDOMDocument *aDocument,
+                             nsILocalFile *aCustomProfileDir)
 {
     if (!EnsureUpdate())
         return NS_ERROR_NULL_POINTER;
 
     mDocumentURI = aDocumentURI;
 
     if (aDocument)
         SetDocument(aDocument);
 
-    return mUpdate->Init(aManifestURI, aDocumentURI, nsnull);
+    return mUpdate->Init(aManifestURI, aDocumentURI, nsnull, aCustomProfileDir);
 }
 
 void
 OfflineCacheUpdateGlue::SetDocument(nsIDOMDocument *aDocument)
 {
     // The design is one document for one cache update on the content process.
     NS_ASSERTION(!mDocument, 
                  "Setting more then a single document on an instance of OfflineCacheUpdateGlue");
--- a/uriloader/prefetch/OfflineCacheUpdateGlue.h
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.h
@@ -30,31 +30,32 @@ namespace docshell {
   NS_SCRIPTABLE NS_IMETHOD GetSucceeded(bool *aSucceeded) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetSucceeded(aSucceeded); } \
   NS_SCRIPTABLE NS_IMETHOD InitPartial(nsIURI *aManifestURI, const nsACString & aClientID, nsIURI *aDocumentURI) { return !_to ? NS_ERROR_NULL_POINTER : _to->InitPartial(aManifestURI, aClientID, aDocumentURI); } \
   NS_SCRIPTABLE NS_IMETHOD AddDynamicURI(nsIURI *aURI) { return !_to ? NS_ERROR_NULL_POINTER : _to->AddDynamicURI(aURI); } \
   NS_SCRIPTABLE NS_IMETHOD AddObserver(nsIOfflineCacheUpdateObserver *aObserver, bool aHoldWeak) { return !_to ? NS_ERROR_NULL_POINTER : _to->AddObserver(aObserver, aHoldWeak); } \
   NS_SCRIPTABLE NS_IMETHOD RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver) { return !_to ? NS_ERROR_NULL_POINTER : _to->RemoveObserver(aObserver); } \
   NS_SCRIPTABLE NS_IMETHOD GetByteProgress(PRUint64 * _result) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetByteProgress(_result); }
 
 class OfflineCacheUpdateGlue : public nsSupportsWeakReference
-                               , public nsIOfflineCacheUpdate
-                               , public nsIOfflineCacheUpdateObserver
+                             , public nsIOfflineCacheUpdate
+                             , public nsIOfflineCacheUpdateObserver
 {
 public:
     NS_DECL_ISUPPORTS
 
 private:
     nsIOfflineCacheUpdate* EnsureUpdate();
 
 public:
     NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(EnsureUpdate())
     NS_SCRIPTABLE NS_IMETHOD Schedule(void);
     NS_SCRIPTABLE NS_IMETHOD Init(nsIURI *aManifestURI, 
                                   nsIURI *aDocumentURI, 
-                                  nsIDOMDocument *aDocument);
+                                  nsIDOMDocument *aDocument,
+                                  nsILocalFile *aCustomProfileDir);
 
     NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
 
     OfflineCacheUpdateGlue();
     ~OfflineCacheUpdateGlue();
 
     void SetDocument(nsIDOMDocument *aDocument);
 
--- a/uriloader/prefetch/OfflineCacheUpdateParent.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -78,17 +78,17 @@ OfflineCacheUpdateParent::Schedule(const
 
     service->FindUpdate(manifestURI, documentURI, getter_AddRefs(update));
     if (!update) {
         update = new nsOfflineCacheUpdate();
 
         nsresult rv;
         // Leave aDocument argument null. Only glues and children keep 
         // document instances.
-        rv = update->Init(manifestURI, documentURI, nsnull);
+        rv = update->Init(manifestURI, documentURI, nsnull, nsnull);
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = update->Schedule();
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     update->AddObserver(this, false);
 
--- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -9,16 +9,17 @@ interface nsIURI;
 interface nsIDOMWindow;
 interface nsIDOMNode;
 interface nsIDOMDocument;
 interface nsIDOMLoadStatus;
 interface nsIOfflineCacheUpdate;
 interface nsIPrincipal;
 interface nsIPrefBranch;
 interface nsIApplicationCache;
+interface nsILocalFile;
 
 [scriptable, uuid(47360d57-8ef4-4a5d-8865-1a27a739ad1a)]
 interface nsIOfflineCacheUpdateObserver : nsISupports {
   const unsigned long STATE_ERROR = 1;
   const unsigned long STATE_CHECKING = 2;
   const unsigned long STATE_NOUPDATE = 3;
   const unsigned long STATE_OBSOLETE = 4;
   const unsigned long STATE_DOWNLOADING = 5;
@@ -100,17 +101,18 @@ interface nsIOfflineCacheUpdate : nsISup
   /**
    * Initialize the update.
    *
    * @param aManifestURI
    *        The manifest URI to be checked.
    * @param aDocumentURI
    *        The page that is requesting the update.
    */
-  void init(in nsIURI aManifestURI, in nsIURI aDocumentURI, in nsIDOMDocument aDocument);
+  void init(in nsIURI aManifestURI, in nsIURI aDocumentURI, in nsIDOMDocument aDocument,
+            [optional] in nsILocalFile aCustomProfileDir);
 
   /**
    * Initialize the update for partial processing. 
    *
    * @param aManifestURI
    *        The manifest URI of the related cache.
    * @param aClientID
    *        Client  ID of the cache to store resource to. This ClientID
@@ -157,17 +159,17 @@ interface nsIOfflineCacheUpdate : nsISup
   void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
 
   /**
    * Return the number of bytes downloaded so far
    */
   readonly attribute PRUint64 byteProgress;
 };
 
-[scriptable, uuid(6fd2030f-7b00-4102-a0e3-d73078821eb1)]
+[scriptable, uuid(dc5de18c-197c-41d2-9584-dd7ac7494611)]
 interface nsIOfflineCacheUpdateService : nsISupports {
     /**
      * Constants for the offline-app permission.
      *
      * XXX: This isn't a great place for this, but it's really the only
      * private offline-app-related interface
      */
 
@@ -188,16 +190,25 @@ interface nsIOfflineCacheUpdateService :
      * existing update is scheduled or running, that update will be returned.
      * Otherwise a new update will be scheduled.
      */
     nsIOfflineCacheUpdate scheduleUpdate(in nsIURI aManifestURI,
                                          in nsIURI aDocumentURI,
                                          in nsIDOMWindow aWindow);
 
     /**
+     * Schedule a cache update for a given offline manifest and let the data
+     * be stored to a custom profile directory.  There is no coalescing of
+     * manifests by manifest URL.
+     */
+    nsIOfflineCacheUpdate scheduleCustomProfileUpdate(in nsIURI aManifestURI,
+                                                      in nsIURI aDocumentURI,
+                                                      in nsILocalFile aProfileDir);
+
+    /**
      * Schedule a cache update for a manifest when the document finishes
      * loading.
      */
     void scheduleOnDocumentStop(in nsIURI aManifestURI,
                                 in nsIURI aDocumentURI,
                                 in nsIDOMDocument aDocument);
 
     /**
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -43,16 +43,19 @@
 using namespace mozilla;
 
 static const PRUint32 kRescheduleLimit = 3;
 // Max number of retries for every entry of pinned app.
 static const PRUint32 kPinnedEntryRetriesLimit = 3;
 // Maximum number of parallel items loads
 static const PRUint32 kParallelLoadLimit = 15;
 
+// Quota for offline apps when preloading
+static const PRInt32  kCustomProfileQuota = 512000;
+
 #if defined(PR_LOGGING)
 //
 // To enable logging (see prlog.h for full details):
 //
 //    set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
 //    set NSPR_LOG_FILE=offlineupdate.log
 //
 // this enables PR_LOG_ALWAYS level information and places all output in
@@ -279,21 +282,23 @@ NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateI
                    nsIChannelEventSink)
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdateItem <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsIURI *aURI,
                                                    nsIURI *aReferrerURI,
+                                                   nsIApplicationCache *aApplicationCache,
                                                    nsIApplicationCache *aPreviousApplicationCache,
                                                    const nsACString &aClientID,
                                                    PRUint32 type)
     : mURI(aURI)
     , mReferrerURI(aReferrerURI)
+    , mApplicationCache(aApplicationCache)
     , mPreviousApplicationCache(aPreviousApplicationCache)
     , mClientID(aClientID)
     , mItemType(type)
     , mChannel(nsnull)
     , mState(nsIDOMLoadStatus::UNINITIALIZED)
     , mBytesRead(0)
 {
 }
@@ -345,16 +350,23 @@ nsOfflineCacheUpdateItem::OpenChannel(ns
     }
 
     nsCOMPtr<nsICachingChannel> cachingChannel =
         do_QueryInterface(mChannel);
     if (cachingChannel) {
         rv = cachingChannel->SetCacheForOfflineUse(true);
         NS_ENSURE_SUCCESS(rv, rv);
 
+        nsCOMPtr<nsILocalFile> cacheDirectory;
+        rv = mApplicationCache->GetCacheDirectory(getter_AddRefs(cacheDirectory));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = cachingChannel->SetProfileDirectory(cacheDirectory);
+        NS_ENSURE_SUCCESS(rv, rv);
+
         if (!mClientID.IsEmpty()) {
             rv = cachingChannel->SetOfflineCacheClientID(mClientID);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     rv = mChannel->AsyncOpen(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -492,20 +504,28 @@ nsOfflineCacheUpdateItem::AsyncOnChannel
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsICachingChannel> newCachingChannel =
         do_QueryInterface(aNewChannel);
     if (newCachingChannel) {
         rv = newCachingChannel->SetCacheForOfflineUse(true);
         NS_ENSURE_SUCCESS(rv, rv);
+
         if (!mClientID.IsEmpty()) {
             rv = newCachingChannel->SetOfflineCacheClientID(mClientID);
             NS_ENSURE_SUCCESS(rv, rv);
         }
+
+        nsCOMPtr<nsILocalFile> cacheDirectory;
+        rv = mApplicationCache->GetCacheDirectory(getter_AddRefs(cacheDirectory));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = newCachingChannel->SetProfileDirectory(cacheDirectory);
+        NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsCAutoString oldScheme;
     mURI->GetScheme(oldScheme);
 
     bool match;
     if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
         LOG(("rejected: redirected to a different scheme\n"));
@@ -657,20 +677,21 @@ nsOfflineCacheUpdateItem::GetStatus(PRUi
 //-----------------------------------------------------------------------------
 
 //-----------------------------------------------------------------------------
 // nsOfflineManifestItem <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineManifestItem::nsOfflineManifestItem(nsIURI *aURI,
                                              nsIURI *aReferrerURI,
+                                             nsIApplicationCache *aApplicationCache,
                                              nsIApplicationCache *aPreviousApplicationCache,
                                              const nsACString &aClientID)
     : nsOfflineCacheUpdateItem(aURI, aReferrerURI,
-                               aPreviousApplicationCache, aClientID,
+                               aApplicationCache, aPreviousApplicationCache, aClientID,
                                nsIApplicationCache::ITEM_MANIFEST)
     , mParserState(PARSE_INIT)
     , mNeedsUpdate(true)
     , mManifestHashInitialized(false)
 {
     ReadStrictFileOriginPolicyPref();
 }
 
@@ -1167,17 +1188,18 @@ nsOfflineCacheUpdate::GetCacheKey(nsIURI
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 nsresult
 nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
                            nsIURI *aDocumentURI,
-                           nsIDOMDocument *aDocument)
+                           nsIDOMDocument *aDocument,
+                           nsILocalFile *aCustomProfileDir)
 {
     nsresult rv;
 
     // Make sure the service has been initialized
     nsOfflineCacheUpdateService* service =
         nsOfflineCacheUpdateService::EnsureService();
     if (!service)
         return NS_ERROR_FAILURE;
@@ -1209,23 +1231,42 @@ nsOfflineCacheUpdate::Init(nsIURI *aMani
     NS_ENSURE_SUCCESS(rv, rv);
 
     mDocumentURI = aDocumentURI;
 
     nsCOMPtr<nsIApplicationCacheService> cacheService =
         do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = cacheService->GetActiveCache(manifestSpec,
-                                      getter_AddRefs(mPreviousApplicationCache));
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (aCustomProfileDir) {
+        // Create only a new offline application cache in the custom profile
+        // This is a preload of a new cache.
+
+        // XXX Custom updates don't support "updating" of an existing cache
+        // in the custom profile at the moment.  This support can be, though,
+        // simply added as well when needed.
+        mPreviousApplicationCache = nsnull;
 
-    rv = cacheService->CreateApplicationCache(manifestSpec,
-                                              getter_AddRefs(mApplicationCache));
-    NS_ENSURE_SUCCESS(rv, rv);
+        rv = cacheService->CreateCustomApplicationCache(manifestSpec,
+                                                        aCustomProfileDir,
+                                                        kCustomProfileQuota,
+                                                        getter_AddRefs(mApplicationCache));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        mCustomProfileDir = aCustomProfileDir;
+    }
+    else {
+        rv = cacheService->GetActiveCache(manifestSpec,
+                                          getter_AddRefs(mPreviousApplicationCache));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = cacheService->CreateApplicationCache(manifestSpec,
+                                                  getter_AddRefs(mApplicationCache));
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     rv = mApplicationCache->GetClientID(mClientID);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
                                                              NULL,
                                                              &mPinned);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1542,17 +1583,17 @@ nsOfflineCacheUpdate::ManifestCheckCompl
         // from a new update through this dead update to them is absolutely
         // correct.
         FinishNoNotify();
 
         nsRefPtr<nsOfflineCacheUpdate> newUpdate =
             new nsOfflineCacheUpdate();
         // Leave aDocument argument null. Only glues and children keep
         // document instances.
-        newUpdate->Init(mManifestURI, mDocumentURI, nsnull);
+        newUpdate->Init(mManifestURI, mDocumentURI, nsnull, mCustomProfileDir);
 
         // In a rare case the manifest will not be modified on the next refetch
         // transfer all master document URIs to the new update to ensure that
         // all documents refering it will be properly cached.
         for (PRInt32 i = 0; i < mDocumentURIs.Count(); i++) {
             newUpdate->StickDocument(mDocumentURIs[i]);
         }
 
@@ -1582,16 +1623,17 @@ nsOfflineCacheUpdate::Begin()
         return NS_OK;
     }
 
     // Start checking the manifest.
     nsCOMPtr<nsIURI> uri;
 
     mManifestItem = new nsOfflineManifestItem(mManifestURI,
                                               mDocumentURI,
+                                              mApplicationCache,
                                               mPreviousApplicationCache,
                                               mClientID);
     if (!mManifestItem) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     mState = STATE_CHECKING;
     mByteProgress = 0;
@@ -2086,18 +2128,21 @@ nsOfflineCacheUpdate::AddURI(nsIURI *aUR
         if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
             // retain both types.
             mItems[i]->mItemType |= aType;
             return NS_OK;
         }
     }
 
     nsRefPtr<nsOfflineCacheUpdateItem> item =
-        new nsOfflineCacheUpdateItem(aURI, mDocumentURI,
-                                     mPreviousApplicationCache, mClientID,
+        new nsOfflineCacheUpdateItem(aURI, 
+                                     mDocumentURI,
+                                     mApplicationCache,
+                                     mPreviousApplicationCache, 
+                                     mClientID,
                                      aType);
     if (!item) return NS_ERROR_OUT_OF_MEMORY;
 
     mItems.AppendElement(item);
     mAddedItems = true;
 
     return NS_OK;
 }
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -49,23 +49,25 @@ public:
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIRUNNABLE
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSICHANNELEVENTSINK
 
     nsOfflineCacheUpdateItem(nsIURI *aURI,
                              nsIURI *aReferrerURI,
+                             nsIApplicationCache *aApplicationCache,
                              nsIApplicationCache *aPreviousApplicationCache,
                              const nsACString &aClientID,
                              PRUint32 aType);
     virtual ~nsOfflineCacheUpdateItem();
 
     nsCOMPtr<nsIURI>           mURI;
     nsCOMPtr<nsIURI>           mReferrerURI;
+    nsCOMPtr<nsIApplicationCache> mApplicationCache;
     nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
     nsCString                  mClientID;
     nsCString                  mCacheKey;
     PRUint32                   mItemType;
 
     nsresult OpenChannel(nsOfflineCacheUpdate *aUpdate);
     nsresult Cancel();
     nsresult GetRequestSucceeded(bool * succeeded);
@@ -87,16 +89,17 @@ protected:
 class nsOfflineManifestItem : public nsOfflineCacheUpdateItem
 {
 public:
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIREQUESTOBSERVER
 
     nsOfflineManifestItem(nsIURI *aURI,
                           nsIURI *aReferrerURI,
+                          nsIApplicationCache *aApplicationCache,
                           nsIApplicationCache *aPreviousApplicationCache,
                           const nsACString &aClientID);
     virtual ~nsOfflineManifestItem();
 
     nsCOMArray<nsIURI> &GetExplicitURIs() { return mExplicitURIs; }
     nsCOMArray<nsIURI> &GetFallbackURIs() { return mFallbackURIs; }
 
     nsTArray<nsCString> &GetOpportunisticNamespaces()
@@ -249,16 +252,17 @@ private:
     bool mAddedItems;
     bool mPartialUpdate;
     bool mSucceeded;
     bool mObsolete;
 
     nsCString mUpdateDomain;
     nsCOMPtr<nsIURI> mManifestURI;
     nsCOMPtr<nsIURI> mDocumentURI;
+    nsCOMPtr<nsILocalFile> mCustomProfileDir;
 
     nsCString mClientID;
     nsCOMPtr<nsIApplicationCache> mApplicationCache;
     nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
 
     nsCOMPtr<nsIObserverService> mObserverService;
 
     nsRefPtr<nsOfflineManifestItem> mManifestItem;
@@ -308,16 +312,17 @@ public:
     nsresult FindUpdate(nsIURI *aManifestURI,
                         nsIURI *aDocumentURI,
                         nsOfflineCacheUpdate **aUpdate);
 
     nsresult Schedule(nsIURI *aManifestURI,
                       nsIURI *aDocumentURI,
                       nsIDOMDocument *aDocument,
                       nsIDOMWindow* aWindow,
+                      nsILocalFile* aCustomProfileDir,
                       nsIOfflineCacheUpdate **aUpdate);
 
     virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate);
 
     /**
      * Returns the singleton nsOfflineCacheUpdateService without an addref, or
      * nsnull if the service couldn't be created.
      */
--- a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -156,17 +156,17 @@ nsOfflineCachePendingUpdate::OnStateChan
 
     LOG(("nsOfflineCachePendingUpdate::OnStateChange [%p, doc=%p]",
          this, progressDoc.get()));
 
     // Only schedule the update if the document loaded successfully
     if (NS_SUCCEEDED(aStatus)) {
         nsCOMPtr<nsIOfflineCacheUpdate> update;
         mService->Schedule(mManifestURI, mDocumentURI,
-                           updateDoc, window, getter_AddRefs(update));
+                           updateDoc, window, nsnull, getter_AddRefs(update));
     }
 
     aWebProgress->RemoveProgressListener(this);
     NS_RELEASE_THIS();
 
     return NS_OK;
 }
 
@@ -431,46 +431,59 @@ nsOfflineCacheUpdateService::FindUpdate(
     return NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 nsOfflineCacheUpdateService::Schedule(nsIURI *aManifestURI,
                                       nsIURI *aDocumentURI,
                                       nsIDOMDocument *aDocument,
                                       nsIDOMWindow* aWindow,
+                                      nsILocalFile* aCustomProfileDir,
                                       nsIOfflineCacheUpdate **aUpdate)
 {
     nsCOMPtr<nsIOfflineCacheUpdate> update;
     if (GeckoProcessType_Default != XRE_GetProcessType()) {
         update = new OfflineCacheUpdateChild(aWindow);
     }
     else {
         update = new OfflineCacheUpdateGlue();
     }
 
     nsresult rv;
 
-    rv = update->Init(aManifestURI, aDocumentURI, aDocument);
+    rv = update->Init(aManifestURI, aDocumentURI, aDocument, aCustomProfileDir);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = update->Schedule();
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ADDREF(*aUpdate = update);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI *aManifestURI,
                                             nsIURI *aDocumentURI,
                                             nsIDOMWindow *aWindow,
                                             nsIOfflineCacheUpdate **aUpdate)
 {
-    return Schedule(aManifestURI, aDocumentURI, nsnull, aWindow, aUpdate);
+    return Schedule(aManifestURI, aDocumentURI, nsnull, aWindow, nsnull, aUpdate);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleCustomProfileUpdate(nsIURI *aManifestURI,
+                                                         nsIURI *aDocumentURI,
+                                                         nsILocalFile *aProfileDir,
+                                                         nsIOfflineCacheUpdate **aUpdate)
+{
+    // The profile directory is mandatory
+    NS_ENSURE_ARG(aProfileDir);
+
+    return Schedule(aManifestURI, aDocumentURI, nsnull, nsnull, aProfileDir, aUpdate);
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdateService::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateService::Observe(nsISupports     *aSubject,