Bug 712836. Make MediaResources which have ended abnormally ineligible for cloning. r=cpearce,a=jpr
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 20 Mar 2012 20:55:40 +1300
changeset 91868 f11042d416a09cddba479dc265dc8885250d5570
parent 91867 ed87206d099a12c8030c19270b6f415d1c38fe4a
child 91869 3d220e21f6f74a0cde6b4086dde1bdc242ec24b2
push idunknown
push userunknown
push dateunknown
reviewerscpearce, jpr
bugs712836
milestone13.0a2
Bug 712836. Make MediaResources which have ended abnormally ineligible for cloning. r=cpearce,a=jpr
content/html/content/src/nsHTMLMediaElement.cpp
content/media/MediaResource.cpp
content/media/MediaResource.h
content/media/nsMediaCache.cpp
content/media/nsMediaCache.h
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1405,17 +1405,20 @@ nsHTMLMediaElement::LookupMediaElementUR
   for (PRUint32 i = 0; i < entry->mElements.Length(); ++i) {
     nsHTMLMediaElement* elem = entry->mElements[i];
     bool equal;
     // Look for elements that have the same principal and CORS mode.
     // Ditto for anything else that could cause us to send different headers.
     if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) && equal &&
         elem->mCORSMode == mCORSMode) {
       NS_ASSERTION(elem->mDecoder && elem->mDecoder->GetResource(), "Decoder gone");
-      return elem;
+      MediaResource* resource = elem->mDecoder->GetResource();
+      if (resource->CanClone()) {
+        return elem;
+      }
     }
   }
   return nsnull;
 }
 
 nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
     mCurrentLoadID(0),
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -525,19 +525,25 @@ nsresult ChannelMediaResource::Close()
 already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
   return principal.forget();
 }
 
+bool ChannelMediaResource::CanClone()
+{
+  return mCacheStream.IsAvailableForSharing();
+}
+
 MediaResource* ChannelMediaResource::CloneData(nsMediaDecoder* aDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
 
   ChannelMediaResource* resource = new ChannelMediaResource(aDecoder, nsnull, mURI);
   if (resource) {
     // Initially the clone is treated as suspended by the cache, because
     // we don't have a channel. If the cache needs to read data from the clone
     // it will call CacheClientResume (or CacheClientSeek with aResume true)
     // which will recreate the channel. This way, if all of the media data
     // is already in the cache we don't create an unneccesary HTTP channel
@@ -905,16 +911,17 @@ public:
   }
 
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately) {}
   virtual void     Resume() {}
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
+  virtual bool     CanClone();
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
   virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
 
   // These methods are called off the main thread.
 
   // Other thread
   virtual void     SetReadMode(nsMediaCacheStream::ReadMode aMode) {}
   virtual void     SetPlaybackRate(PRUint32 aBytesPerSecond) {}
@@ -1082,16 +1089,21 @@ already_AddRefed<nsIPrincipal> FileMedia
   nsCOMPtr<nsIPrincipal> principal;
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   if (!secMan || !mChannel)
     return nsnull;
   secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
   return principal.forget();
 }
 
+bool FileMediaResource::CanClone()
+{
+  return true;
+}
+
 MediaResource* FileMediaResource::CloneData(nsMediaDecoder* aDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsHTMLMediaElement* element = aDecoder->GetMediaElement();
   if (!element) {
     // The decoder is being shut down, so we can't clone
     return nsnull;
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -186,16 +186,22 @@ public:
   // since we don't expect to resume again any time soon. Otherwise we
   // may resume again soon so resources should be held for a little
   // while.
   virtual void Suspend(bool aCloseImmediately) = 0;
   // Resume any downloads that have been suspended.
   virtual void Resume() = 0;
   // Get the current principal for the channel
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
+  // If this returns false, then we shouldn't try to clone this MediaResource
+  // because its underlying resources are not suitable for reuse (e.g.
+  // because the underlying connection has been lost, or this resource
+  // just can't be safely cloned). If this returns true, CloneData could
+  // still fail. If this returns false, CloneData should not be called.
+  virtual bool CanClone() { return false; }
   // Create a new stream of the same type that refers to the same URI
   // with a new channel. Any cached data associated with the original
   // stream should be accessible in the new stream too.
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder) = 0;
 
   // These methods are called off the main thread.
   // The mode is initially MODE_PLAYBACK.
   virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) = 0;
@@ -386,16 +392,17 @@ public:
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately);
   virtual void     Resume();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
   // Return true if the stream has been closed.
   bool IsClosed() const { return mCacheStream.IsClosed(); }
+  virtual bool     CanClone();
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
   virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
   virtual void     EnsureCacheUpToDate();
 
   // Other thread
   virtual void     SetReadMode(nsMediaCacheStream::ReadMode aMode);
   virtual void     SetPlaybackRate(PRUint32 aBytesPerSecond);
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
--- a/content/media/nsMediaCache.cpp
+++ b/content/media/nsMediaCache.cpp
@@ -173,16 +173,22 @@ public:
   // This can return partial reads.
   nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
                          PRInt32* aBytes);
   // This will fail if all aLength bytes are not read
   nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
   // This will fail if all aLength bytes are not written
   nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
 
+  PRInt64 AllocateResourceID()
+  {
+    mReentrantMonitor.AssertCurrentThreadIn();
+    return mNextResourceID++;
+  }
+
   // mReentrantMonitor must be held, called on main thread.
   // These methods are used by the stream to set up and tear down streams,
   // and to handle reads and writes.
   // Add aStream to the list of streams.
   void OpenStream(nsMediaCacheStream* aStream);
   // Remove aStream from the list of streams.
   void ReleaseStream(nsMediaCacheStream* aStream);
   // Free all blocks belonging to aStream.
@@ -1573,17 +1579,17 @@ nsMediaCache::AllocateAndWriteBlock(nsMe
 void
 nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream));
   mStreams.AppendElement(aStream);
-  aStream->mResourceID = mNextResourceID++;
+  aStream->mResourceID = AllocateResourceID();
 
   // Queue an update since a new stream has been opened.
   gMediaCache->QueueUpdate();
 }
 
 void
 nsMediaCache::ReleaseStream(nsMediaCacheStream* aStream)
 {
@@ -1851,16 +1857,23 @@ nsMediaCacheStream::NotifyDataReceived(P
 
 void
 nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
 
+  if (NS_FAILED(aStatus)) {
+    // Disconnect from other streams sharing our resource, since they
+    // should continue trying to load. Our load might have been deliberately
+    // canceled and that shouldn't affect other streams.
+    mResourceID = gMediaCache->AllocateResourceID();
+  }
+
   PRInt32 blockOffset = PRInt32(mChannelOffset%BLOCK_SIZE);
   if (blockOffset > 0) {
     // Write back the partial block
     memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
            BLOCK_SIZE - blockOffset);
     gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
         mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
     // Wake up readers who may be waiting for this data
@@ -1877,16 +1890,17 @@ nsMediaCacheStream::NotifyDataEnded(nsre
       NS_ASSERTION(!stream->mDidNotifyDataEnded, "Stream already ended!");
       stream->mDidNotifyDataEnded = true;
       stream->mNotifyDataEndedStatus = aStatus;
       stream->mClient->CacheClientNotifyDataEnded(aStatus);
     }
   }
 
   mChannelEnded = true;
+  gMediaCache->QueueUpdate();
 }
 
 nsMediaCacheStream::~nsMediaCacheStream()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
 
   if (gMediaCache) {
@@ -2329,16 +2343,19 @@ nsMediaCacheStream::Init()
   gMediaCache->OpenStream(this);
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
 nsMediaCacheStream::InitAsClone(nsMediaCacheStream* aOriginal)
 {
+  if (!aOriginal->IsAvailableForSharing())
+    return NS_ERROR_FAILURE;
+
   if (mInitialized)
     return NS_OK;
 
   nsresult rv = Init();
   if (NS_FAILED(rv))
     return rv;
   mResourceID = aOriginal->mResourceID;
 
--- a/content/media/nsMediaCache.h
+++ b/content/media/nsMediaCache.h
@@ -227,24 +227,25 @@ public:
     MODE_PLAYBACK
   };
 
   // aClient provides the underlying transport that cache will use to read
   // data for this stream.
   nsMediaCacheStream(ChannelMediaResource* aClient)
     : mClient(aClient), mResourceID(0), mInitialized(false),
       mHasHadUpdate(false),
+      mClosed(false),
+      mDidNotifyDataEnded(false),
       mIsSeekable(false), mCacheSuspended(false),
-      mChannelEnded(false), mDidNotifyDataEnded(false),
+      mChannelEnded(false),
       mUsingNullPrincipal(false),
       mChannelOffset(0), mStreamLength(-1),  
       mStreamOffset(0), mPlaybackBytesPerSecond(10000),
       mPinCount(0), mCurrentMode(MODE_PLAYBACK),
-      mMetadataInPartialBlockBuffer(false),
-      mClosed(false) {}
+      mMetadataInPartialBlockBuffer(false) {}
   ~nsMediaCacheStream();
 
   // Set up this stream with the cache. Can fail on OOM. One
   // of InitAsClone or Init must be called before any other method on
   // this class. Does nothing if already initialized.
   nsresult Init();
 
   // Set up this stream with the cache, assuming it's for the same data
@@ -261,16 +262,22 @@ public:
   // we do an HTTP load the seekability may be different (and sometimes
   // is, in practice, due to the effects of caching proxies).
   void SetSeekable(bool aIsSeekable);
   // This must be called (and return) before the ChannelMediaResource
   // used to create this nsMediaCacheStream is deleted.
   void Close();
   // This returns true when the stream has been closed
   bool IsClosed() const { return mClosed; }
+  // Returns true when this stream is can be shared by a new resource load
+  bool IsAvailableForSharing() const
+  {
+    return !mClosed &&
+      (!mDidNotifyDataEnded || NS_SUCCEEDED(mNotifyDataEndedStatus));
+  }
   // Get the principal for this stream.
   nsIPrincipal* GetCurrentPrincipal() { return mPrincipal; }
   // Ensure a global media cache update has run with this stream present.
   // This ensures the cache has had a chance to suspend or unsuspend this stream.
   // Called only on main thread. This can change the state of streams, fire
   // notifications, etc.
   void EnsureCacheUpdate();
 
@@ -461,30 +468,33 @@ private:
   // All streams with the same mResourceID are loading the same
   // underlying resource and should share data.
   PRInt64                mResourceID;
   // Set to true when Init or InitAsClone has been called
   bool                   mInitialized;
   // Set to true when nsMediaCache::Update() has finished while this stream
   // was present.
   bool                   mHasHadUpdate;
+  // Set to true when the stream has been closed either explicitly or
+  // due to an internal cache error
+  bool                   mClosed;
+  // True if CacheClientNotifyDataEnded has been called for this stream.
+  bool                   mDidNotifyDataEnded;
 
   // The following fields are protected by the cache's monitor but are
   // only written on the main thread. 
 
   // The last reported seekability state for the underlying channel
   bool mIsSeekable;
   // True if the cache has suspended our channel because the cache is
   // full and the priority of the data that would be received is lower
   // than the priority of the data already in the cache
   bool mCacheSuspended;
   // True if the channel ended and we haven't seeked it again.
   bool mChannelEnded;
-  // True if CacheClientNotifyDataEnded has been called for this stream.
-  bool mDidNotifyDataEnded;
   // True if mPrincipal is a null principal because we saw data from
   // multiple origins
   bool mUsingNullPrincipal;
   // The offset where the next data from the channel will arrive
   PRInt64      mChannelOffset;
   // The reported or discovered length of the data, or -1 if nothing is
   // known
   PRInt64      mStreamLength;
@@ -505,25 +515,23 @@ private:
   BlockList         mMetadataBlocks;
   // The list of played-back blocks; the first block is the most recently used
   BlockList         mPlayedBlocks;
   // The last reported estimate of the decoder's playback rate
   PRUint32          mPlaybackBytesPerSecond;
   // The number of times this stream has been Pinned without a
   // corresponding Unpin
   PRUint32          mPinCount;
-  // The status used when we did CacheClientNotifyDataEnded
+  // The status used when we did CacheClientNotifyDataEnded. Only valid
+  // when mDidNotifyDataEnded is true.
   nsresult          mNotifyDataEndedStatus;
   // The last reported read mode
   ReadMode          mCurrentMode;
   // True if some data in mPartialBlockBuffer has been read as metadata
   bool              mMetadataInPartialBlockBuffer;
-  // Set to true when the stream has been closed either explicitly or
-  // due to an internal cache error
-  bool              mClosed;
 
   // The following field is protected by the cache's monitor but are
   // only written on the main thread.
 
   // Data received for the block containing mChannelOffset. Data needs
   // to wait here so we can write back a complete block. The first
   // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
   // the rest are garbage.