mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
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:
+10
-12
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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>)|.
|
||||
{
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user