diff --git a/dom/media/DecodedStream.cpp b/dom/media/DecodedStream.cpp index 2c6304f023..3beffaa89b 100644 --- a/dom/media/DecodedStream.cpp +++ b/dom/media/DecodedStream.cpp @@ -69,10 +69,8 @@ private: bool mStreamFinishedOnMainThread; }; -DecodedStreamData::DecodedStreamData(int64_t aInitialTime, - SourceMediaStream* aStream) +DecodedStreamData::DecodedStreamData(SourceMediaStream* aStream) : mAudioFramesWritten(0) - , mInitialTime(aInitialTime) , mNextVideoTime(-1) , mNextAudioTime(-1) , mStreamInitialized(false) @@ -102,9 +100,9 @@ DecodedStreamData::IsFinished() const } int64_t -DecodedStreamData::GetClock() const +DecodedStreamData::GetPosition() const { - return mInitialTime + mListener->GetLastOutputTime(); + return mListener->GetLastOutputTime(); } class OutputStreamListener : public MediaStreamListener { @@ -189,10 +187,10 @@ DecodedStream::GetData() const void DecodedStream::DestroyData() { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(NS_IsMainThread()); GetReentrantMonitor().AssertCurrentThreadIn(); - // Avoid the redundant blocking to output stream. + // Avoid the redundant blocking to output stream. if (!mData) { return; } @@ -222,18 +220,18 @@ DecodedStream::DestroyData() } void -DecodedStream::RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph) +DecodedStream::RecreateData(MediaStreamGraph* aGraph) { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(NS_IsMainThread()); GetReentrantMonitor().AssertCurrentThreadIn(); MOZ_ASSERT((aGraph && !mData && OutputStreams().IsEmpty()) || // first time (!aGraph && mData)); // 2nd time and later auto source = aGraph->CreateSourceStream(nullptr); DestroyData(); - mData.reset(new DecodedStreamData(aInitialTime, source)); + mData.reset(new DecodedStreamData(source)); - // Note that the delay between removing ports in DestroyDecodedStream + // Note that the delay between removing ports in DestroyDecodedStream // and adding new ones won't cause a glitch since all graph operations // between main-thread stable states take effect atomically. auto& outputStreams = OutputStreams(); @@ -247,7 +245,7 @@ DecodedStream::RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph) nsTArray& DecodedStream::OutputStreams() { - GetReentrantMonitor().AssertCurrentThreadIn(); + GetReentrantMonitor().AssertCurrentThreadIn(); return mOutputStreams; } diff --git a/dom/media/DecodedStream.h b/dom/media/DecodedStream.h index 0ccdb29280..b1d7c59d1b 100644 --- a/dom/media/DecodedStream.h +++ b/dom/media/DecodedStream.h @@ -37,19 +37,16 @@ class Image; */ class DecodedStreamData { public: - DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream); + explicit DecodedStreamData(SourceMediaStream* aStream); ~DecodedStreamData(); bool IsFinished() const; - int64_t GetClock() const; + int64_t GetPosition() const; /* The following group of fields are protected by the decoder's monitor * and can be read or written on any thread. */ // Count of audio frames written to the stream int64_t mAudioFramesWritten; - // Saved value of aInitialTime. Timestamp of the first audio and/or - // video packet written. - const int64_t mInitialTime; // microseconds // mNextVideoTime is the end timestamp for the last packet sent to the stream. // Therefore video packets starting at or after this time need to be copied // to the output stream. @@ -92,7 +89,7 @@ public: explicit DecodedStream(ReentrantMonitor& aMonitor); DecodedStreamData* GetData() const; void DestroyData(); - void RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph); + void RecreateData(MediaStreamGraph* aGraph); nsTArray& OutputStreams(); ReentrantMonitor& GetReentrantMonitor() const; void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded); diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index 5052a49c37..1defdfd4f1 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -281,6 +281,10 @@ public: virtual void DisableHardwareAcceleration() {} + // Returns true if this decoder reader uses hardware accelerated video + // decoding. + virtual bool VideoIsHardwareAccelerated() const { return false; } + protected: virtual ~MediaDecoderReader(); diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 75c9bcb739..bd4f3a46c5 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -103,11 +103,10 @@ const int64_t NO_VIDEO_AMPLE_AUDIO_DIVISOR = 8; static const uint32_t LOW_VIDEO_FRAMES = 1; // Threshold in usecs that used to check if we are low on decoded video. -// If the last video frame's end time |mDecodedVideoEndTime| doesn't exceed -// |clock time + LOW_VIDEO_THRESHOLD_USECS*mPlaybackRate| calculation in -// Advanceframe(), we are low on decoded video frames and trying to skip to next -// keyframe. -static const int32_t LOW_VIDEO_THRESHOLD_USECS = 16000; +// If the last video frame's end time |mDecodedVideoEndTime| is more than +// |LOW_VIDEO_THRESHOLD_USECS*mPlaybackRate| after the current clock in +// Advanceframe(), the video decode is lagging, and we skip to next keyframe. +static const int32_t LOW_VIDEO_THRESHOLD_USECS = 60000; // Arbitrary "frame duration" when playing only audio. static const int AUDIO_DURATION_USECS = 40000; @@ -207,6 +206,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mFragmentEndTime(-1), mReader(aReader), mCurrentPosition(mTaskQueue, 0, "MediaDecoderStateMachine::mCurrentPosition (Canonical)"), + mStreamStartTime(-1), mAudioStartTime(-1), mAudioEndTime(-1), mDecodedAudioEndTime(-1), @@ -216,7 +216,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mPlaybackRate(1.0), mLogicalPlaybackRate(mTaskQueue, 1.0, "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"), mPreservesPitch(mTaskQueue, true, "MediaDecoderStateMachine::mPreservesPitch (Mirror)"), - mAmpleVideoFrames(MIN_VIDEO_QUEUE_SIZE), mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS), mAmpleAudioThresholdUsecs(detail::AMPLE_AUDIO_USECS), mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS), @@ -271,6 +270,14 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, // timeEndPeriod() call. timeBeginPeriod(1); #endif + + AudioQueue().AddPopListener( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnAudioPopped), + mTaskQueue); + + VideoQueue().AddPopListener( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnVideoPopped), + mTaskQueue); } MediaDecoderStateMachine::~MediaDecoderStateMachine() @@ -317,7 +324,8 @@ MediaDecoderStateMachine::InitializationTask() mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::UpdateStreamBlockingForPlayState); } -bool MediaDecoderStateMachine::HasFutureAudio() { +bool MediaDecoderStateMachine::HasFutureAudio() +{ MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio"); @@ -332,14 +340,16 @@ bool MediaDecoderStateMachine::HasFutureAudio() { AudioQueue().IsFinished()); } -bool MediaDecoderStateMachine::HaveNextFrameData() { +bool MediaDecoderStateMachine::HaveNextFrameData() +{ MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); return (!HasAudio() || HasFutureAudio()) && (!HasVideo() || VideoQueue().GetSize() > 0); } -int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() { +int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() +{ MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); int64_t audioDecoded = AudioQueue().Duration(); @@ -359,7 +369,7 @@ void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio, // This logic has to mimic AudioSink closely to make sure we write // the exact same silences CheckedInt64 audioWrittenOffset = aStream->mAudioFramesWritten + - UsecsToFrames(mInfo.mAudio.mRate, aStream->mInitialTime + mStartTime); + UsecsToFrames(mInfo.mAudio.mRate, mStreamStartTime); CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime); if (!audioWrittenOffset.isValid() || @@ -434,6 +444,7 @@ void MediaDecoderStateMachine::SendStreamData() MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()"); + MOZ_ASSERT(mStreamStartTime != -1); DecodedStreamData* stream = GetDecodedStream(); @@ -452,7 +463,7 @@ void MediaDecoderStateMachine::SendStreamData() SourceMediaStream::ADDTRACK_QUEUED); stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId, TaskQueue(), GetWakeDecoderRunnable()); - stream->mNextAudioTime = mStartTime + stream->mInitialTime; + stream->mNextAudioTime = mStreamStartTime; } if (mInfo.HasVideo()) { TrackID videoTrackId = mInfo.mVideo.mTrackId; @@ -462,11 +473,7 @@ void MediaDecoderStateMachine::SendStreamData() stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId, TaskQueue(), GetWakeDecoderRunnable()); - // TODO: We can't initialize |mNextVideoTime| until |mStartTime| - // is set. This is a good indication that DecodedStreamData is in - // deep coupling with the state machine and we should move the class - // into MediaDecoderStateMachine. - stream->mNextVideoTime = mStartTime + stream->mInitialTime; + stream->mNextVideoTime = mStreamStartTime; } mediaStream->FinishAddTracks(); stream->mStreamInitialized = true; @@ -502,6 +509,12 @@ void MediaDecoderStateMachine::SendStreamData() endPosition = std::max(endPosition, mediaStream->TicksToTimeRoundDown(mInfo.mAudio.mRate, stream->mAudioFramesWritten)); + + CheckedInt64 playedUsecs = mStreamStartTime + + FramesToUsecs(stream->mAudioFramesWritten, mInfo.mAudio.mRate); + if (playedUsecs.isValid()) { + OnAudioEndTimeUpdate(playedUsecs.value()); + } } if (mInfo.HasVideo()) { @@ -554,7 +567,7 @@ void MediaDecoderStateMachine::SendStreamData() } endPosition = std::max(endPosition, mediaStream->MicrosecondsToStreamTimeRoundDown( - stream->mNextVideoTime - stream->mInitialTime - mStartTime)); + stream->mNextVideoTime - mStreamStartTime)); } if (!stream->mHaveSentFinish) { @@ -575,8 +588,7 @@ void MediaDecoderStateMachine::SendStreamData() // Therefore we only discard those behind the stream clock to throttle // the decoding speed. if (a && a->mTime <= clockTime) { - OnAudioEndTimeUpdate(std::max(mAudioEndTime, a->GetEndTime())); - nsRefPtr releaseMe = PopAudio(); + nsRefPtr releaseMe = AudioQueue().PopFront(); continue; } break; @@ -634,7 +646,7 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedVideo() MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); - if (static_cast(VideoQueue().GetSize()) < mAmpleVideoFrames * mPlaybackRate) { + if (static_cast(VideoQueue().GetSize()) < GetAmpleVideoFrames() * mPlaybackRate) { return false; } @@ -693,7 +705,7 @@ MediaDecoderStateMachine::NeedToSkipToNextKeyframe() return false; } - // We'll skip the video decode to the nearest keyframe if we're low on + // We'll skip the video decode to the next keyframe if we're low on // audio, or if we're low on video, provided we're not running low on // data to decode. If we're running low on downloaded data to decode, // we won't start keyframe skipping, as we'll be pausing playback to buffer @@ -706,9 +718,10 @@ MediaDecoderStateMachine::NeedToSkipToNextKeyframe() (GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate); bool isLowOnDecodedVideo = !mIsVideoPrerolling && - (mDecodedVideoEndTime - GetClock() < - LOW_VIDEO_THRESHOLD_USECS * mPlaybackRate); + ((GetClock() - mDecodedVideoEndTime) * mPlaybackRate > + LOW_VIDEO_THRESHOLD_USECS); bool lowUndecoded = HasLowUndecodedData(); + if ((isLowOnDecodedAudio || isLowOnDecodedVideo) && !lowUndecoded) { DECODER_LOG("Skipping video decode to the next keyframe lowAudio=%d lowVideo=%d lowUndecoded=%d async=%d", isLowOnDecodedAudio, isLowOnDecodedVideo, lowUndecoded, mReader->IsAsync()); @@ -902,22 +915,25 @@ MediaDecoderStateMachine::PushFront(VideoData* aSample) UpdateNextFrameStatus(); } -already_AddRefed -MediaDecoderStateMachine::PopAudio() +void +MediaDecoderStateMachine::OnAudioPopped() { MOZ_ASSERT(OnTaskQueue()); - nsRefPtr sample = AudioQueue().PopFront(); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); UpdateNextFrameStatus(); - return sample.forget(); + DispatchAudioDecodeTaskIfNeeded(); } -already_AddRefed -MediaDecoderStateMachine::PopVideo() +void +MediaDecoderStateMachine::OnVideoPopped() { MOZ_ASSERT(OnTaskQueue()); - nsRefPtr sample = VideoQueue().PopFront(); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); UpdateNextFrameStatus(); - return sample.forget(); + DispatchVideoDecodeTaskIfNeeded(); + // Notify the decode thread that the video queue's buffers may have + // free'd up space for more frames. + mDecoder->GetReentrantMonitor().NotifyAll(); } void @@ -1388,7 +1404,8 @@ void MediaDecoderStateMachine::VolumeChanged() } } -bool MediaDecoderStateMachine::IsRealTime() const { +bool MediaDecoderStateMachine::IsRealTime() const +{ return mRealTime; } @@ -1867,15 +1884,9 @@ MediaDecoderStateMachine::InitiateSeek() mCurrentSeek.mTarget.mTime = seekTime; if (mAudioCaptured) { - // TODO: We should re-create the decoded stream after seek completed as we do - // for audio thread since it is until then we know which position we seek to - // as far as fast-seek is concerned. It also fix the problem where stream - // clock seems to go backwards during seeking. - nsCOMPtr event = - NS_NewRunnableMethodWithArg(this, - &MediaDecoderStateMachine::RecreateDecodedStream, - seekTime - mStartTime); - AbstractThread::MainThread()->Dispatch(event.forget()); + nsCOMPtr r = NS_NewRunnableMethodWithArgs( + this, &MediaDecoderStateMachine::RecreateDecodedStream, nullptr); + AbstractThread::MainThread()->Dispatch(r.forget()); } mDropAudioUntilNextDiscontinuity = HasAudio(); @@ -2217,13 +2228,10 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata) } if (HasVideo()) { - mAmpleVideoFrames = (mReader->IsAsync() && mInfo.mVideo.mIsHardwareAccelerated) - ? std::max(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE) - : std::max(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE); DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d", mReader->IsAsync(), - mInfo.mVideo.mIsHardwareAccelerated, - mAmpleVideoFrames); + mReader->VideoIsHardwareAccelerated(), + GetAmpleVideoFrames()); } mDecoder->StartProgressUpdates(); @@ -2320,17 +2328,6 @@ MediaDecoderStateMachine::DecodeFirstFrame() MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME); DECODER_LOG("DecodeFirstFrame started"); - if (HasAudio()) { - RefPtr decodeTask( - NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded)); - AudioQueue().AddPopListener(decodeTask, TaskQueue()); - } - if (HasVideo()) { - RefPtr decodeTask( - NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded)); - VideoQueue().AddPopListener(decodeTask, TaskQueue()); - } - if (IsRealTime()) { SetStartTime(0); nsresult res = FinishDecodeFirstFrame(); @@ -2466,6 +2463,7 @@ MediaDecoderStateMachine::SeekCompleted() } else { newCurrentTime = video ? video->mTime : seekTime; } + mStreamStartTime = newCurrentTime; mPlayDuration = newCurrentTime - mStartTime; mDecoder->StartProgressUpdates(); @@ -2603,11 +2601,6 @@ nsresult MediaDecoderStateMachine::RunStateMachine() mDelayedScheduler.Reset(); // Must happen on state machine task queue. mDispatchedStateMachine = false; - // If audio is being captured, stop the audio sink if it's running - if (mAudioCaptured) { - StopAudioThread(); - } - MediaResource* resource = mDecoder->GetResource(); NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER); @@ -2791,6 +2784,7 @@ MediaDecoderStateMachine::Reset() mVideoFrameEndTime = -1; mDecodedVideoEndTime = -1; + mStreamStartTime = -1; mAudioStartTime = -1; mAudioEndTime = -1; mDecodedAudioEndTime = -1; @@ -2872,6 +2866,14 @@ MediaDecoderStateMachine::GetAudioClock() const (mAudioSink ? mAudioSink->GetPosition() : 0); } +int64_t MediaDecoderStateMachine::GetStreamClock() const +{ + MOZ_ASSERT(OnTaskQueue()); + AssertCurrentThreadInMonitor(); + MOZ_ASSERT(mStreamStartTime != -1); + return mStreamStartTime + GetDecodedStream()->GetPosition(); +} + int64_t MediaDecoderStateMachine::GetVideoStreamPosition() const { AssertCurrentThreadInMonitor(); @@ -2901,19 +2903,15 @@ int64_t MediaDecoderStateMachine::GetClock() const clock_time = mPlayDuration + mStartTime; } else { if (mAudioCaptured) { - clock_time = mStartTime + GetDecodedStream()->GetClock(); + clock_time = GetStreamClock(); } else if (HasAudio() && !mAudioCompleted) { clock_time = GetAudioClock(); } else { // Audio is disabled on this system. Sync to the system clock. clock_time = GetVideoStreamPosition(); } - // Ensure the clock can never go backwards. - // Note we allow clock going backwards in capture mode during seeking. - NS_ASSERTION(GetMediaTime() <= clock_time || - mPlaybackRate <= 0 || - (mAudioCaptured && mState == DECODER_STATE_SEEKING), - "Clock should go forwards."); + NS_ASSERTION(GetMediaTime() <= clock_time || mPlaybackRate <= 0, + "Clock should go forwards."); } return clock_time; @@ -2958,10 +2956,7 @@ void MediaDecoderStateMachine::AdvanceFrame() currentFrame->mTime, clock_time, ++droppedFrames); } currentFrame = frame; - nsRefPtr releaseMe = PopVideo(); - // Notify the decode thread that the video queue's buffers may have - // free'd up space for more frames. - mDecoder->GetReentrantMonitor().NotifyAll(); + nsRefPtr releaseMe = VideoQueue().PopFront(); OnPlaybackOffsetUpdate(frame->mOffset); if (VideoQueue().GetSize() == 0) break; @@ -3201,6 +3196,7 @@ void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs) // first actual audio frame we have, we'll inject silence during playback // to ensure the audio starts at the correct time. mAudioStartTime = mStartTime; + mStreamStartTime = mStartTime; DECODER_LOG("Set media start time to %lld", mStartTime); } @@ -3302,7 +3298,8 @@ void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() } void -MediaDecoderStateMachine::ScheduleStateMachine() { +MediaDecoderStateMachine::ScheduleStateMachine() +{ AssertCurrentThreadInMonitor(); if (mDispatchedStateMachine) { return; @@ -3350,6 +3347,7 @@ bool MediaDecoderStateMachine::OnTaskQueue() const bool MediaDecoderStateMachine::IsStateMachineScheduled() const { + MOZ_ASSERT(OnTaskQueue()); return mDispatchedStateMachine || mDelayedScheduler.IsScheduled(); } @@ -3473,6 +3471,15 @@ void MediaDecoderStateMachine::DispatchAudioCaptured() MOZ_ASSERT(self->OnTaskQueue()); ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor()); if (!self->mAudioCaptured) { + // Stop the audio sink if it's running. + self->StopAudioThread(); + // GetMediaTime() could return -1 because we haven't decoded + // the 1st frame. But this is OK since we will update mStreamStartTime + // again in SetStartTime(). + self->mStreamStartTime = self->GetMediaTime(); + // Reset mAudioEndTime which will be updated as we send audio data to + // stream. Otherwise it will remain -1 if we don't have audio. + self->mAudioEndTime = -1; self->mAudioCaptured = true; self->ScheduleStateMachine(); } @@ -3488,7 +3495,7 @@ void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream, ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); if (!GetDecodedStream()) { - RecreateDecodedStream(mCurrentPosition.ReadOnWrongThread()); + RecreateDecodedStream(aStream->Graph()); } mDecodedStream.Connect(aStream, aFinishWhenEnded); DispatchAudioCaptured(); @@ -3527,12 +3534,19 @@ void MediaDecoderStateMachine::UpdateStreamBlockingForStateMachinePlaying() } } -void MediaDecoderStateMachine::RecreateDecodedStream(int64_t aInitialTime) +void MediaDecoderStateMachine::RecreateDecodedStream(MediaStreamGraph* aGraph) { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - DECODER_LOG("RecreateDecodedStream aInitialTime=%lld!", aInitialTime); - mDecodedStream.RecreateData(aInitialTime, MediaStreamGraph::GetInstance()); + mDecodedStream.RecreateData(aGraph); +} + +uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const +{ + AssertCurrentThreadInMonitor(); + return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated()) + ? std::max(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE) + : std::max(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE); } } // namespace mozilla diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index bd3fdf7ee2..200190f463 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -176,9 +176,8 @@ private: // Recreates mDecodedStream. Call this to create mDecodedStream at first, // and when seeking, to ensure a new stream is set up with fresh buffers. - // aInitialTime is relative to mStartTime. // Decoder monitor must be held. - void RecreateDecodedStream(int64_t aInitialTime); + void RecreateDecodedStream(MediaStreamGraph* aGraph); void Shutdown(); public: @@ -266,15 +265,15 @@ public: // Must be called with the decode monitor held. bool IsBuffering() const { + MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); - return mState == DECODER_STATE_BUFFERING; } // Must be called with the decode monitor held. bool IsSeeking() const { + MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); - return mState == DECODER_STATE_SEEKING; } @@ -329,6 +328,7 @@ public: // Drop reference to decoder. Only called during shutdown dance. void BreakCycles() { + MOZ_ASSERT(NS_IsMainThread()); if (mReader) { mReader->BreakCycles(); } @@ -388,10 +388,12 @@ public: void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason); void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(OnTaskQueue()); OnNotDecoded(MediaData::AUDIO_DATA, aReason); } void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(OnTaskQueue()); OnNotDecoded(MediaData::VIDEO_DATA, aReason); } @@ -413,12 +415,8 @@ protected: void PushFront(AudioData* aSample); void PushFront(VideoData* aSample); - // Pops MediaData* samples from their respective MediaQueues. - // Note that the audio queue is also drained on the audio thread, - // which we can't easily react to - This should be fixed when we - // remove the audio thread in bug 750596. - already_AddRefed PopAudio(); - already_AddRefed PopVideo(); + void OnAudioPopped(); + void OnVideoPopped(); void VolumeChanged(); void LogicalPlaybackRateChanged(); @@ -482,6 +480,7 @@ protected: bool OutOfDecodedVideo() { + MOZ_ASSERT(OnTaskQueue()); // In buffering mode, we keep the last already-played frame in the queue. int emptyVideoSize = mState == DECODER_STATE_BUFFERING ? 1 : 0; return IsVideoDecoding() && !VideoQueue().IsFinished() && VideoQueue().GetSize() <= emptyVideoSize; @@ -521,6 +520,8 @@ protected: // Called on the state machine thread. int64_t GetAudioClock() const; + int64_t GetStreamClock() const; + // Get the video stream position, taking the |playbackRate| change into // account. This is a position in the media, not the duration of the playback // so far. @@ -774,40 +775,41 @@ public: // Class for managing delayed dispatches of the state machine. class DelayedScheduler { - public: - explicit DelayedScheduler(MediaDecoderStateMachine* aSelf) - : mSelf(aSelf), mMediaTimer(new MediaTimer()) {} + public: + explicit DelayedScheduler(MediaDecoderStateMachine* aSelf) + : mSelf(aSelf), mMediaTimer(new MediaTimer()) {} - bool IsScheduled() const { return !mTarget.IsNull(); } + bool IsScheduled() const { return !mTarget.IsNull(); } - void Reset() - { - MOZ_ASSERT(mSelf->OnTaskQueue(), "Must be on state machine queue to disconnect"); - if (IsScheduled()) { - mRequest.Disconnect(); - mTarget = TimeStamp(); - } - } - - void Ensure(mozilla::TimeStamp& aTarget) - { - MOZ_ASSERT(mSelf->OnTaskQueue()); - if (IsScheduled() && mTarget <= aTarget) { - return; - } - Reset(); - mTarget = aTarget; - mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->Then( - mSelf->TaskQueue(), __func__, mSelf, - &MediaDecoderStateMachine::OnDelayedSchedule, - &MediaDecoderStateMachine::NotReached)); - } - - void CompleteRequest() - { - mRequest.Complete(); + void Reset() + { + MOZ_ASSERT(mSelf->OnTaskQueue(), "Must be on state machine queue to disconnect"); + if (IsScheduled()) { + mRequest.Disconnect(); mTarget = TimeStamp(); } + } + + void Ensure(mozilla::TimeStamp& aTarget) + { + MOZ_ASSERT(mSelf->OnTaskQueue()); + if (IsScheduled() && mTarget <= aTarget) { + return; + } + Reset(); + mTarget = aTarget; + mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->Then( + mSelf->TaskQueue(), __func__, mSelf, + &MediaDecoderStateMachine::OnDelayedSchedule, + &MediaDecoderStateMachine::NotReached)); + } + + void CompleteRequest() + { + MOZ_ASSERT(mSelf->OnTaskQueue()); + mRequest.Complete(); + mTarget = TimeStamp(); + } private: MediaDecoderStateMachine* mSelf; @@ -1110,6 +1112,9 @@ protected: public: AbstractCanonical* CanonicalCurrentPosition() { return &mCurrentPosition; } protected: + // The presentation time of the first audio/video frame that is sent to the + // media stream. + int64_t mStreamStartTime; // The presentation time of the first audio frame that was played in // microseconds. We can add this to the audio stream position to determine @@ -1156,10 +1161,11 @@ protected: uint32_t mBufferingWait; int64_t mLowDataThresholdUsecs; - // If we've got more than mAmpleVideoFrames decoded video frames waiting in + // If we've got more than this number of decoded video frames waiting in // the video queue, we will not decode any more video frames until some have // been consumed by the play state machine thread. - uint32_t mAmpleVideoFrames; + // Must hold monitor. + uint32_t GetAmpleVideoFrames() const; // Low audio threshold. If we've decoded less than this much audio we // consider our audio decode "behind", and we may skip video decoding @@ -1190,6 +1196,7 @@ protected: // samples we must consume before are considered to be finished prerolling. uint32_t AudioPrerollUsecs() const { + MOZ_ASSERT(OnTaskQueue()); if (IsRealTime()) { return 0; } @@ -1200,7 +1207,8 @@ protected: } uint32_t VideoPrerollFrames() const { - return IsRealTime() ? 0 : mAmpleVideoFrames / 2; + MOZ_ASSERT(OnTaskQueue()); + return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2; } bool DonePrerollingAudio() @@ -1220,6 +1228,7 @@ protected: void StopPrerollingAudio() { + MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); if (mIsAudioPrerolling) { mIsAudioPrerolling = false; @@ -1229,6 +1238,7 @@ protected: void StopPrerollingVideo() { + MOZ_ASSERT(OnTaskQueue()); AssertCurrentThreadInMonitor(); if (mIsVideoPrerolling) { mIsVideoPrerolling = false; @@ -1260,6 +1270,7 @@ protected: MediaPromiseRequestHolder mAudioWaitRequest; const char* AudioRequestStatus() { + MOZ_ASSERT(OnTaskQueue()); if (mAudioDataRequest.Exists()) { MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); return "pending"; @@ -1273,6 +1284,7 @@ protected: MediaPromiseRequestHolder mVideoDataRequest; const char* VideoRequestStatus() { + MOZ_ASSERT(OnTaskQueue()); if (mVideoDataRequest.Exists()) { MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); return "pending"; @@ -1284,6 +1296,7 @@ protected: MediaPromiseRequestHolder& WaitRequestRef(MediaData::Type aType) { + MOZ_ASSERT(OnTaskQueue()); return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest; } diff --git a/dom/media/MediaInfo.h b/dom/media/MediaInfo.h index 4c21aabacd..db2b2ed67e 100644 --- a/dom/media/MediaInfo.h +++ b/dom/media/MediaInfo.h @@ -66,7 +66,6 @@ public: mLanguage = aLanguage; mEnabled = aEnabled; mTrackId = aTrackId; - mType = aType; } // Fields common with MediaTrack object. @@ -242,8 +241,6 @@ public: // Describing how many degrees video frames should be rotated in clock-wise to // get correct view. Rotation mRotation; - - bool mIsHardwareAccelerated; }; class AudioInfo : public TrackInfo { diff --git a/dom/media/fmp4/MP4Reader.cpp b/dom/media/fmp4/MP4Reader.cpp index bde20eaad8..5a72908e46 100644 --- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -1100,4 +1100,10 @@ MP4Reader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOff } } +bool +MP4Reader::VideoIsHardwareAccelerated() const +{ + return mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(); +} + } // namespace mozilla diff --git a/dom/media/fmp4/MP4Reader.h b/dom/media/fmp4/MP4Reader.h index 8ec65f8871..c58ae055d9 100644 --- a/dom/media/fmp4/MP4Reader.h +++ b/dom/media/fmp4/MP4Reader.h @@ -83,6 +83,8 @@ public: static bool IsVideoAccelerated(layers::LayersBackend aBackend); + virtual bool VideoIsHardwareAccelerated() const override; + private: bool InitDemuxer(); diff --git a/dom/media/mediasource/MediaSourceReader.h b/dom/media/mediasource/MediaSourceReader.h index bb7ce6b6eb..df3d136e02 100644 --- a/dom/media/mediasource/MediaSourceReader.h +++ b/dom/media/mediasource/MediaSourceReader.h @@ -146,10 +146,17 @@ public: void SetMediaSourceDuration(double aDuration /* seconds */); virtual bool IsAsync() const override { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); return (!GetAudioReader() || GetAudioReader()->IsAsync()) && (!GetVideoReader() || GetVideoReader()->IsAsync()); } + + virtual bool VideoIsHardwareAccelerated() const override { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + return GetVideoReader() && GetVideoReader()->VideoIsHardwareAccelerated(); + } + // Returns true if aReader is a currently active audio or video bool IsActiveReader(MediaDecoderReader* aReader); diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 9d5dd342d1..2da36181d0 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -1506,8 +1506,8 @@ nsPresContext::SetFullZoom(float aZoom) float oldHeightDevPixels = oldHeightAppUnits / float(mCurAppUnitsPerDevPixel); mDeviceContext->SetFullZoom(aZoom); - NS_ASSERTION(!mSupressResizeReflow, "two zooms happening at the same time? impossible!"); - mSupressResizeReflow = true; + NS_ASSERTION(!mSuppressResizeReflow, "two zooms happening at the same time? impossible!"); + mSuppressResizeReflow = true; mFullZoom = aZoom; mShell->GetViewManager()-> @@ -1516,7 +1516,7 @@ nsPresContext::SetFullZoom(float aZoom) AppUnitsPerDevPixelChanged(); - mSupressResizeReflow = false; + mSuppressResizeReflow = false; } gfxSize @@ -2137,11 +2137,14 @@ nsPresContext::FlushUserFontSet() return; } - if (!mFontFaceSet) { + bool changed = false; + + if (!mFontFaceSet && !rules.IsEmpty()) { mFontFaceSet = new FontFaceSet(mDocument->GetInnerWindow(), this); } - mFontFaceSet->EnsureUserFontSet(this); - bool changed = mFontFaceSet->UpdateRules(rules); + if (mFontFaceSet) { + changed = mFontFaceSet->UpdateRules(rules); + } // We need to enqueue a style change reflow (for later) to // reflect that we're modifying @font-face rules. (However, @@ -2224,7 +2227,10 @@ nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont) // it contains that specific font (i.e. the one chosen within the family // given the weight, width, and slant from the nsStyleFont). If it does, // mark that frame dirty and skip inspecting its descendants. - nsFontFaceUtils::MarkDirtyForFontChange(mShell->GetRootFrame(), aUpdatedFont); + nsIFrame* root = mShell->GetRootFrame(); + if (root) { + nsFontFaceUtils::MarkDirtyForFontChange(root, aUpdatedFont); + } } FontFaceSet* diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index e60a3db30a..cc4841d0eb 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -876,7 +876,7 @@ public: // as that value may be out of date when mPaintFlashingInitialized is false. bool GetPaintFlashing() const; - bool SupressingResizeReflow() const { return mSupressResizeReflow; } + bool SuppressingResizeReflow() const { return mSuppressResizeReflow; } virtual gfxUserFontSet* GetUserFontSetExternal(); gfxUserFontSet* GetUserFontSetInternal(); @@ -1380,7 +1380,7 @@ protected: // resize reflow is suppressed when the only change has been to zoom // the document rather than to change the document's dimensions - unsigned mSupressResizeReflow : 1; + unsigned mSuppressResizeReflow : 1; unsigned mIsVisual : 1; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 4bc965e033..381b7f649b 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -1802,7 +1802,7 @@ PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight) // Take this ref after viewManager so it'll make sure to go away first. nsCOMPtr kungFuDeathGrip(this); - if (!GetPresContext()->SupressingResizeReflow()) { + if (!GetPresContext()->SuppressingResizeReflow()) { // Have to make sure that the content notifications are flushed before we // start messing with the frame model; otherwise we can get content doubling. mDocument->FlushPendingNotifications(Flush_ContentAndNotify); diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp index 50fc229fda..eb543066fa 100644 --- a/layout/style/FontFaceSet.cpp +++ b/layout/style/FontFaceSet.cpp @@ -82,10 +82,9 @@ FontFaceSet::FontFaceSet(nsPIDOMWindow* aWindow, nsPresContext* aPresContext) , mDocument(aPresContext->Document()) , mStatus(FontFaceSetLoadStatus::Loaded) , mNonRuleFacesDirty(false) - , mReadyIsResolved(true) - , mDispatchedLoadingEvent(false) , mHasLoadingFontFaces(false) , mHasLoadingFontFacesIsDirty(false) + , mDelayedLoadCheck(false) { MOZ_COUNT_CTOR(FontFaceSet); @@ -109,6 +108,8 @@ FontFaceSet::FontFaceSet(nsPIDOMWindow* aWindow, nsPresContext* aPresContext) } mDocument->CSSLoader()->AddObserver(this); + + mUserFontSet = new UserFontSet(this); } FontFaceSet::~FontFaceSet() @@ -148,16 +149,6 @@ FontFaceSet::RemoveDOMContentLoadedListener() } } -FontFaceSet::UserFontSet* -FontFaceSet::EnsureUserFontSet(nsPresContext* aPresContext) -{ - if (!mUserFontSet) { - mUserFontSet = new UserFontSet(this); - mPresContext = aPresContext; - } - return mUserFontSet; -} - already_AddRefed FontFaceSet::Load(const nsAString& aFont, const nsAString& aText, @@ -1379,7 +1370,19 @@ FontFaceSet::OnFontFaceStatusChanged(FontFace* aFontFace) } else { MOZ_ASSERT(aFontFace->Status() == FontFaceLoadStatus::Loaded || aFontFace->Status() == FontFaceLoadStatus::Error); - CheckLoadingFinished(); + // When a font finishes downloading, nsPresContext::UserFontSetUpdated + // will be called immediately afterwards to request a reflow of the + // relevant elements in the document. We want to wait until the reflow + // request has been done before the FontFaceSet is marked as Loaded so + // that we don't briefly set the FontFaceSet to Loaded and then Loading + // again once the reflow is pending. So we go around the event loop + // and call CheckLoadingFinished() after the reflow has been queued. + if (!mDelayedLoadCheck) { + mDelayedLoadCheck = true; + nsCOMPtr checkTask = + NS_NewRunnableMethod(this, &FontFaceSet::CheckLoadingFinishedAfterDelay); + NS_DispatchToMainThread(checkTask); + } } } @@ -1389,17 +1392,31 @@ FontFaceSet::DidRefresh() CheckLoadingFinished(); } +void +FontFaceSet::CheckLoadingFinishedAfterDelay() +{ + mDelayedLoadCheck = false; + CheckLoadingFinished(); +} + void FontFaceSet::CheckLoadingStarted() { - if (HasLoadingFontFaces() && !mDispatchedLoadingEvent) { - mStatus = FontFaceSetLoadStatus::Loading; - mDispatchedLoadingEvent = true; - (new AsyncEventDispatcher(this, NS_LITERAL_STRING("loading"), - false))->PostDOMEvent(); + if (!HasLoadingFontFaces()) { + return; } - if (mReadyIsResolved && PrefEnabled()) { + if (mStatus == FontFaceSetLoadStatus::Loading) { + // We have already dispatched a loading event and replaced mReady + // with a fresh, unresolved promise. + return; + } + + mStatus = FontFaceSetLoadStatus::Loading; + (new AsyncEventDispatcher(this, NS_LITERAL_STRING("loading"), + false))->PostDOMEvent(); + + if (PrefEnabled()) { nsRefPtr ready; if (GetParentObject()) { ErrorResult rv; @@ -1408,7 +1425,6 @@ FontFaceSet::CheckLoadingStarted() if (ready) { mReady.swap(ready); - mReadyIsResolved = false; } } } @@ -1475,7 +1491,12 @@ FontFaceSet::MightHavePendingFontLoads() void FontFaceSet::CheckLoadingFinished() { - if (mReadyIsResolved) { + if (mDelayedLoadCheck) { + // Wait until the runnable posted in OnFontFaceStatusChanged calls us. + return; + } + + if (mStatus == FontFaceSetLoadStatus::Loaded) { // We've already resolved mReady and dispatched the loadingdone/loadingerror // events. return; @@ -1487,10 +1508,8 @@ FontFaceSet::CheckLoadingFinished() } mStatus = FontFaceSetLoadStatus::Loaded; - mDispatchedLoadingEvent = false; if (mReady) { mReady->MaybeResolve(this); - mReadyIsResolved = true; } // Now dispatch the loadingdone/loadingerror events. diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index fad3b7a27c..5aaa9fbcc0 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -97,7 +97,6 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - UserFontSet* EnsureUserFontSet(nsPresContext* aPresContext); UserFontSet* GetUserFontSet() { return mUserFontSet; } // Called when this font set is no longer associated with a presentation. @@ -227,6 +226,12 @@ private: */ void CheckLoadingFinished(); + /** + * Callback for invoking CheckLoadingFinished after going through the + * event loop. See OnFontFaceStatusChanged. + */ + void CheckLoadingFinishedAfterDelay(); + /** * Dispatches a CSSFontFaceLoadEvent to this object. */ @@ -324,13 +329,6 @@ private: // Whether mNonRuleFaces has changed since last time UpdateRules ran. bool mNonRuleFacesDirty; - // Whether we have called MaybeResolve() on mReady. - bool mReadyIsResolved; - - // Whether we have already dispatched loading events for the current set - // of loading FontFaces. - bool mDispatchedLoadingEvent; - // Whether any FontFace objects in mRuleFaces or mNonRuleFaces are // loading. Only valid when mHasLoadingFontFacesIsDirty is false. Don't use // this variable directly; call the HasLoadingFontFaces method instead. @@ -338,6 +336,10 @@ private: // This variable is only valid when mLoadingDirty is false. bool mHasLoadingFontFacesIsDirty; + + // Whether CheckLoadingFinished calls should be ignored. See comment in + // OnFontFaceStatusChanged. + bool mDelayedLoadCheck; }; } // namespace dom diff --git a/layout/style/test/test_font_loading_api.html b/layout/style/test/test_font_loading_api.html index 57d22f6ce4..eef5f5f1d0 100644 --- a/layout/style/test/test_font_loading_api.html +++ b/layout/style/test/test_font_loading_api.html @@ -615,7 +615,8 @@ function runTest() { }).then(function() { // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched - // when a FontFace with status "error" is added to the FontFaceSet. + // when a FontFace that eventually becomes status "error" is added to the + // FontFaceSet. var face; var awaitEvents = new Promise(function(aResolve, aReject) { @@ -657,14 +658,16 @@ function runTest() { }; }); - face = new FontFace("test", new ArrayBuffer(0)); + face = new FontFace("test", "url(x)"); + face.load(); + is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29)"); + document.fonts.add(face); return face.loaded .then(function() { ok(false, "the FontFace should not load (TEST 29)"); }, function(aError) { is(face.status, "error", "FontFace should have status \"error\" (TEST 29)"); - document.fonts.add(face); return awaitEvents; }) .then(function() { @@ -675,7 +678,7 @@ function runTest() { }).then(function() { // (TEST 30) Test that a loadingdone event is dispatched when a FontFace - // with status "loaded" is added to the FontFaceSet. + // that eventually becomes status "loaded" is added to the FontFaceSet. var face; var awaitEvents = new Promise(function(aResolve, aReject) { @@ -701,12 +704,14 @@ function runTest() { }; }); - face = new FontFace("test", fontData); + face = new FontFace("test", "url(BitPattern.woff)"); + face.load(); + is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30)"); + document.fonts.add(face); return face.loaded .then(function() { is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30)"); - document.fonts.add(face); return awaitEvents; }) .then(function() { @@ -724,7 +729,7 @@ function runTest() { var onloadingdoneTriggered = false, loadingdoneDispatched = false; function check() { - if (loadingdoneDispatched && loadingdoneDispatched) { + if (onloadingdoneTriggered && loadingdoneDispatched) { document.fonts.onloadingdone = null; document.fonts.removeEventListener("loadingdone", doneListener); aResolve(); @@ -744,17 +749,19 @@ function runTest() { }); face = new FontFace("test", "url(BitPattern.woff)"); + is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31)"); + document.fonts.add(face); return face.load() .then(function() { - is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31)"); - document.fonts.add(face); return awaitEvents; }) .then(function() { + is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31)"); document.fonts.clear(); return document.fonts.ready; }); + }).then(function() { // (TEST 32) Test that pending restyles prevent document.fonts.status @@ -864,6 +871,229 @@ function runTest() { // as the neverending style sheet load will interfere with subsequent // sub-tests. + }).then(function() { + + // (TEST 35) Test that no loadingdone event is dispatched when a FontFace + // with "loaded" status is added to a "loaded" FontFaceSet. + var gotLoadingDone = false; + document.fonts.onloadingdone = function(aEvent) { + gotLoadingDone = true; + }; + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35)"); + var face = new FontFace("test", fontData); + + return face.loaded + .then(function() { + is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35)"); + document.fonts.add(face); + is(document.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35)"); + return document.fonts.ready; + }) + .then(function() { + ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35)"); + document.fonts.onloadingdone = null; + document.fonts.clear(); + }); + + }).then(function() { + + // (TEST 36) Test that no loadingdone or loadingerror event is dispatched + // when a FontFace with "error" status is added to a "loaded" FontFaceSet. + var gotLoadingDone = false, gotLoadingError = false; + document.fonts.onloadingdone = function(aEvent) { + gotLoadingDone = true; + }; + document.fonts.onloadingerror = function(aEvent) { + gotLoadingError = true; + }; + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36)"); + var face = new FontFace("test", new ArrayBuffer(0)); + + return face.loaded + .then(function() { + ok(false, "FontFace should not have loaded (TEST 36)"); + }, function() { + is(face.status, "error", "FontFace should have status \"error\" (TEST 36)"); + document.fonts.add(face); + is(document.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36)"); + return document.fonts.ready; + }) + .then(function() { + ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36)"); + ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36)"); + document.fonts.onloadingdone = null; + document.fonts.onloadingerror = null; + document.fonts.clear(); + }); + + }).then(function() { + + // (TEST 37) Test that a FontFace only has one loadingdone event dispatched + // at the FontFaceSet containing it. + + var events = [], face, face2; + + document.fonts.onloadingdone = function(e) { + events.push(e); + }; + document.fonts.onloadingerror = function(e) { + events.push(e); + }; + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37)"); + + face = new FontFace("test", "url(BitPattern.woff)"); + face.load(); + document.fonts.add(face); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37)"); + + return document.fonts.ready + .then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37)"); + is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37)"); + + face2 = new FontFace("test2", "url(BitPattern.woff)"); + face2.load(); + document.fonts.add(face2); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37)"); + + return document.fonts.ready; + }).then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37)"); + is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37)"); + + is(events.length, 2, "should receive two events (TEST 37)"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37)"); + is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37)"); + is(events[0].fontfaces[0], face, "first event should have the first FontFace"); + + is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37)"); + is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37)"); + is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37)"); + + document.fonts.onloadingdone = null; + document.fonts.onloadingerror = null; + document.fonts.clear(); + return document.fonts.ready; + }); + + }).then(function() { + + // (TEST 38) Test that a FontFace only has one loadingerror event dispatched + // at the FontFaceSet containing it. + + var events = [], face, face2; + + document.fonts.onloadingdone = function(e) { + events.push(e); + }; + document.fonts.onloadingerror = function(e) { + events.push(e); + }; + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38)"); + + face = new FontFace("test", "url(x)"); + face.load(); + document.fonts.add(face); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38)"); + + return document.fonts.ready + .then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38)"); + is(face.status, "error", "first FontFace should have status \"error\" (TEST 38)"); + + face2 = new FontFace("test2", "url(x)"); + face2.load(); + document.fonts.add(face2); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38)"); + + return document.fonts.ready; + }).then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38)"); + is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38)"); + + is(events.length, 4, "should receive four events (TEST 38)"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38)"); + is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38)"); + + is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38)"); + is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38)"); + is(events[1].fontfaces[0], face, "second event should have the first FontFace"); + + is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38)"); + is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38)"); + + is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38)"); + is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38)"); + is(events[3].fontfaces[0], face2, "third event should have the second FontFace"); + + document.fonts.onloadingdone = null; + document.fonts.onloadingerror = null; + document.fonts.clear(); + return document.fonts.ready; + }); + + }).then(function() { + + // (TEST 39) Test that a FontFace for an @font-face rule only has one + // loadingdone event dispatched at the FontFaceSet containing it. + + var style = document.querySelector("style"); + var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff); } " + + "@font-face { font-family: test2; src: url(BitPattern.woff?2); }"; + + style.textContent = ruleText; + + var all = Array.from(document.fonts); + var events = []; + + document.fonts.onloadingdone = function(e) { + events.push(e); + }; + document.fonts.onloadingerror = function(e) { + events.push(e); + }; + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)"); + + all[0].load(); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)"); + + return document.fonts.ready + .then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)"); + is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)"); + is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)"); + + all[1].load(); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)"); + + return document.fonts.ready; + }).then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)"); + is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)"); + + is(events.length, 2, "should receive two events (TEST 39)"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)"); + is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)"); + is(events[0].fontfaces[0], all[0], "first event should have the first FontFace"); + + is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)"); + is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)"); + is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)"); + + document.fonts.onloadingdone = null; + document.fonts.onloadingerror = null; + document.fonts.clear(); + return document.fonts.ready; + }); + }).then(function() { // End of the tests. diff --git a/mfbt/nsRefPtr.h b/mfbt/nsRefPtr.h index 06c2a01623..dd3cf047c4 100644 --- a/mfbt/nsRefPtr.h +++ b/mfbt/nsRefPtr.h @@ -100,21 +100,21 @@ public: } template - nsRefPtr(already_AddRefed& aSmartPtr) + MOZ_IMPLICIT nsRefPtr(already_AddRefed& aSmartPtr) : mRawPtr(aSmartPtr.take()) // construct from |already_AddRefed| { } template - nsRefPtr(already_AddRefed&& aSmartPtr) + MOZ_IMPLICIT nsRefPtr(already_AddRefed&& aSmartPtr) : mRawPtr(aSmartPtr.take()) // construct from |otherRefPtr.forget()| { } template - nsRefPtr(nsRefPtr&& aSmartPtr) + MOZ_IMPLICIT nsRefPtr(nsRefPtr&& aSmartPtr) : mRawPtr(aSmartPtr.forget().take()) // construct from |Move(nsRefPtr)|. { diff --git a/xpcom/base/nsAutoPtr.h b/xpcom/base/nsAutoPtr.h index 96f0add59a..cd6956304e 100644 --- a/xpcom/base/nsAutoPtr.h +++ b/xpcom/base/nsAutoPtr.h @@ -96,12 +96,26 @@ public: { } + template + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + nsAutoPtr(nsAutoPtr&& aSmartPtr) : mRawPtr(aSmartPtr.forget()) // Construct by transferring ownership from another smart pointer. { } + template + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr&& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + // Assignment operators nsAutoPtr& @@ -119,12 +133,27 @@ public: return *this; } + template + nsAutoPtr& operator=(nsAutoPtr& aRhs) + // assign by transferring ownership from another smart pointer. + { + assign(aRhs.forget()); + return *this; + } + nsAutoPtr& operator=(nsAutoPtr&& aRhs) { assign(aRhs.forget()); return *this; } + template + nsAutoPtr& operator=(nsAutoPtr&& aRhs) + { + assign(aRhs.forget()); + return *this; + } + // Other pointer operators T* diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h index ec1c55d924..268d47efa4 100644 --- a/xpcom/glue/nsTArray.h +++ b/xpcom/glue/nsTArray.h @@ -2243,7 +2243,7 @@ protected: } template - nsAutoArrayBase(nsTArray_Impl&& aOther) + explicit nsAutoArrayBase(nsTArray_Impl&& aOther) { Init(); this->SwapElements(aOther); diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h index fe08228713..f39623fbe5 100644 --- a/xpcom/glue/nsThreadUtils.h +++ b/xpcom/glue/nsThreadUtils.h @@ -363,7 +363,7 @@ struct StoreCopyPassByValue typedef T passed_type; stored_type m; template - StoreCopyPassByValue(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByValue(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return m; } }; template @@ -377,7 +377,7 @@ struct StoreCopyPassByConstLRef typedef const T& passed_type; stored_type m; template - StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return m; } }; template @@ -391,7 +391,7 @@ struct StoreCopyPassByLRef typedef T& passed_type; stored_type m; template - StoreCopyPassByLRef(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByLRef(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return m; } }; template @@ -405,7 +405,7 @@ struct StoreCopyPassByRRef typedef T&& passed_type; stored_type m; template - StoreCopyPassByRRef(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByRRef(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return mozilla::Move(m); } }; template @@ -419,7 +419,7 @@ struct StoreRefPassByLRef typedef T& passed_type; stored_type m; template - StoreRefPassByLRef(A& a) : m(a) {} + explicit StoreRefPassByLRef(A& a) : m(a) {} passed_type PassAsParameter() { return m; } }; template @@ -433,7 +433,7 @@ struct StoreConstRefPassByConstLRef typedef const T& passed_type; stored_type m; template - StoreConstRefPassByConstLRef(const A& a) : m(a) {} + explicit StoreConstRefPassByConstLRef(const A& a) : m(a) {} passed_type PassAsParameter() { return m; } }; template @@ -447,7 +447,7 @@ struct StorensRefPtrPassByPtr typedef T* passed_type; stored_type m; template - StorensRefPtrPassByPtr(A a) : m(a) {} + explicit StorensRefPtrPassByPtr(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return m.get(); } }; template @@ -461,7 +461,7 @@ struct StorePtrPassByPtr typedef T* passed_type; stored_type m; template - StorePtrPassByPtr(A a) : m(a) {} + explicit StorePtrPassByPtr(A a) : m(a) {} passed_type PassAsParameter() { return m; } }; template @@ -475,7 +475,7 @@ struct StoreConstPtrPassByConstPtr typedef const T* passed_type; stored_type m; template - StoreConstPtrPassByConstPtr(A a) : m(a) {} + explicit StoreConstPtrPassByConstPtr(A a) : m(a) {} passed_type PassAsParameter() { return m; } }; template @@ -489,7 +489,7 @@ struct StoreCopyPassByConstPtr typedef const T* passed_type; stored_type m; template - StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return &m; } }; template @@ -503,7 +503,7 @@ struct StoreCopyPassByPtr typedef T* passed_type; stored_type m; template - StoreCopyPassByPtr(A&& a) : m(mozilla::Forward(a)) {} + explicit StoreCopyPassByPtr(A&& a) : m(mozilla::Forward(a)) {} passed_type PassAsParameter() { return &m; } }; template @@ -535,6 +535,36 @@ template struct HasRefCountMethods : decltype(HasRefCountMethodsTest(0)) {}; +template +struct IsRefcountedSmartPointer : public mozilla::FalseType +{}; + +template +struct IsRefcountedSmartPointer> : public mozilla::TrueType +{}; + +template +struct IsRefcountedSmartPointer> : public mozilla::TrueType +{}; + +template +struct StripSmartPointer +{ + typedef void Type; +}; + +template +struct StripSmartPointer> +{ + typedef T Type; +}; + +template +struct StripSmartPointer> +{ + typedef T Type; +}; + template struct PointerStorageClass : mozilla::Conditional::value, @@ -552,12 +582,20 @@ struct LValueReferenceStorageClass StoreRefPassByLRef> {}; +template +struct SmartPointerStorageClass + : mozilla::Conditional::value, + StorensRefPtrPassByPtr< + typename StripSmartPointer::Type>, + StoreCopyPassByValue> +{}; + template struct NonLValueReferenceStorageClass : mozilla::Conditional::value, StoreCopyPassByRRef< typename mozilla::RemoveReference::Type>, - StoreCopyPassByValue> + typename SmartPointerStorageClass::Type> {}; template @@ -587,6 +625,8 @@ struct NonParameterStorageClass // - const T& -> StoreConstRefPassByConstLRef: Store const T&, pass const T&. // - T& -> StoreRefPassByLRef : Store T&, pass T&. // - T&& -> StoreCopyPassByRRef : Store T, pass Move(T). +// - nsRefPtr, nsCOMPtr +// -> StorensRefPtrPassByPtr : Store nsRefPtr, pass T* // - Other T -> StoreCopyPassByValue : Store T, pass T. // Other available explicit options: // - StoreCopyPassByConstLRef : Store T, pass const T&. @@ -622,7 +662,7 @@ struct nsRunnableMethodArguments { typename ::detail::ParameterStorage::Type m0; template - nsRunnableMethodArguments(A0&& a0) + explicit nsRunnableMethodArguments(A0&& a0) : m0(mozilla::Forward(a0)) {} template void apply(C* o, M m) diff --git a/xpcom/string/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h index 2ad27ffc41..fa75ba8293 100644 --- a/xpcom/string/nsTLiteralString.h +++ b/xpcom/string/nsTLiteralString.h @@ -28,7 +28,7 @@ public: */ template - nsTLiteralString_CharT(const char_type (&aStr)[N]) + explicit nsTLiteralString_CharT(const char_type (&aStr)[N]) : string_type(const_cast(aStr), N - 1, F_TERMINATED | F_LITERAL) { }