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 107377 156bdf05dd97b402adc64bc8afdd29670a71fe0e
parent 107376 a8c051013c48a48c86dab032d54403d16e1a64ad
child 107378 09c5ad99ae92299f99c8809a53e66f86181969f5
push id15001
push userjduell@mozilla.com
push dateTue, 18 Sep 2012 16:06:02 +0000
treeherdermozilla-inbound@156bdf05dd97 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi, smaug
bugs756648
milestone18.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 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]