Bug 741059 - Part 1: Add APIs to nsIChannel to allow callers to override the private bit on the channel; r=jduell
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 04 Sep 2012 20:37:45 -0400
changeset 107260 83a7ea5398a988ff151d06f65b4fc852328e30e8
parent 107259 7096242b4edde22375d8511d2386d745a429f1ea
child 107261 4dc194e18570657487241780d77fcdd5b4be7dce
push id14941
push usereakhgari@mozilla.com
push dateMon, 17 Sep 2012 18:42:43 +0000
treeherdermozilla-inbound@89027f6e7662 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs741059
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 741059 - Part 1: Add APIs to nsIChannel to allow callers to override the private bit on the channel; r=jduell This is probably the worst patch that I have ever written! We add a setPrivate method to nsIPrivateBrowsingChannel which will be implemented by channels who care about private browsing. This allows the client to explicitly override the private bit on the channel. NS_UsePrivateBrowsing is also taught about this override bit using the internal nsIPrivateBrowsingChannel::IsPrivateModeOverriden API. This patch implements the new API for HTTP, FTP and wyciwyg channels. This also modifies the IPC implementations of these channels to correctly transfer that bit to the parent process if it has been set in the child process channel.
docshell/base/SerializedLoadContext.cpp
docshell/base/SerializedLoadContext.h
netwerk/base/public/Makefile.in
netwerk/base/public/nsIPrivateBrowsingChannel.idl
netwerk/base/public/nsNetUtil.h
netwerk/base/src/PrivateBrowsingChannel.h
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/FTPChannelChild.h
netwerk/protocol/ftp/FTPChannelParent.cpp
netwerk/protocol/ftp/nsFTPChannel.cpp
netwerk/protocol/ftp/nsFTPChannel.h
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/websocket/WebSocketChannelParent.cpp
netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
netwerk/protocol/wyciwyg/WyciwygChannelChild.h
netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
netwerk/protocol/wyciwyg/nsWyciwygChannel.h
netwerk/test/unit/head_cache.js
netwerk/test/unit/test_bug248970_cache.js
netwerk/test/unit/test_private_channel.js
netwerk/test/unit/xpcshell.ini
--- a/docshell/base/SerializedLoadContext.cpp
+++ b/docshell/base/SerializedLoadContext.cpp
@@ -16,36 +16,52 @@ SerializedLoadContext::SerializedLoadCon
   Init(aLoadContext);
 }
 
 SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel)
 {
   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;
+    bool isOverriden = false;
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
+    if (pbChannel &&
+        NS_SUCCEEDED(pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) &&
+        isOverriden) {
+      mUsePrivateBrowsing = isPrivate;
+      mIsPrivateBitValid = true;
+    }
+  }
 }
 
 SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel)
 {
   nsCOMPtr<nsILoadContext> loadContext;
   NS_QueryNotificationCallbacks(aChannel, loadContext);
   Init(loadContext);
 }
 
 void
 SerializedLoadContext::Init(nsILoadContext* aLoadContext)
 {
   if (aLoadContext) {
     mIsNotNull = true;
+    mIsPrivateBitValid = true;
     aLoadContext->GetIsContent(&mIsContent);
     aLoadContext->GetUsePrivateBrowsing(&mUsePrivateBrowsing);
     aLoadContext->GetAppId(&mAppId);
     aLoadContext->GetIsInBrowserElement(&mIsInBrowserElement);
   } else {
     mIsNotNull = false;
+    mIsPrivateBitValid = false;
     // none of below values really matter when mIsNotNull == false:
     // we won't be GetInterfaced to nsILoadContext
     mIsContent = true;
     mUsePrivateBrowsing = false;
     mAppId = 0;
     mIsInBrowserElement = false;
   }
 }
--- a/docshell/base/SerializedLoadContext.h
+++ b/docshell/base/SerializedLoadContext.h
@@ -31,23 +31,31 @@ public:
   }
 
   SerializedLoadContext(nsILoadContext* aLoadContext);
   SerializedLoadContext(nsIChannel* aChannel);
   SerializedLoadContext(nsIWebSocketChannel* aChannel);
 
   void Init(nsILoadContext* aLoadContext);
 
-  bool IsNotNull() const 
+  bool IsNotNull() const
   {
     return mIsNotNull;
   }
 
+  bool IsPrivateBitValid() const
+  {
+    return mIsPrivateBitValid;
+  }
+
   // used to indicate if child-side LoadContext * was null.
   bool          mIsNotNull;
+  // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if
+  // mIsNotNull is false, i.e., child LoadContext was null.
+  bool          mIsPrivateBitValid;
   bool          mIsContent;
   bool          mUsePrivateBrowsing;
   bool          mIsInBrowserElement;
   uint32_t      mAppId;
 };
 
 // Function to serialize over IPDL
 template<>
--- a/netwerk/base/public/Makefile.in
+++ b/netwerk/base/public/Makefile.in
@@ -53,16 +53,17 @@ XPIDLSRCS	= \
 		nsIInputStreamPump.idl \
 		nsIInputStreamChannel.idl \
                 nsIIOService2.idl \
 		nsIMIMEInputStream.idl \
 		nsINetAddr.idl \
                 nsINetworkLinkService.idl \
 		nsIPermission.idl \
 		nsIPermissionManager.idl \
+		nsIPrivateBrowsingChannel.idl \
 		nsIPrivateBrowsingService.idl \
 		nsIProgressEventSink.idl \
 		nsIPrompt.idl \
 		nsIProtocolProxyService.idl \
 		nsIProtocolProxyService2.idl \
 		nsIProtocolProxyFilter.idl \
 		nsIProtocolProxyCallback.idl \
 		nsIProxiedProtocolHandler.idl \
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIPrivateBrowsingChannel.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is implemented by channels which support overriding the
+ * privacy state of the channel.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(424b7efc-5d56-4717-9a71-8db9dcaaa618)]
+interface nsIPrivateBrowsingChannel : nsISupports
+{
+    /**
+     * Determine whether the channel is tied to a private browsing window.
+     *
+     * This value can be set only before the channel is opened.  Setting it
+     * after that does not have any effect.  This value overrides the privacy
+     * state of the channel, which means that if you call this method, then
+     * the loadGroup and load context will no longer be consulted when we
+     * need to know the private mode status for a channel.
+     *
+     * Note that this value is only meant to be used when the channel's privacy
+     * status cannot be obtained from the loadGroup or load context (for
+     * example, when the channel is not associated with any loadGroup or load
+     * context.)  Setting this value directly should be avoided if possible.
+     *
+     * Implementations must enforce the ordering semantics of this function by
+     * raising errors if setPrivate is called on a channel which has a loadGroup
+     * and/or callbacks that implement nsILoadContext, or if the loadGroup
+     * or notificationCallbacks are set after setPrivate has been called.
+     *
+     * @param aPrivate whether the channel should be opened in private mode.
+     */
+    void setPrivate(in boolean aPrivate);
+
+    /*
+     * This function is used to determine whether the channel's private mode
+     * has been overridden by a call to setPrivate.  It is intended to be used
+     * by NS_UsePrivateBrowsing(), and you should not call it directly.
+     *
+     * @param aValue the overridden value.  This will only be set if the function
+     *               returns true.
+     */
+    [noscript] boolean isPrivateModeOverriden(out boolean aValue);
+};
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -71,16 +71,17 @@
 #include "nsIChannelEventSink.h"
 #include "nsIChannelPolicy.h"
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
 #include "nsIRedirectChannelRegistrar.h"
 #include "nsIMIMEHeaderParam.h"
 #include "nsILoadContext.h"
 #include "mozilla/Services.h"
+#include "nsIPrivateBrowsingChannel.h"
 
 #ifdef MOZILLA_INTERNAL_API
 
 inline already_AddRefed<nsIIOService>
 do_GetIOService(nsresult* error = 0)
 {
     already_AddRefed<nsIIOService> ret = mozilla::services::GetIOService();
     if (error)
@@ -1320,16 +1321,24 @@ NS_QueryNotificationCallbacks(nsIInterfa
 
 /**
  * Returns true if channel is using Private Browsing, or false if not.
  * Returns false if channel's callbacks don't implement nsILoadContext.
  */
 inline bool
 NS_UsePrivateBrowsing(nsIChannel *channel)
 {
+    bool isPrivate = false;
+    bool isOverriden = false;
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+    if (pbChannel &&
+        NS_SUCCEEDED(pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) &&
+        isOverriden) {
+        return isPrivate;
+    }
     nsCOMPtr<nsILoadContext> loadContext;
     NS_QueryNotificationCallbacks(channel, loadContext);
     return loadContext && loadContext->UsePrivateBrowsing();
 }
 
 /**
  * Gets AppId and isInBrowserElement from channel's nsILoadContext.
  * Returns false if error or channel's callbacks don't implement nsILoadContext.
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/PrivateBrowsingChannel.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* 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_PrivateBrowsingChannel_h__
+#define mozilla_net_PrivateBrowsingChannel_h__
+
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+
+namespace mozilla {
+namespace net {
+
+template <class Channel>
+class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel
+{
+public:
+  PrivateBrowsingChannel() :
+    mPrivateBrowsingOverriden(false),
+    mPrivateBrowsing(false)
+  {
+  }
+
+  NS_IMETHOD SetPrivate(bool aPrivate)
+  {
+      // Make sure that we don't have a load group or a load context
+      // This is a fatal error in debug builds, and a runtime error in release
+      // builds.
+      nsILoadGroup* loadGroup = static_cast<Channel*>(this)->mLoadGroup;
+      nsCOMPtr<nsILoadContext> loadContext;
+      if (!loadGroup) {
+        NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+      }
+      MOZ_ASSERT(!loadGroup && !loadContext);
+      if (loadGroup || loadContext) {
+        return NS_ERROR_FAILURE;
+      }
+
+      mPrivateBrowsingOverriden = true;
+      mPrivateBrowsing = aPrivate;
+      return NS_OK;
+  }
+
+  NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool *aResult)
+  {
+      NS_ENSURE_ARG_POINTER(aValue);
+      NS_ENSURE_ARG_POINTER(aResult);
+      *aResult = mPrivateBrowsingOverriden;
+      if (mPrivateBrowsingOverriden) {
+          *aValue = mPrivateBrowsing;
+      }
+      return NS_OK;
+  }
+
+  bool CanSetCallbacks() const
+  {
+      // Make sure that the private bit override flag is not set.
+      // This is a fatal error in debug builds, and a runtime error in release
+      // builds.
+      MOZ_ASSERT(!mPrivateBrowsingOverriden);
+      return !mPrivateBrowsingOverriden;
+  }
+
+  bool CanSetLoadGroup() const
+  {
+      // We can set a load group whenever we can set a callback
+      return CanSetCallbacks();
+  }
+
+protected:
+  bool mPrivateBrowsingOverriden;
+  bool mPrivateBrowsing;
+};
+
+}
+}
+
+#endif
+
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -64,23 +64,24 @@ FTPChannelChild::ReleaseIPDLReference()
   mIPCOpen = false;
   Release();
 }
 
 //-----------------------------------------------------------------------------
 // FTPChannelChild::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS_INHERITED5(FTPChannelChild,
+NS_IMPL_ISUPPORTS_INHERITED6(FTPChannelChild,
                              nsBaseChannel,
                              nsIFTPChannel,
                              nsIUploadChannel,
                              nsIResumableChannel,
                              nsIProxiedChannel,
-                             nsIChildChannel)
+                             nsIChildChannel,
+                             nsIPrivateBrowsingChannel)
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime)
 {
   *lastModifiedTime = mLastModifiedTime;
   return NS_OK;
@@ -541,11 +542,31 @@ FTPChannelChild::CompleteRedirectSetup(n
     mLoadGroup->AddRequest(this, nullptr);
 
   // We already have an open IPDL connection to the parent. If on-modify-request
   // listeners or load group observers canceled us, let the parent handle it
   // and send it back to us naturally.
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FTPChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+  if (!CanSetCallbacks()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return nsBaseChannel::SetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+  if (!CanSetLoadGroup()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return nsBaseChannel::SetLoadGroup(aLoadGroup);
+}
+
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/FTPChannelChild.h
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -13,32 +13,34 @@
 #include "nsBaseChannel.h"
 #include "nsIFTPChannel.h"
 #include "nsIUploadChannel.h"
 #include "nsIProxiedChannel.h"
 #include "nsIResumableChannel.h"
 #include "nsIChildChannel.h"
 
 #include "nsIStreamListener.h"
+#include "PrivateBrowsingChannel.h"
 
 namespace mozilla {
 namespace net {
 
 // This class inherits logic from nsBaseChannel that is not needed for an
 // e10s child channel, but it works.  At some point we could slice up
 // nsBaseChannel and have a new class that has only the common logic for
 // nsFTPChannel/FTPChannelChild.
 
 class FTPChannelChild : public PFTPChannelChild
                       , public nsBaseChannel
                       , public nsIFTPChannel
                       , public nsIUploadChannel
                       , public nsIResumableChannel
                       , public nsIProxiedChannel
                       , public nsIChildChannel
+                      , public mozilla::net::PrivateBrowsingChannel<FTPChannelChild>
 {
 public:
   typedef ::nsIStreamListener nsIStreamListener;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIFTPCHANNEL
   NS_DECL_NSIUPLOADCHANNEL
   NS_DECL_NSIRESUMABLECHANNEL
@@ -87,21 +89,25 @@ protected:
                         const URIParams& aURI);
   void DoOnDataAvailable(const nsCString& data,
                          const uint64_t& offset,
                          const uint32_t& count);
   void DoOnStopRequest(const nsresult& statusCode);
   void DoFailedAsyncOpen(const nsresult& statusCode);
   void DoDeleteSelf();
 
+  NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks);
+  NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup);
+
   friend class FTPStartRequestEvent;
   friend class FTPDataAvailableEvent;
   friend class FTPStopRequestEvent;
   friend class FTPFailedAsyncOpenEvent;
   friend class FTPDeleteSelfEvent;
+  friend class mozilla::net::PrivateBrowsingChannel<FTPChannelChild>;
 
 private:
   // Called asynchronously from Resume: continues any pending calls into client.
   void CompleteResume();
   nsresult AsyncCall(void (FTPChannelChild::*funcPtr)(),
                      nsRunnableMethod<FTPChannelChild> **retval = nullptr);
 
   nsCOMPtr<nsIInputStream> mUploadStream;
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -97,16 +97,21 @@ FTPChannelParent::RecvAsyncOpen(const UR
   }
 
   rv = mChannel->ResumeAt(aStartPos, aEntityID);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   if (loadContext.IsNotNull())
     mLoadContext = new LoadContext(loadContext);
+  else if (loadContext.IsPrivateBitValid()) {
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(chan);
+    if (pbChannel)
+      pbChannel->SetPrivate(loadContext.mUsePrivateBrowsing);
+  }
 
   rv = mChannel->AsyncOpen(this, nullptr);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
   
   return true;
 }
 
--- a/netwerk/protocol/ftp/nsFTPChannel.cpp
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -40,22 +40,23 @@ PRTimeToSeconds(PRTime t_usec)
 // the other for the data channel. The command channel is the first
 // connection made and is used to negotiate the second, data, channel.
 // The data channel is driven by the command channel and is either
 // initiated by the server (PORT command) or by the client (PASV command).
 // Client initiation is the most common case and is attempted first.
 
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS_INHERITED4(nsFtpChannel,
+NS_IMPL_ISUPPORTS_INHERITED5(nsFtpChannel,
                              nsBaseChannel,
                              nsIUploadChannel,
                              nsIResumableChannel,
                              nsIFTPChannel,
-                             nsIProxiedChannel)
+                             nsIProxiedChannel,
+                             nsIPrivateBrowsingChannel)
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsFtpChannel::SetUploadStream(nsIInputStream *stream,
                               const nsACString &contentType,
                               int32_t contentLength)
 {
@@ -212,8 +213,28 @@ nsFtpChannel::GetFTPEventSink(nsCOMPtr<n
         nsCOMPtr<nsIFTPEventSink> ftpSink;
         GetCallback(ftpSink);
         if (ftpSink) {
             mFTPEventSink = new FTPEventSinkProxy(ftpSink);
         }
     }
     aResult = mFTPEventSink;
 }
+
+NS_IMETHODIMP
+nsFtpChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+  if (!CanSetCallbacks()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return nsBaseChannel::SetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsFtpChannel::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+  if (!CanSetLoadGroup()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return nsBaseChannel::SetLoadGroup(aLoadGroup);
+}
--- a/netwerk/protocol/ftp/nsFTPChannel.h
+++ b/netwerk/protocol/ftp/nsFTPChannel.h
@@ -24,29 +24,31 @@
 #include "nsIFTPChannel.h"
 #include "nsIUploadChannel.h"
 #include "nsIProxyInfo.h"
 #include "nsIProxiedChannel.h"
 #include "nsIResumableChannel.h"
 #include "nsHashPropertyBag.h"
 #include "nsFtpProtocolHandler.h"
 #include "nsNetUtil.h"
+#include "PrivateBrowsingChannel.h"
 
 class nsFtpChannel : public nsBaseChannel,
                      public nsIFTPChannel,
                      public nsIUploadChannel,
                      public nsIResumableChannel,
-                     public nsIProxiedChannel
+                     public nsIProxiedChannel,
+                     public mozilla::net::PrivateBrowsingChannel<nsFtpChannel>
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIUPLOADCHANNEL
     NS_DECL_NSIRESUMABLECHANNEL
     NS_DECL_NSIPROXIEDCHANNEL
-    
+
     nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi)
         : mProxyInfo(pi)
         , mStartPos(0)
         , mResumeRequested(false)
         , mLastModifiedTime(0)
     {
         SetURI(uri);
     }
@@ -89,16 +91,21 @@ public:
 
 protected:
     virtual ~nsFtpChannel() {}
     virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
                                        nsIChannel** channel);
     virtual bool GetStatusArg(nsresult status, nsString &statusArg);
     virtual void OnCallbacksChanged();
 
+    NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks);
+    NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup);
+
+    friend class mozilla::net::PrivateBrowsingChannel<nsFtpChannel>;
+
 private:
     nsCOMPtr<nsIProxyInfo>    mProxyInfo; 
     nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
     nsCOMPtr<nsIInputStream>  mUploadStream;
     uint64_t                  mStartPos;
     nsCString                 mEntityID;
     bool                      mResumeRequested;
     PRTime                    mLastModifiedTime;
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -43,17 +43,16 @@ HttpBaseChannel::HttpBaseChannel()
   , mUploadStreamHasHeaders(false)
   , mInheritApplicationCache(true)
   , mChooseApplicationCache(false)
   , mLoadedFromApplicationCache(false)
   , mChannelIsForDownload(false)
   , mTracingEnabled(true)
   , mTimingEnabled(false)
   , mAllowSpdy(true)
-  , mPrivateBrowsing(false)
   , mSuspendCount(0)
 {
   LOG(("Creating HttpBaseChannel @%x\n", this));
 
   // grab a reference to the handler to ensure that it doesn't go away.
   NS_ADDREF(gHttpHandler);
 
   // Subfields of unions cannot be targeted in an initializer list
@@ -133,27 +132,28 @@ HttpBaseChannel::Init(nsIURI *aURI,
 
   return rv;
 }
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS_INHERITED9( HttpBaseChannel,
+NS_IMPL_ISUPPORTS_INHERITED10(HttpBaseChannel,
                               nsHashPropertyBag,
                               nsIRequest,
                               nsIChannel,
                               nsIEncodedChannel,
                               nsIHttpChannel,
                               nsIHttpChannelInternal,
                               nsIUploadChannel,
                               nsIUploadChannel2,
                               nsISupportsPriority,
-                              nsITraceableChannel)
+                              nsITraceableChannel,
+                              nsIPrivateBrowsingChannel)
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::GetName(nsACString& aName)
 {
@@ -184,16 +184,20 @@ HttpBaseChannel::GetLoadGroup(nsILoadGro
   *aLoadGroup = mLoadGroup;
   NS_IF_ADDREF(*aLoadGroup);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
 {
+  if (!CanSetLoadGroup()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mLoadGroup = aLoadGroup;
   mProgressSink = nullptr;
   mPrivateBrowsing = NS_UsePrivateBrowsing(this);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
@@ -264,20 +268,23 @@ HttpBaseChannel::GetNotificationCallback
   *aCallbacks = mCallbacks;
   NS_IF_ADDREF(*aCallbacks);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
 {
+  if (!CanSetCallbacks()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mCallbacks = aCallbacks;
   mProgressSink = nullptr;
 
-  // Will never change unless SetNotificationCallbacks called again, so cache
   mPrivateBrowsing = NS_UsePrivateBrowsing(this);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetContentType(nsACString& aContentType)
 {
   if (!mResponseHead) {
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -25,16 +25,17 @@
 #include "nsIStringEnumerator.h"
 #include "nsISupportsPriority.h"
 #include "nsIApplicationCache.h"
 #include "nsIResumableChannel.h"
 #include "nsITraceableChannel.h"
 #include "nsILoadContext.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "nsThreadUtils.h"
+#include "PrivateBrowsingChannel.h"
 
 namespace mozilla {
 namespace net {
 
 /*
  * This class is a partial implementation of nsIHttpChannel.  It contains code
  * shared by nsHttpChannel and HttpChannelChild. 
  * - Note that this class has nothing to do with nsBaseChannel, which is an
@@ -45,16 +46,17 @@ class HttpBaseChannel : public nsHashPro
                       , public nsIEncodedChannel
                       , public nsIHttpChannel
                       , public nsIHttpChannelInternal
                       , public nsIUploadChannel
                       , public nsIUploadChannel2
                       , public nsISupportsPriority
                       , public nsIResumableChannel
                       , public nsITraceableChannel
+                      , public PrivateBrowsingChannel<HttpBaseChannel>
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIUPLOADCHANNEL
   NS_DECL_NSIUPLOADCHANNEL2
   NS_DECL_NSITRACEABLECHANNEL
 
   HttpBaseChannel();
@@ -203,16 +205,18 @@ protected:
   template <class T>
   void GetCallback(nsCOMPtr<T> &aResult)
   {
     NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                   NS_GET_TEMPLATE_IID(T),
                                   getter_AddRefs(aResult));
   }
 
+  friend class PrivateBrowsingChannel<HttpBaseChannel>;
+
   nsCOMPtr<nsIURI>                  mURI;
   nsCOMPtr<nsIURI>                  mOriginalURI;
   nsCOMPtr<nsIURI>                  mDocumentURI;
   nsCOMPtr<nsIStreamListener>       mListener;
   nsCOMPtr<nsISupports>             mListenerContext;
   nsCOMPtr<nsILoadGroup>            mLoadGroup;
   nsCOMPtr<nsISupports>             mOwner;
   nsCOMPtr<nsIInterfaceRequestor>   mCallbacks;
@@ -258,17 +262,16 @@ protected:
   uint32_t                          mInheritApplicationCache    : 1;
   uint32_t                          mChooseApplicationCache     : 1;
   uint32_t                          mLoadedFromApplicationCache : 1;
   uint32_t                          mChannelIsForDownload       : 1;
   uint32_t                          mTracingEnabled             : 1;
   // True if timing collection is enabled
   uint32_t                          mTimingEnabled              : 1;
   uint32_t                          mAllowSpdy                  : 1;
-  uint32_t                          mPrivateBrowsing            : 1;
 
   // Current suspension depth for this channel object
   uint32_t                          mSuspendCount;
 
   nsAutoPtr<nsTArray<nsCString> >   mRedirectedCachekeys;
 };
 
 // Share some code while working around C++'s absurd inability to handle casting
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -147,16 +147,20 @@ HttpChannelParent::RecvAsyncOpen(const U
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   if (loadContext.IsNotNull()) {
     if (mTabParent)
       mLoadContext = new LoadContext(loadContext, mTabParent->GetOwnerElement());
     else
       mLoadContext = new LoadContext(loadContext);
+  } else if (loadContext.IsPrivateBitValid()) {
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
+    if (pbChannel)
+      pbChannel->SetPrivate(loadContext.mUsePrivateBrowsing);
   }
 
   nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
 
   if (doResumeAt)
     httpChan->ResumeAt(startPos, entityID);
 
   if (originalUri)
--- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -62,16 +62,21 @@ WebSocketChannelParent::RecvAsyncOpen(co
     mChannel =
       do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
   }
   if (NS_FAILED(rv))
     goto fail;
 
   if (loadContext.IsNotNull())
     mLoadContext = new LoadContext(loadContext);
+#ifdef DEBUG
+  else
+    // websocket channels cannot have a private bit override
+    MOZ_ASSERT(!loadContext.IsPrivateBitValid());
+#endif
 
   rv = mChannel->SetNotificationCallbacks(this);
   if (NS_FAILED(rv))
     goto fail;
 
   rv = mChannel->SetProtocol(aProtocol);
   if (NS_FAILED(rv))
     goto fail;
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -16,20 +16,21 @@
 #include "nsILoadContext.h"
 #include "mozilla/ipc/URIUtils.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
-NS_IMPL_ISUPPORTS3(WyciwygChannelChild,
+NS_IMPL_ISUPPORTS4(WyciwygChannelChild,
                    nsIRequest,
                    nsIChannel,
-                   nsIWyciwygChannel)
+                   nsIWyciwygChannel,
+                   nsIPrivateBrowsingChannel)
 
 
 WyciwygChannelChild::WyciwygChannelChild()
   : mStatus(NS_OK)
   , mIsPending(false)
   , mCanceled(false)
   , mLoadFlags(LOAD_NORMAL)
   , mContentLength(-1)
@@ -379,16 +380,20 @@ WyciwygChannelChild::GetLoadGroup(nsILoa
 {
   *aLoadGroup = mLoadGroup;
   NS_IF_ADDREF(*aLoadGroup);
   return NS_OK;
 }
 NS_IMETHODIMP
 WyciwygChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup)
 {
+  if (!CanSetLoadGroup()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mLoadGroup = aLoadGroup;
   NS_QueryNotificationCallbacks(mCallbacks,
                                 mLoadGroup,
                                 NS_GET_IID(nsIProgressEventSink),
                                 getter_AddRefs(mProgressSink));
   return NS_OK;
 }
 
@@ -461,16 +466,20 @@ WyciwygChannelChild::GetNotificationCall
 {
   *aCallbacks = mCallbacks;
   NS_IF_ADDREF(*aCallbacks);
   return NS_OK;
 }
 NS_IMETHODIMP
 WyciwygChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks)
 {
+  if (!CanSetCallbacks()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mCallbacks = aCallbacks;
   NS_QueryNotificationCallbacks(mCallbacks,
                                 mLoadGroup,
                                 NS_GET_IID(nsIProgressEventSink),
                                 getter_AddRefs(mProgressSink));
   return NS_OK;
 }
 
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
@@ -5,16 +5,17 @@
 #ifndef mozilla_net_WyciwygChannelChild_h
 #define mozilla_net_WyciwygChannelChild_h
 
 #include "mozilla/net/PWyciwygChannelChild.h"
 #include "mozilla/net/ChannelEventQueue.h"
 #include "nsIWyciwygChannel.h"
 #include "nsIChannel.h"
 #include "nsIProgressEventSink.h"
+#include "PrivateBrowsingChannel.h"
 
 namespace mozilla {
 namespace net {
 
 // TODO: replace with IPDL states
 enum WyciwygChannelChildState {
   WCC_NEW,
   WCC_INIT,
@@ -29,16 +30,17 @@ enum WyciwygChannelChildState {
   WCC_ONWRITE,
   WCC_ONCLOSED
 };
 
 
 // Header file contents
 class WyciwygChannelChild : public PWyciwygChannelChild
                           , public nsIWyciwygChannel
+                          , public PrivateBrowsingChannel<WyciwygChannelChild>
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUEST
   NS_DECL_NSICHANNEL
   NS_DECL_NSIWYCIWYGCHANNEL
 
   WyciwygChannelChild();
@@ -67,16 +69,18 @@ protected:
                       const int32_t& source,
                       const nsCString& charset,
                       const nsCString& securityInfo);
   void OnDataAvailable(const nsCString& data,
                        const uint64_t& offset);
   void OnStopRequest(const nsresult& statusCode);
   void CancelEarly(const nsresult& statusCode);
 
+  friend class PrivateBrowsingChannel<WyciwygChannelChild>;
+
 private:
   nsresult                          mStatus;
   bool                              mIsPending;
   bool                              mCanceled;
   uint32_t                          mLoadFlags;
   int32_t                           mContentLength;
   int32_t                           mCharsetSource;
   nsCString                         mCharset;
--- a/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
@@ -104,16 +104,21 @@ WyciwygChannelParent::RecvAsyncOpen(cons
     return SendCancelEarly(rv);
 
   rv = mChannel->SetLoadFlags(aLoadFlags);
   if (NS_FAILED(rv))
     return SendCancelEarly(rv);
 
   if (loadContext.IsNotNull())
     mLoadContext = new LoadContext(loadContext);
+  else if (loadContext.IsPrivateBitValid()) {
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
+    if (pbChannel)
+      pbChannel->SetPrivate(loadContext.mUsePrivateBrowsing);
+  }
 
   rv = mChannel->AsyncOpen(this, nullptr);
   if (NS_FAILED(rv))
     return SendCancelEarly(rv);
 
   return true;
 }
 
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -78,34 +78,34 @@ private:
 
 
 // nsWyciwygChannel methods 
 nsWyciwygChannel::nsWyciwygChannel()
   : mStatus(NS_OK),
     mIsPending(false),
     mCharsetAndSourceSet(false),
     mNeedToWriteCharset(false),
-    mPrivateBrowsing(false),
     mCharsetSource(kCharsetUninitialized),
     mContentLength(-1),
     mLoadFlags(LOAD_NORMAL)
 {
 }
 
 nsWyciwygChannel::~nsWyciwygChannel() 
 {
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS6(nsWyciwygChannel,
+NS_IMPL_THREADSAFE_ISUPPORTS7(nsWyciwygChannel,
                               nsIChannel,
                               nsIRequest,
                               nsIStreamListener,
                               nsIRequestObserver,
-                              nsICacheListener, 
-                              nsIWyciwygChannel)
+                              nsICacheListener,
+                              nsIWyciwygChannel,
+                              nsIPrivateBrowsingChannel)
 
 nsresult
 nsWyciwygChannel::Init(nsIURI* uri)
 {
   NS_ENSURE_ARG_POINTER(uri);
 
   nsresult rv;
 
@@ -183,21 +183,26 @@ nsWyciwygChannel::GetLoadGroup(nsILoadGr
   *aLoadGroup = mLoadGroup;
   NS_IF_ADDREF(*aLoadGroup);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWyciwygChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
 {
+  if (!CanSetLoadGroup()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mLoadGroup = aLoadGroup;
   NS_QueryNotificationCallbacks(mCallbacks,
                                 mLoadGroup,
                                 NS_GET_IID(nsIProgressEventSink),
                                 getter_AddRefs(mProgressSink));
+  mPrivateBrowsing = NS_UsePrivateBrowsing(this);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWyciwygChannel::SetLoadFlags(uint32_t aLoadFlags)
 {
   mLoadFlags = aLoadFlags;
   return NS_OK;
@@ -261,23 +266,26 @@ nsWyciwygChannel::GetNotificationCallbac
   *aCallbacks = mCallbacks.get();
   NS_IF_ADDREF(*aCallbacks);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWyciwygChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
 {
+  if (!CanSetCallbacks()) {
+    return NS_ERROR_FAILURE;
+  }
+
   mCallbacks = aNotificationCallbacks;
   NS_QueryNotificationCallbacks(mCallbacks,
                                 mLoadGroup,
                                 NS_GET_IID(nsIProgressEventSink),
                                 getter_AddRefs(mProgressSink));
 
-  // Will never change unless SetNotificationCallbacks called again, so cache
   mPrivateBrowsing = NS_UsePrivateBrowsing(this);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsWyciwygChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
 {
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
@@ -21,24 +21,26 @@
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
 #include "nsICacheListener.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsIURI.h"
 #include "nsIEventTarget.h"
 #include "nsILoadContext.h"
 #include "nsNetUtil.h"
+#include "PrivateBrowsingChannel.h"
 
 extern PRLogModuleInfo * gWyciwygLog;
 
 //-----------------------------------------------------------------------------
 
 class nsWyciwygChannel: public nsIWyciwygChannel,
                         public nsIStreamListener,
-                        public nsICacheListener
+                        public nsICacheListener,
+                        public mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIREQUEST
     NS_DECL_NSICHANNEL
     NS_DECL_NSIWYCIWYGCHANNEL
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
@@ -63,21 +65,22 @@ protected:
     nsresult OpenCacheEntry(const nsACString & aCacheKey, nsCacheAccessMode aWriteAccess);
 
     void WriteCharsetAndSourceToCache(int32_t aSource,
                                       const nsCString& aCharset);
 
     void NotifyListener();
     bool IsOnCacheIOThread();
 
+    friend class mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>;
+
     nsresult                            mStatus;
     bool                                mIsPending;
     bool                                mCharsetAndSourceSet;
     bool                                mNeedToWriteCharset;
-    bool                                mPrivateBrowsing;
     int32_t                             mCharsetSource;
     nsCString                           mCharset;
     int32_t                             mContentLength;
     uint32_t                            mLoadFlags;
     nsCOMPtr<nsIURI>                    mURI;
     nsCOMPtr<nsIURI>                    mOriginalURI;
     nsCOMPtr<nsISupports>               mOwner;
     nsCOMPtr<nsIInterfaceRequestor>     mCallbacks;
--- a/netwerk/test/unit/head_cache.js
+++ b/netwerk/test/unit/head_cache.js
@@ -50,8 +50,30 @@ function syncWithCacheIOThread(callback)
     "HTTP",
     Components.interfaces.nsICache.STORE_ANYWHERE,
     Components.interfaces.nsICache.ACCESS_READ,
     function(status, entry) {
       do_check_eq(status, Components.results.NS_ERROR_CACHE_KEY_NOT_FOUND);
       callback();
     });
 }
+
+function get_device_entry_count(device) {
+  var cs = get_cache_service();
+  var entry_count = -1;
+
+  var visitor = {
+    visitDevice: function (deviceID, deviceInfo) {
+      if (device == deviceID)
+        entry_count = deviceInfo.entryCount;
+      return false;
+    },
+    visitEntry: function (deviceID, entryInfo) {
+      do_throw("nsICacheVisitor.visitEntry should not be called " +
+        "when checking the availability of devices");
+    }
+  };
+
+  // get the device entry count
+  cs.visitEntries(visitor);
+
+  return entry_count;
+}
--- a/netwerk/test/unit/test_bug248970_cache.js
+++ b/netwerk/test/unit/test_bug248970_cache.js
@@ -47,38 +47,16 @@ function check_devices_available(devices
 
   // see if any extra devices have been found
   if (found_devices.length > devices.length) {
     do_throw("Expected to find these devices: [" + devices.join(", ") +
       "], but instead got: [" + found_devices.join(", ") + "]");
   }
 }
 
-function get_device_entry_count(device) {
-  var cs = get_cache_service();
-  var entry_count = -1;
-
-  var visitor = {
-    visitDevice: function (deviceID, deviceInfo) {
-      if (device == deviceID)
-        entry_count = deviceInfo.entryCount;
-      return false;
-    },
-    visitEntry: function (deviceID, entryInfo) {
-      do_throw("nsICacheVisitor.visitEntry should not be called " +
-        "when checking the availability of devices");
-    }
-  };
-
-  // get the device entry count
-  cs.visitEntries(visitor);
-
-  return entry_count;
-}
-
 function make_input_stream_scriptable(input) {
   var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
                 createInstance(Ci.nsIScriptableInputStream);
   wrapper.init(input);
   return wrapper;
 }
 
 const entries = [
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_private_channel.js
@@ -0,0 +1,50 @@
+//
+// Private channel test
+//
+
+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 testpath = "/simple";
+
+function run_test() {
+  // Simulate a profile dir for xpcshell
+  do_get_profile();
+
+  // Start off with an empty cache
+  evict_cache_entries();
+
+  httpserver.registerPathHandler(testpath, serverHandler);
+  httpserver.start(4444);
+
+  var channel = setupChannel(testpath);
+
+  channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+  channel.setPrivate(true);
+
+  channel.asyncOpen(new ChannelListener(checkRequest, channel), null);
+
+  do_test_pending();
+}
+
+function setupChannel(path) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  return chan = ios.newChannel("http://localhost:4444" + path, "", null)
+                   .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function serverHandler(metadata, response) {
+  response.write("HTTP/1.0 200 OK\r\n\r\nfoobar");
+  respose.finish();
+}
+
+function checkRequest(request, data, context) {
+  do_check_eq(get_device_entry_count("disk"), 0);
+  do_check_eq(get_device_entry_count("memory"), 1);
+  httpserver.stop(do_test_finished);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -150,16 +150,17 @@ skip-if = os == "android"
 # Bug 732363: test fails on windows for unknown reasons.
 skip-if = os == "win"
 [test_nojsredir.js]
 [test_offline_status.js]
 [test_parse_content_type.js]
 [test_permmgr.js]
 [test_plaintext_sniff.js]
 [test_post.js]
+[test_private_channel.js]
 [test_progress.js]
 [test_protocolproxyservice.js]
 [test_proxy-failover_canceled.js]
 [test_proxy-failover_passing.js]
 [test_proxy-replace_canceled.js]
 [test_proxy-replace_passing.js]
 [test_psl.js]
 [test_range_requests.js]