Bug 712836. Make MediaResources which have ended abnormally ineligible for cloning. r=cpearce,a=lsblakk
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1402,17 +1402,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->GetStream(), "Decoder gone");
- return elem;
+ nsMediaStream* resource = elem->mDecoder->GetStream();
+ if (resource->CanClone()) {
+ return elem;
+ }
}
}
return nsnull;
}
nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsGenericHTMLElement(aNodeInfo),
mCurrentLoadID(0),
--- a/content/media/nsMediaCache.cpp
+++ b/content/media/nsMediaCache.cpp
@@ -172,16 +172,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.
@@ -1565,17 +1571,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)
{
@@ -1843,16 +1849,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
@@ -1869,16 +1882,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) {
@@ -2321,16 +2335,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
@@ -225,24 +225,26 @@ public:
MODE_PLAYBACK
};
// aClient provides the underlying transport that cache will use to read
// data for this stream.
nsMediaCacheStream(nsMediaChannelStream* 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
@@ -259,16 +261,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 nsMediaChannelStream
// 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();
@@ -459,30 +467,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;
@@ -503,25 +514,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.
--- a/content/media/nsMediaStream.cpp
+++ b/content/media/nsMediaStream.cpp
@@ -528,19 +528,25 @@ nsresult nsMediaChannelStream::Close()
already_AddRefed<nsIPrincipal> nsMediaChannelStream::GetCurrentPrincipal()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
return principal.forget();
}
+bool nsMediaChannelStream::CanClone()
+{
+ return mCacheStream.IsAvailableForSharing();
+}
+
nsMediaStream* nsMediaChannelStream::CloneData(nsMediaDecoder* aDecoder)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
nsMediaChannelStream* stream = new nsMediaChannelStream(aDecoder, nsnull, mURI);
if (stream) {
// 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
@@ -908,16 +914,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 nsMediaStream* 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) {}
@@ -1085,16 +1092,21 @@ already_AddRefed<nsIPrincipal> nsMediaFi
nsCOMPtr<nsIPrincipal> principal;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
if (!secMan || !mChannel)
return nsnull;
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
return principal.forget();
}
+bool nsMediaFileStream::CanClone()
+{
+ return true;
+}
+
nsMediaStream* nsMediaFileStream::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/nsMediaStream.h
+++ b/content/media/nsMediaStream.h
@@ -179,16 +179,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 nsMediaStream* 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;
@@ -382,16 +388,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 nsMediaStream* 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);