Bug 756648 - Implement "cookie jars" for apps. r=biesi,smaug
authorJason Duell <jduell.mcbugs@gmail.com>
Tue, 18 Sep 2012 12:04:04 -0400
changeset 107385 156bdf05dd97b402adc64bc8afdd29670a71fe0e
parent 107384 a8c051013c48a48c86dab032d54403d16e1a64ad
child 107386 09c5ad99ae92299f99c8809a53e66f86181969f5
push idunknown
push userunknown
push dateunknown
reviewersbiesi, smaug
bugs756648
milestone18.0a1
Bug 756648 - Implement "cookie jars" for apps. r=biesi,smaug
docshell/base/SerializedLoadContext.cpp
extensions/cookie/test/unit/test_cookies_sync_failure.js
netwerk/base/public/nsNetUtil.h
netwerk/cookie/CookieServiceChild.cpp
netwerk/cookie/CookieServiceParent.cpp
netwerk/cookie/CookieServiceParent.h
netwerk/cookie/PCookieService.ipdl
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/test/unit/head_channels.js
netwerk/test/unit/test_cookiejars.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_cookie_header_wrap.js
netwerk/test/unit_ipc/test_cookie_wrap.js
netwerk/test/unit_ipc/test_cookiejars_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/docshell/base/SerializedLoadContext.cpp
+++ b/docshell/base/SerializedLoadContext.cpp
@@ -13,16 +13,21 @@ namespace IPC {
 
 SerializedLoadContext::SerializedLoadContext(nsILoadContext* aLoadContext)
 {
   Init(aLoadContext);
 }
 
 SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel)
 {
+  if (!aChannel) {
+    Init(nullptr);
+    return;
+  }
+
   nsCOMPtr<nsILoadContext> loadContext;
   NS_QueryNotificationCallbacks(aChannel, loadContext);
   Init(loadContext);
 
   if (!loadContext) {
     // Attempt to retrieve the private bit from the channel if it has been
     // overriden.
     bool isPrivate = false;
@@ -35,17 +40,19 @@ SerializedLoadContext::SerializedLoadCon
       mIsPrivateBitValid = true;
     }
   }
 }
 
 SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel)
 {
   nsCOMPtr<nsILoadContext> loadContext;
-  NS_QueryNotificationCallbacks(aChannel, loadContext);
+  if (aChannel) {
+    NS_QueryNotificationCallbacks(aChannel, loadContext);
+  }
   Init(loadContext);
 }
 
 void
 SerializedLoadContext::Init(nsILoadContext* aLoadContext)
 {
   if (aLoadContext) {
     mIsNotNull = true;
--- a/extensions/cookie/test/unit/test_cookies_sync_failure.js
+++ b/extensions/cookie/test/unit/test_cookies_sync_failure.js
@@ -13,16 +13,18 @@
 // 4) Migration fails. This will have different modes depending on the initial
 //    version:
 //    a) Schema 1: the 'lastAccessed' column already exists.
 //    b) Schema 2: the 'baseDomain' column already exists; or 'baseDomain'
 //       cannot be computed for a particular host.
 //    c) Schema 3: the 'creationTime' column already exists; or the
 //       'moz_uniqueid' index already exists.
 
+let COOKIE_DATABASE_SCHEMA_CURRENT = 5;
+
 let test_generator = do_run_test();
 
 function run_test() {
   do_test_pending();
   do_run_generator(test_generator);
 }
 
 function finish_test() {
@@ -57,16 +59,20 @@ function do_run_test() {
   this.sub_generator = run_test_2(test_generator);
   sub_generator.next();
   yield;
 
   this.sub_generator = run_test_3(test_generator, 99);
   sub_generator.next();
   yield;
 
+  this.sub_generator = run_test_3(test_generator, COOKIE_DATABASE_SCHEMA_CURRENT);
+  sub_generator.next();
+  yield;
+
   this.sub_generator = run_test_3(test_generator, 4);
   sub_generator.next();
   yield;
 
   this.sub_generator = run_test_3(test_generator, 3);
   sub_generator.next();
   yield;
 
@@ -201,17 +207,17 @@ function run_test_3(generator, schema)
   do_check_eq(do_count_cookies(), 0);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Check that the schema version has been reset.
   let db = Services.storage.openDatabase(cookieFile);
-  do_check_eq(db.schemaVersion, 4);
+  do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
   db.close();
 
   // Clean up.
   cookieFile.remove(false);
   do_check_false(cookieFile.exists());
   do_run_generator(generator);
 }
 
@@ -228,17 +234,17 @@ function run_test_4_exists(generator, sc
   do_check_eq(do_count_cookies(), 0);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Check that the schema version has been reset and the backup file exists.
   db = Services.storage.openDatabase(cookieFile);
-  do_check_eq(db.schemaVersion, 4);
+  do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
   db.close();
   do_check_true(backupFile.exists());
 
   // Clean up.
   cookieFile.remove(false);
   backupFile.remove(false);
   do_check_false(cookieFile.exists());
   do_check_false(backupFile.exists());
@@ -259,17 +265,17 @@ function run_test_4_baseDomain(generator
   do_check_eq(do_count_cookies(), 0);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Check that the schema version has been reset and the backup file exists.
   db = Services.storage.openDatabase(cookieFile);
-  do_check_eq(db.schemaVersion, 4);
+  do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
   db.close();
   do_check_true(backupFile.exists());
 
   // Clean up.
   cookieFile.remove(false);
   backupFile.remove(false);
   do_check_false(cookieFile.exists());
   do_check_false(backupFile.exists());
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -1334,16 +1334,22 @@ NS_UsePrivateBrowsing(nsIChannel *channe
         isOverriden) {
         return isPrivate;
     }
     nsCOMPtr<nsILoadContext> loadContext;
     NS_QueryNotificationCallbacks(channel, loadContext);
     return loadContext && loadContext->UsePrivateBrowsing();
 }
 
+// Constants duplicated from nsIScriptSecurityManager so we avoid having necko
+// know about script security manager.
+#define NECKO_NO_APP_ID 0
+// Note: UNKNOWN also equals PR_UINT32_MAX
+#define NECKO_UNKNOWN_APP_ID 4294967295
+
 /**
  * Gets AppId and isInBrowserElement from channel's nsILoadContext.
  * Returns false if error or channel's callbacks don't implement nsILoadContext.
  */
 inline bool
 NS_GetAppInfo(nsIChannel *aChannel, uint32_t *aAppID, bool *aIsInBrowserElement)
 {
     nsCOMPtr<nsILoadContext> loadContext;
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -112,17 +112,18 @@ CookieServiceChild::GetCookieStringInter
   if (RequireThirdPartyCheck())
     mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
 
   URIParams uriParams;
   SerializeURI(aHostURI, uriParams);
 
   // Synchronously call the parent.
   nsAutoCString result;
-  SendGetCookieString(uriParams, !!isForeign, aFromHttp, &result);
+  SendGetCookieString(uriParams, !!isForeign, aFromHttp,
+                      IPC::SerializedLoadContext(aChannel), &result);
   if (!result.IsEmpty())
     *aCookieString = ToNewCString(result);
 
   return NS_OK;
 }
 
 nsresult
 CookieServiceChild::SetCookieStringInternal(nsIURI *aHostURI,
@@ -144,17 +145,17 @@ CookieServiceChild::SetCookieStringInter
   if (aServerTime)
     serverTime.Rebind(aServerTime);
 
   URIParams uriParams;
   SerializeURI(aHostURI, uriParams);
 
   // Synchronously call the parent.
   SendSetCookieString(uriParams, !!isForeign, cookieString, serverTime,
-                      aFromHttp);
+                      aFromHttp, IPC::SerializedLoadContext(aChannel));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CookieServiceChild::Observe(nsISupports     *aSubject,
                             const char      *aTopic,
                             const PRUnichar *aData)
 {
--- a/netwerk/cookie/CookieServiceParent.cpp
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -29,50 +29,79 @@ CookieServiceParent::CookieServiceParent
 CookieServiceParent::~CookieServiceParent()
 {
 }
 
 bool
 CookieServiceParent::RecvGetCookieString(const URIParams& aHost,
                                          const bool& aIsForeign,
                                          const bool& aFromHttp,
+                                         const IPC::SerializedLoadContext&
+                                               aLoadContext,
                                          nsCString* aResult)
 {
   if (!mCookieService)
     return true;
 
   // Deserialize URI. Having a host URI is mandatory and should always be
   // provided by the child; thus we consider failure fatal.
   nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
   if (!hostURI)
     return false;
 
-  mCookieService->GetCookieStringInternal(hostURI, aIsForeign,
-                                          aFromHttp, *aResult);
+  uint32_t appId;
+  bool isInBrowserElement;
+  GetAppInfoFromLoadContext(aLoadContext, appId, isInBrowserElement);
+
+  mCookieService->GetCookieStringInternal(hostURI, aIsForeign, aFromHttp, appId,
+                                          isInBrowserElement, *aResult);
   return true;
 }
 
 bool
 CookieServiceParent::RecvSetCookieString(const URIParams& aHost,
                                          const bool& aIsForeign,
                                          const nsCString& aCookieString,
                                          const nsCString& aServerTime,
-                                         const bool& aFromHttp)
+                                         const bool& aFromHttp,
+                                         const IPC::SerializedLoadContext&
+                                               aLoadContext)
 {
   if (!mCookieService)
     return true;
 
   // Deserialize URI. Having a host URI is mandatory and should always be
   // provided by the child; thus we consider failure fatal.
   nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
   if (!hostURI)
     return false;
 
+  uint32_t appId;
+  bool isInBrowserElement;
+  GetAppInfoFromLoadContext(aLoadContext, appId, isInBrowserElement);
+
   nsDependentCString cookieString(aCookieString, 0);
-  mCookieService->SetCookieStringInternal(hostURI, aIsForeign,
-                                          cookieString, aServerTime,
-                                          aFromHttp);
+  mCookieService->SetCookieStringInternal(hostURI, aIsForeign, cookieString,
+                                          aServerTime, aFromHttp, appId,
+                                          isInBrowserElement);
   return true;
 }
 
+void
+CookieServiceParent::GetAppInfoFromLoadContext(
+                       const IPC::SerializedLoadContext &aLoadContext,
+                        uint32_t& aAppId,
+                        bool& aIsInBrowserElement)
+{
+  // TODO: bug 782542: what to do when we get null loadContext?  For now assume
+  // NECKO_NO_APP_ID.
+  aAppId = NECKO_NO_APP_ID;
+  aIsInBrowserElement = false;
+
+  if (aLoadContext.IsNotNull()) {
+    aAppId = aLoadContext.mAppId;
+    aIsInBrowserElement = aLoadContext.mIsInBrowserElement;
+  }
+}
+
 }
 }
 
--- a/netwerk/cookie/CookieServiceParent.h
+++ b/netwerk/cookie/CookieServiceParent.h
@@ -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/. */
 
 #ifndef mozilla_net_CookieServiceParent_h
 #define mozilla_net_CookieServiceParent_h
 
 #include "mozilla/net/PCookieServiceParent.h"
+#include "SerializedLoadContext.h"
 
 class nsCookieService;
 class nsIIOService;
 
 namespace mozilla {
 namespace net {
 
 class CookieServiceParent : public PCookieServiceParent
@@ -19,23 +20,30 @@ class CookieServiceParent : public PCook
 public:
   CookieServiceParent();
   virtual ~CookieServiceParent();
 
 protected:
   virtual bool RecvGetCookieString(const URIParams& aHost,
                                    const bool& aIsForeign,
                                    const bool& aFromHttp,
+                                   const IPC::SerializedLoadContext&
+                                         loadContext,
                                    nsCString* aResult);
 
   virtual bool RecvSetCookieString(const URIParams& aHost,
                                    const bool& aIsForeign,
                                    const nsCString& aCookieString,
                                    const nsCString& aServerTime,
-                                   const bool& aFromHttp);
+                                   const bool& aFromHttp,
+                                   const IPC::SerializedLoadContext&
+                                         loadContext);
+
+  void GetAppInfoFromLoadContext(const IPC::SerializedLoadContext& aLoadContext,
+                                 uint32_t& aAppId, bool& aIsInBrowserElement);
 
   nsRefPtr<nsCookieService> mCookieService;
 };
 
 }
 }
 
 #endif // mozilla_net_CookieServiceParent_h
--- a/netwerk/cookie/PCookieService.ipdl
+++ b/netwerk/cookie/PCookieService.ipdl
@@ -3,16 +3,20 @@
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PNecko;
 include URIParams;
 
+include "SerializedLoadContext.h";
+
+using IPC::SerializedLoadContext;
+
 namespace mozilla {
 namespace net {
 
 /**
  * PCookieService
  *
  * Provides IPDL methods for setting and getting cookies. These are stored on
  * and managed by the parent; the child process goes through the parent for
@@ -42,26 +46,30 @@ parent:
    *        access to cookies. This should be obtained from
    *        mozIThirdPartyUtil.isThirdPartyChannel. Third party requests may be
    *        rejected depending on user preferences; if those checks are
    *        disabled, this parameter is ignored.
    * @param fromHttp
    *        Whether the result is for an HTTP request header. This should be
    *        true for nsICookieService.getCookieStringFromHttp calls, false
    *        otherwise.
+   * @param loadContext
+   *        The loadContext from the HTTP channel or document that the cookie is
+   *        being set on.
    *
    * @see nsICookieService.getCookieString
    * @see nsICookieService.getCookieStringFromHttp
    * @see mozIThirdPartyUtil.isThirdPartyChannel
    *
    * @return the resulting cookie string.
    */
   sync GetCookieString(URIParams host,
                        bool isForeign,
-                       bool fromHttp)
+                       bool fromHttp,
+                       SerializedLoadContext loadContext)
        returns (nsCString result);
 
   /*
    * Set a cookie string.
    *
    * @param host
    *        Same as the 'aURI' argument to nsICookieService.setCookieString.
    * @param isForeign
@@ -75,25 +83,29 @@ parent:
    * @param serverTime
    *        Same as the 'aServerTime' argument to
    *        nsICookieService.setCookieStringFromHttp. If the string is empty or
    *        null (e.g. for non-HTTP requests), the current local time is used.
    * @param fromHttp
    *        Whether the result is for an HTTP request header. This should be
    *        true for nsICookieService.setCookieStringFromHttp calls, false
    *        otherwise.
+   * @param loadContext
+   *        The loadContext from the HTTP channel or document that the cookie is
+   *        being set on.
    *
    * @see nsICookieService.setCookieString
    * @see nsICookieService.setCookieStringFromHttp
    * @see mozIThirdPartyUtil.isThirdPartyChannel
    */
   SetCookieString(URIParams host,
                   bool isForeign,
                   nsCString cookieString,
                   nsCString serverTime,
-                  bool fromHttp);
+                  bool fromHttp,
+                  SerializedLoadContext loadContext);
 
   __delete__();
 };
 
 }
 }
 
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -1,9 +1,10 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #ifdef MOZ_LOGGING
 // this next define has to appear before the include of prlog.h
 #define FORCE_PR_LOG // Allow logging in the release build
@@ -47,29 +48,36 @@
 #include "mozilla/storage.h"
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/Util.h" // for DebugOnly
 #include "mozilla/Attributes.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
+// Create key from baseDomain that will access the default cookie namespace.
+// TODO: When we figure out what the API will look like for nsICookieManager{2}
+// on content processes (see bug 777620), change to use the appropriate app
+// namespace.  For now those IDLs aren't supported on child processes.
+#define DEFAULT_APP_KEY(baseDomain) \
+        nsCookieKey(baseDomain, NECKO_NO_APP_ID, false)
+
 /******************************************************************************
  * nsCookieService impl:
  * useful types & constants
  ******************************************************************************/
 
 static nsCookieService *gCookieService;
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
 
 #define COOKIES_FILE "cookies.sqlite"
-#define COOKIES_SCHEMA_VERSION 4
+#define COOKIES_SCHEMA_VERSION 5
 
 static const int64_t kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
 static const int64_t kCookiePurgeAge =
   int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
 
 static const char kOldCookieFileName[] = "cookies.txt";
 
 #undef  LIMIT
@@ -94,17 +102,17 @@ static const uint32_t BEHAVIOR_REJECT   
 static const char kPrefCookieBehavior[]     = "network.cookie.cookieBehavior";
 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
 static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
 static const char kPrefCookiePurgeAge[]     = "network.cookie.purgeAge";
 static const char kPrefThirdPartySession[]  = "network.cookie.thirdparty.sessionOnly";
 
 static void
 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
-                     const nsCString &aBaseDomain,
+                     const nsCookieKey &aKey,
                      const nsCookie *aCookie);
 
 // struct for temporarily storing cookie attributes during header parsing
 struct nsCookieAttributes
 {
   nsAutoCString name;
   nsAutoCString value;
   nsAutoCString host;
@@ -462,17 +470,19 @@ public:
     while (1) {
       rv = aResult->GetNextRow(getter_AddRefs(row));
       NS_ASSERT_SUCCESS(rv);
 
       if (!row)
         break;
 
       CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
-      row->GetUTF8String(9, tuple->baseDomain);
+      row->GetUTF8String(9, tuple->key.mBaseDomain);
+      tuple->key.mAppId = static_cast<uint32_t>(row->AsInt32(10));
+      tuple->key.mInBrowserElement = static_cast<bool>(row->AsInt32(11));
       tuple->cookie = gCookieService->GetCookieFromRow(row);
     }
 
     return NS_OK;
   }
   NS_IMETHOD HandleCompletion(uint16_t aReason)
   {
     // Process the completion of the read operation. If we have been canceled,
@@ -925,16 +935,62 @@ nsCookieService::TryInitDB(bool aRecreat
         // Create a unique index on (name, host, path) to allow fast lookup.
         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "CREATE UNIQUE INDEX moz_uniqueid "
           "ON moz_cookies (name, host, path)"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       // Fall through to the next upgrade.
 
+    case 4:
+      {
+        // We need to add appId/inBrowserElement, plus change a constraint on
+        // the table (unique entries now include appId/inBrowserElement):
+        // this requires creating a new table and copying the data to it.  We
+        // then rename the new table to the old name.
+        //
+        // Why we made this change: appId/inBrowserElement allow "cookie jars"
+        // for Firefox OS. We create a separate cookie namespace per {appId,
+        // inBrowserElement}.  When upgrading, we convert existing cookies
+        // (which imply we're on desktop/mobile) to use {0, false}, as that is
+        // the only namespace used by a non-Firefox-OS implementation.
+
+        // Rename existing table
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Drop existing index (CreateTable will create new one for new table)
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "DROP INDEX moz_basedomain"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Create new table (with new fields and new unique constraint)
+        rv = CreateTable();
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Copy data from old table, using appId/inBrowser=0 for existing rows
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "INSERT INTO moz_cookies "
+          "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
+          " lastAccessed, creationTime, isSecure, isHttpOnly) "
+          "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
+          " lastAccessed, creationTime, isSecure, isHttpOnly "
+          "FROM moz_cookies_old"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        // Drop old table
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "DROP TABLE moz_cookies_old"));
+        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+        COOKIE_LOGSTRING(PR_LOG_DEBUG, 
+          ("Upgraded database to schema version 5"));
+      }
+
       // No more upgrades. Update the schema version.
       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
     case COOKIES_SCHEMA_VERSION:
       break;
 
     case 0:
@@ -960,16 +1016,18 @@ nsCookieService::TryInitDB(bool aRecreat
     default:
       {
         // check if all the expected columns exist
         nsCOMPtr<mozIStorageStatement> stmt;
         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
           "SELECT "
             "id, "
             "baseDomain, "
+            "appId, "
+            "inBrowserElement, "
             "name, "
             "value, "
             "host, "
             "path, "
             "expiry, "
             "lastAccessed, "
             "creationTime, "
             "isSecure, "
@@ -1000,27 +1058,31 @@ nsCookieService::TryInitDB(bool aRecreat
     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "PRAGMA wal_autocheckpoint = 16"));
 
   // cache frequently used statements (for insertion, deletion, and updating)
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "INSERT INTO moz_cookies ("
       "baseDomain, "
+      "appId, "
+      "inBrowserElement, "
       "name, "
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly"
     ") VALUES ("
       ":baseDomain, "
+      ":appId, "
+      ":inBrowserElement, "
       ":name, "
       ":value, "
       ":host, "
       ":path, "
       ":expiry, "
       ":lastAccessed, "
       ":creationTime, "
       ":isSecure, "
@@ -1071,37 +1133,43 @@ nsCookieService::TryInitDB(bool aRecreat
 nsresult
 nsCookieService::CreateTable()
 {
   // Set the schema version, before creating the table.
   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
     COOKIES_SCHEMA_VERSION);
   if (NS_FAILED(rv)) return rv;
 
-  // Create the table.
+  // Create the table.  We default appId/inBrowserElement to 0: this is so if
+  // users revert to an older Firefox version that doesn't know about these
+  // fields, any cookies set will still work once they upgrade back.
   rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE moz_cookies ("
       "id INTEGER PRIMARY KEY, "
       "baseDomain TEXT, "
+      "appId INTEGER DEFAULT 0, "
+      "inBrowserElement INTEGER DEFAULT 0, "
       "name TEXT, "
       "value TEXT, "
       "host TEXT, "
       "path TEXT, "
       "expiry INTEGER, "
       "lastAccessed INTEGER, "
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
-      "CONSTRAINT moz_uniqueid UNIQUE (name, host, path)"
+      "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
     ")"));
   if (NS_FAILED(rv)) return rv;
 
   // Create an index on baseDomain.
   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
+    "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+                                                "appId, "
+                                                "inBrowserElement)"));
 }
 
 void
 nsCookieService::CloseDBStates()
 {
   // Null out our private and pointer DBStates regardless.
   mPrivateDBState = NULL;
   mDBState = NULL;
@@ -1249,17 +1317,17 @@ RebuildDBCallback(nsCookieEntry *aEntry,
   mozIStorageBindingParamsArray* paramsArray =
     static_cast<mozIStorageBindingParamsArray*>(aArg);
 
   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie* cookie = cookies[i];
 
     if (!cookie->IsSession()) {
-      bindCookieParameters(paramsArray, aEntry->GetKey(), cookie);
+      bindCookieParameters(paramsArray, aEntry, cookie);
     }
   }
 
   return PL_DHASH_NEXT;
 }
 
 void
 nsCookieService::RebuildCorruptDB(DBState* aDBState)
@@ -1417,18 +1485,26 @@ nsCookieService::GetCookieStringCommon(n
 {
   NS_ENSURE_ARG(aHostURI);
   NS_ENSURE_ARG(aCookie);
 
   // Determine whether the request is foreign. Failure is acceptable.
   bool isForeign = true;
   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
 
+  // Get app info, if channel is present.  Else assume default namespace.
+  uint32_t appId = NECKO_NO_APP_ID;
+  bool inBrowserElement = false;
+  if (aChannel) {
+    NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
+  }
+
   nsAutoCString result;
-  GetCookieStringInternal(aHostURI, isForeign, aHttpBound, result);
+  GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
+                          inBrowserElement, result);
   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::SetCookieString(nsIURI     *aHostURI,
                                  nsIPrompt  *aPrompt,
                                  const char *aCookieHeader,
@@ -1458,29 +1534,38 @@ nsCookieService::SetCookieStringCommon(n
 {
   NS_ENSURE_ARG(aHostURI);
   NS_ENSURE_ARG(aCookieHeader);
 
   // Determine whether the request is foreign. Failure is acceptable.
   bool isForeign = true;
   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
 
+  // Get app info, if channel is present.  Else assume default namespace.
+  uint32_t appId = NECKO_NO_APP_ID;
+  bool inBrowserElement = false;
+  if (aChannel) {
+    NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
+  }
+
   nsDependentCString cookieString(aCookieHeader);
   nsDependentCString serverTime(aServerTime ? aServerTime : "");
   SetCookieStringInternal(aHostURI, isForeign, cookieString,
-                          serverTime, aFromHttp);
+                          serverTime, aFromHttp, appId, inBrowserElement);
   return NS_OK;
 }
 
 void
 nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
                                          bool                aIsForeign,
                                          nsDependentCString &aCookieHeader,
                                          const nsCString    &aServerTime,
-                                         bool                aFromHttp)
+                                         bool                aFromHttp,
+                                         uint32_t            aAppId,
+                                         bool                aInBrowserElement)
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
 
@@ -1493,19 +1578,21 @@ nsCookieService::SetCookieStringInternal
   nsAutoCString baseDomain;
   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
   if (NS_FAILED(rv)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
                       "couldn't get base domain from URI");
     return;
   }
 
+  nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
+
   // check default prefs
-  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, baseDomain,
-                                         requireHostMatch, aCookieHeader.get());
+  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
+                                         aCookieHeader.get());
   // fire a notification if cookie was rejected (but not if there was an error)
   switch (cookieStatus) {
   case STATUS_REJECTED:
     NotifyRejected(aHostURI);
     return;
   case STATUS_REJECTED_WITH_ERROR:
     return;
   default:
@@ -1523,17 +1610,17 @@ nsCookieService::SetCookieStringInternal
                                        &tempServerTime);
   if (result == PR_SUCCESS) {
     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
   } else {
     serverTime = PR_Now() / PR_USEC_PER_SEC;
   }
 
   // process each cookie in the header
-  while (SetCookieInternal(aHostURI, baseDomain, requireHostMatch, cookieStatus,
+  while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
                            aCookieHeader, serverTime, aFromHttp)) {
     // document.cookie can only set one cookie at a time
     if (!aFromHttp)
       break;
   }
 }
 
 // notify observers that a cookie was rejected due to the users' prefs.
@@ -1707,17 +1794,17 @@ nsCookieService::Add(const nsACString &a
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  AddInternal(baseDomain, cookie, currentTimeInUsec, nullptr, nullptr, true);
+  AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::Remove(const nsACString &aHost,
                         const nsACString &aName,
                         const nsACString &aPath,
                         bool             aBlocked)
@@ -1733,17 +1820,17 @@ nsCookieService::Remove(const nsACString
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsListIter matchIter;
   nsRefPtr<nsCookie> cookie;
-  if (FindCookie(baseDomain,
+  if (FindCookie(DEFAULT_APP_KEY(baseDomain),
                  host,
                  PromiseFlatCString(aName),
                  PromiseFlatCString(aPath),
                  matchIter)) {
     cookie = matchIter.Cookie();
     RemoveCookieFromList(matchIter);
   }
 
@@ -1788,17 +1875,19 @@ nsCookieService::Read()
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
-      "baseDomain "
+      "baseDomain, "
+      "appId,  "
+      "inBrowserElement "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // Set up a statement to delete any rows with a NULL 'baseDomain'
   // column. This takes care of any cookies set by browsers that don't
   // understand the 'baseDomain' column, where the database schema version
   // is from one that does. (This would occur when downgrading.)
@@ -1880,21 +1969,20 @@ nsCookieService::AsyncReadComplete()
   // read on the main thread. Note that transactions on the cookie table may
   // have occurred on the main thread since, making the background data stale.
   for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
     const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
 
     // Tiebreak: if the given base domain has already been read in, ignore
     // the background data. Note that readSet may contain domains that were
     // queried but found not to be in the db -- that's harmless.
-    if (mDefaultDBState->readSet.GetEntry(tuple.baseDomain))
+    if (mDefaultDBState->readSet.GetEntry(tuple.key))
       continue;
 
-    AddCookieToList(tuple.baseDomain, tuple.cookie, mDefaultDBState, NULL,
-      false);
+    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, NULL, false);
   }
 
   mDefaultDBState->stmtReadDomain = nullptr;
   mDefaultDBState->pendingRead = nullptr;
   mDefaultDBState->readListener = nullptr;
   mDefaultDBState->syncConn = nullptr;
   mDefaultDBState->hostArray.Clear();
   mDefaultDBState->readSet.Clear();
@@ -1928,27 +2016,27 @@ nsCookieService::CancelAsyncRead(bool aP
 
   // Only clear the 'readSet' table if we no longer need to know what set of
   // data is already accounted for.
   if (aPurgeReadSet)
     mDefaultDBState->readSet.Clear();
 }
 
 void
-nsCookieService::EnsureReadDomain(const nsCString &aBaseDomain)
+nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
 {
   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
     "not in default db state");
 
   // Fast path 1: nothing to read, or we've already finished reading.
   if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
     return;
 
   // Fast path 2: already read in this particular domain.
-  if (NS_LIKELY(mDefaultDBState->readSet.GetEntry(aBaseDomain)))
+  if (NS_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
     return;
 
   // Read in the data synchronously.
   nsresult rv;
   if (!mDefaultDBState->stmtReadDomain) {
     // Cache the statement, since it's likely to be used again.
     rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT "
@@ -1957,17 +2045,19 @@ nsCookieService::EnsureReadDomain(const 
         "host, "
         "path, "
         "expiry, "
         "lastAccessed, "
         "creationTime, "
         "isSecure, "
         "isHttpOnly "
       "FROM moz_cookies "
-      "WHERE baseDomain = :baseDomain"),
+      "WHERE baseDomain = :baseDomain "
+      "  AND appId = :appId "
+      "  AND inBrowserElement = :inBrowserElement"),
       getter_AddRefs(mDefaultDBState->stmtReadDomain));
 
     if (NS_FAILED(rv)) {
       // Recreate the database.
       COOKIE_LOGSTRING(PR_LOG_DEBUG,
         ("EnsureReadDomain(): corruption detected when creating statement "
          "with rv 0x%x", rv));
       HandleCorruptDB(mDefaultDBState);
@@ -1975,18 +2065,25 @@ nsCookieService::EnsureReadDomain(const 
     }
   }
 
   NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
 
   mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
 
   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
-    NS_LITERAL_CSTRING("baseDomain"), aBaseDomain);
+    NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
+  NS_ASSERT_SUCCESS(rv);
+  rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
+    NS_LITERAL_CSTRING("appId"), aKey.mAppId);
   NS_ASSERT_SUCCESS(rv);
+  rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
+    NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
+  NS_ASSERT_SUCCESS(rv);
+
 
   bool hasResult;
   nsCString name, value, host, path;
   nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
   while (1) {
     rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
     if (NS_FAILED(rv)) {
       // Recreate the database.
@@ -2001,25 +2098,26 @@ nsCookieService::EnsureReadDomain(const 
       break;
 
     array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
   }
 
   // Add the cookies to the table in a single operation. This makes sure that
   // either all the cookies get added, or in the case of corruption, none.
   for (uint32_t i = 0; i < array.Length(); ++i) {
-    AddCookieToList(aBaseDomain, array[i], mDefaultDBState, NULL, false);
+    AddCookieToList(aKey, array[i], mDefaultDBState, NULL, false);
   }
 
   // Add it to the hashset of read entries, so we don't read it again.
-  mDefaultDBState->readSet.PutEntry(aBaseDomain);
+  mDefaultDBState->readSet.PutEntry(aKey);
 
   COOKIE_LOGSTRING(PR_LOG_DEBUG,
-    ("EnsureReadDomain(): %ld cookies read for base domain %s",
-     array.Length(), aBaseDomain.get()));
+    ("EnsureReadDomain(): %ld cookies read for base domain %s, "
+     " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
+     (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
 }
 
 void
 nsCookieService::EnsureReadComplete()
 {
   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
     "not in default db state");
 
@@ -2038,31 +2136,34 @@ nsCookieService::EnsureReadComplete()
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
-      "baseDomain "
+      "baseDomain, "
+      "appId,  "
+      "inBrowserElement "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
 
   if (NS_FAILED(rv)) {
     // Recreate the database.
     COOKIE_LOGSTRING(PR_LOG_DEBUG,
       ("EnsureReadComplete(): corruption detected when creating statement "
        "with rv 0x%x", rv));
     HandleCorruptDB(mDefaultDBState);
     return;
   }
 
   nsCString baseDomain, name, value, host, path;
-  bool hasResult;
+  uint32_t appId;
+  bool inBrowserElement, hasResult;
   nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
   while (1) {
     rv = stmt->ExecuteStep(&hasResult);
     if (NS_FAILED(rv)) {
       // Recreate the database.
       COOKIE_LOGSTRING(PR_LOG_DEBUG,
         ("EnsureReadComplete(): corruption detected when reading result "
          "with rv 0x%x", rv));
@@ -2070,29 +2171,32 @@ nsCookieService::EnsureReadComplete()
       return;
     }
 
     if (!hasResult)
       break;
 
     // Make sure we haven't already read the data.
     stmt->GetUTF8String(9, baseDomain);
-    if (mDefaultDBState->readSet.GetEntry(baseDomain))
+    appId = static_cast<uint32_t>(stmt->AsInt32(10));
+    inBrowserElement = static_cast<bool>(stmt->AsInt32(11));
+    nsCookieKey key(baseDomain, appId, inBrowserElement);
+    if (mDefaultDBState->readSet.GetEntry(key))
       continue;
 
     CookieDomainTuple* tuple = array.AppendElement();
-    tuple->baseDomain = baseDomain;
+    tuple->key = key;
     tuple->cookie = GetCookieFromRow(stmt);
   }
 
   // Add the cookies to the table in a single operation. This makes sure that
   // either all the cookies get added, or in the case of corruption, none.
   for (uint32_t i = 0; i < array.Length(); ++i) {
     CookieDomainTuple& tuple = array[i];
-    AddCookieToList(tuple.baseDomain, tuple.cookie, mDefaultDBState, NULL,
+    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, NULL,
       false);
   }
 
   mDefaultDBState->syncConn = nullptr;
   mDefaultDBState->readSet.Clear();
 
   COOKIE_LOGSTRING(PR_LOG_DEBUG,
     ("EnsureReadComplete(): %ld cookies read", array.Length()));
@@ -2213,16 +2317,19 @@ nsCookieService::ImportCookies(nsIFile *
       continue;
     }
 
     // compute the baseDomain from the host
     rv = GetBaseDomainFromHost(host, baseDomain);
     if (NS_FAILED(rv))
       continue;
 
+    // pre-existing cookies have appId=0, inBrowser=false
+    nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+
     // Create a new nsCookie and assign the data. We don't know the cookie
     // creation time, so just use the current time to generate a unique one.
     nsRefPtr<nsCookie> newCookie =
       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
                        host,
                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
                        expires,
@@ -2235,20 +2342,21 @@ nsCookieService::ImportCookies(nsIFile *
       return NS_ERROR_OUT_OF_MEMORY;
     }
     
     // trick: preserve the most-recently-used cookie ordering,
     // by successively decrementing the lastAccessed time
     lastAccessedCounter--;
 
     if (originalCookieCount == 0) {
-      AddCookieToList(baseDomain, newCookie, mDefaultDBState, paramsArray);
+      AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
     }
     else {
-      AddInternal(baseDomain, newCookie, currentTimeInUsec, NULL, NULL, true);
+      AddInternal(key, newCookie, currentTimeInUsec,
+                  NULL, NULL, true);
     }
   }
 
   // If we need to write to disk, do so now.
   if (paramsArray) {
     uint32_t length;
     paramsArray->GetLength(&length);
     if (length) {
@@ -2300,16 +2408,18 @@ public:
     return aCookie1->CreationTime() < aCookie2->CreationTime();
   }
 };
 
 void
 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
                                          bool aIsForeign,
                                          bool aHttpBound,
+                                         uint32_t aAppId,
+                                         bool aInBrowserElement,
                                          nsCString &aCookieString)
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
@@ -2327,18 +2437,18 @@ nsCookieService::GetCookieStringInternal
   if (NS_SUCCEEDED(rv))
     rv = aHostURI->GetPath(pathFromURI);
   if (NS_FAILED(rv)) {
     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
     return;
   }
 
   // check default prefs
-  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, baseDomain,
-                                         requireHostMatch, nullptr);
+  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
+                                         nullptr);
   // for GetCookie(), we don't fire rejection notifications.
   switch (cookieStatus) {
   case STATUS_REJECTED:
   case STATUS_REJECTED_WITH_ERROR:
     return;
   default:
     break;
   }
@@ -2352,20 +2462,21 @@ nsCookieService::GetCookieStringInternal
   }
 
   nsCookie *cookie;
   nsAutoTArray<nsCookie*, 8> foundCookieList;
   int64_t currentTimeInUsec = PR_Now();
   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
   bool stale = false;
 
-  EnsureReadDomain(baseDomain);
+  nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
+  EnsureReadDomain(key);
 
   // perform the hash lookup
-  nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
+  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   if (!entry)
     return;
 
   // iterate the cookies!
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     cookie = cookies[i];
 
@@ -2484,17 +2595,17 @@ nsCookieService::GetCookieStringInternal
   if (!aCookieString.IsEmpty())
     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
 }
 
 // processes a single cookie, and returns true if there are more cookies
 // to be processed
 bool
 nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
-                                   const nsCString               &aBaseDomain,
+                                   const nsCookieKey             &aKey,
                                    bool                           aRequireHostMatch,
                                    CookieStatus                   aStatus,
                                    nsDependentCString            &aCookieHeader,
                                    int64_t                        aServerTime,
                                    bool                           aFromHttp)
 {
   NS_ASSERTION(aHostURI, "null host!");
 
@@ -2531,17 +2642,17 @@ nsCookieService::SetCookieInternal(nsIUR
   }
 
   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
     return newCookie;
   }
 
   // domain & path checks
-  if (!CheckDomain(cookieAttributes, aHostURI, aBaseDomain, aRequireHostMatch)) {
+  if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
     return newCookie;
   }
   if (!CheckPath(cookieAttributes, aHostURI)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
     return newCookie;
   }
 
@@ -2581,45 +2692,45 @@ nsCookieService::SetCookieInternal(nsIUR
 
     // update isSession and expiry attributes, in case they changed
     cookie->SetIsSession(cookieAttributes.isSession);
     cookie->SetExpiry(cookieAttributes.expiryTime);
   }
 
   // add the cookie to the list. AddInternal() takes care of logging.
   // we get the current time again here, since it may have changed during prompting
-  AddInternal(aBaseDomain, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
+  AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
               aFromHttp);
   return newCookie;
 }
 
 // this is a backend function for adding a cookie to the list, via SetCookie.
 // also used in the cookie manager, for profile migration from IE.
 // it either replaces an existing cookie; or adds the cookie to the hashtable,
 // and deletes a cookie (if maximum number of cookies has been
 // reached). also performs list maintenance by removing expired cookies.
 void
-nsCookieService::AddInternal(const nsCString               &aBaseDomain,
+nsCookieService::AddInternal(const nsCookieKey             &aKey,
                              nsCookie                      *aCookie,
                              int64_t                        aCurrentTimeInUsec,
                              nsIURI                        *aHostURI,
                              const char                    *aCookieHeader,
                              bool                           aFromHttp)
 {
   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
 
   // if the new cookie is httponly, make sure we're not coming from script
   if (!aFromHttp && aCookie->IsHttpOnly()) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
       "cookie is httponly; coming from script");
     return;
   }
 
   nsListIter matchIter;
-  bool foundCookie = FindCookie(aBaseDomain, aCookie->Host(),
+  bool foundCookie = FindCookie(aKey, aCookie->Host(),
     aCookie->Name(), aCookie->Path(), matchIter);
 
   nsRefPtr<nsCookie> oldCookie;
   nsCOMPtr<nsIArray> purgedList;
   if (foundCookie) {
     oldCookie = matchIter.Cookie();
 
     // Check if the old cookie is stale (i.e. has already expired). If so, we
@@ -2674,17 +2785,17 @@ nsCookieService::AddInternal(const nsCSt
     // check if cookie has already expired
     if (aCookie->Expiry() <= currentTime) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
         "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
-    nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
+    nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
       nsListIter iter;
       FindStaleCookie(entry, currentTime, iter);
       oldCookie = iter.Cookie();
 
       // remove the oldest cookie from the domain
       RemoveCookieFromList(iter);
       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
@@ -2702,17 +2813,17 @@ nsCookieService::AddInternal(const nsCSt
         // older than the actual oldest cookie, we'll just purge more eagerly.
         purgedList = PurgeCookies(aCurrentTimeInUsec);
       }
     }
   }
 
   // Add the cookie to the db. We do not supply a params array for batching
   // because this might result in removals and additions being out of order.
-  AddCookieToList(aBaseDomain, aCookie, mDBState, NULL);
+  AddCookieToList(aKey, aCookie, mDBState, NULL);
   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
 
   // Now that list mutations are complete, notify observers. We do it here
   // because observers may themselves attempt to mutate the list.
   if (purgedList) {
     NotifyChanged(purgedList, NS_LITERAL_STRING("batch-deleted").get());
   }
 
@@ -3048,17 +3159,16 @@ static inline bool IsSubdomainOf(const n
   if (a.Length() > b.Length())
     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
   return false;
 }
 
 CookieStatus
 nsCookieService::CheckPrefs(nsIURI          *aHostURI,
                             bool             aIsForeign,
-                            const nsCString &aBaseDomain,
                             bool             aRequireHostMatch,
                             const char      *aCookieHeader)
 {
   nsresult rv;
 
   // don't let ftp sites get/set cookies (could be a security issue)
   bool ftp;
   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
@@ -3506,17 +3616,17 @@ nsCookieService::CookieExists(nsICookie2
   rv = aCookie->GetPath(path);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsListIter iter;
-  *aFoundCookie = FindCookie(baseDomain, host, name, path, iter);
+  *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), host, name, path, iter);
   return NS_OK;
 }
 
 // For a given base domain, find either an expired cookie or the oldest cookie
 // by lastAccessed time.
 void
 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
                                  int64_t aCurrentTime,
@@ -3560,20 +3670,21 @@ nsCookieService::CountCookiesFromHost(co
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  EnsureReadDomain(baseDomain);
+  nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+  EnsureReadDomain(key);
 
   // Return a count of all cookies, including expired.
-  nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
+  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
   return NS_OK;
 }
 
 // get an enumerator of cookies stored by a particular host. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
@@ -3588,42 +3699,43 @@ nsCookieService::GetCookiesFromHost(cons
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  EnsureReadDomain(baseDomain);
-
-  nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
+  nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+  EnsureReadDomain(key);
+
+  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   if (!entry)
     return NS_NewEmptyEnumerator(aEnumerator);
 
   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     cookieList.AppendObject(cookies[i]);
   }
 
   return NS_NewArrayEnumerator(aEnumerator, cookieList);
 }
 
 // find an exact cookie specified by host, name, and path that hasn't expired.
 bool
-nsCookieService::FindCookie(const nsCString      &aBaseDomain,
+nsCookieService::FindCookie(const nsCookieKey    &aKey,
                             const nsAFlatCString &aHost,
                             const nsAFlatCString &aName,
                             const nsAFlatCString &aPath,
                             nsListIter           &aIter)
 {
-  EnsureReadDomain(aBaseDomain);
-
-  nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
+  EnsureReadDomain(aKey);
+
+  nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
   if (!entry)
     return false;
 
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
 
     if (aHost.Equals(cookie->Host()) &&
@@ -3692,32 +3804,40 @@ nsCookieService::RemoveCookieFromList(co
     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
   }
 
   --mDBState->cookieCount;
 }
 
 void
 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
-                     const nsCString &aBaseDomain,
+                     const nsCookieKey &aKey,
                      const nsCookie *aCookie)
 {
   NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
 
   // Use the asynchronous binding methods to ensure that we do not acquire the
   // database lock.
   nsCOMPtr<mozIStorageBindingParams> params;
   DebugOnly<nsresult> rv =
     aParamsArray->NewBindingParams(getter_AddRefs(params));
   NS_ASSERT_SUCCESS(rv);
 
   // Bind our values to params
   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
-                                    aBaseDomain);
+                                    aKey.mBaseDomain);
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
+                               aKey.mAppId);
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
+                               aKey.mInBrowserElement ? 1 : 0);
   NS_ASSERT_SUCCESS(rv);
 
   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
                                     aCookie->Name());
   NS_ASSERT_SUCCESS(rv);
 
   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
                                     aCookie->Value());
@@ -3752,45 +3872,45 @@ bindCookieParameters(mozIStorageBindingP
   NS_ASSERT_SUCCESS(rv);
 
   // Bind the params to the array.
   rv = aParamsArray->AddParams(params);
   NS_ASSERT_SUCCESS(rv);
 }
 
 void
-nsCookieService::AddCookieToList(const nsCString               &aBaseDomain,
+nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
                                  nsCookie                      *aCookie,
                                  DBState                       *aDBState,
                                  mozIStorageBindingParamsArray *aParamsArray,
                                  bool                           aWriteToDB)
 {
   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
                "Not writing to the DB but have a params array?");
   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
                "Do not have a DB connection but have a params array?");
 
-  nsCookieEntry *entry = aDBState->hostTable.PutEntry(aBaseDomain);
+  nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
   NS_ASSERTION(entry, "can't insert element into a null entry!");
 
   entry->GetCookies().AppendElement(aCookie);
   ++aDBState->cookieCount;
 
   // keep track of the oldest cookie, for when it comes time to purge
   if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
     aDBState->cookieOldestTime = aCookie->LastAccessed();
 
   // if it's a non-session cookie and hasn't just been read from the db, write it out.
   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
     if (!paramsArray) {
       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
     }
-    bindCookieParameters(paramsArray, aBaseDomain, aCookie);
+    bindCookieParameters(paramsArray, aKey, aCookie);
 
     // If we were supplied an array to store parameters, we shouldn't call
     // executeAsync - someone up the stack will do this for us.
     if (!aParamsArray) {
       nsresult rv = stmt->BindParameters(paramsArray);
       NS_ASSERT_SUCCESS(rv);
       nsCOMPtr<mozIStoragePendingStatement> handle;
       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -42,76 +42,110 @@ struct nsListIter;
 struct nsEnumerationData;
 
 namespace mozilla {
 namespace net {
 class CookieServiceParent;
 }
 }
 
-// hash entry class
-class nsCookieEntry : public PLDHashEntryHdr
+// hash key class
+class nsCookieKey : public PLDHashEntryHdr
+{
+public:
+  typedef const nsCookieKey& KeyType;
+  typedef const nsCookieKey* KeyTypePointer;
+
+  nsCookieKey()
+  {}
+
+  nsCookieKey(const nsCString &baseDomain, uint32_t appId, bool inBrowser)
+    : mBaseDomain(baseDomain)
+    , mAppId(appId)
+    , mInBrowserElement(inBrowser)
+  {}
+
+  nsCookieKey(const KeyTypePointer other)
+    : mBaseDomain(other->mBaseDomain)
+    , mAppId(other->mAppId)
+    , mInBrowserElement(other->mInBrowserElement)
+  {}
+
+  nsCookieKey(const KeyType &other)
+    : mBaseDomain(other.mBaseDomain)
+    , mAppId(other.mAppId)
+    , mInBrowserElement(other.mInBrowserElement)
+  {}
+
+  ~nsCookieKey()
+  {}
+
+  bool KeyEquals(KeyTypePointer other) const
+  {
+    return mBaseDomain == other->mBaseDomain &&
+           mAppId == other->mAppId &&
+           mInBrowserElement == other->mInBrowserElement;
+  }
+
+  static KeyTypePointer KeyToPointer(KeyType aKey)
+  {
+    return &aKey;
+  }
+
+  static PLDHashNumber HashKey(KeyTypePointer aKey)
+  {
+    // TODO: more efficient way to generate hash?
+    nsAutoCString temp(aKey->mBaseDomain);
+    temp.Append("#");
+    temp.Append(aKey->mAppId);
+    temp.Append("#");
+    temp.Append(aKey->mInBrowserElement ? 1 : 0);
+    return mozilla::HashString(temp);
+  }
+
+  enum { ALLOW_MEMMOVE = true };
+
+  nsCString   mBaseDomain;
+  uint32_t    mAppId;
+  bool        mInBrowserElement;
+};
+
+// Inherit from nsCookieKey so this can be stored in nsTHashTable
+// TODO: why aren't we using nsClassHashTable<nsCookieKey, ArrayType>?
+class nsCookieEntry : public nsCookieKey
 {
   public:
     // Hash methods
-    typedef const nsCString& KeyType;
-    typedef const nsCString* KeyTypePointer;
     typedef nsTArray< nsRefPtr<nsCookie> > ArrayType;
     typedef ArrayType::index_type IndexType;
 
-    explicit
-    nsCookieEntry(KeyTypePointer aBaseDomain)
-     : mBaseDomain(*aBaseDomain)
-    {
-    }
+    nsCookieEntry(KeyTypePointer aKey)
+     : nsCookieKey(aKey)
+    {}
 
     nsCookieEntry(const nsCookieEntry& toCopy)
     {
       // if we end up here, things will break. nsTHashtable shouldn't
       // allow this, since we set ALLOW_MEMMOVE to true.
       NS_NOTREACHED("nsCookieEntry copy constructor is forbidden!");
     }
 
     ~nsCookieEntry()
-    {
-    }
-
-    KeyType GetKey() const
-    {
-      return mBaseDomain;
-    }
-
-    bool KeyEquals(KeyTypePointer aKey) const
-    {
-      return mBaseDomain == *aKey;
-    }
-
-    static KeyTypePointer KeyToPointer(KeyType aKey)
-    {
-      return &aKey;
-    }
-
-    static PLDHashNumber HashKey(KeyTypePointer aKey)
-    {
-      return mozilla::HashString(*aKey);
-    }
-
-    enum { ALLOW_MEMMOVE = true };
+    {}
 
     inline ArrayType& GetCookies() { return mCookies; }
 
   private:
-    nsCString mBaseDomain;
     ArrayType mCookies;
 };
 
-// encapsulates a (baseDomain, nsCookie) tuple for temporary storage purposes.
+// encapsulates a (key, nsCookie) tuple for temporary storage purposes.
 struct CookieDomainTuple
 {
-  nsCString baseDomain;
+  nsCookieKey key;
   nsRefPtr<nsCookie> cookie;
 };
 
 // encapsulates in-memory and on-disk DB states, so we can
 // conveniently switch state when entering or exiting private browsing.
 struct DBState
 {
   DBState() : cookieCount(0), cookieOldestTime(LL_MAXINT), corruptFlag(OK)
@@ -147,17 +181,17 @@ struct DBState
   // since it may need to outlive the DBState's database connection.
   ReadCookieDBListener*                 readListener;
   // An array of (baseDomain, cookie) tuples representing data read in
   // asynchronously. This is merged into hostTable once read is complete.
   nsTArray<CookieDomainTuple>           hostArray;
   // A hashset of baseDomains read in synchronously, while the async read is
   // in flight. This is used to keep track of which data in hostArray is stale
   // when the time comes to merge.
-  nsTHashtable<nsCStringHashKey>        readSet;
+  nsTHashtable<nsCookieKey>        readSet;
 
   // DB completion handlers.
   nsCOMPtr<mozIStorageStatementCallback>  insertListener;
   nsCOMPtr<mozIStorageStatementCallback>  updateListener;
   nsCOMPtr<mozIStorageStatementCallback>  removeListener;
   nsCOMPtr<mozIStorageCompletionCallback> closeListener;
 };
 
@@ -214,39 +248,40 @@ class nsCookieService : public nsICookie
     void                          CloseDefaultDBConnection();
     void                          HandleDBClosed(DBState* aDBState);
     void                          HandleCorruptDB(DBState* aDBState);
     void                          RebuildCorruptDB(DBState* aDBState);
     OpenDBResult                  Read();
     template<class T> nsCookie*   GetCookieFromRow(T &aRow);
     void                          AsyncReadComplete();
     void                          CancelAsyncRead(bool aPurgeReadSet);
-    void                          EnsureReadDomain(const nsCString &aBaseDomain);
+    void                          EnsureReadDomain(const nsCookieKey &aKey);
     void                          EnsureReadComplete();
     nsresult                      NormalizeHost(nsCString &aHost);
     nsresult                      GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, bool &aRequireHostMatch);
     nsresult                      GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
     nsresult                      GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie);
-    void                          GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, nsCString &aCookie);
+    void                          GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, uint32_t aAppId, bool aInBrowserElement, nsCString &aCookie);
     nsresult                      SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp);
-    void                          SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp);
-    bool                          SetCookieInternal(nsIURI *aHostURI, const nsCString& aBaseDomain, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp);
-    void                          AddInternal(const nsCString& aBaseDomain, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp);
+    void                          SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, uint32_t aAppId, bool aInBrowserElement);
+    bool                          SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp);
+    void                          AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp);
     void                          RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = NULL);
-    void                          AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, DBState *aDBState, mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB = true);
+    void                          AddCookieToList(const nsCookieKey& aKey, nsCookie *aCookie, DBState *aDBState, mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB = true);
     void                          UpdateCookieInList(nsCookie *aCookie, int64_t aLastAccessed, mozIStorageBindingParamsArray *aParamsArray);
     static bool                   GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, bool &aEqualsFound);
     static bool                   ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
-    CookieStatus                  CheckPrefs(nsIURI *aHostURI, bool aIsForeign, const nsCString &aBaseDomain, bool aRequireHostMatch, const char *aCookieHeader);
+    bool                          RequireThirdPartyCheck();
+    CookieStatus                  CheckPrefs(nsIURI *aHostURI, bool aIsForeign, bool aRequireHostMatch, const char *aCookieHeader);
     bool                          CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch);
     static bool                   CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static bool                   GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
     void                          RemoveAllFromMemory();
     already_AddRefed<nsIArray>    PurgeCookies(int64_t aCurrentTimeInUsec);
-    bool                          FindCookie(const nsCString& aBaseDomain, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter);
+    bool                          FindCookie(const nsCookieKey& aKey, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter);
     static void                   FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsListIter &aIter);
     void                          NotifyRejected(nsIURI *aHostURI);
     void                          NotifyChanged(nsISupports *aSubject, const PRUnichar *aData);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
 
   protected:
     // cached members.
--- a/netwerk/test/unit/head_channels.js
+++ b/netwerk/test/unit/head_channels.js
@@ -179,8 +179,42 @@ ChannelEventSink.prototype = {
 
   asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
     if (this._flags & ES_ABORT_REDIRECT)
       throw Cr.NS_BINDING_ABORTED;
 
     callback.onRedirectVerifyCallback(Cr.NS_OK);
   }
 };
+
+
+/**
+ * Class that implements nsILoadContext.  Use it as callbacks for channel when
+ * test needs it.
+ */
+function LoadContextCallback(appId, inBrowserElement, isPrivate, isContent) {
+  this.appId = appId;
+  this.isInBrowserElement = inBrowserElement;
+  this.usePrivateBrowsing = isPrivate;
+  this.isContent = isContent;
+}
+
+LoadContextCallback.prototype = {
+  associatedWindow: null,
+  topWindow : null,
+  isAppOfType: function(appType) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: function(iid) {
+    if (iid == Ci.nsILoadContext ||
+               Ci.nsIInterfaceRequestor ||
+               Ci.nsISupports) {
+        return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+  getInterface: function(iid) {
+    if (iid.equals(Ci.nsILoadContext))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+}
+
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+/*
+ *  Test that channels with different
+ *  AppIds/inBrowserElements/usePrivateBrowsing (from nsILoadContext callback)
+ *  are stored in separate namespaces ("cookie jars")
+ */ 
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+var httpserver = new HttpServer();
+
+var cookieSetPath = "/setcookie";
+var cookieCheckPath = "/checkcookie";
+
+// Test array:
+//  - element 0: name for cookie, used both to set and later to check 
+//  - element 1: loadContext (determines cookie namespace)
+//
+// TODO: bug 722850: make private browsing work per-app, and add tests.  For now
+// all values are 'false' for PB.
+
+var tests = [
+  { cookieName: 'LCC_App0_BrowF_PrivF', 
+    loadContext: new LoadContextCallback(0, false, false, 1) }, 
+  { cookieName: 'LCC_App0_BrowT_PrivF', 
+    loadContext: new LoadContextCallback(0, true,  false, 1) }, 
+  { cookieName: 'LCC_App1_BrowF_PrivF', 
+    loadContext: new LoadContextCallback(1, false, false, 1) }, 
+  { cookieName: 'LCC_App1_BrowT_PrivF', 
+    loadContext: new LoadContextCallback(1, true,  false, 1) }, 
+];
+
+// test number: index into 'tests' array
+var i = 0;
+
+function setupChannel(path)
+{
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel("http://localhost:4444" + path, "", null);
+  chan.notificationCallbacks = tests[i].loadContext;
+  chan.QueryInterface(Ci.nsIHttpChannel);
+  return chan;
+}
+
+function setCookie() {
+  var channel = setupChannel(cookieSetPath);
+  channel.setRequestHeader("foo-set-cookie", tests[i].cookieName, false);
+  channel.asyncOpen(new ChannelListener(setNextCookie, null), null);
+}
+
+function setNextCookie(request, data, context) 
+{
+  if (++i == tests.length) {
+    // all cookies set: switch to checking them
+    i = 0;
+    checkCookie();
+  } else {
+    do_print("setNextCookie:i=" + i);
+    setCookie();
+  }
+}
+
+// Open channel that should send one and only one correct Cookie: header to
+// server, corresponding to it's namespace
+function checkCookie()
+{
+  var channel = setupChannel(cookieCheckPath);
+  channel.asyncOpen(new ChannelListener(completeCheckCookie, null), null);
+}
+
+function completeCheckCookie(request, data, context) {
+  // Look for all cookies in what the server saw: fail if we see any besides the
+  // one expected cookie for each namespace;
+  var expectedCookie = tests[i].cookieName;
+  request.QueryInterface(Ci.nsIHttpChannel);
+  var cookiesSeen = request.getResponseHeader("foo-saw-cookies");
+
+  var j;
+  for (j = 0; j < tests.length; j++) {
+    var cookieToCheck = tests[j].cookieName;
+    found = (cookiesSeen.indexOf(cookieToCheck) != -1);
+    if (found && expectedCookie != cookieToCheck) {
+      do_throw("test index " + i + ": found unexpected cookie '" 
+          + cookieToCheck + "': in '" + cookiesSeen + "'");
+    } else if (!found && expectedCookie == cookieToCheck) {
+      do_throw("test index " + i + ": missing expected cookie '" 
+          + expectedCookie + "': in '" + cookiesSeen + "'");
+    }
+  }
+  // If we get here we're good.
+  do_print("Saw only correct cookie '" + expectedCookie + "'");
+  do_check_true(true);
+
+
+  if (++i == tests.length) {
+    // end of tests
+    httpserver.stop(do_test_finished);
+  } else {
+    checkCookie();
+  }
+}
+
+function run_test()
+{
+  httpserver.registerPathHandler(cookieSetPath, cookieSetHandler);
+  httpserver.registerPathHandler(cookieCheckPath, cookieCheckHandler);
+  httpserver.start(4444);
+
+  setCookie();
+  do_test_pending();
+}
+
+function cookieSetHandler(metadata, response)
+{
+  var cookieName = metadata.getHeader("foo-set-cookie");
+
+  response.setStatusLine(metadata.httpVersion, 200, "Ok");
+  response.setHeader("Set-Cookie", cookieName + "=1; Path=/", false);
+  response.setHeader("Content-Type", "text/plain");
+  response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function cookieCheckHandler(metadata, response)
+{
+  var cookies = metadata.getHeader("Cookie");
+
+  response.setStatusLine(metadata.httpVersion, 200, "Ok");
+  response.setHeader("foo-saw-cookies", cookies, false);
+  response.setHeader("Content-Type", "text/plain");
+  response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -90,16 +90,17 @@ fail-if = os == "android"
 [test_doomentry.js]
 [test_cacheflags.js]
 [test_channel_close.js]
 [test_compareURIs.js]
 [test_compressappend.js]
 [test_content_encoding_gzip.js]
 [test_content_sniffer.js]
 [test_cookie_header.js]
+[test_cookiejars.js]
 [test_data_protocol.js]
 [test_dns_service.js]
 [test_dns_localredirect.js]
 [test_duplicate_headers.js]
 [test_event_sink.js]
 [test_extract_charset_from_content_type.js]
 [test_force_sniffing.js]
 [test_fallback_no-cache-entry_canceled.js]
rename from netwerk/test/unit_ipc/test_cookie_wrap.js
rename to netwerk/test/unit_ipc/test_cookie_header_wrap.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+  run_test_in_child("../unit/test_cookiejars.js");
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head = head_channels_clone.js
 tail = 
 
 [test_cacheflags_wrap.js]
 [test_channel_close_wrap.js]
-[test_cookie_wrap.js]
+[test_cookie_header_wrap.js]
+[test_cookiejars_wrap.js]
 [test_duplicate_headers_wrap.js]
 [test_event_sink_wrap.js]
 [test_head_wrap.js]
 [test_headers_wrap.js]
 [test_httpcancel_wrap.js]
 [test_httpsuspend_wrap.js]
 [test_post_wrap.js]
 [test_progress_wrap.js]