Bug 722850 - Part 2: Query the private browsing status of channels used to manipulate cookies. r=mconnor
authorJosh Matthews <josh@joshmatthews.net>
Wed, 08 Feb 2012 13:37:07 -0500
changeset 113489 320c1567e431f64c4be5bb5acfec2d7e68a543c3
parent 113488 d907ac7bf66917b1a8815bde505ede8bb416cd6b
child 113490 2a9fbd15cad4fe21dee53ea0e7858c700d07de5b
push id23872
push useremorley@mozilla.com
push dateFri, 16 Nov 2012 17:06:27 +0000
treeherdermozilla-central@a7ed19f7d21a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconnor
bugs722850
milestone19.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 722850 - Part 2: Query the private browsing status of channels used to manipulate cookies. r=mconnor
netwerk/cookie/CookieServiceParent.cpp
netwerk/cookie/CookieServiceParent.h
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/test/unit/test_bug248970_cookie.js
netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/netwerk/cookie/CookieServiceParent.cpp
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -6,16 +6,37 @@
 #include "mozilla/net/CookieServiceParent.h"
 
 #include "mozilla/ipc/URIUtils.h"
 #include "nsCookieService.h"
 #include "nsNetUtil.h"
 
 using namespace mozilla::ipc;
 
+static void
+GetAppInfoFromLoadContext(const IPC::SerializedLoadContext &aLoadContext,
+                          uint32_t& aAppId,
+                          bool& aIsInBrowserElement,
+                          bool& aIsPrivate)
+{
+  // 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;
+  aIsPrivate = false;
+
+  if (aLoadContext.IsNotNull()) {
+    aAppId = aLoadContext.mAppId;
+    aIsInBrowserElement = aLoadContext.mIsInBrowserElement;
+  }
+
+  if (aLoadContext.IsPrivateBitValid())
+    aIsPrivate = aLoadContext.mUsePrivateBrowsing;
+}
+
 namespace mozilla {
 namespace net {
 
 CookieServiceParent::CookieServiceParent()
 {
   // Instantiate the cookieservice via the service manager, so it sticks around
   // until shutdown.
   nsCOMPtr<nsICookieService> cs = do_GetService(NS_COOKIESERVICE_CONTRACTID);
@@ -43,21 +64,21 @@ CookieServiceParent::RecvGetCookieString
 
   // 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);
+  bool isInBrowserElement, isPrivate;
+  GetAppInfoFromLoadContext(aLoadContext, appId, isInBrowserElement, isPrivate);
 
   mCookieService->GetCookieStringInternal(hostURI, aIsForeign, aFromHttp, appId,
-                                          isInBrowserElement, *aResult);
+                                          isInBrowserElement, isPrivate, *aResult);
   return true;
 }
 
 bool
 CookieServiceParent::RecvSetCookieString(const URIParams& aHost,
                                          const bool& aIsForeign,
                                          const nsCString& aCookieString,
                                          const nsCString& aServerTime,
@@ -70,38 +91,21 @@ CookieServiceParent::RecvSetCookieString
 
   // 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);
+  bool isInBrowserElement, isPrivate;
+  GetAppInfoFromLoadContext(aLoadContext, appId, isInBrowserElement, isPrivate);
 
   nsDependentCString cookieString(aCookieString, 0);
   mCookieService->SetCookieStringInternal(hostURI, aIsForeign, cookieString,
                                           aServerTime, aFromHttp, appId,
-                                          isInBrowserElement);
+                                          isInBrowserElement, isPrivate);
   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
@@ -32,19 +32,16 @@ protected:
   virtual bool RecvSetCookieString(const URIParams& aHost,
                                    const bool& aIsForeign,
                                    const nsCString& aCookieString,
                                    const nsCString& aServerTime,
                                    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/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -42,16 +42,17 @@
 #include "nsNetUtil.h"
 #include "nsNetCID.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsNetCID.h"
 #include "mozilla/storage.h"
 #include "mozilla/Util.h" // for DebugOnly
 #include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/Likely.h"
 #include "nsIAppsService.h"
 #include "mozIApplication.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 // Create key from baseDomain that will access the default cookie namespace.
@@ -679,17 +680,17 @@ nsCookieService::Init()
 
   // Init our default, and possibly private DBStates.
   InitDBStates();
 
   mObserverService = mozilla::services::GetObserverService();
   NS_ENSURE_STATE(mObserverService);
   mObserverService->AddObserver(this, "profile-before-change", true);
   mObserverService->AddObserver(this, "profile-do-change", true);
-  mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true);
+  mObserverService->AddObserver(this, "last-pb-context-exited", true);
 
   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
   if (!mPermissionService) {
     NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
     COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
   }
 
   return NS_OK;
@@ -701,27 +702,17 @@ nsCookieService::InitDBStates()
   NS_ASSERTION(!mDBState, "already have a DBState");
   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
 
   // Create a new default DBState and set our current one.
   mDefaultDBState = new DBState();
   mDBState = mDefaultDBState;
 
-  // If we're in private browsing mode, create a private DBState.
-  nsCOMPtr<nsIPrivateBrowsingService> pbs =
-    do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
-  if (pbs) {
-    bool inPrivateBrowsing = false;
-    pbs->GetPrivateBrowsingEnabled(&inPrivateBrowsing);
-    if (inPrivateBrowsing) {
-      mPrivateDBState = new DBState();
-      mDBState = mPrivateDBState;
-    }
-  }
+  mPrivateDBState = new DBState();
 
   // Get our cookie file.
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
     getter_AddRefs(mDefaultDBState->cookieFile));
   if (NS_FAILED(rv)) {
     // We've already set up our DBStates appropriately; nothing more to do.
     COOKIE_LOGSTRING(PR_LOG_WARNING,
       ("InitDBStates(): couldn't get cookie file"));
@@ -1472,39 +1463,22 @@ nsCookieService::Observe(nsISupports    
     // ready for us if and when we switch back to it.
     InitDBStates();
 
   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
     if (prefBranch)
       PrefChanged(prefBranch);
 
-  } else if (!strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC)) {
-    if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
-      NS_ASSERTION(mDefaultDBState, "don't have a default state");
-      NS_ASSERTION(mDBState == mDefaultDBState, "not in default state");
-      NS_ASSERTION(!mPrivateDBState, "already have a private state");
-
-      // Create a new DBState, and swap it in.
-      mPrivateDBState = new DBState();
-      mDBState = mPrivateDBState;
-
-    } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
-      NS_ASSERTION(mDefaultDBState, "don't have a default state");
-      NS_ASSERTION(mDBState == mPrivateDBState, "not in private state");
-      NS_ASSERTION(!mPrivateDBState->dbConn, "private DB connection not null");
-
-      // Clear the private DBState, and restore the default one.
-      mPrivateDBState = NULL;
-      mDBState = mDefaultDBState;
-    }
-
-    NotifyChanged(nullptr, NS_LITERAL_STRING("reload").get());
+  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+    // Flush all the cookies stored by private browsing contexts
+    mPrivateDBState = new DBState();
   }
 
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::GetCookieString(nsIURI     *aHostURI,
                                  nsIChannel *aChannel,
                                  char       **aCookie)
 {
@@ -1535,19 +1509,21 @@ nsCookieService::GetCookieStringCommon(n
 
   // 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);
   }
 
+  bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
+
   nsAutoCString result;
   GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
-                          inBrowserElement, result);
+                          inBrowserElement, isPrivate, result);
   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::SetCookieString(nsIURI     *aHostURI,
                                  nsIPrompt  *aPrompt,
                                  const char *aCookieHeader,
@@ -1584,39 +1560,46 @@ nsCookieService::SetCookieStringCommon(n
 
   // 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);
   }
 
+  bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
+
   nsDependentCString cookieString(aCookieHeader);
   nsDependentCString serverTime(aServerTime ? aServerTime : "");
   SetCookieStringInternal(aHostURI, isForeign, cookieString,
-                          serverTime, aFromHttp, appId, inBrowserElement);
+                          serverTime, aFromHttp, appId, inBrowserElement,
+                          isPrivate);
   return NS_OK;
 }
 
 void
 nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
                                          bool                aIsForeign,
                                          nsDependentCString &aCookieHeader,
                                          const nsCString    &aServerTime,
                                          bool                aFromHttp,
                                          uint32_t            aAppId,
-                                         bool                aInBrowserElement)
+                                         bool                aInBrowserElement,
+                                         bool                aIsPrivate)
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
 
+  AutoRestore<DBState*> savePrevDBState(mDBState);
+  mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
+
   // get the base domain for the host URI.
   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
   // file:// URI's (i.e. with an empty host) are allowed, but any other
   // scheme must have a non-empty host. A trailing dot in the host
   // is acceptable.
   bool requireHostMatch;
   nsAutoCString baseDomain;
   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
@@ -2462,25 +2445,29 @@ public:
 };
 
 void
 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
                                          bool aIsForeign,
                                          bool aHttpBound,
                                          uint32_t aAppId,
                                          bool aInBrowserElement,
+                                         bool aIsPrivate,
                                          nsCString &aCookieString)
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
 
+  AutoRestore<DBState*> savePrevDBState(mDBState);
+  mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
+
   // get the base domain, host, and path from the URI.
   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
   // file:// URI's (i.e. with an empty host) are allowed, but any other
   // scheme must have a non-empty host. A trailing dot in the host
   // is acceptable.
   bool requireHostMatch;
   nsAutoCString baseDomain, hostFromURI, pathFromURI;
   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -262,19 +262,19 @@ class nsCookieService : public nsICookie
     void                          AsyncReadComplete();
     void                          CancelAsyncRead(bool aPurgeReadSet);
     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, uint32_t aAppId, bool aInBrowserElement, nsCString &aCookie);
+  void                            GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, uint32_t aAppId, bool aInBrowserElement, bool aIsPrivate, 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, uint32_t aAppId, bool aInBrowserElement);
+  void                            SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, uint32_t aAppId, bool aInBrowserElement, bool aIsPrivate);
     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 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);
     bool                          RequireThirdPartyCheck();
@@ -311,18 +311,18 @@ class nsCookieService : public nsICookie
     nsCOMPtr<nsIObserverService>     mObserverService;
     nsCOMPtr<nsICookiePermission>    mPermissionService;
     nsCOMPtr<mozIThirdPartyUtil>     mThirdPartyUtil;
     nsCOMPtr<nsIEffectiveTLDService> mTLDService;
     nsCOMPtr<nsIIDNService>          mIDNService;
     nsCOMPtr<mozIStorageService>     mStorageService;
 
     // we have two separate DB states: one for normal browsing and one for
-    // private browsing, switching between them as appropriate. this state
-    // encapsulates both the in-memory table and the on-disk DB.
+    // private browsing, switching between them on a per-cookie-request basis.
+    // this state encapsulates both the in-memory table and the on-disk DB.
     // note that the private states' dbConn should always be null - we never
     // want to be dealing with the on-disk DB when in private browsing.
     DBState                      *mDBState;
     nsRefPtr<DBState>             mDefaultDBState;
     nsRefPtr<DBState>             mPrivateDBState;
 
     // cached prefs
     uint8_t                       mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT}
--- a/netwerk/test/unit/test_bug248970_cookie.js
+++ b/netwerk/test/unit/test_bug248970_cookie.js
@@ -1,150 +1,134 @@
 /* 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/. */
 
-var _PBSvc = null;
-function get_PBSvc() {
-  if (_PBSvc)
-    return _PBSvc;
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var httpserver;
 
-  try {
-    _PBSvc = Components.classes["@mozilla.org/privatebrowsing;1"].
-             getService(Components.interfaces.nsIPrivateBrowsingService);
-    return _PBSvc;
-  } catch (e) {}
-  return null;
+function inChildProcess() {
+  return Cc["@mozilla.org/xre/app-info;1"]
+           .getService(Ci.nsIXULRuntime)
+           .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;  
+}
+function makeChan(path) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel("http://localhost:4444/" + path, null, null)
+                .QueryInterface(Ci.nsIHttpChannel);
+  return chan;
 }
 
-var _CMSvc = null;
-function get_CookieManager() {
-  if (_CMSvc)
-    return _CMSvc;
-
-  return _CMSvc = Components.classes["@mozilla.org/cookiemanager;1"].
-                  getService(Components.interfaces.nsICookieManager2);
-}
+function setup_chan(path, isPrivate, callback) {
+  var chan = makeChan(path);
+  chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+  chan.asyncOpen(new ChannelListener(callback), null);  
+ }
 
-function is_cookie_available1(domain, path, name, value,
-                              secure, httponly, session, expires) {
-  var cm = get_CookieManager();
-  var enumerator = cm.enumerator;
-  while (enumerator.hasMoreElements()) {
-    var cookie = enumerator.getNext().QueryInterface(Components.interfaces.nsICookie);
-    if (cookie.host == domain &&
-        cookie.path == path &&
-        cookie.name == name &&
-        cookie.value == value &&
-        cookie.isSecure == secure &&
-        cookie.expires == expires)
-      return true;
-  }
-  return false;
+function set_cookie(value, callback) {
+  return setup_chan('set?cookie=' + value, false, callback);
 }
 
-function is_cookie_available2(domain, path, name, value,
-                              secure, httponly, session, expires) {
-  var cookie = {
-    name: name,
-    value: value,
-    isDomain: true,
-    host: domain,
-    path: path,
-    isSecure: secure,
-    expires: expires,
-    status: 0,
-    policy: 0,
-    isSession: session,
-    expiry: expires,
-    isHttpOnly: httponly,
-    QueryInterface: function(iid) {
-      var validIIDs = [Components.interfaces.nsISupports,
-                       Components.interfaces.nsICookie,
-                       Components.interfaces.nsICookie2];
-      for (var i = 0; i < validIIDs.length; ++i) {
-        if (iid == validIIDs[i])
-          return this;
-      }
-      throw Components.results.NS_ERROR_NO_INTERFACE;
-    }
-  };
+function set_private_cookie(value, callback) {
+  return setup_chan('set?cookie=' + value, true, callback);
+}
 
-  var cm = get_CookieManager();
-  return cm.cookieExists(cookie);
+function check_cookie_presence(value, isPrivate, expected, callback) {
+  var chan = setup_chan('present?cookie=' + value.replace('=','|'), isPrivate, function(req) {
+    req.QueryInterface(Ci.nsIHttpChannel);
+    do_check_eq(req.responseStatus, expected ? 200 : 404);
+    callback(req);
+  });
 }
 
-var cc_observer = null;
-function setup_cookie_changed_observer() {
-  cc_observer = {
-    gotReloaded: false,
-    QueryInterface: function (iid) {
-      const interfaces = [Components.interfaces.nsIObserver,
-                          Components.interfaces.nsISupports];
-      if (!interfaces.some(function(v) iid.equals(v)))
-        throw Components.results.NS_ERROR_NO_INTERFACE;
-      return this;
-    },
-    observe: function (subject, topic, data) {
-      if (topic == "cookie-changed") {
-        if (!subject) {
-          if (data == "reload")
-            this.gotReloaded = true;
-        }
-      }
+function presentHandler(metadata, response) {
+  var present = false;
+  var match = /cookie=([^&]*)/.exec(metadata.queryString);
+  if (match) {
+    try {
+      present = metadata.getHeader("Cookie").indexOf(match[1].replace("|","=")) != -1;
+    } catch (x) {
     }
-  };
-  var os = Components.classes["@mozilla.org/observer-service;1"].
-           getService(Components.interfaces.nsIObserverService);
-  os.addObserver(cc_observer, "cookie-changed", false);
+  }
+  response.setStatusLine("1.0", present ? 200 : 404, "");
+}
+
+function setHandler(metadata, response) {
+  response.setStatusLine("1.0", 200, "Cookie set");
+  var match = /cookie=([^&]*)/.exec(metadata.queryString);
+  if (match) {
+    response.setHeader("Set-Cookie", match[1]);
+  }
 }
 
 function run_test() {
-  var pb = get_PBSvc();
-  if (pb) { // Private Browsing might not be available
-    var prefBranch = Components.classes["@mozilla.org/preferences-service;1"].
-                     getService(Components.interfaces.nsIPrefBranch);
-    prefBranch.setBoolPref("browser.privatebrowsing.keep_current_session", true);
-
-    var cm = get_CookieManager();
-    do_check_neq(cm, null);
-
-    setup_cookie_changed_observer();
-    do_check_neq(cc_observer, null);
-
+  httpserver = new HttpServer();
+  httpserver.registerPathHandler("/set", setHandler);
+  httpserver.registerPathHandler("/present", presentHandler);
+  httpserver.start(4444);
+  
+  do_test_pending();
+  
+  function check_cookie(req) {
+    req.QueryInterface(Ci.nsIHttpChannel);
+    do_check_eq(req.responseStatus, 200);
     try {
-      // create Cookie-A
-      const time = (new Date("Jan 1, 2030")).getTime() / 1000;
-      cm.add("pbtest.example.com", "/", "C1", "V1", false, true, false, time);
-      // make sure Cookie-A is retrievable
-      do_check_true(is_cookie_available1("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-      do_check_true(is_cookie_available2("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-      // enter private browsing mode
-      pb.privateBrowsingEnabled = true;
-      // make sure the "cleared" notification was fired
-      do_check_true(cc_observer.gotReloaded);
-      cc_observer.gotReloaded = false;
-      // make sure Cookie-A is not retrievable
-      do_check_false(is_cookie_available1("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-      do_check_false(is_cookie_available2("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-      // create Cookie-B
-      const time2 = (new Date("Jan 2, 2030")).getTime() / 1000;
-      cm.add("pbtest2.example.com", "/", "C2", "V2", false, true, false, time2);
-      // make sure Cookie-B is retrievable
-      do_check_true(is_cookie_available1("pbtest2.example.com", "/", "C2", "V2", false, true, false, time2));
-      do_check_true(is_cookie_available2("pbtest2.example.com", "/", "C2", "V2", false, true, false, time2));
-      // exit private browsing mode
-      pb.privateBrowsingEnabled = false;
-      // make sure the "reload" notification was fired
-      do_check_true(cc_observer.gotReloaded);
-      // make sure Cookie-B is not retrievable
-      do_check_false(is_cookie_available1("pbtest2.example.com", "/", "C2", "V2", false, true, false, time2));
-      do_check_false(is_cookie_available2("pbtest2.example.com", "/", "C2", "V2", false, true, false, time2));
-      // make sure Cookie-A is retrievable
-      do_check_true(is_cookie_available1("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-      do_check_true(is_cookie_available2("pbtest.example.com", "/", "C1", "V1", false, true, false, time));
-    } catch (e) {
-      do_throw("Unexpected exception while testing cookies: " + e);
+      do_check_true(req.getResponseHeader("Set-Cookie") != "", "expected a Set-Cookie header");
+    } catch (x) {
+      do_throw("missing Set-Cookie header");
     }
 
-    prefBranch.clearUserPref("browser.privatebrowsing.keep_current_session");
+    runNextTest();
+  }
+
+  let tests = [];
+  
+  function runNextTest() {
+    do_execute_soon(tests.shift());
   }
+  
+  tests.push(function() {
+    set_cookie("C1=V1", check_cookie);
+  });
+  tests.push(function() {
+    set_private_cookie("C2=V2", check_cookie);
+  });
+  tests.push(function() {
+    // Check that the first cookie is present in a non-private request
+    check_cookie_presence("C1=V1", false, true, runNextTest);
+  });
+  tests.push(function() {
+    // Check that the second cookie is present in a private request
+    check_cookie_presence("C2=V2", true, true, runNextTest);
+  });
+  tests.push(function() {
+    // Check that the first cookie is not present in a private request
+    check_cookie_presence("C1=V1", true, false, runNextTest);
+  });
+  tests.push(function() {
+    // Check that the second cookie is not present in a non-private request
+    check_cookie_presence("C2=V2", false, false, runNextTest);
+  });
+
+  // The following test only works in a non-e10s situation at the moment,
+  // since the notification needs to run in the parent process but there is
+  // no existing mechanism to make that happen.  
+  if (!inChildProcess()) {
+    tests.push(function() {
+      // Simulate all private browsing instances being closed
+      var obsvc = Cc["@mozilla.org/observer-service;1"].
+        getService(Ci.nsIObserverService);
+      obsvc.notifyObservers(null, "last-pb-context-exited", null);
+      // Check that all private cookies are now unavailable in new private requests
+      check_cookie_presence("C2=V2", true, false, runNextTest);
+    });
+  }
+  
+  tests.push(function() { httpserver.stop(do_test_finished); });
+  
+  runNextTest();
 }
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+  run_test_in_child("../unit/test_bug248970_cookie.js");
+}
\ No newline at end of file
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 head = head_channels_clone.js
 tail = 
 
+[test_bug248970_cookie_wrap.js]
 [test_cacheflags_wrap.js]
 [test_channel_close_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]