import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1128380: Make AmpleVideoFrames calculation dynamic. r=cpearce (e1e11d289)
- Bug 1161901 - Sprinkle more assertions and fix some formatting. r=jww (1a008614e)
- Bug 1152218 - Have a larger tolerance to decide if video data is low or not. r=cpearce. (72dc49d9b)
- indent fix (b6fe75862)
- Bug 1161901 - Relax assertion. r=jww (f20d88804)
- Bug 1155500: Have nsAutoPtr transfers ownership when copying related objects. r=froydnj (eddd7e87b)
- Bug 1188203 - Fix more constructors in XPCOM; r=froydnj (51eb10519)
- Bug 1179787 - part 1 - forward StorensRefPtrPassByPtr's constructor argument; r=botond (9af32af89)
- Bug 1179787 - part 2 - add template logic for smart pointer template arguments in NS_NewRunnableMethod*; r=botond (380172704)
- fix indentation (c4b2356bd)
- Bug 1162381. Part 2 - refactor DecodedStreamData::mInitialTime to be consistent with AudioSink when computing stream position. r=roc. (8574a32a1)
- Bug 1172778 - Update readyState when audio samples are popped by AudioSink. r=cpearce. (3f64e7151)
- Bug 1172392 - Align update of audio end time of decoded stream with that of AudioSink. r=roc. (01e7ba014)
- Bug 1152023 - Null check root frame when marking frames dirty in response to a user font load. r=jdaggett (e6ca132fd)
- Bug 1162366 - Fix spelling of nsPresContext::mSupressResizeReflow and associated methods. r=dholbert (35d4ce30c)
- Bug 1154148 - Create a FontFaceSet's UserFontSet eagerly. r=jdaggett (29d2b2b57)
- Bug 1154150 - Only create a FontFaceSet if we find @font-face rules. r=jdaggett (cf4b6979a)
- Bug 1144450 - Part 1: Fix a typo in test_font_loading_api.html. r=jdaggett (8117e267b)
- Bug 1144450 - Part 2: Don't replace a FontFaceSet's ready promise when there are no loading FontFaces. r=jdaggett (196fbeb2d)
- Bug 1144450 - Part 3: Replace uses of mDispatchedLoadingEvent with mStatus, as they're equivalent. r=jdaggett (bbf1e2324)
- Bug 1144450 - Part 4: Replace uses of mReadyIsResolved with mStatus, as they're equivalent. r=jdaggett (ddb5dbce4)
- Bug 1144450 - Part 5: Fix/add tests for not firing loadingdone/loadingerror when adding an already-loaded/error FontFace to a FontFaceSet. r=jdaggett (821bea9fc)
- Bug 1144977 - Part 2: Tests. r=jdaggett (14bcd5b00)
- Bug 1145937 - Don't set FontFaceSet status to Loaded between a font download completion and the document reflow. r=jdaggett (444fb88d1)
This commit is contained in:
2021-03-29 10:34:24 +08:00
parent 76ed670e87
commit c7618a1247
20 changed files with 574 additions and 210 deletions
+10 -12
View File
@@ -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<OutputStreamData>&
DecodedStream::OutputStreams()
{
GetReentrantMonitor().AssertCurrentThreadIn();
GetReentrantMonitor().AssertCurrentThreadIn();
return mOutputStreams;
}
+3 -6
View File
@@ -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<OutputStreamData>& OutputStreams();
ReentrantMonitor& GetReentrantMonitor() const;
void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
+4
View File
@@ -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();
+92 -78
View File
@@ -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<AudioData> releaseMe = PopAudio();
nsRefPtr<AudioData> releaseMe = AudioQueue().PopFront();
continue;
}
break;
@@ -634,7 +646,7 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
MOZ_ASSERT(OnTaskQueue());
AssertCurrentThreadInMonitor();
if (static_cast<uint32_t>(VideoQueue().GetSize()) < mAmpleVideoFrames * mPlaybackRate) {
if (static_cast<uint32_t>(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<AudioData>
MediaDecoderStateMachine::PopAudio()
void
MediaDecoderStateMachine::OnAudioPopped()
{
MOZ_ASSERT(OnTaskQueue());
nsRefPtr<AudioData> sample = AudioQueue().PopFront();
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
UpdateNextFrameStatus();
return sample.forget();
DispatchAudioDecodeTaskIfNeeded();
}
already_AddRefed<VideoData>
MediaDecoderStateMachine::PopVideo()
void
MediaDecoderStateMachine::OnVideoPopped()
{
MOZ_ASSERT(OnTaskQueue());
nsRefPtr<VideoData> 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<nsIRunnable> event =
NS_NewRunnableMethodWithArg<int64_t>(this,
&MediaDecoderStateMachine::RecreateDecodedStream,
seekTime - mStartTime);
AbstractThread::MainThread()->Dispatch(event.forget());
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArgs<MediaStreamGraph*>(
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<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
: std::max<uint32_t>(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<nsIRunnable> decodeTask(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
AudioQueue().AddPopListener(decodeTask, TaskQueue());
}
if (HasVideo()) {
RefPtr<nsIRunnable> 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<VideoData> 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<VideoData> 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<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
: std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
}
} // namespace mozilla
+56 -43
View File
@@ -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<AudioData> PopAudio();
already_AddRefed<VideoData> 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<int64_t>* 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<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest;
const char* AudioRequestStatus()
{
MOZ_ASSERT(OnTaskQueue());
if (mAudioDataRequest.Exists()) {
MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists());
return "pending";
@@ -1273,6 +1284,7 @@ protected:
MediaPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mVideoDataRequest;
const char* VideoRequestStatus()
{
MOZ_ASSERT(OnTaskQueue());
if (mVideoDataRequest.Exists()) {
MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists());
return "pending";
@@ -1284,6 +1296,7 @@ protected:
MediaPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>& WaitRequestRef(MediaData::Type aType)
{
MOZ_ASSERT(OnTaskQueue());
return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest;
}
-3
View File
@@ -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 {
+6
View File
@@ -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
+2
View File
@@ -83,6 +83,8 @@ public:
static bool IsVideoAccelerated(layers::LayersBackend aBackend);
virtual bool VideoIsHardwareAccelerated() const override;
private:
bool InitDemuxer();
@@ -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);
+13 -7
View File
@@ -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*
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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<nsIPresShell> 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);
+42 -23
View File
@@ -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<Promise>
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<nsIRunnable> 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<Promise> 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.
+10 -8
View File
@@ -97,7 +97,6 @@ public:
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> 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
+239 -9
View File
@@ -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.
+3 -3
View File
@@ -100,21 +100,21 @@ public:
}
template <typename I>
nsRefPtr(already_AddRefed<I>& aSmartPtr)
MOZ_IMPLICIT nsRefPtr(already_AddRefed<I>& aSmartPtr)
: mRawPtr(aSmartPtr.take())
// construct from |already_AddRefed|
{
}
template <typename I>
nsRefPtr(already_AddRefed<I>&& aSmartPtr)
MOZ_IMPLICIT nsRefPtr(already_AddRefed<I>&& aSmartPtr)
: mRawPtr(aSmartPtr.take())
// construct from |otherRefPtr.forget()|
{
}
template <typename I>
nsRefPtr(nsRefPtr<I>&& aSmartPtr)
MOZ_IMPLICIT nsRefPtr(nsRefPtr<I>&& aSmartPtr)
: mRawPtr(aSmartPtr.forget().take())
// construct from |Move(nsRefPtr<SomeSubclassOfT>)|.
{
+29
View File
@@ -96,12 +96,26 @@ public:
{
}
template <typename I>
MOZ_IMPLICIT nsAutoPtr(nsAutoPtr<I>& aSmartPtr)
: mRawPtr(aSmartPtr.forget())
// Construct by transferring ownership from another smart pointer.
{
}
nsAutoPtr(nsAutoPtr<T>&& aSmartPtr)
: mRawPtr(aSmartPtr.forget())
// Construct by transferring ownership from another smart pointer.
{
}
template <typename I>
MOZ_IMPLICIT nsAutoPtr(nsAutoPtr<I>&& aSmartPtr)
: mRawPtr(aSmartPtr.forget())
// Construct by transferring ownership from another smart pointer.
{
}
// Assignment operators
nsAutoPtr<T>&
@@ -119,12 +133,27 @@ public:
return *this;
}
template <typename I>
nsAutoPtr<T>& operator=(nsAutoPtr<I>& aRhs)
// assign by transferring ownership from another smart pointer.
{
assign(aRhs.forget());
return *this;
}
nsAutoPtr<T>& operator=(nsAutoPtr<T>&& aRhs)
{
assign(aRhs.forget());
return *this;
}
template <typename I>
nsAutoPtr<T>& operator=(nsAutoPtr<I>&& aRhs)
{
assign(aRhs.forget());
return *this;
}
// Other pointer operators
T*
+1 -1
View File
@@ -2243,7 +2243,7 @@ protected:
}
template<typename Allocator>
nsAutoArrayBase(nsTArray_Impl<elem_type, Allocator>&& aOther)
explicit nsAutoArrayBase(nsTArray_Impl<elem_type, Allocator>&& aOther)
{
Init();
this->SwapElements(aOther);
+53 -13
View File
@@ -363,7 +363,7 @@ struct StoreCopyPassByValue
typedef T passed_type;
stored_type m;
template <typename A>
StoreCopyPassByValue(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByValue(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -377,7 +377,7 @@ struct StoreCopyPassByConstLRef
typedef const T& passed_type;
stored_type m;
template <typename A>
StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -391,7 +391,7 @@ struct StoreCopyPassByLRef
typedef T& passed_type;
stored_type m;
template <typename A>
StoreCopyPassByLRef(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByLRef(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -405,7 +405,7 @@ struct StoreCopyPassByRRef
typedef T&& passed_type;
stored_type m;
template <typename A>
StoreCopyPassByRRef(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByRRef(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return mozilla::Move(m); }
};
template<typename S>
@@ -419,7 +419,7 @@ struct StoreRefPassByLRef
typedef T& passed_type;
stored_type m;
template <typename A>
StoreRefPassByLRef(A& a) : m(a) {}
explicit StoreRefPassByLRef(A& a) : m(a) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -433,7 +433,7 @@ struct StoreConstRefPassByConstLRef
typedef const T& passed_type;
stored_type m;
template <typename A>
StoreConstRefPassByConstLRef(const A& a) : m(a) {}
explicit StoreConstRefPassByConstLRef(const A& a) : m(a) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -447,7 +447,7 @@ struct StorensRefPtrPassByPtr
typedef T* passed_type;
stored_type m;
template <typename A>
StorensRefPtrPassByPtr(A a) : m(a) {}
explicit StorensRefPtrPassByPtr(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return m.get(); }
};
template<typename S>
@@ -461,7 +461,7 @@ struct StorePtrPassByPtr
typedef T* passed_type;
stored_type m;
template <typename A>
StorePtrPassByPtr(A a) : m(a) {}
explicit StorePtrPassByPtr(A a) : m(a) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -475,7 +475,7 @@ struct StoreConstPtrPassByConstPtr
typedef const T* passed_type;
stored_type m;
template <typename A>
StoreConstPtrPassByConstPtr(A a) : m(a) {}
explicit StoreConstPtrPassByConstPtr(A a) : m(a) {}
passed_type PassAsParameter() { return m; }
};
template<typename S>
@@ -489,7 +489,7 @@ struct StoreCopyPassByConstPtr
typedef const T* passed_type;
stored_type m;
template <typename A>
StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return &m; }
};
template<typename S>
@@ -503,7 +503,7 @@ struct StoreCopyPassByPtr
typedef T* passed_type;
stored_type m;
template <typename A>
StoreCopyPassByPtr(A&& a) : m(mozilla::Forward<A>(a)) {}
explicit StoreCopyPassByPtr(A&& a) : m(mozilla::Forward<A>(a)) {}
passed_type PassAsParameter() { return &m; }
};
template<typename S>
@@ -535,6 +535,36 @@ template<class T>
struct HasRefCountMethods : decltype(HasRefCountMethodsTest<T>(0))
{};
template<typename T>
struct IsRefcountedSmartPointer : public mozilla::FalseType
{};
template<typename T>
struct IsRefcountedSmartPointer<nsRefPtr<T>> : public mozilla::TrueType
{};
template<typename T>
struct IsRefcountedSmartPointer<nsCOMPtr<T>> : public mozilla::TrueType
{};
template<typename T>
struct StripSmartPointer
{
typedef void Type;
};
template<typename T>
struct StripSmartPointer<nsRefPtr<T>>
{
typedef T Type;
};
template<typename T>
struct StripSmartPointer<nsCOMPtr<T>>
{
typedef T Type;
};
template<typename TWithoutPointer>
struct PointerStorageClass
: mozilla::Conditional<HasRefCountMethods<TWithoutPointer>::value,
@@ -552,12 +582,20 @@ struct LValueReferenceStorageClass
StoreRefPassByLRef<TWithoutRef>>
{};
template<typename T>
struct SmartPointerStorageClass
: mozilla::Conditional<IsRefcountedSmartPointer<T>::value,
StorensRefPtrPassByPtr<
typename StripSmartPointer<T>::Type>,
StoreCopyPassByValue<T>>
{};
template<typename T>
struct NonLValueReferenceStorageClass
: mozilla::Conditional<mozilla::IsRvalueReference<T>::value,
StoreCopyPassByRRef<
typename mozilla::RemoveReference<T>::Type>,
StoreCopyPassByValue<T>>
typename SmartPointerStorageClass<T>::Type>
{};
template<typename T>
@@ -587,6 +625,8 @@ struct NonParameterStorageClass
// - const T& -> StoreConstRefPassByConstLRef<T>: Store const T&, pass const T&.
// - T& -> StoreRefPassByLRef<T> : Store T&, pass T&.
// - T&& -> StoreCopyPassByRRef<T> : Store T, pass Move(T).
// - nsRefPtr<T>, nsCOMPtr<T>
// -> StorensRefPtrPassByPtr<T> : Store nsRefPtr<T>, pass T*
// - Other T -> StoreCopyPassByValue<T> : Store T, pass T.
// Other available explicit options:
// - StoreCopyPassByConstLRef<T> : Store T, pass const T&.
@@ -622,7 +662,7 @@ struct nsRunnableMethodArguments<T0>
{
typename ::detail::ParameterStorage<T0>::Type m0;
template<typename A0>
nsRunnableMethodArguments(A0&& a0)
explicit nsRunnableMethodArguments(A0&& a0)
: m0(mozilla::Forward<A0>(a0))
{}
template<class C, typename M> void apply(C* o, M m)
+1 -1
View File
@@ -28,7 +28,7 @@ public:
*/
template<size_type N>
nsTLiteralString_CharT(const char_type (&aStr)[N])
explicit nsTLiteralString_CharT(const char_type (&aStr)[N])
: string_type(const_cast<char_type*>(aStr), N - 1, F_TERMINATED | F_LITERAL)
{
}