зеркало из https://github.com/mozilla/gecko-dev.git
merge autoland to mozilla-central a=merge
This commit is contained in:
Коммит
7dde8b7620
|
@ -3842,6 +3842,9 @@
|
|||
spinnerDisplayed: function () {
|
||||
this.assert(!this.spinnerTab);
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
|
||||
// We have a second, similar probe for capturing recordings of
|
||||
// when the spinner is displayed for very long periods.
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
|
||||
this.addMarker("AsyncTabSwitch:SpinnerShown");
|
||||
},
|
||||
|
||||
|
@ -3850,6 +3853,7 @@
|
|||
this.log("DEBUG: spinner time = " +
|
||||
TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
|
||||
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
|
||||
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
|
||||
this.addMarker("AsyncTabSwitch:SpinnerHidden");
|
||||
// we do not get a onPaint after displaying the spinner
|
||||
this.maybeFinishTabSwitch();
|
||||
|
|
|
@ -1076,6 +1076,9 @@ MediaDecoderStateMachine::ExitState()
|
|||
case DECODER_STATE_COMPLETED:
|
||||
mSentPlaybackEndedEvent = false;
|
||||
break;
|
||||
case DECODER_STATE_SHUTDOWN:
|
||||
MOZ_DIAGNOSTIC_ASSERT(false, "Shouldn't escape the SHUTDOWN state.");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1224,12 +1227,10 @@ MediaDecoderStateMachine::Shutdown()
|
|||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
// Once we've entered the shutdown state here there's no going back.
|
||||
// Change state before issuing shutdown request to threads so those
|
||||
// threads can start exiting cleanly during the Shutdown call.
|
||||
ScheduleStateMachine();
|
||||
SetState(DECODER_STATE_SHUTDOWN);
|
||||
|
||||
mDelayedScheduler.Reset();
|
||||
|
||||
mBufferedUpdateRequest.DisconnectIfExists();
|
||||
|
||||
mQueuedSeek.RejectIfExists(__func__);
|
||||
|
@ -1255,6 +1256,40 @@ MediaDecoderStateMachine::Shutdown()
|
|||
|
||||
mMediaSink->Shutdown();
|
||||
|
||||
// Prevent dangling pointers by disconnecting the listeners.
|
||||
mAudioQueueListener.Disconnect();
|
||||
mVideoQueueListener.Disconnect();
|
||||
mMetadataManager.Disconnect();
|
||||
|
||||
// Disconnect canonicals and mirrors before shutting down our task queue.
|
||||
mBuffered.DisconnectIfConnected();
|
||||
mIsReaderSuspended.DisconnectIfConnected();
|
||||
mEstimatedDuration.DisconnectIfConnected();
|
||||
mExplicitDuration.DisconnectIfConnected();
|
||||
mPlayState.DisconnectIfConnected();
|
||||
mNextPlayState.DisconnectIfConnected();
|
||||
mVolume.DisconnectIfConnected();
|
||||
mLogicalPlaybackRate.DisconnectIfConnected();
|
||||
mPreservesPitch.DisconnectIfConnected();
|
||||
mSameOriginMedia.DisconnectIfConnected();
|
||||
mMediaPrincipalHandle.DisconnectIfConnected();
|
||||
mPlaybackBytesPerSecond.DisconnectIfConnected();
|
||||
mPlaybackRateReliable.DisconnectIfConnected();
|
||||
mDecoderPosition.DisconnectIfConnected();
|
||||
mMediaSeekable.DisconnectIfConnected();
|
||||
mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
|
||||
mIsVisible.DisconnectIfConnected();
|
||||
|
||||
mDuration.DisconnectAll();
|
||||
mIsShutdown.DisconnectAll();
|
||||
mNextFrameStatus.DisconnectAll();
|
||||
mCurrentPosition.DisconnectAll();
|
||||
mPlaybackOffset.DisconnectAll();
|
||||
mIsAudioDataAudible.DisconnectAll();
|
||||
|
||||
// Shut down the watch manager to stop further notifications.
|
||||
mWatchManager.Shutdown();
|
||||
|
||||
DECODER_LOG("Shutdown started");
|
||||
|
||||
// Put a task in the decode queue to shutdown the reader.
|
||||
|
@ -1963,16 +1998,8 @@ void
|
|||
MediaDecoderStateMachine::DecodeError()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
if (IsShutdown()) {
|
||||
// Already shutdown.
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!IsShutdown());
|
||||
DECODER_WARN("Decode error");
|
||||
// Change state to SHUTDOWN so we have no more processing.
|
||||
SetState(DECODER_STATE_SHUTDOWN);
|
||||
|
||||
// Notify the decode error and MediaDecoder will shut down MDSM.
|
||||
mOnPlaybackEvent.Notify(MediaEventType::DecodeError);
|
||||
}
|
||||
|
@ -2222,45 +2249,6 @@ RefPtr<ShutdownPromise>
|
|||
MediaDecoderStateMachine::FinishShutdown()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
// The reader's listeners hold references to the state machine,
|
||||
// creating a cycle which keeps the state machine and its shared
|
||||
// thread pools alive. So break it here.
|
||||
|
||||
// Prevent dangling pointers by disconnecting the listeners.
|
||||
mAudioQueueListener.Disconnect();
|
||||
mVideoQueueListener.Disconnect();
|
||||
mMetadataManager.Disconnect();
|
||||
|
||||
// Disconnect canonicals and mirrors before shutting down our task queue.
|
||||
mBuffered.DisconnectIfConnected();
|
||||
mIsReaderSuspended.DisconnectIfConnected();
|
||||
mEstimatedDuration.DisconnectIfConnected();
|
||||
mExplicitDuration.DisconnectIfConnected();
|
||||
mPlayState.DisconnectIfConnected();
|
||||
mNextPlayState.DisconnectIfConnected();
|
||||
mVolume.DisconnectIfConnected();
|
||||
mLogicalPlaybackRate.DisconnectIfConnected();
|
||||
mPreservesPitch.DisconnectIfConnected();
|
||||
mSameOriginMedia.DisconnectIfConnected();
|
||||
mMediaPrincipalHandle.DisconnectIfConnected();
|
||||
mPlaybackBytesPerSecond.DisconnectIfConnected();
|
||||
mPlaybackRateReliable.DisconnectIfConnected();
|
||||
mDecoderPosition.DisconnectIfConnected();
|
||||
mMediaSeekable.DisconnectIfConnected();
|
||||
mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
|
||||
mIsVisible.DisconnectIfConnected();
|
||||
|
||||
mDuration.DisconnectAll();
|
||||
mIsShutdown.DisconnectAll();
|
||||
mNextFrameStatus.DisconnectAll();
|
||||
mCurrentPosition.DisconnectAll();
|
||||
mPlaybackOffset.DisconnectAll();
|
||||
mIsAudioDataAudible.DisconnectAll();
|
||||
|
||||
// Shut down the watch manager before shutting down our task queue.
|
||||
mWatchManager.Shutdown();
|
||||
|
||||
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
|
||||
"How did we escape from the shutdown state?");
|
||||
DECODER_LOG("Shutting down state machine task queue");
|
||||
|
@ -2623,13 +2611,13 @@ MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
|
|||
TimeStamp now = TimeStamp::Now();
|
||||
TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds);
|
||||
|
||||
SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds());
|
||||
|
||||
RefPtr<MediaDecoderStateMachine> self = this;
|
||||
mDelayedScheduler.Ensure(target, [self] () {
|
||||
self->OnDelayedSchedule();
|
||||
}, [self] () {
|
||||
self->NotReached();
|
||||
// It is OK to capture 'this' without causing UAF because the callback
|
||||
// always happens before shutdown.
|
||||
mDelayedScheduler.Ensure(target, [this] () {
|
||||
mDelayedScheduler.CompleteRequest();
|
||||
RunStateMachine();
|
||||
}, [] () {
|
||||
MOZ_DIAGNOSTIC_ASSERT(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2673,7 +2661,8 @@ void MediaDecoderStateMachine::PreservesPitchChanged()
|
|||
mMediaSink->SetPreservesPitch(mPreservesPitch);
|
||||
}
|
||||
|
||||
bool MediaDecoderStateMachine::IsShutdown()
|
||||
bool
|
||||
MediaDecoderStateMachine::IsShutdown() const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
return mIsShutdown;
|
||||
|
|
|
@ -319,23 +319,13 @@ private:
|
|||
// request is discarded.
|
||||
void ScheduleStateMachineIn(int64_t aMicroseconds);
|
||||
|
||||
void OnDelayedSchedule()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mDelayedScheduler.CompleteRequest();
|
||||
ScheduleStateMachine();
|
||||
}
|
||||
|
||||
void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); }
|
||||
|
||||
// Discard audio/video data that are already played by MSG.
|
||||
void DiscardStreamData();
|
||||
bool HaveEnoughDecodedAudio();
|
||||
bool HaveEnoughDecodedVideo();
|
||||
|
||||
// Returns true if the state machine has shutdown or is in the process of
|
||||
// shutting down. The decoder monitor must be held while calling this.
|
||||
bool IsShutdown();
|
||||
// True if shutdown process has begun.
|
||||
bool IsShutdown() const;
|
||||
|
||||
// Returns true if we're currently playing. The decoder monitor must
|
||||
// be held.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "mp4_demuxer/ResourceStream.h"
|
||||
#include "mp4_demuxer/BufferStream.h"
|
||||
#include "mp4_demuxer/Index.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
// Used for telemetry
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
@ -27,6 +28,8 @@ mozilla::LogModule* GetDemuxerLog() {
|
|||
return gMediaDemuxerLog;
|
||||
}
|
||||
|
||||
#define LOG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("MP4Demuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer
|
||||
|
@ -55,7 +58,7 @@ public:
|
|||
private:
|
||||
friend class MP4Demuxer;
|
||||
void NotifyDataArrived();
|
||||
void UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples);
|
||||
already_AddRefed<MediaRawData> GetNextSample();
|
||||
void EnsureUpToDateIndex();
|
||||
void SetNextKeyFrameTime();
|
||||
RefPtr<MP4Demuxer> mParent;
|
||||
|
@ -68,6 +71,7 @@ private:
|
|||
RefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
bool mNeedSPSForTelemetry;
|
||||
bool mIsH264 = false;
|
||||
};
|
||||
|
||||
|
||||
|
@ -241,6 +245,7 @@ MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
|
|||
if (videoInfo &&
|
||||
(mInfo->mMimeType.EqualsLiteral("video/mp4") ||
|
||||
mInfo->mMimeType.EqualsLiteral("video/avc"))) {
|
||||
mIsH264 = true;
|
||||
RefPtr<MediaByteBuffer> extraData = videoInfo->mExtraData;
|
||||
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extraData);
|
||||
mp4_demuxer::SPSData spsdata;
|
||||
|
@ -289,15 +294,76 @@ MP4TrackDemuxer::Seek(media::TimeUnit aTime)
|
|||
mIterator->Seek(seekTime);
|
||||
|
||||
// Check what time we actually seeked to.
|
||||
mQueuedSample = mIterator->GetNext();
|
||||
if (mQueuedSample) {
|
||||
seekTime = mQueuedSample->mTime;
|
||||
}
|
||||
RefPtr<MediaRawData> sample;
|
||||
do {
|
||||
sample = GetNextSample();
|
||||
if (!sample) {
|
||||
return SeekPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__);
|
||||
}
|
||||
if (!sample->Size()) {
|
||||
// This sample can't be decoded, continue searching.
|
||||
continue;
|
||||
}
|
||||
if (sample->mKeyframe) {
|
||||
mQueuedSample = sample;
|
||||
seekTime = mQueuedSample->mTime;
|
||||
}
|
||||
} while (!mQueuedSample);
|
||||
|
||||
SetNextKeyFrameTime();
|
||||
|
||||
return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__);
|
||||
}
|
||||
|
||||
already_AddRefed<MediaRawData>
|
||||
MP4TrackDemuxer::GetNextSample()
|
||||
{
|
||||
RefPtr<MediaRawData> sample = mIterator->GetNext();
|
||||
if (!sample) {
|
||||
return nullptr;
|
||||
}
|
||||
if (mInfo->GetAsVideoInfo()) {
|
||||
sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
|
||||
if (mIsH264) {
|
||||
mp4_demuxer::H264::FrameType type =
|
||||
mp4_demuxer::H264::GetFrameType(sample);
|
||||
switch (type) {
|
||||
case mp4_demuxer::H264::FrameType::I_FRAME: MOZ_FALLTHROUGH;
|
||||
case mp4_demuxer::H264::FrameType::OTHER:
|
||||
{
|
||||
bool keyframe = type == mp4_demuxer::H264::FrameType::I_FRAME;
|
||||
if (sample->mKeyframe != keyframe) {
|
||||
NS_WARNING(nsPrintfCString("Frame incorrectly marked as %skeyframe @ pts:%lld dur:%u dts:%lld",
|
||||
keyframe ? "" : "non-",
|
||||
sample->mTime,
|
||||
sample->mDuration,
|
||||
sample->mTimecode).get());
|
||||
sample->mKeyframe = keyframe;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case mp4_demuxer::H264::FrameType::INVALID:
|
||||
NS_WARNING(nsPrintfCString("Invalid H264 frame @ pts:%lld dur:%u dts:%lld",
|
||||
sample->mTime,
|
||||
sample->mDuration,
|
||||
sample->mTimecode).get());
|
||||
// We could reject the sample now, however demuxer errors are fatal.
|
||||
// So we keep the invalid frame, relying on the H264 decoder to
|
||||
// handle the error later.
|
||||
// TODO: make demuxer errors non-fatal.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sample->mCrypto.mValid) {
|
||||
nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
|
||||
writer->mCrypto.mMode = mInfo->mCrypto.mMode;
|
||||
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
|
||||
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
|
||||
}
|
||||
return sample.forget();
|
||||
}
|
||||
|
||||
RefPtr<MP4TrackDemuxer::SamplesPromise>
|
||||
MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
|
||||
{
|
||||
|
@ -308,12 +374,14 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
|
|||
}
|
||||
|
||||
if (mQueuedSample) {
|
||||
MOZ_ASSERT(mQueuedSample->mKeyframe,
|
||||
"mQueuedSample must be a keyframe");
|
||||
samples->mSamples.AppendElement(mQueuedSample);
|
||||
mQueuedSample = nullptr;
|
||||
aNumSamples--;
|
||||
}
|
||||
RefPtr<MediaRawData> sample;
|
||||
while (aNumSamples && (sample = mIterator->GetNext())) {
|
||||
while (aNumSamples && (sample = GetNextSample())) {
|
||||
if (!sample->Size()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -324,7 +392,19 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
|
|||
if (samples->mSamples.IsEmpty()) {
|
||||
return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__);
|
||||
} else {
|
||||
UpdateSamples(samples->mSamples);
|
||||
for (const auto& sample : samples->mSamples) {
|
||||
// Collect telemetry from h264 Annex B SPS.
|
||||
if (mNeedSPSForTelemetry && mp4_demuxer::AnnexB::HasSPS(sample)) {
|
||||
RefPtr<MediaByteBuffer> extradata =
|
||||
mp4_demuxer::AnnexB::ExtractExtraData(sample);
|
||||
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extradata);
|
||||
}
|
||||
}
|
||||
|
||||
if (mNextKeyframeTime.isNothing() ||
|
||||
samples->mSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
|
||||
SetNextKeyFrameTime();
|
||||
}
|
||||
return SamplesPromise::CreateAndResolve(samples, __func__);
|
||||
}
|
||||
}
|
||||
|
@ -349,33 +429,6 @@ MP4TrackDemuxer::Reset()
|
|||
SetNextKeyFrameTime();
|
||||
}
|
||||
|
||||
void
|
||||
MP4TrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples)
|
||||
{
|
||||
for (size_t i = 0; i < aSamples.Length(); i++) {
|
||||
MediaRawData* sample = aSamples[i];
|
||||
// Collect telemetry from h264 Annex B SPS.
|
||||
if (mNeedSPSForTelemetry && mp4_demuxer::AnnexB::HasSPS(sample)) {
|
||||
RefPtr<MediaByteBuffer> extradata =
|
||||
mp4_demuxer::AnnexB::ExtractExtraData(sample);
|
||||
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extradata);
|
||||
}
|
||||
if (sample->mCrypto.mValid) {
|
||||
nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
|
||||
writer->mCrypto.mMode = mInfo->mCrypto.mMode;
|
||||
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
|
||||
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
|
||||
}
|
||||
if (mInfo->GetAsVideoInfo()) {
|
||||
sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
|
||||
}
|
||||
}
|
||||
if (mNextKeyframeTime.isNothing() ||
|
||||
aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
|
||||
SetNextKeyFrameTime();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
|
||||
{
|
||||
|
@ -397,7 +450,7 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
|
|||
uint32_t parsed = 0;
|
||||
bool found = false;
|
||||
RefPtr<MediaRawData> sample;
|
||||
while (!found && (sample = mIterator->GetNext())) {
|
||||
while (!found && (sample = GetNextSample())) {
|
||||
parsed++;
|
||||
if (sample->mKeyframe && sample->mTime >= aTimeThreshold.ToMicroseconds()) {
|
||||
found = true;
|
||||
|
@ -441,3 +494,5 @@ MP4TrackDemuxer::BreakCycles()
|
|||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#undef LOG
|
||||
|
|
|
@ -109,7 +109,7 @@ public:
|
|||
// WMF H.264 Video Decoder and Apple ATDecoder
|
||||
// do not support YUV444 format.
|
||||
// For consistency, all decoders should be checked.
|
||||
if (!mp4_demuxer::H264::DecodeSPSFromExtraData(extraData, spsdata) ||
|
||||
if (mp4_demuxer::H264::DecodeSPSFromExtraData(extraData, spsdata) &&
|
||||
spsdata.chroma_format_idc == PDMFactory::kYUV444) {
|
||||
return SupportChecker::Result::kVideoFormatNotSupported;
|
||||
}
|
||||
|
@ -123,10 +123,10 @@ public:
|
|||
Check()
|
||||
{
|
||||
for (auto& checker : mCheckerList) {
|
||||
auto result = checker();
|
||||
if (result != SupportChecker::Result::kSupported) {
|
||||
return result;
|
||||
}
|
||||
auto result = checker();
|
||||
if (result != SupportChecker::Result::kSupported) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return SupportChecker::Result::kSupported;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,14 @@ H264Converter::Input(MediaRawData* aSample)
|
|||
}
|
||||
|
||||
if (mInitPromiseRequest.Exists()) {
|
||||
if (mNeedKeyframe) {
|
||||
if (!aSample->mKeyframe) {
|
||||
// Frames dropped, we need a new one.
|
||||
mCallback->InputExhausted();
|
||||
return NS_OK;
|
||||
}
|
||||
mNeedKeyframe = false;
|
||||
}
|
||||
mMediaRawSamples.AppendElement(aSample);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -72,6 +80,7 @@ H264Converter::Input(MediaRawData* aSample)
|
|||
if (rv == NS_ERROR_NOT_INITIALIZED) {
|
||||
// We are missing the required SPS to create the decoder.
|
||||
// Ignore for the time being, the MediaRawData will be dropped.
|
||||
mCallback->InputExhausted();
|
||||
return NS_OK;
|
||||
}
|
||||
} else {
|
||||
|
@ -79,11 +88,18 @@ H264Converter::Input(MediaRawData* aSample)
|
|||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mNeedKeyframe && !aSample->mKeyframe) {
|
||||
mCallback->InputExhausted();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mNeedAVCC &&
|
||||
!mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mNeedKeyframe = false;
|
||||
|
||||
aSample->mExtraData = mCurrentConfig.mExtraData;
|
||||
|
||||
return mDecoder->Input(aSample);
|
||||
|
@ -92,6 +108,7 @@ H264Converter::Input(MediaRawData* aSample)
|
|||
nsresult
|
||||
H264Converter::Flush()
|
||||
{
|
||||
mNeedKeyframe = true;
|
||||
if (mDecoder) {
|
||||
return mDecoder->Flush();
|
||||
}
|
||||
|
@ -101,6 +118,7 @@ H264Converter::Flush()
|
|||
nsresult
|
||||
H264Converter::Drain()
|
||||
{
|
||||
mNeedKeyframe = true;
|
||||
if (mDecoder) {
|
||||
return mDecoder->Drain();
|
||||
}
|
||||
|
@ -137,6 +155,24 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics)
|
|||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
UpdateConfigFromExtraData(mCurrentConfig.mExtraData);
|
||||
|
||||
mp4_demuxer::SPSData spsdata;
|
||||
if (mp4_demuxer::H264::DecodeSPSFromExtraData(mCurrentConfig.mExtraData, spsdata)) {
|
||||
// Do some format check here.
|
||||
// WMF H.264 Video Decoder and Apple ATDecoder do not support YUV444 format.
|
||||
if (spsdata.chroma_format_idc == 3 /*YUV444*/) {
|
||||
mLastError = NS_ERROR_FAILURE;
|
||||
if (aDiagnostics) {
|
||||
aDiagnostics->SetVideoFormatNotSupport();
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else if (mNeedAVCC) {
|
||||
// SPS was invalid.
|
||||
mLastError = NS_ERROR_FAILURE;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!mNeedAVCC) {
|
||||
// When using a decoder handling AnnexB, we get here only once from the
|
||||
// constructor. We do want to get the dimensions extracted from the SPS.
|
||||
|
@ -157,6 +193,9 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics)
|
|||
mLastError = NS_ERROR_FAILURE;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mNeedKeyframe = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -190,11 +229,22 @@ void
|
|||
H264Converter::OnDecoderInitDone(const TrackType aTrackType)
|
||||
{
|
||||
mInitPromiseRequest.Complete();
|
||||
bool gotInput = false;
|
||||
for (uint32_t i = 0 ; i < mMediaRawSamples.Length(); i++) {
|
||||
if (NS_FAILED(mDecoder->Input(mMediaRawSamples[i]))) {
|
||||
const RefPtr<MediaRawData>& sample = mMediaRawSamples[i];
|
||||
if (mNeedKeyframe) {
|
||||
if (!sample->mKeyframe) {
|
||||
continue;
|
||||
}
|
||||
mNeedKeyframe = false;
|
||||
}
|
||||
if (NS_FAILED(mDecoder->Input(sample))) {
|
||||
mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
|
||||
}
|
||||
}
|
||||
if (!gotInput) {
|
||||
mCallback->InputExhausted();
|
||||
}
|
||||
mMediaRawSamples.Clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ private:
|
|||
RefPtr<GMPCrashHelper> mGMPCrashHelper;
|
||||
bool mNeedAVCC;
|
||||
nsresult mLastError;
|
||||
bool mNeedKeyframe = true;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -463,7 +463,7 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques
|
|||
info->SetControlChannel(ctrlChannel);
|
||||
info->SetDevice(device);
|
||||
return static_cast<PresentationPresentingInfo*>(
|
||||
info.get())->NotifyResponderReady();
|
||||
info.get())->DoReconnect();
|
||||
}
|
||||
|
||||
// This is the case for a new session.
|
||||
|
|
|
@ -405,11 +405,16 @@ PresentationSessionInfo::ContinueTermination()
|
|||
NS_IMETHODIMP
|
||||
PresentationSessionInfo::NotifyTransportReady()
|
||||
{
|
||||
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
||||
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mState != nsIPresentationSessionListener::STATE_CONNECTING &&
|
||||
mState != nsIPresentationSessionListener::STATE_CONNECTED) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mIsTransportReady = true;
|
||||
|
||||
// Established RTCDataChannel implies responder is ready.
|
||||
|
@ -484,11 +489,15 @@ PresentationSessionInfo::NotifyData(const nsACString& aData)
|
|||
NS_IMETHODIMP
|
||||
PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
|
||||
{
|
||||
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
||||
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
||||
|
||||
SetBuilder(nullptr);
|
||||
|
||||
if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// The session transport is managed by content process
|
||||
if (!transport) {
|
||||
return NS_OK;
|
||||
|
@ -1195,6 +1204,7 @@ nsresult
|
|||
PresentationPresentingInfo::InitTransportAndSendAnswer()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CONNECTING);
|
||||
|
||||
uint8_t type = 0;
|
||||
nsresult rv = mRequesterDescription->GetType(&type);
|
||||
|
@ -1308,8 +1318,8 @@ PresentationPresentingInfo::IsAccessible(base::ProcessId aProcessId)
|
|||
nsresult
|
||||
PresentationPresentingInfo::NotifyResponderReady()
|
||||
{
|
||||
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
||||
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
||||
|
||||
if (mTimer) {
|
||||
mTimer->Cancel();
|
||||
|
@ -1344,6 +1354,19 @@ PresentationPresentingInfo::NotifyResponderFailure()
|
|||
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationPresentingInfo::DoReconnect()
|
||||
{
|
||||
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
||||
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
||||
|
||||
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
|
||||
|
||||
SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
|
||||
|
||||
return NotifyResponderReady();
|
||||
}
|
||||
|
||||
// nsIPresentationControlChannelListener
|
||||
NS_IMETHODIMP
|
||||
PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
|
||||
|
|
|
@ -254,6 +254,8 @@ public:
|
|||
|
||||
bool IsAccessible(base::ProcessId aProcessId) override;
|
||||
|
||||
nsresult DoReconnect();
|
||||
|
||||
private:
|
||||
~PresentationPresentingInfo()
|
||||
{
|
||||
|
|
|
@ -240,19 +240,19 @@ const mockedSessionTransport = {
|
|||
return this._selfAddress;
|
||||
},
|
||||
buildTCPSenderTransport: function(transport, listener) {
|
||||
sendAsyncMessage('data-transport-initialized');
|
||||
this._listener = listener;
|
||||
this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
|
||||
this._listener.onSessionTransport(this);
|
||||
this._listener = null;
|
||||
sendAsyncMessage('data-transport-initialized');
|
||||
|
||||
setTimeout(()=>{
|
||||
this._listener.onSessionTransport(this);
|
||||
this._listener = null;
|
||||
this.simulateTransportReady();
|
||||
}, 0);
|
||||
},
|
||||
buildTCPReceiverTransport: function(description, listener) {
|
||||
this._listener = listener;
|
||||
this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
|
||||
this._role = Ci.nsIPresentationService.ROLE_RECEIVER;
|
||||
|
||||
var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
|
||||
this._selfAddress = {
|
||||
|
@ -269,7 +269,6 @@ const mockedSessionTransport = {
|
|||
},
|
||||
// in-process case
|
||||
buildDataChannelTransport: function(role, window, listener) {
|
||||
dump("PresentationSessionChromeScript: build data channel transport\n");
|
||||
this._listener = listener;
|
||||
this._role = role;
|
||||
|
||||
|
|
|
@ -42,318 +42,437 @@ function setup() {
|
|||
}
|
||||
|
||||
function testStartConnectionCancelPrompt() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
});
|
||||
|
||||
info('--- testStartConnectionCancelPrompt ---');
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
ok(false, "|start| shouldn't succeed in this case.");
|
||||
aReject();
|
||||
},
|
||||
function(aError) {
|
||||
is(aError.name, "NotAllowedError", "NotAllowedError is expected when the prompt is canceled.");
|
||||
aResolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionNoDevice() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
|
||||
});
|
||||
|
||||
info('--- testStartConnectionNoDevice ---');
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
ok(false, "|start| shouldn't succeed in this case.");
|
||||
aReject();
|
||||
},
|
||||
function(aError) {
|
||||
is(aError.name, "NotFoundError", "NotFoundError is expected when no available device.");
|
||||
aResolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit ---');
|
||||
return Promise.all([
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
is(aReason, SpecialPowers.Cr.NS_ERROR_FAILURE, "The control channel is closed with NS_ERROR_FAILURE");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
is(aConnection.state, "connecting", "The initial state should be connecting.");
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
aResolve();
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit ---');
|
||||
return Promise.all([
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
is(aConnection.state, "connecting", "The initial state should be connecting.");
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
aResolve();
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady ---');
|
||||
return Promise.all([
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
is(aReason, SpecialPowers.Cr.NS_ERROR_ABORT, "The control channel is closed with NS_ERROR_ABORT");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
is(aConnection.state, "connecting", "The initial state should be connecting.");
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
aResolve();
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady -- ');
|
||||
return Promise.all([
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
is(aConnection.state, "connecting", "The initial state should be connecting.");
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
aResolve();
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function testStartConnectionUnexpectedDataTransportClose() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
info('--- testStartConnectionUnexpectedDataTransportClose ---');
|
||||
return Promise.all([
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
info("recv offer-sent.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
});
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
is(aConnection.state, "connecting", "The initial state should be connecting.");
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
aResolve();
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
aConnection.onclose = function() {
|
||||
aConnection.onclose = null;
|
||||
is(aConnection.state, "closed", "Connection should be closed.");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
|
|
|
@ -488,4 +488,41 @@ H264::ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData)
|
|||
return maxRefFrames;
|
||||
}
|
||||
|
||||
/* static */ H264::FrameType
|
||||
H264::GetFrameType(const mozilla::MediaRawData* aSample)
|
||||
{
|
||||
if (!AnnexB::IsAVCC(aSample)) {
|
||||
// We must have a valid AVCC frame with extradata.
|
||||
return FrameType::INVALID;
|
||||
}
|
||||
MOZ_ASSERT(aSample->Data());
|
||||
|
||||
int nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
|
||||
|
||||
ByteReader reader(aSample->Data(), aSample->Size());
|
||||
|
||||
while (reader.Remaining() >= nalLenSize) {
|
||||
uint32_t nalLen;
|
||||
switch (nalLenSize) {
|
||||
case 1: nalLen = reader.ReadU8(); break;
|
||||
case 2: nalLen = reader.ReadU16(); break;
|
||||
case 3: nalLen = reader.ReadU24(); break;
|
||||
case 4: nalLen = reader.ReadU32(); break;
|
||||
}
|
||||
if (!nalLen) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t* p = reader.Read(nalLen);
|
||||
if (!p) {
|
||||
return FrameType::INVALID;
|
||||
}
|
||||
if ((p[0] & 0x1f) == 5) {
|
||||
// IDR NAL.
|
||||
return FrameType::I_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
return FrameType::OTHER;
|
||||
}
|
||||
|
||||
} // namespace mp4_demuxer
|
||||
|
|
|
@ -350,6 +350,17 @@ public:
|
|||
// clamped to be in the range of [4, 16]; otherwise return 4.
|
||||
static uint32_t ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData);
|
||||
|
||||
enum class FrameType
|
||||
{
|
||||
I_FRAME,
|
||||
OTHER,
|
||||
INVALID,
|
||||
};
|
||||
|
||||
// Returns the frame type. Returns I_FRAME if the sample is an IDR
|
||||
// (Instantaneous Decoding Refresh) Picture.
|
||||
static FrameType GetFrameType(const mozilla::MediaRawData* aSample);
|
||||
|
||||
private:
|
||||
static void vui_parameters(BitReader& aBr, SPSData& aDest);
|
||||
// Read HRD parameters, all data is ignored.
|
||||
|
|
|
@ -140,6 +140,7 @@ public class Tab {
|
|||
mPluginViews = new ArrayList<View>();
|
||||
mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS;
|
||||
mLoadProgress = LOAD_PROGRESS_INIT;
|
||||
mIconRequestBuilder = Icons.with(mAppContext).pageUrl(mUrl);
|
||||
|
||||
updateBookmark();
|
||||
}
|
||||
|
@ -452,13 +453,6 @@ public class Tab {
|
|||
return;
|
||||
}
|
||||
|
||||
if (mIconRequestBuilder == null) {
|
||||
// For the first internal homepage we might want to load a favicon without ever receiving
|
||||
// a location change event first. In this case we didn't start to build a request yet.
|
||||
// Let's do that now.
|
||||
mIconRequestBuilder = Icons.with(mAppContext).pageUrl(mUrl);
|
||||
}
|
||||
|
||||
mRunningIconRequest = mIconRequestBuilder
|
||||
.build()
|
||||
.execute(new IconCallback() {
|
||||
|
|
|
@ -312,6 +312,7 @@
|
|||
@BINPATH@/components/addonManager.js
|
||||
@BINPATH@/components/amContentHandler.js
|
||||
@BINPATH@/components/amInstallTrigger.js
|
||||
@BINPATH@/components/amWebAPI.js
|
||||
@BINPATH@/components/amWebInstallListener.js
|
||||
@BINPATH@/components/nsBlocklistService.js
|
||||
#ifndef RELEASE_BUILD
|
||||
|
|
|
@ -122,7 +122,6 @@ UNIFIED_SOURCES += [
|
|||
'nsPKCS11Slot.cpp',
|
||||
'nsPKCS12Blob.cpp',
|
||||
'nsProtectedAuthThread.cpp',
|
||||
'nsPSMBackgroundThread.cpp',
|
||||
'nsRandomGenerator.cpp',
|
||||
'nsSecureBrowserUIImpl.cpp',
|
||||
'nsSecurityHeaderParser.cpp',
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsPSMBackgroundThread.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
void nsPSMBackgroundThread::nsThreadRunner(void *arg)
|
||||
{
|
||||
nsPSMBackgroundThread *self = static_cast<nsPSMBackgroundThread *>(arg);
|
||||
PR_SetCurrentThreadName(self->mName.BeginReading());
|
||||
self->Run();
|
||||
}
|
||||
|
||||
nsPSMBackgroundThread::nsPSMBackgroundThread()
|
||||
: mThreadHandle(nullptr),
|
||||
mMutex("nsPSMBackgroundThread.mMutex"),
|
||||
mCond(mMutex, "nsPSMBackgroundThread.mCond"),
|
||||
mExitState(ePSMThreadRunning)
|
||||
{
|
||||
}
|
||||
|
||||
nsresult nsPSMBackgroundThread::startThread(const nsCSubstring & name)
|
||||
{
|
||||
mName = name;
|
||||
|
||||
mThreadHandle = PR_CreateThread(PR_USER_THREAD, nsThreadRunner, static_cast<void*>(this),
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
|
||||
NS_ASSERTION(mThreadHandle, "Could not create nsPSMBackgroundThread\n");
|
||||
|
||||
if (!mThreadHandle)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsPSMBackgroundThread::~nsPSMBackgroundThread()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
nsPSMBackgroundThread::exitRequested(const MutexAutoLock & /*proofOfLock*/) const
|
||||
{
|
||||
return exitRequestedNoLock();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsPSMBackgroundThread::postStoppedEventToMainThread(
|
||||
MutexAutoLock const & /*proofOfLock*/)
|
||||
{
|
||||
NS_ASSERTION(PR_GetCurrentThread() == mThreadHandle,
|
||||
"Background thread stopped from another thread");
|
||||
|
||||
mExitState = ePSMThreadStopped;
|
||||
// requestExit is waiting for an event, so give it one.
|
||||
return NS_DispatchToMainThread(new Runnable());
|
||||
}
|
||||
|
||||
void nsPSMBackgroundThread::requestExit()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(),
|
||||
"nsPSMBackgroundThread::requestExit called off main thread.");
|
||||
|
||||
if (!mThreadHandle)
|
||||
return;
|
||||
|
||||
{
|
||||
MutexAutoLock threadLock(mMutex);
|
||||
if (mExitState < ePSMThreadStopRequested) {
|
||||
mExitState = ePSMThreadStopRequested;
|
||||
mCond.NotifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetCurrentThread();
|
||||
for (;;) {
|
||||
{
|
||||
MutexAutoLock threadLock(mMutex);
|
||||
if (mExitState == ePSMThreadStopped)
|
||||
break;
|
||||
}
|
||||
NS_ProcessPendingEvents(mainThread, PR_MillisecondsToInterval(50));
|
||||
}
|
||||
|
||||
PR_JoinThread(mThreadHandle);
|
||||
mThreadHandle = nullptr;
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef _NSPSMBACKGROUNDTHREAD_H_
|
||||
#define _NSPSMBACKGROUNDTHREAD_H_
|
||||
|
||||
#include "nspr.h"
|
||||
#include "nscore.h"
|
||||
#include "mozilla/CondVar.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsNSSComponent.h"
|
||||
|
||||
class nsPSMBackgroundThread
|
||||
{
|
||||
protected:
|
||||
static void nsThreadRunner(void *arg);
|
||||
virtual void Run(void) = 0;
|
||||
|
||||
// used to join the thread
|
||||
PRThread *mThreadHandle;
|
||||
|
||||
// Shared mutex used for condition variables,
|
||||
// and to protect access to mExitState.
|
||||
// Derived classes may use it to protect additional
|
||||
// resources.
|
||||
mozilla::Mutex mMutex;
|
||||
|
||||
// Used to signal the thread's Run loop when a job is added
|
||||
// and/or exit is requested.
|
||||
mozilla::CondVar mCond;
|
||||
|
||||
bool exitRequested(::mozilla::MutexAutoLock const & proofOfLock) const;
|
||||
bool exitRequestedNoLock() const { return mExitState != ePSMThreadRunning; }
|
||||
nsresult postStoppedEventToMainThread(::mozilla::MutexAutoLock const & proofOfLock);
|
||||
|
||||
private:
|
||||
enum {
|
||||
ePSMThreadRunning = 0,
|
||||
ePSMThreadStopRequested = 1,
|
||||
ePSMThreadStopped = 2
|
||||
} mExitState;
|
||||
|
||||
// The thread's name.
|
||||
nsCString mName;
|
||||
|
||||
public:
|
||||
nsPSMBackgroundThread();
|
||||
virtual ~nsPSMBackgroundThread();
|
||||
|
||||
nsresult startThread(const nsCSubstring & name);
|
||||
void requestExit();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -150,7 +150,7 @@
|
|||
"Entrust Root Certification Authority - EC1",
|
||||
"Entrust Root Certification Authority - G2",
|
||||
"Entrust.net Premium 2048 Secure Server CA",
|
||||
"Equifax Secure CA",
|
||||
// "Equifax Secure Certificate Authority",
|
||||
"GeoTrust Global CA",
|
||||
"GeoTrust Global CA 2",
|
||||
"GeoTrust Primary Certification Authority",
|
||||
|
|
|
@ -57,14 +57,31 @@ function downloadRoots() {
|
|||
return roots;
|
||||
}
|
||||
|
||||
function makeFormattedNickname(cert) {
|
||||
if (cert.nickname.startsWith("Builtin Object Token:")) {
|
||||
return `"${cert.nickname.substring("Builtin Object Token:".length)}"`;
|
||||
}
|
||||
// Otherwise, this isn't a built-in and we have to comment it out.
|
||||
if (cert.commonName) {
|
||||
return `// "${cert.commonName}"`;
|
||||
}
|
||||
if (cert.organizationalUnit) {
|
||||
return `// "${cert.organizationalUnit}"`;
|
||||
}
|
||||
if (cert.organization) {
|
||||
return `// "${cert.organization}"`;
|
||||
}
|
||||
throw new Error(`couldn't make nickname for ${cert.subjectName}`);
|
||||
}
|
||||
|
||||
var roots = downloadRoots();
|
||||
var rootNicknames = [];
|
||||
for (var root of roots) {
|
||||
rootNicknames.push(root.nickname.substring("Builtin Object Token:".length));
|
||||
rootNicknames.push(makeFormattedNickname(root));
|
||||
}
|
||||
rootNicknames.sort(function(rootA, rootB) {
|
||||
let rootALowercase = rootA.toLowerCase();
|
||||
let rootBLowercase = rootB.toLowerCase();
|
||||
let rootALowercase = rootA.toLowerCase().replace(/(^[^"]*")|"/g, "");
|
||||
let rootBLowercase = rootB.toLowerCase().replace(/(^[^"]*")|"/g, "");
|
||||
if (rootALowercase < rootBLowercase) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -82,7 +99,7 @@ for (var nickname of rootNicknames) {
|
|||
dump(",\n");
|
||||
}
|
||||
first = false;
|
||||
dump(" \"" + nickname + "\"");
|
||||
dump(" " + nickname);
|
||||
}
|
||||
dump("\n");
|
||||
dump(" ]\n");
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
this.EXPORTED_SYMBOLS = ["loadKinto"];
|
||||
|
||||
/*
|
||||
* Version 4.0.3 - 8100433
|
||||
* Version 4.0.4 - 03f82da
|
||||
*/
|
||||
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.loadKinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
|
@ -1805,15 +1805,7 @@ class Collection {
|
|||
});
|
||||
}).then(syncResultObject => {
|
||||
syncResultObject.lastModified = changeObject.lastModified;
|
||||
// Don't persist lastModified value if any conflict or error occured
|
||||
if (!syncResultObject.ok) {
|
||||
return syncResultObject;
|
||||
}
|
||||
// No conflict occured, persist collection's lastModified value
|
||||
return this.db.saveLastModified(syncResultObject.lastModified).then(lastModified => {
|
||||
this._lastModified = lastModified;
|
||||
return syncResultObject;
|
||||
});
|
||||
return syncResultObject;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2216,7 +2208,18 @@ class Collection {
|
|||
// Avoid redownloading our own changes during the last pull.
|
||||
const pullOpts = _extends({}, options, { exclude: result.published });
|
||||
return this.pullChanges(client, result, pullOpts);
|
||||
}).then(syncResultObject => {
|
||||
// Don't persist lastModified value if any conflict or error occured
|
||||
if (!syncResultObject.ok) {
|
||||
return syncResultObject;
|
||||
}
|
||||
// No conflict occured, persist collection's lastModified value
|
||||
return this.db.saveLastModified(syncResultObject.lastModified).then(lastModified => {
|
||||
this._lastModified = lastModified;
|
||||
return syncResultObject;
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure API default remote is reverted if a custom one's been used
|
||||
return (0, _utils.pFinally)(syncPromise, () => this.api.remote = previousRemote);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/bookmark_utils.js");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-sync/main.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CollectionValidator", "CollectionProblemData"];
|
||||
|
||||
class CollectionProblemData {
|
||||
constructor() {
|
||||
this.missingIDs = 0;
|
||||
this.duplicates = [];
|
||||
this.clientMissing = [];
|
||||
this.serverMissing = [];
|
||||
this.serverDeleted = [];
|
||||
this.serverUnexpected = [];
|
||||
this.differences = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a list summarizing problems found. Each entry contains {name, count},
|
||||
* where name is the field name for the problem, and count is the number of times
|
||||
* the problem was encountered.
|
||||
*
|
||||
* Validation has failed if all counts are not 0.
|
||||
*/
|
||||
getSummary() {
|
||||
return [
|
||||
{ name: "clientMissing", count: this.clientMissing.length },
|
||||
{ name: "serverMissing", count: this.serverMissing.length },
|
||||
{ name: "serverDeleted", count: this.serverDeleted.length },
|
||||
{ name: "serverUnexpected", count: this.serverUnexpected.length },
|
||||
{ name: "differences", count: this.differences.length },
|
||||
{ name: "missingIDs", count: this.missingIDs },
|
||||
{ name: "duplicates", count: this.duplicates.length }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionValidator {
|
||||
// Construct a generic collection validator. This is intended to be called by
|
||||
// subclasses.
|
||||
// - name: Name of the engine
|
||||
// - idProp: Property that identifies a record. That is, if a client and server
|
||||
// record have the same value for the idProp property, they should be
|
||||
// compared against eachother.
|
||||
// - props: Array of properties that should be compared
|
||||
constructor(name, idProp, props) {
|
||||
this.name = name;
|
||||
this.props = props;
|
||||
this.idProp = idProp;
|
||||
}
|
||||
|
||||
// Should a custom ProblemData type be needed, return it here.
|
||||
emptyProblemData() {
|
||||
return new CollectionProblemData();
|
||||
}
|
||||
|
||||
getServerItems(engine) {
|
||||
let collection = engine._itemSource();
|
||||
let collectionKey = engine.service.collectionKeys.keyForCollection(engine.name);
|
||||
collection.full = true;
|
||||
let items = [];
|
||||
collection.recordHandler = function(item) {
|
||||
item.decrypt(collectionKey);
|
||||
items.push(item.cleartext);
|
||||
};
|
||||
collection.get();
|
||||
return items;
|
||||
}
|
||||
|
||||
// Should return a promise that resolves to an array of client items.
|
||||
getClientItems() {
|
||||
return Promise.reject("Must implement");
|
||||
}
|
||||
|
||||
// Turn the client item into something that can be compared with the server item,
|
||||
// and is also safe to mutate.
|
||||
normalizeClientItem(item) {
|
||||
return Cu.cloneInto(item, {});
|
||||
}
|
||||
|
||||
// Turn the server item into something that can be easily compared with the client
|
||||
// items.
|
||||
normalizeServerItem(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// Return whether or not a server item should be present on the client. Expected
|
||||
// to be overridden.
|
||||
clientUnderstands(item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return whether or not a client item should be present on the server. Expected
|
||||
// to be overridden
|
||||
syncedByClient(item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare the server item and the client item, and return a list of property
|
||||
// names that are different. Can be overridden if needed.
|
||||
getDifferences(client, server) {
|
||||
let differences = [];
|
||||
for (let prop of this.props) {
|
||||
let clientProp = client[prop];
|
||||
let serverProp = server[prop];
|
||||
if ((clientProp || "") !== (serverProp || "")) {
|
||||
differences.push(prop);
|
||||
}
|
||||
}
|
||||
return differences;
|
||||
}
|
||||
|
||||
// Returns an object containing
|
||||
// problemData: an instance of the class returned by emptyProblemData(),
|
||||
// clientRecords: Normalized client records
|
||||
// records: Normalized server records,
|
||||
// deletedRecords: Array of ids that were marked as deleted by the server.
|
||||
compareClientWithServer(clientItems, serverItems) {
|
||||
clientItems = clientItems.map(item => this.normalizeClientItem(item));
|
||||
serverItems = serverItems.map(item => this.normalizeServerItem(item));
|
||||
let problems = this.emptyProblemData();
|
||||
let seenServer = new Map();
|
||||
let serverDeleted = new Set();
|
||||
let allRecords = new Map();
|
||||
|
||||
for (let record of serverItems) {
|
||||
let id = record[this.idProp];
|
||||
if (!id) {
|
||||
++problems.missingIDs;
|
||||
continue;
|
||||
}
|
||||
if (record.deleted) {
|
||||
serverDeleted.add(record);
|
||||
} else {
|
||||
let possibleDupe = seenServer.get(id);
|
||||
if (possibleDupe) {
|
||||
problems.duplicates.push(id);
|
||||
} else {
|
||||
seenServer.set(id, record);
|
||||
allRecords.set(id, { server: record, client: null, });
|
||||
}
|
||||
record.understood = this.clientUnderstands(record);
|
||||
}
|
||||
}
|
||||
|
||||
let recordPairs = [];
|
||||
let seenClient = new Map();
|
||||
for (let record of clientItems) {
|
||||
let id = record[this.idProp];
|
||||
record.shouldSync = this.syncedByClient(record);
|
||||
seenClient.set(id, record);
|
||||
let combined = allRecords.get(id);
|
||||
if (combined) {
|
||||
combined.client = record;
|
||||
} else {
|
||||
allRecords.set(id, { client: record, server: null });
|
||||
}
|
||||
}
|
||||
|
||||
for (let [id, { server, client }] of allRecords) {
|
||||
if (!client && !server) {
|
||||
throw new Error("Impossible: no client or server record for " + id);
|
||||
} else if (server && !client) {
|
||||
if (server.understood) {
|
||||
problems.clientMissing.push(id);
|
||||
}
|
||||
} else if (client && !server) {
|
||||
if (client.shouldSync) {
|
||||
problems.serverMissing.push(id);
|
||||
}
|
||||
} else {
|
||||
if (!client.shouldSync) {
|
||||
if (!problems.serverUnexpected.includes(id)) {
|
||||
problems.serverUnexpected.push(id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let differences = this.getDifferences(client, server);
|
||||
if (differences && differences.length) {
|
||||
problems.differences.push({ id, differences });
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
problemData: problems,
|
||||
clientRecords: clientItems,
|
||||
records: serverItems,
|
||||
deletedRecords: [...serverDeleted]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,12 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec'];
|
||||
this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec', 'PasswordValidator'];
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/collection_validator.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
|
@ -325,3 +326,46 @@ PasswordTracker.prototype = {
|
|||
}
|
||||
},
|
||||
};
|
||||
|
||||
class PasswordValidator extends CollectionValidator {
|
||||
constructor() {
|
||||
super("passwords", "id", [
|
||||
"hostname",
|
||||
"formSubmitURL",
|
||||
"httpRealm",
|
||||
"password",
|
||||
"passwordField",
|
||||
"username",
|
||||
"usernameField",
|
||||
]);
|
||||
}
|
||||
|
||||
getClientItems() {
|
||||
let logins = Services.logins.getAllLogins({});
|
||||
let syncHosts = Utils.getSyncCredentialsHosts()
|
||||
let result = logins.map(l => l.QueryInterface(Ci.nsILoginMetaInfo))
|
||||
.filter(l => !syncHosts.has(l.hostname));
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
normalizeClientItem(item) {
|
||||
return {
|
||||
id: item.guid,
|
||||
guid: item.guid,
|
||||
hostname: item.hostname,
|
||||
formSubmitURL: item.formSubmitURL,
|
||||
httpRealm: item.httpRealm,
|
||||
password: item.password,
|
||||
passwordField: item.passwordField,
|
||||
username: item.username,
|
||||
usernameField: item.usernameField,
|
||||
original: item,
|
||||
}
|
||||
}
|
||||
|
||||
normalizeServerItem(item) {
|
||||
return Object.assign({ guid: item.id }, item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ EXTRA_JS_MODULES['services-sync'] += [
|
|||
'modules/bookmark_utils.js',
|
||||
'modules/bookmark_validator.js',
|
||||
'modules/browserid_identity.js',
|
||||
'modules/collection_validator.js',
|
||||
'modules/engines.js',
|
||||
'modules/FxaMigrator.jsm',
|
||||
'modules/identity.js',
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://services-sync/engines/passwords.js");
|
||||
|
||||
function getDummyServerAndClient() {
|
||||
return {
|
||||
server: [
|
||||
{
|
||||
id: "11111",
|
||||
guid: "11111",
|
||||
hostname: "https://www.11111.com",
|
||||
formSubmitURL: "https://www.11111.com/login",
|
||||
password: "qwerty123",
|
||||
passwordField: "pass",
|
||||
username: "foobar",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
},
|
||||
{
|
||||
id: "22222",
|
||||
guid: "22222",
|
||||
hostname: "https://www.22222.org",
|
||||
formSubmitURL: "https://www.22222.org/login",
|
||||
password: "hunter2",
|
||||
passwordField: "passwd",
|
||||
username: "baz12345",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
},
|
||||
{
|
||||
id: "33333",
|
||||
guid: "33333",
|
||||
hostname: "https://www.33333.com",
|
||||
formSubmitURL: "https://www.33333.com/login",
|
||||
password: "p4ssw0rd",
|
||||
passwordField: "passwad",
|
||||
username: "quux",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
},
|
||||
],
|
||||
client: [
|
||||
{
|
||||
id: "11111",
|
||||
guid: "11111",
|
||||
hostname: "https://www.11111.com",
|
||||
formSubmitURL: "https://www.11111.com/login",
|
||||
password: "qwerty123",
|
||||
passwordField: "pass",
|
||||
username: "foobar",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
},
|
||||
{
|
||||
id: "22222",
|
||||
guid: "22222",
|
||||
hostname: "https://www.22222.org",
|
||||
formSubmitURL: "https://www.22222.org/login",
|
||||
password: "hunter2",
|
||||
passwordField: "passwd",
|
||||
username: "baz12345",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
|
||||
},
|
||||
{
|
||||
id: "33333",
|
||||
guid: "33333",
|
||||
hostname: "https://www.33333.com",
|
||||
formSubmitURL: "https://www.33333.com/login",
|
||||
password: "p4ssw0rd",
|
||||
passwordField: "passwad",
|
||||
username: "quux",
|
||||
usernameField: "user",
|
||||
httpRealm: null,
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
add_test(function test_valid() {
|
||||
let { server, client } = getDummyServerAndClient();
|
||||
let validator = new PasswordValidator();
|
||||
let { problemData, clientRecords, records, deletedRecords } =
|
||||
validator.compareClientWithServer(client, server);
|
||||
equal(clientRecords.length, 3);
|
||||
equal(records.length, 3)
|
||||
equal(deletedRecords.length, 0);
|
||||
deepEqual(problemData, validator.emptyProblemData());
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_missing() {
|
||||
let validator = new PasswordValidator();
|
||||
{
|
||||
let { server, client } = getDummyServerAndClient();
|
||||
|
||||
client.pop();
|
||||
|
||||
let { problemData, clientRecords, records, deletedRecords } =
|
||||
validator.compareClientWithServer(client, server);
|
||||
|
||||
equal(clientRecords.length, 2);
|
||||
equal(records.length, 3)
|
||||
equal(deletedRecords.length, 0);
|
||||
|
||||
let expected = validator.emptyProblemData();
|
||||
expected.clientMissing.push("33333");
|
||||
deepEqual(problemData, expected);
|
||||
}
|
||||
{
|
||||
let { server, client } = getDummyServerAndClient();
|
||||
|
||||
server.pop();
|
||||
|
||||
let { problemData, clientRecords, records, deletedRecords } =
|
||||
validator.compareClientWithServer(client, server);
|
||||
|
||||
equal(clientRecords.length, 3);
|
||||
equal(records.length, 2)
|
||||
equal(deletedRecords.length, 0);
|
||||
|
||||
let expected = validator.emptyProblemData();
|
||||
expected.serverMissing.push("33333");
|
||||
deepEqual(problemData, expected);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
|
||||
add_test(function test_deleted() {
|
||||
let { server, client } = getDummyServerAndClient();
|
||||
let deletionRecord = { id: "444444", guid: "444444", deleted: true };
|
||||
|
||||
server.push(deletionRecord);
|
||||
let validator = new PasswordValidator();
|
||||
|
||||
let { problemData, clientRecords, records, deletedRecords } =
|
||||
validator.compareClientWithServer(client, server);
|
||||
|
||||
equal(clientRecords.length, 3);
|
||||
equal(records.length, 4);
|
||||
deepEqual(deletedRecords, [deletionRecord]);
|
||||
|
||||
let expected = validator.emptyProblemData();
|
||||
deepEqual(problemData, expected);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
|
@ -170,6 +170,7 @@ skip-if = debug
|
|||
skip-if = debug
|
||||
[test_places_guid_downgrade.js]
|
||||
[test_password_store.js]
|
||||
[test_password_validator.js]
|
||||
[test_password_tracker.js]
|
||||
# Too many intermittent "ASSERTION: thread pool wasn't shutdown: '!mPool'" (bug 804479)
|
||||
skip-if = debug
|
||||
|
|
|
@ -24,6 +24,7 @@ Cu.import("resource://services-sync/constants.js");
|
|||
Cu.import("resource://services-sync/main.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/bookmark_validator.js");
|
||||
Cu.import("resource://services-sync/engines/passwords.js");
|
||||
// TPS modules
|
||||
Cu.import("resource://tps/logger.jsm");
|
||||
|
||||
|
@ -112,6 +113,7 @@ var TPS = {
|
|||
_usSinceEpoch: 0,
|
||||
_requestedQuit: false,
|
||||
shouldValidateBookmarks: false,
|
||||
shouldValidatePasswords: false,
|
||||
|
||||
_init: function TPS__init() {
|
||||
// Check if Firefox Accounts is enabled
|
||||
|
@ -416,6 +418,7 @@ var TPS = {
|
|||
},
|
||||
|
||||
HandlePasswords: function (passwords, action) {
|
||||
this.shouldValidatePasswords = true;
|
||||
try {
|
||||
for (let password of passwords) {
|
||||
let password_id = -1;
|
||||
|
@ -656,14 +659,47 @@ var TPS = {
|
|||
Logger.logInfo("Bookmark validation finished");
|
||||
},
|
||||
|
||||
ValidatePasswords() {
|
||||
let serverRecordDumpStr;
|
||||
try {
|
||||
Logger.logInfo("About to perform password validation");
|
||||
let pwEngine = Weave.Service.engineManager.get("passwords");
|
||||
let validator = new PasswordValidator();
|
||||
let serverRecords = validator.getServerItems(pwEngine);
|
||||
let clientRecords = Async.promiseSpinningly(validator.getClientItems());
|
||||
serverRecordDumpStr = JSON.stringify(serverRecords);
|
||||
|
||||
let { problemData } = validator.compareClientWithServer(clientRecords, serverRecords);
|
||||
|
||||
for (let { name, count } of problemData.getSummary()) {
|
||||
if (count) {
|
||||
Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`);
|
||||
}
|
||||
Logger.AssertEqual(count, 0, `Password validation error of type ${name}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Dump the client records (should always be doable)
|
||||
DumpPasswords();
|
||||
// Dump the server records if gotten them already.
|
||||
if (serverRecordDumpStr) {
|
||||
Logger.logInfo("Server password records:\n" + serverRecordDumpStr + "\n");
|
||||
}
|
||||
this.DumpError("Password validation failed", e);
|
||||
}
|
||||
Logger.logInfo("Password validation finished");
|
||||
},
|
||||
|
||||
RunNextTestAction: function() {
|
||||
try {
|
||||
if (this._currentAction >=
|
||||
this._phaselist[this._currentPhase].length) {
|
||||
// Run necessary validations and then finish up
|
||||
if (this.shouldValidateBookmarks) {
|
||||
// Run bookmark validation and then finish up
|
||||
this.ValidateBookmarks();
|
||||
}
|
||||
if (this.shouldValidatePasswords) {
|
||||
this.ValidatePasswords();
|
||||
}
|
||||
// we're all done
|
||||
Logger.logInfo("test phase " + this._currentPhase + ": " +
|
||||
(this._errors ? "FAIL" : "PASS"));
|
||||
|
@ -1100,6 +1136,9 @@ var Passwords = {
|
|||
},
|
||||
verifyNot: function Passwords__verifyNot(passwords) {
|
||||
this.HandlePasswords(passwords, ACTION_VERIFY_NOT);
|
||||
},
|
||||
skipValidation() {
|
||||
TPS.shouldValidatePasswords = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ task:
|
|||
scopes:
|
||||
- 'docker-worker:cache:level-{{level}}-hg-shared'
|
||||
- 'docker-worker:cache:level-{{level}}-checkouts'
|
||||
- 'secrets:get:project/taskcluster/gecko/hgfingerprint'
|
||||
|
||||
payload:
|
||||
# Thirty minutes should be enough for lint checks
|
||||
|
@ -39,6 +40,9 @@ task:
|
|||
GECKO_HEAD_REPOSITORY: '{{head_repository}}'
|
||||
GECKO_HEAD_REV: '{{head_rev}}'
|
||||
|
||||
features:
|
||||
taskclusterProxy: true
|
||||
|
||||
extra:
|
||||
build_product: '{{build_product}}'
|
||||
build_name: {{build_name}}
|
||||
|
|
|
@ -19,11 +19,16 @@ import argparse
|
|||
import datetime
|
||||
import errno
|
||||
import grp
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
|
||||
FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
|
||||
|
||||
|
||||
def print_line(prefix, m):
|
||||
|
@ -88,8 +93,28 @@ def vcs_checkout(args):
|
|||
print('revision is not specified for checkout')
|
||||
sys.exit(1)
|
||||
|
||||
# Obtain certificate fingerprints.
|
||||
try:
|
||||
print_line(b'vcs', 'fetching hg.mozilla.org fingerprint from %s\n' %
|
||||
FINGERPRINT_URL)
|
||||
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
|
||||
secret = res.read()
|
||||
except urllib2.URLError as e:
|
||||
print('error retrieving hg fingerprint: %s' % e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
secret = json.loads(secret, encoding='utf-8')
|
||||
except ValueError:
|
||||
print('invalid JSON in hg fingerprint secret')
|
||||
sys.exit(1)
|
||||
|
||||
hgmo_fingerprint = secret['secret']['fingerprints'].encode('ascii')
|
||||
|
||||
res = run_and_prefix_output(b'vcs', [
|
||||
b'/usr/bin/hg', b'robustcheckout',
|
||||
b'/usr/bin/hg',
|
||||
b'--config', b'hostsecurity.hg.mozilla.org:fingerprints=%s' % hgmo_fingerprint,
|
||||
b'robustcheckout',
|
||||
b'--sharebase', b'/home/worker/hg-shared',
|
||||
b'--purge',
|
||||
b'--upstream', base_repo,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[mediasource-sequencemode-append-buffer.html]
|
||||
type: testharness
|
||||
prefs: [media.mediasource.enabled:true]
|
||||
[Test sequence AppendMode appendBuffer(first media segment)]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[Test sequence AppendMode appendBuffer(second media segment)]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[Test sequence AppendMode appendBuffer(second media segment, then first media segment)]
|
||||
expected: FAIL
|
||||
|
|
@ -261,8 +261,8 @@
|
|||
removeAppendedDataTests(function(test, mediaSource, sourceBuffer, duration, subType, segmentInfo)
|
||||
{
|
||||
var expectations = {
|
||||
webm: ("{ [3.187, " + duration + ") }"),
|
||||
mp4: ("{ [3.187, " + duration + ") }"),
|
||||
webm: ("{ [3.315, " + duration + ") }"),
|
||||
mp4: ("{ [3.298, " + duration + ") }"),
|
||||
};
|
||||
|
||||
// Note: Range doesn't start exactly at the end of the remove range because there isn't
|
||||
|
@ -274,8 +274,8 @@
|
|||
{
|
||||
var start = Math.max(segmentInfo.media[0].timev, segmentInfo.media[0].timea).toFixed(3);
|
||||
var expectations = {
|
||||
webm: ("{ [" + start + ", 1.012) [3.187, " + duration + ") }"),
|
||||
mp4: ("{ [" + start + ", 0.996) [3.187, " + duration + ") }"),
|
||||
webm: ("{ [" + start + ", 1.005) [3.315, " + duration + ") }"),
|
||||
mp4: ("{ [" + start + ", 0.997) [3.298, " + duration + ") }"),
|
||||
};
|
||||
|
||||
// Note: The first resulting range ends slightly after start because the removal algorithm only removes
|
||||
|
@ -288,7 +288,7 @@
|
|||
{
|
||||
var start = Math.max(segmentInfo.media[0].timev, segmentInfo.media[0].timea).toFixed(3);
|
||||
var expectations = {
|
||||
webm: "{ [" + start + ", 1.029) }",
|
||||
webm: "{ [" + start + ", 1.013) }",
|
||||
mp4: "{ [" + start + ", 1.022) }",
|
||||
};
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
// Prior to EOS, the buffered range end time may not have fully reached the next media
|
||||
// segment's timecode (adjusted by any timestampOffset). It should not exceed it though.
|
||||
// Therefore, an exact assertBufferedEquals() will not work here.
|
||||
assert_equals(sourceBuffer.buffered.length, 1, "sourceBuffer.buffered has 1 range before EOS");
|
||||
assert_greater_than(sourceBuffer.buffered.length, 0, "sourceBuffer.buffered has at least 1 range before EOS");
|
||||
assert_equals(threeDecimalPlaces(sourceBuffer.buffered.start(0)),
|
||||
threeDecimalPlaces(expectedBufferedRangeStartTime),
|
||||
"sourceBuffer.buffered range begins where expected before EOS");
|
||||
|
|
|
@ -1,63 +1,38 @@
|
|||
|
||||
(function(window) {
|
||||
var SEGMENT_INFO_LIST = [
|
||||
{
|
||||
url: 'mp4/test.mp4',
|
||||
type: 'video/mp4; codecs="mp4a.40.2,avc1.4d400d"',
|
||||
duration: 6.0756,
|
||||
init: { offset: 0, size: 1197 },
|
||||
duration: 6.549,
|
||||
init: { offset: 0, size: 1413 },
|
||||
media: [
|
||||
{ offset: 1241, size: 17845, timev: 0.033200, timea: 0, endtimev: 0.531200, endtimea: 0.510839 },
|
||||
{ offset: 19130, size: 5551, timev: 0.464800, timea: 0.510839, endtimev: 0.796800, endtimea: 0.812698 },
|
||||
{ offset: 24725, size: 10944, timev: 0.796800, timea: 0.812698, endtimev: 0.929600, endtimea: 0.905578 },
|
||||
{ offset: 35713, size: 7131, timev: 0.863200, timea: 0.905578, endtimev: 1.195200, endtimea: 1.184217 },
|
||||
{ offset: 42888, size: 2513, timev: 1.128800, timea: 1.184217, endtimev: 1.328000, endtimea: 1.300317 },
|
||||
{ offset: 45457, size: 3022, timev: 1.261600, timea: 1.300317, endtimev: 1.460800, endtimea: 1.509297 },
|
||||
{ offset: 48479, size: 815, timev: 1.494000, timea: 1.509297, endtimev: 1.527200, endtimea: 1.532517 },
|
||||
{ offset: 49338, size: 2818, timev: 1.460800, timea: 1.532517, endtimev: 1.626800, endtimea: 1.648616 },
|
||||
{ offset: 52200, size: 11581, timev: 1.626800, timea: 1.648616, endtimev: 1.792800, endtimea: 1.764716 },
|
||||
{ offset: 63825, size: 3003, timev: 1.726400, timea: 1.764716, endtimev: 1.925600, endtimea: 1.973696 },
|
||||
{ offset: 66872, size: 6390, timev: 1.925600, timea: 1.973696, endtimev: 2.191200, endtimea: 2.159455 },
|
||||
{ offset: 73306, size: 3740, timev: 2.124800, timea: 2.159455, endtimev: 2.390400, endtimea: 2.368435 },
|
||||
{ offset: 77102, size: 11779, timev: 2.324000, timea: 2.368435, endtimev: 2.523200, endtimea: 2.577414 },
|
||||
{ offset: 88881, size: 851, timev: 2.556400, timea: 2.577414, endtimev: 2.589600, endtimea: 2.600634 },
|
||||
{ offset: 89776, size: 4236, timev: 2.523200, timea: 2.600634, endtimev: 2.788800, endtimea: 2.832834 },
|
||||
{ offset: 94056, size: 9538, timev: 2.788800, timea: 2.832834, endtimev: 3.187200, endtimea: 3.204353 },
|
||||
{ offset: 103638, size: 13295, timev: 3.187200, timea: 3.204353, endtimev: 3.452800, endtimea: 3.436553 },
|
||||
{ offset: 116977, size: 309, timev: 3.386400, timea: 3.436553, endtimev: 3.419600, endtimea: 3.506213 },
|
||||
{ offset: 117330, size: 5806, timev: 3.452800, timea: 3.506213, endtimev: 3.784800, endtimea: 3.831292 },
|
||||
{ offset: 123180, size: 4392, timev: 3.784800, timea: 3.831292, endtimev: 4.017200, endtimea: 4.040272 },
|
||||
{ offset: 127616, size: 15408, timev: 4.017200, timea: 4.040272, endtimev: 4.249600, endtimea: 4.295691 },
|
||||
{ offset: 143068, size: 9899, timev: 4.249600, timea: 4.295691, endtimev: 4.814000, endtimea: 4.829750 },
|
||||
{ offset: 153011, size: 11562, timev: 4.814000, timea: 4.829750, endtimev: 4.980000, endtimea: 5.015510 },
|
||||
{ offset: 164617, size: 7398, timev: 4.980000, timea: 5.015510, endtimev: 5.245600, endtimea: 5.294149 },
|
||||
{ offset: 172059, size: 5698, timev: 5.245600, timea: 5.294149, endtimev: 5.577600, endtimea: 5.549569 },
|
||||
{ offset: 177801, size: 11682, timev: 5.511200, timea: 5.549569, endtimev: 5.710400, endtimea: 5.758548 },
|
||||
{ offset: 189527, size: 3023, timev: 5.710400, timea: 5.758548, endtimev: 5.909600, endtimea: 5.897868 },
|
||||
{ offset: 192594, size: 5726, timev: 5.843200, timea: 5.897868, endtimev: 6.075600, endtimea: 6.037188 },
|
||||
{ offset: 1413, size: 24034, timev: 0.095000, timea: 0, endtimev: 0.896666, endtimea: 0.882358 },
|
||||
{ offset: 25447, size: 21757, timev: 0.896666, timea: 0.882358, endtimev: 1.696666, endtimea: 1.671836 },
|
||||
{ offset: 47204, size: 23591, timev: 1.696666, timea: 1.671836, endtimev: 2.498333, endtimea: 2.461315 },
|
||||
{ offset: 70795, size: 22614, timev: 2.498333, timea: 2.461315, endtimev: 3.298333, endtimea: 3.297233 },
|
||||
{ offset: 93409, size: 18353, timev: 3.298333, timea: 3.297233, endtimev: 4.100000, endtimea: 4.086712},
|
||||
{ offset: 111762, size: 23935, timev: 4.100000, timea: 4.086712, endtimev: 4.900000, endtimea: 4.876190 },
|
||||
{ offset: 135697, size: 21911, timev: 4.900000, timea: 4.876190, endtimev: 5.701666, endtimea: 5.665668 },
|
||||
{ offset: 157608, size: 23776, timev: 5.701666, timea: 5.665668, endtimev: 6.501666, endtimea: 6.501587 },
|
||||
{ offset: 181384, size: 5843, timev: 6.501666, timea: 6.501587, endtimev: 6.501666, endtimea: 6.501678 },
|
||||
]
|
||||
},
|
||||
{
|
||||
url: 'webm/test.webm',
|
||||
type: 'video/webm; codecs="vp8, vorbis"',
|
||||
duration: 6.042,
|
||||
init: { offset: 0, size: 4357 },
|
||||
duration: 6.552,
|
||||
init: { offset: 0, size: 4116 },
|
||||
media: [
|
||||
{ offset: 4357, size: 11830, timev: 0, timea: 0, endtimev: 0.398000, endtimea: 0.384000 },
|
||||
{ offset: 16187, size: 12588, timev: 0.398000, timea: 0.385000, endtimev: 0.798000, endtimea: 0.779000 },
|
||||
{ offset: 28775, size: 14588, timev: 0.797000, timea: 0.779000, endtimev: 1.195000, endtimea: 1.174000 },
|
||||
{ offset: 43363, size: 13023, timev: 1.195000, timea: 1.174000, endtimev: 1.593000, endtimea: 1.592000 },
|
||||
{ offset: 56386, size: 13127, timev: 1.594000, timea: 1.592000, endtimev: 1.992000, endtimea: 1.988000 },
|
||||
{ offset: 69513, size: 14456, timev: 1.992000, timea: 1.987000, endtimev: 2.390000, endtimea: 2.381000 },
|
||||
{ offset: 83969, size: 13458, timev: 2.390000, timea: 2.381000, endtimev: 2.790000, endtimea: 2.776000 },
|
||||
{ offset: 97427, size: 14566, timev: 2.789000, timea: 2.776000, endtimev: 3.187000, endtimea: 3.171000 },
|
||||
{ offset: 111993, size: 13201, timev: 3.187000, timea: 3.171000, endtimev: 3.585000, endtimea: 3.565000 },
|
||||
{ offset: 125194, size: 14061, timev: 3.586000, timea: 3.566000, endtimev: 3.984000, endtimea: 3.960000 },
|
||||
{ offset: 139255, size: 15353, timev: 3.984000, timea: 3.960000, endtimev: 4.382000, endtimea: 4.378000 },
|
||||
{ offset: 154608, size: 13618, timev: 4.382000, timea: 4.378000, endtimev: 4.782000, endtimea: 4.773000 },
|
||||
{ offset: 168226, size: 15094, timev: 4.781000, timea: 4.773000, endtimev: 5.179000, endtimea: 5.169000 },
|
||||
{ offset: 183320, size: 13069, timev: 5.179000, timea: 5.168000, endtimev: 5.577000, endtimea: 5.562000 },
|
||||
{ offset: 196389, size: 13788, timev: 5.578000, timea: 5.563000, endtimev: 5.976000, endtimea: 5.957000 },
|
||||
{ offset: 210177, size: 9009, timev: 5.976000, timea: 5.957000, endtimev: 6.042000, endtimea: 6.050000 },
|
||||
{ offset: 4116, size: 26583, timev: 0.112000, timea: 0, endtimev: 0.913000, endtimea: 0.912000 },
|
||||
{ offset: 30699, size: 20555, timev: 0.913000, timea: 0.912000, endtimev: 1.714000, endtimea: 1.701000 },
|
||||
{ offset: 51254, size: 22668, timev: 1.714000, timea: 1.701000, endtimev: 2.515000, endtimea: 2.514000 },
|
||||
{ offset: 73922, size: 21943, timev: 2.515000, timea: 2.514000, endtimev: 3.315000, endtimea: 3.303000 },
|
||||
{ offset: 95865, size: 23015, timev: 3.315000, timea: 3.303000, endtimev: 4.116000, endtimea: 4.093000},
|
||||
{ offset: 118880, size: 20406, timev: 4.116000, timea: 4.093000, endtimev: 4.917000, endtimea: 4.906000 },
|
||||
{ offset: 139286, size: 21537, timev: 4.917000, timea: 4.906000, endtimev: 5.718000, endtimea: 5.695000 },
|
||||
{ offset: 160823, size: 24027, timev: 5.718000, timea: 5.695000, endtimev: 6.519000, endtimea: 6.508000 },
|
||||
{ offset: 184850, size: 5955, timev: 6.519000, timea: 6.508000, endtimev: 6.577000, endtimea: 6.577000},
|
||||
],
|
||||
}
|
||||
];
|
||||
|
|
Двоичные данные
testing/web-platform/tests/media-source/mp4/test.mp4
Двоичные данные
testing/web-platform/tests/media-source/mp4/test.mp4
Двоичный файл не отображается.
Двоичные данные
testing/web-platform/tests/media-source/webm/test.webm
Двоичные данные
testing/web-platform/tests/media-source/webm/test.webm
Двоичный файл не отображается.
|
@ -269,22 +269,22 @@ var ExtensionTestUtils = {
|
|||
|
||||
addonManagerStarted: false,
|
||||
|
||||
startAddonManager() {
|
||||
if (this.addonManagerStarted) {
|
||||
return;
|
||||
}
|
||||
this.addonManagerStarted = true;
|
||||
|
||||
let appInfo = {};
|
||||
Cu.import("resource://testing-common/AppInfo.jsm", appInfo);
|
||||
|
||||
appInfo.updateAppInfo({
|
||||
mockAppInfo() {
|
||||
const {updateAppInfo} = Cu.import("resource://testing-common/AppInfo.jsm", {});
|
||||
updateAppInfo({
|
||||
ID: "xpcshell@tests.mozilla.org",
|
||||
name: "XPCShell",
|
||||
version: "48",
|
||||
platformVersion: "48",
|
||||
});
|
||||
},
|
||||
|
||||
startAddonManager() {
|
||||
if (this.addonManagerStarted) {
|
||||
return;
|
||||
}
|
||||
this.addonManagerStarted = true;
|
||||
this.mockAppInfo();
|
||||
|
||||
let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
|
||||
.QueryInterface(Ci.nsITimerCallback);
|
||||
|
|
|
@ -2,8 +2,24 @@
|
|||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
|
||||
const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
|
||||
return stringSvc.createBundle("chrome://global/locale/extensions.properties");
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "promptService",
|
||||
"@mozilla.org/embedcomp/prompt-service;1",
|
||||
"nsIPromptService");
|
||||
|
||||
function _(key, ...args) {
|
||||
if (args.length) {
|
||||
return strBundle.formatStringFromName(key, args, args.length);
|
||||
}
|
||||
return strBundle.GetStringFromName(key);
|
||||
}
|
||||
|
||||
function installType(addon) {
|
||||
if (addon.temporarilyInstalled) {
|
||||
|
@ -52,11 +68,42 @@ extensions.registerSchemaAPI("management", "addon_parent", context => {
|
|||
}
|
||||
|
||||
resolve(extInfo);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
uninstallSelf: function(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options && options.showConfirmDialog) {
|
||||
let message = _("uninstall.confirmation.message", extension.name);
|
||||
if (options.dialogMessage) {
|
||||
message = `${options.dialogMessage}\n${message}`;
|
||||
}
|
||||
let title = _("uninstall.confirmation.title", extension.name);
|
||||
let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING +
|
||||
promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING;
|
||||
let button0Title = _("uninstall.confirmation.button-0.label");
|
||||
let button1Title = _("uninstall.confirmation.button-1.label");
|
||||
let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0});
|
||||
if (response == 1) {
|
||||
return reject({message: "User cancelled uninstall of extension"});
|
||||
}
|
||||
}
|
||||
AddonManager.getAddonByID(extension.id, addon => {
|
||||
let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
|
||||
if (!canUninstall) {
|
||||
return reject({message: "The add-on cannot be uninstalled"});
|
||||
}
|
||||
try {
|
||||
addon.uninstall();
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -76,6 +76,12 @@ extensions.registerSchemaAPI("runtime", "addon_parent", context => {
|
|||
return context.lastError;
|
||||
},
|
||||
|
||||
getBrowserInfo: function() {
|
||||
const {name, vendor, version, appBuildID} = Services.appinfo;
|
||||
const info = {name, vendor, version, buildID: appBuildID};
|
||||
return Promise.resolve(info);
|
||||
},
|
||||
|
||||
getPlatformInfo: function() {
|
||||
return Promise.resolve(ExtensionUtils.PlatformInfo);
|
||||
},
|
||||
|
|
|
@ -217,7 +217,6 @@
|
|||
{
|
||||
"name": "uninstallSelf",
|
||||
"type": "function",
|
||||
"unsupported": true,
|
||||
"description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
|
@ -230,6 +229,11 @@
|
|||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
|
||||
},
|
||||
"dialogMessage": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "The message to display to a user when being asked to confirm removal of the extension."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -89,6 +89,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BrowserInfo",
|
||||
"type": "object",
|
||||
"description": "An object containing information about the current browser.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the browser, for example 'Firefox'."
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "The name of the browser vendor, for example 'Mozilla'."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The browser's version, for example '42.0.0' or '0.8.1pre'."
|
||||
},
|
||||
"buildID": {
|
||||
"type": "string",
|
||||
"description": "The browser's build ID/date, for example '20160101'."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "RequestUpdateCheckStatus",
|
||||
"type": "string",
|
||||
|
@ -367,6 +390,25 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getBrowserInfo",
|
||||
"type": "function",
|
||||
"description": "Returns information about the current browser.",
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "callback",
|
||||
"description": "Called with results",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "browserInfo",
|
||||
"$ref": "BrowserInfo"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getPlatformInfo",
|
||||
"type": "function",
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://testing-common/AddonTestUtils.jsm");
|
||||
Cu.import("resource://testing-common/MockRegistrar.jsm");
|
||||
|
||||
const {promiseAddonByID} = AddonTestUtils;
|
||||
const id = "uninstall_self_test@tests.mozilla.com";
|
||||
|
||||
const manifest = {
|
||||
applications: {
|
||||
gecko: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
name: "test extension name",
|
||||
version: "1.0",
|
||||
};
|
||||
|
||||
const waitForUninstalled = new Promise(resolve => {
|
||||
const listener = {
|
||||
onUninstalled: (addon) => {
|
||||
equal(addon.id, id, "The expected add-on has been uninstalled");
|
||||
AddonManager.getAddonByID(addon.id, checkedAddon => {
|
||||
equal(checkedAddon, null, "Add-on no longer exists");
|
||||
AddonManager.removeAddonListener(listener);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
};
|
||||
AddonManager.addAddonListener(listener);
|
||||
});
|
||||
|
||||
let promptService = {
|
||||
_response: null,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
|
||||
confirmEx: function(...args) {
|
||||
this._confirmExArgs = args;
|
||||
return this._response;
|
||||
},
|
||||
};
|
||||
|
||||
add_task(function* setup() {
|
||||
let fakePromptService = MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService);
|
||||
do_register_cleanup(() => {
|
||||
MockRegistrar.unregister(fakePromptService);
|
||||
});
|
||||
yield ExtensionTestUtils.startAddonManager();
|
||||
});
|
||||
|
||||
add_task(function* test_management_uninstall_no_prompt() {
|
||||
function background() {
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
browser.management.uninstallSelf();
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest,
|
||||
background,
|
||||
useAddonManager: "temporary",
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
let addon = yield promiseAddonByID(id);
|
||||
notEqual(addon, null, "Add-on is installed");
|
||||
extension.sendMessage("uninstall");
|
||||
yield waitForUninstalled;
|
||||
yield extension.markUnloaded();
|
||||
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
|
||||
});
|
||||
|
||||
add_task(function* test_management_uninstall_prompt_uninstall() {
|
||||
promptService._response = 0;
|
||||
|
||||
function background() {
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
browser.management.uninstallSelf({showConfirmDialog: true});
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest,
|
||||
background,
|
||||
useAddonManager: "temporary",
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
let addon = yield promiseAddonByID(id);
|
||||
notEqual(addon, null, "Add-on is installed");
|
||||
extension.sendMessage("uninstall");
|
||||
yield waitForUninstalled;
|
||||
yield extension.markUnloaded();
|
||||
|
||||
// Test localization strings
|
||||
equal(promptService._confirmExArgs[1], `Uninstall ${manifest.name}`);
|
||||
equal(promptService._confirmExArgs[2],
|
||||
`The extension “${manifest.name}” is requesting to be uninstalled. What would you like to do?`);
|
||||
equal(promptService._confirmExArgs[4], "Uninstall");
|
||||
equal(promptService._confirmExArgs[5], "Keep Installed");
|
||||
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
|
||||
});
|
||||
|
||||
add_task(function* test_management_uninstall_prompt_keep() {
|
||||
promptService._response = 1;
|
||||
|
||||
function background() {
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
browser.management.uninstallSelf({showConfirmDialog: true}).then(() => {
|
||||
browser.test.fail("uninstallSelf rejects when user declines uninstall");
|
||||
}, error => {
|
||||
browser.test.assertEq("User cancelled uninstall of extension",
|
||||
error.message,
|
||||
"Expected rejection when user declines uninstall");
|
||||
browser.test.sendMessage("uninstall-rejected");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest,
|
||||
background,
|
||||
useAddonManager: "temporary",
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
let addon = yield promiseAddonByID(id);
|
||||
notEqual(addon, null, "Add-on is installed");
|
||||
extension.sendMessage("uninstall");
|
||||
yield extension.awaitMessage("uninstall-rejected");
|
||||
addon = yield promiseAddonByID(id);
|
||||
notEqual(addon, null, "Add-on remains installed");
|
||||
yield extension.unload();
|
||||
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
add_task(function* setup() {
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
});
|
||||
|
||||
add_task(function* test_getBrowserInfo() {
|
||||
function background() {
|
||||
browser.runtime.getBrowserInfo().then(info => {
|
||||
browser.test.assertEq(info.name, "XPCShell", "name is valid");
|
||||
browser.test.assertEq(info.vendor, "Mozilla", "vendor is Mozilla");
|
||||
browser.test.assertEq(info.version, "48", "version is correct");
|
||||
browser.test.assertEq(info.buildID, "20160315", "buildID is correct");
|
||||
|
||||
browser.test.notifyPass("runtime.getBrowserInfo");
|
||||
});
|
||||
}
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({background});
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("runtime.getBrowserInfo");
|
||||
yield extension.unload();
|
||||
});
|
|
@ -38,11 +38,13 @@ skip-if = release_build
|
|||
[test_ext_json_parser.js]
|
||||
[test_ext_localStorage.js]
|
||||
[test_ext_management.js]
|
||||
[test_ext_management_uninstall_self.js]
|
||||
[test_ext_manifest_content_security_policy.js]
|
||||
[test_ext_manifest_incognito.js]
|
||||
[test_ext_manifest_minimum_chrome_version.js]
|
||||
[test_ext_onmessage_removelistener.js]
|
||||
[test_ext_runtime_connect_no_receiver.js]
|
||||
[test_ext_runtime_getBrowserInfo.js]
|
||||
[test_ext_runtime_getPlatformInfo.js]
|
||||
[test_ext_runtime_sendMessage.js]
|
||||
[test_ext_runtime_sendMessage_errors.js]
|
||||
|
|
|
@ -4493,6 +4493,16 @@
|
|||
"n_buckets": 20,
|
||||
"description": "Firefox: If the spinner interstitial displays during tab switching, records the time in ms the graphic is visible"
|
||||
},
|
||||
"FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS": {
|
||||
"expires_in_version": "54",
|
||||
"kind": "exponential",
|
||||
"low": 1000,
|
||||
"high": 64000,
|
||||
"n_buckets": 7,
|
||||
"bug_numbers": [1301104],
|
||||
"alert_emails": ["mconley@mozilla.com"],
|
||||
"description": "Firefox: If the spinner interstitial displays during tab switching, records the time in ms the graphic is visible. This probe is similar to FX_TAB_SWITCH_SPINNER_VISIBLE_MS, but is for truly degenerate cases."
|
||||
},
|
||||
"FX_TAB_CLICK_MS": {
|
||||
"expires_in_version": "default",
|
||||
"kind": "exponential",
|
||||
|
|
|
@ -401,7 +401,7 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
|
|||
// "saving states to HashStore".
|
||||
let statePrefName = "browser.safebrowsing.provider.google4.state." + listName;
|
||||
let stateBase64 = this.prefs_.getPref(statePrefName, "");
|
||||
stateArray.push(stateBase64 ? atob(stateBase64) : "");
|
||||
stateArray.push(stateBase64);
|
||||
});
|
||||
|
||||
let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
|
||||
|
|
|
@ -54,12 +54,12 @@ interface nsIUrlClassifierUtils : nsISupports
|
|||
* Make update request for given lists and their states.
|
||||
*
|
||||
* @param aListNames An array of list name represented in string.
|
||||
* @param aState An array of states (in string) for each list.
|
||||
* @param aState An array of states (encoded in base64 format) for each list.
|
||||
* @param aCount The array length of aList and aState.
|
||||
*
|
||||
* @returns A string to store request. Not null-terminated.
|
||||
*/
|
||||
ACString makeUpdateRequestV4([array, size_is(aCount)] in string aListNames,
|
||||
[array, size_is(aCount)] in string aStates,
|
||||
[array, size_is(aCount)] in string aStatesBase64,
|
||||
in uint32_t aCount);
|
||||
};
|
||||
|
|
|
@ -100,7 +100,7 @@ typedef FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints Constraints;
|
|||
|
||||
static void
|
||||
InitListUpdateRequest(ThreatType aThreatType,
|
||||
const char* aState,
|
||||
const char* aStateBase64,
|
||||
ListUpdateRequest* aListUpdateRequest)
|
||||
{
|
||||
aListUpdateRequest->set_threat_type(aThreatType);
|
||||
|
@ -114,8 +114,12 @@ InitListUpdateRequest(ThreatType aThreatType,
|
|||
aListUpdateRequest->set_allocated_constraints(contraints);
|
||||
|
||||
// Only set non-empty state.
|
||||
if (aState[0] != '\0') {
|
||||
aListUpdateRequest->set_state(aState);
|
||||
if (aStateBase64[0] != '\0') {
|
||||
nsCString stateBinary;
|
||||
nsresult rv = Base64Decode(nsCString(aStateBase64), stateBinary);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aListUpdateRequest->set_state(stateBinary.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,7 +271,7 @@ nsUrlClassifierUtils::GetProtocolVersion(const nsACString& aProvider,
|
|||
|
||||
NS_IMETHODIMP
|
||||
nsUrlClassifierUtils::MakeUpdateRequestV4(const char** aListNames,
|
||||
const char** aStates,
|
||||
const char** aStatesBase64,
|
||||
uint32_t aCount,
|
||||
nsACString &aRequest)
|
||||
{
|
||||
|
@ -284,7 +288,7 @@ nsUrlClassifierUtils::MakeUpdateRequestV4(const char** aListNames,
|
|||
continue; // Unknown list name.
|
||||
}
|
||||
auto lur = r.mutable_list_update_requests()->Add();
|
||||
InitListUpdateRequest(static_cast<ThreatType>(threatType), aStates[i], lur);
|
||||
InitListUpdateRequest(static_cast<ThreatType>(threatType), aStatesBase64[i], lur);
|
||||
}
|
||||
|
||||
// Then serialize.
|
||||
|
|
|
@ -154,7 +154,7 @@ add_test(function test_partialUpdateV4() {
|
|||
// test_update_all_tables, this update request should send
|
||||
// a partial update to the server.
|
||||
let requestV4 = gUrlUtils.makeUpdateRequestV4([TEST_TABLE_DATA_V4.tableName],
|
||||
[NEW_CLIENT_STATE],
|
||||
[btoa(NEW_CLIENT_STATE)],
|
||||
1);
|
||||
gExpectedQueryV4 = "&$req=" + btoa(requestV4);
|
||||
|
||||
|
@ -311,24 +311,12 @@ function readFileToString(aFilename) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
function buildUpdateRequestV4InBase64() {
|
||||
|
||||
let request = urlUtils.makeUpdateRequestV4([TEST_TABLE_DATA_V4.tableName],
|
||||
[""],
|
||||
1);
|
||||
return btoa(request);
|
||||
}
|
||||
|
||||
function waitUntilStateSavedToPref(expectedState, callback) {
|
||||
const STATE_PREF_NAME_PREFIX = 'browser.safebrowsing.provider.google4.state.';
|
||||
|
||||
let stateBase64 = '';
|
||||
|
||||
try {
|
||||
// The reason we get pref from 'googpub-phish-proto' instead of
|
||||
// 'test-phish-proto' is 'googpub-phish-proto' would be returned
|
||||
// while we look up the list name from SOCIAL_ENGINEERING_PUBLIC.
|
||||
// See nsUrlClassifierUtils::THREAT_TYPE_CONV_TABLE.
|
||||
stateBase64 =
|
||||
prefBranch.getCharPref(STATE_PREF_NAME_PREFIX + 'test-phish-proto');
|
||||
} catch (e) {}
|
||||
|
|
|
@ -136,11 +136,15 @@ def read_output(*args):
|
|||
class HGRepoInfo:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
rev = read_output('hg', '-R', path,
|
||||
'parent', '--template={node|short}')
|
||||
# Look for the default hg path. If SRVSRV_ROOT is set, we
|
||||
|
||||
rev = os.environ.get('MOZ_SOURCE_CHANGESET')
|
||||
if not rev:
|
||||
rev = read_output('hg', '-R', path,
|
||||
'parent', '--template={node|short}')
|
||||
|
||||
# Look for the default hg path. If MOZ_SOURCE_REPO is set, we
|
||||
# don't bother asking hg.
|
||||
hg_root = os.environ.get("SRCSRV_ROOT")
|
||||
hg_root = os.environ.get('MOZ_SOURCE_REPO')
|
||||
if hg_root:
|
||||
root = hg_root
|
||||
else:
|
||||
|
@ -158,7 +162,7 @@ class HGRepoInfo:
|
|||
if cleanroot is None:
|
||||
print >> sys.stderr, textwrap.dedent("""\
|
||||
Could not determine repo info for %s. This is either not a clone of the web-based
|
||||
repository, or you have not specified SRCSRV_ROOT, or the clone is corrupt.""") % path
|
||||
repository, or you have not specified MOZ_SOURCE_REPO, or the clone is corrupt.""") % path
|
||||
sys.exit(1)
|
||||
self.rev = rev
|
||||
self.root = root
|
||||
|
@ -205,7 +209,7 @@ class GitRepoInfo:
|
|||
if cleanroot is None:
|
||||
print >> sys.stderr, textwrap.dedent("""\
|
||||
Could not determine repo info for %s (%s). This is either not a clone of a web-based
|
||||
repository, or you have not specified SRCSRV_ROOT, or the clone is corrupt.""") % (path, root)
|
||||
repository, or you have not specified MOZ_SOURCE_REPO, or the clone is corrupt.""") % (path, root)
|
||||
sys.exit(1)
|
||||
self.rev = rev
|
||||
self.cleanroot = cleanroot
|
||||
|
@ -614,7 +618,7 @@ class Dumper:
|
|||
|
||||
# tries to get the vcs root from the .mozconfig first - if it's not set
|
||||
# the tinderbox vcs path will be assigned further down
|
||||
vcs_root = os.environ.get("SRCSRV_ROOT")
|
||||
vcs_root = os.environ.get('MOZ_SOURCE_REPO')
|
||||
for arch_num, arch in enumerate(self.archs):
|
||||
self.files_record[files] = 0 # record that we submitted jobs for this tuple of files
|
||||
self.SubmitJob(files[-1], 'ProcessFilesWork', args=(files, arch_num, arch, vcs_root, after, after_arg), callback=self.ProcessFilesFinished)
|
||||
|
|
|
@ -59,6 +59,13 @@ class HelperMixin(object):
|
|||
symbolstore.srcdirRepoInfo = {}
|
||||
symbolstore.vcsFileInfoCache = {}
|
||||
|
||||
# Remove environment variables that can influence tests.
|
||||
for e in ('MOZ_SOURCE_CHANGESET', 'MOZ_SOURCE_REPO'):
|
||||
try:
|
||||
del os.environ[e]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
symbolstore.srcdirRepoInfo = {}
|
||||
|
@ -264,6 +271,16 @@ class TestGetVCSFilename(HelperMixin, unittest.TestCase):
|
|||
self.assertEqual("hg:example.com/other:bar.c:0987ffff",
|
||||
symbolstore.GetVCSFilename(filename2, [srcdir1, srcdir2])[0])
|
||||
|
||||
def testVCSFilenameEnv(self):
|
||||
# repo URL and changeset read from environment variables if defined.
|
||||
os.environ['MOZ_SOURCE_REPO'] = 'https://somewhere.com/repo'
|
||||
os.environ['MOZ_SOURCE_CHANGESET'] = 'abcdef0123456'
|
||||
os.mkdir(os.path.join(self.test_dir, '.hg'))
|
||||
filename = os.path.join(self.test_dir, 'foo.c')
|
||||
self.assertEqual('hg:somewhere.com/repo:foo.c:abcdef0123456',
|
||||
symbolstore.GetVCSFilename(filename, [self.test_dir])[0])
|
||||
|
||||
|
||||
class TestRepoManifest(HelperMixin, unittest.TestCase):
|
||||
def testRepoManifest(self):
|
||||
manifest = os.path.join(self.test_dir, "sources.xml")
|
||||
|
|
|
@ -18,3 +18,12 @@ csp.error.missing-source = ‘%1$S’ must include the source %2$S
|
|||
|
||||
#LOCALIZATION NOTE (csp.error.illegal-host-wildcard) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
|
||||
csp.error.illegal-host-wildcard = %2$S: wildcard sources in ‘%1$S’ directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)
|
||||
|
||||
#LOCALIZATION NOTE (uninstall.confirmation.title) %S is the name of the extension which is about to be uninstalled.
|
||||
uninstall.confirmation.title = Uninstall %S
|
||||
|
||||
#LOCALIZATION NOTE (uninstall.confirmation.message) %S is the name of the extension which is about to be uninstalled.
|
||||
uninstall.confirmation.message = The extension “%S” is requesting to be uninstalled. What would you like to do?
|
||||
|
||||
uninstall.confirmation.button-0.label = Uninstall
|
||||
uninstall.confirmation.button-1.label = Keep Installed
|
||||
|
|
|
@ -41,7 +41,6 @@ const kModalStyle = `
|
|||
.findbar-modalHighlight-outline {
|
||||
position: absolute;
|
||||
background: #ffc535;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 0 0 rgba(0,0,0,.1);
|
||||
color: #000;
|
||||
display: -moz-box;
|
||||
|
@ -115,6 +114,11 @@ const kModalStyle = `
|
|||
|
||||
.findbar-modalHighlight-outlineMask[brighttext] > .findbar-modalHighlight-rect {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.findbar-modalHighlight-outline,
|
||||
.findbar-modalHighlight-rect {
|
||||
border-radius: 3px;
|
||||
}`;
|
||||
|
||||
function mockAnonymousContentNode(domNode) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче