Bug 1208371 - Ensure a media element's ImageContainer is protected when playing a stream. r=mt,jesup

HTMLMediaElement needs special protection when playing a stream since its
ImageContainer can outlive the video track of a stream.

Consider for instance when a (cross-origin) video track is removed from a
DOMMediaStream by a user and the remaining video track (non-CORS) does not yet
contain any actual video frames. The HTMLMediaElement will display a frame from
the removed track but the DOMMediaStream's principal has been updated to not
include the principal from the removed track.

With this patch we handle this by letting VideoFrameContainer notify
HTMLMediaElement when it has flushed out all video frames belonging to a
certain PrincipalHandle. I.e., when a new PrincipalHandle has been applied to the
underlying ImageContainer.

MozReview-Commit-ID: LvIZPl6Rdgj

--HG--
extra : rebase_source : cfbad5e5e7f43af4da4bfc213494b7b8e22cde17
This commit is contained in:
Andreas Pehrson 2016-02-04 09:27:09 +08:00
Родитель 6862394047
Коммит e899edc733
5 изменённых файлов: 122 добавлений и 7 удалений

Просмотреть файл

@ -4193,14 +4193,53 @@ HTMLMediaElement::PrincipalChanged(DOMMediaStream* aStream)
"%p. Waiting for it to reach VideoFrameContainer before "
"setting.", this, aStream->GetVideoPrincipal()));
if (mVideoFrameContainer) {
UpdateSrcStreamVideoPrincipal(aStream->GetVideoPrincipal());
UpdateSrcStreamVideoPrincipal(mVideoFrameContainer->GetLastPrincipalHandle());
}
}
void
HTMLMediaElement::UpdateSrcStreamVideoPrincipal(nsIPrincipal* aPrincipal)
HTMLMediaElement::UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle)
{
mSrcStreamVideoPrincipal = aPrincipal;
nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
mSrcStream->GetVideoTracks(videoTracks);
PrincipalHandle handle(aPrincipalHandle);
bool matchesTrackPrincipal = false;
for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
if (PrincipalHandleMatches(handle,
track->GetPrincipal()) &&
!track->Ended()) {
// When the PrincipalHandle for the VideoFrameContainer changes to that of
// a track in mSrcStream we know that a removed track was displayed but
// is no longer so.
matchesTrackPrincipal = true;
LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's "
"PrincipalHandle matches track %p. That's all we "
"need.", this, track.get()));
break;
}
}
if (matchesTrackPrincipal) {
mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
}
}
void
HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
const PrincipalHandle& aNewPrincipalHandle)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mSrcStream) {
return;
}
LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in "
"VideoFrameContainer.",
this));
UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
}
nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName)

Просмотреть файл

@ -231,7 +231,12 @@ public:
// From PrincipalChangeObserver<DOMMediaStream>.
void PrincipalChanged(DOMMediaStream* aStream) override;
void UpdateSrcStreamVideoPrincipal(nsIPrincipal* aPrincipal);
void UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle);
// Called after the MediaStream we're playing rendered a frame to aContainer
// with a different principalHandle than the previous frame.
void PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
const PrincipalHandle& aNewPrincipalHandle);
// Dispatch events
virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;

Просмотреть файл

@ -918,6 +918,7 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
// Collect any new frames produced in this iteration.
AutoTArray<ImageContainer::NonOwningImage,4> newImages;
PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
RefPtr<Image> blackImage;
MOZ_ASSERT(mProcessedTime >= aStream->mBufferStartTime, "frame position before buffer?");
@ -981,6 +982,8 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
}
newImages.AppendElement(ImageContainer::NonOwningImage(image, targetTime));
lastPrincipalHandle = chunk->GetPrincipalHandle();
aStream->mLastPlayedVideoFrame = *frame;
}
@ -993,6 +996,10 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
for (uint32_t i = 0; i < aStream->mVideoOutputs.Length(); ++i) {
VideoFrameContainer* output = aStream->mVideoOutputs[i];
bool principalHandleChanged =
lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
lastPrincipalHandle != output->GetLastPrincipalHandle();
// Find previous frames that may still be valid.
AutoTArray<ImageContainer::OwningImage,4> previousImages;
output->GetImageContainer()->GetCurrentImages(&previousImages);
@ -1032,6 +1039,12 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
image.mFrameID = output->NewFrameID();
images.AppendElement(image);
}
if (principalHandleChanged) {
output->UpdatePrincipalHandleForFrameID(lastPrincipalHandle,
newImages.LastElement().mFrameID);
}
output->SetCurrentFrames(aStream->mLastPlayedVideoFrame.GetIntrinsicSize(),
images);

Просмотреть файл

@ -20,7 +20,8 @@ VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
: mElement(aElement),
mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
mFrameID(0),
mIntrinsicSizeChanged(false), mImageSizeChanged(false)
mIntrinsicSizeChanged(false), mImageSizeChanged(false),
mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE), mFrameIDForPendingPrincipalHandle(0)
{
NS_ASSERTION(aElement, "aElement must not be null");
NS_ASSERTION(mImageContainer, "aContainer must not be null");
@ -29,6 +30,23 @@ VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
VideoFrameContainer::~VideoFrameContainer()
{}
PrincipalHandle VideoFrameContainer::GetLastPrincipalHandle()
{
MutexAutoLock lock(mMutex);
return mLastPrincipalHandle;
}
void VideoFrameContainer::UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
const ImageContainer::FrameID& aFrameID)
{
MutexAutoLock lock(mMutex);
if (mPendingPrincipalHandle == aPrincipalHandle) {
return;
}
mPendingPrincipalHandle = aPrincipalHandle;
mFrameIDForPendingPrincipalHandle = aFrameID;
}
void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
Image* aImage,
const TimeStamp& aTargetTime)
@ -69,8 +87,33 @@ void VideoFrameContainer::SetCurrentFramesLocked(const gfx::IntSize& aIntrinsicS
// composite it can then block on |mImageContainer|'s lock, causing a
// deadlock. We use this hack to defer the destruction of the current image
// until it is safe.
nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
mImageContainer->GetCurrentImages(&kungFuDeathGrip);
nsTArray<ImageContainer::OwningImage> oldImages;
mImageContainer->GetCurrentImages(&oldImages);
ImageContainer::FrameID lastFrameIDForOldPrincipalHandle =
mFrameIDForPendingPrincipalHandle - 1;
if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
((!oldImages.IsEmpty() &&
oldImages.LastElement().mFrameID >= lastFrameIDForOldPrincipalHandle) ||
(!aImages.IsEmpty() &&
aImages[0].mFrameID > lastFrameIDForOldPrincipalHandle))) {
// We are releasing the last FrameID prior to `lastFrameIDForOldPrincipalHandle`
// OR
// there are no FrameIDs prior to `lastFrameIDForOldPrincipalHandle` in the new
// set of images.
// This means that the old principal handle has been flushed out and we can
// notify our video element about this change.
RefPtr<VideoFrameContainer> self = this;
PrincipalHandle principalHandle = mPendingPrincipalHandle;
mLastPrincipalHandle = mPendingPrincipalHandle;
mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
mFrameIDForPendingPrincipalHandle = 0;
NS_DispatchToMainThread(NS_NewRunnableFunction([self, principalHandle]() {
if (self->mElement) {
self->mElement->PrincipalHandleChangedForVideoFrameContainer(self, principalHandle);
}
}));
}
if (aImages.IsEmpty()) {
mImageContainer->ClearAllImages();

Просмотреть файл

@ -13,6 +13,7 @@
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "ImageContainer.h"
#include "MediaSegment.h"
namespace mozilla {
@ -42,6 +43,13 @@ public:
already_AddRefed<ImageContainer> aContainer);
// Call on any thread
// Returns the last principalHandle we notified mElement about.
PrincipalHandle GetLastPrincipalHandle();
// We will notify mElement that aPrincipalHandle has been applied when all
// FrameIDs prior to aFrameID have been flushed out.
// aFrameID is ignored if aPrincipalHandle already is our pending principalHandle.
void UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
const ImageContainer::FrameID& aFrameID);
B2G_ACL_EXPORT void SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage,
const TimeStamp& aTargetTime);
void SetCurrentFrames(const gfx::IntSize& aIntrinsicSize,
@ -111,6 +119,13 @@ protected:
// frame is fully invalidated instead of just invalidating for the image change
// in the ImageLayer.
bool mImageSizeChanged;
// The last PrincipalHandle we notified mElement about.
PrincipalHandle mLastPrincipalHandle;
// The PrincipalHandle the client has notified us is changing with FrameID
// mFrameIDForPendingPrincipalHandle.
PrincipalHandle mPendingPrincipalHandle;
// The FrameID for which mPendingPrincipal is first valid.
ImageContainer::FrameID mFrameIDForPendingPrincipalHandle;
};
} // namespace mozilla