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

- Bug 1246051 - have MediaQueue<T>::Peek/PeekFront return a RefPtr<> to avoid dangling pointers per comment 0. r=gerald. (00f334efb1)
- Bug 1264199: P1. Perform audio conversion in the MSDM taskqueue and ahead of use. r=kinetik (001936e3ea)
- Bug 1267983 - include MediaQueue.h; r=jwwang (036107d765)
- Bug 1264199: P0. Fix nsDequeue/MediaQueue methods constness. r=jwwang (9aa33dfcb5)
- Bug 1264199: P0.1. Export SaferMultDiv method. r=gerald (0b7a35ae4d)
- Bug 1264199: P2. Ensure the AudioStream only ever receive the same content format. r=kinetik (a180d09279)
- Bug 1264199: P3. Attempt to minimize audio quality loss and unnecessary processing. r=kinetik (29d57b5a33)
- Bug 1264199: P4. Add mono to stereo upmix to AudioConverter. r=rillian (49c029bd86)
- Bug 1264199: P5. Perform all downmixing operations in DecodedAudioDataSink. r=kinetik (05a479f095)
- Bug 1264199: P6. Drain resampler when changing format or reaching the end. r=kinetik (8639102a94)
- Bug 1264199: P8. Handle potential resampling errors. r=kinetik (1267e4e73d)
- Bug 1264199: P9. Include pending frames in HasUnplayedFrames calculation. r=jwwang (ce7097fc90)
This commit is contained in:
2024-08-28 23:32:14 +08:00
parent f32bf3ebba
commit 10f5941b9c
54 changed files with 3454 additions and 187 deletions
+3
View File
@@ -1090,6 +1090,9 @@ pref("dom.mozSettings.SettingsService.verbose.enabled", false);
// readwrite.
pref("dom.mozSettings.allowForceReadOnly", false);
// RequestSync API is enabled by default on B2G.
pref("dom.requestSync.enabled", true);
// Comma separated list of activity names that can only be provided by
// the system app in dev mode.
pref("dom.activities.developer_mode_only", "import-app");
+1
View File
@@ -17,6 +17,7 @@ Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
Cu.import('resource://gre/modules/Keyboard.jsm');
Cu.import('resource://gre/modules/ErrorPage.jsm');
Cu.import('resource://gre/modules/AlertsHelper.jsm');
Cu.import('resource://gre/modules/RequestSyncService.jsm');
Cu.import('resource://gre/modules/SystemUpdateService.jsm');
#ifdef MOZ_WIDGET_GONK
Cu.import('resource://gre/modules/MultiscreenHandler.jsm');
+3
View File
@@ -383,6 +383,9 @@
@RESPATH@/components/zipwriter.xpt
; JavaScript components
@RESPATH@/components/RequestSync.manifest
@RESPATH@/components/RequestSyncManager.js
@RESPATH@/components/RequestSyncScheduler.js
@RESPATH@/components/ChromeNotifications.js
@RESPATH@/components/ChromeNotifications.manifest
@RESPATH@/components/ConsoleAPI.manifest
+4
View File
@@ -549,6 +549,10 @@
@RESPATH@/components/htmlMenuBuilder.js
@RESPATH@/components/htmlMenuBuilder.manifest
@RESPATH@/components/RequestSync.manifest
@RESPATH@/components/RequestSyncManager.js
@RESPATH@/components/RequestSyncScheduler.js
@RESPATH@/components/PermissionSettings.js
@RESPATH@/components/PermissionSettings.manifest
@RESPATH@/components/ContactManager.js
+5
View File
@@ -472,6 +472,11 @@ this.PermissionsTable = { geolocation: {
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
"requestsync-manager": {
app: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
"secureelement-manage": {
app: DENY_ACTION,
privileged: DENY_ACTION,
+7 -1
View File
@@ -1,5 +1,11 @@
/**
* Support logic to run a test file as an installed app.
* Support logic to run a test file as an installed app. This file is derived
* from dom/requestsync/tests/test_basic_app.html but uses
* DOMApplicationRegistry in a chrome script (shim_app_as_test_chrome.js) to
* directly install the apps instead of mozApps.install because mozApps.install
* can't install privileged/certified apps. (This is the same mechanism used by
* the Firefox OS Gaia email app's backend test runner.)
*
* You really only want to do this if your test cares about the app's origin
* or you REALLY want to double-check AvailableIn and other WebIDL-provided
* security mechanisms.
+116 -29
View File
@@ -7,6 +7,7 @@
#include "AudioConverter.h"
#include <string.h>
#include <speex/speex_resampler.h>
#include <cmath>
/*
* Parts derived from MythTV AudioConvert Class
@@ -26,25 +27,13 @@ AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
MOZ_DIAGNOSTIC_ASSERT(aIn.Format() == aOut.Format() &&
aIn.Interleaved() == aOut.Interleaved(),
"No format or rate conversion is supported at this stage");
MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
MOZ_DIAGNOSTIC_ASSERT(aOut.Channels() <= 2 ||
aIn.Channels() == aOut.Channels(),
"Only downmixing to mono or stereo is supported at this stage");
"Only down/upmixing to mono or stereo is supported at this stage");
MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported");
mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap);
if (aIn.Rate() != aOut.Rate()) {
int error;
mResampler = speex_resampler_init(aOut.Channels(),
aIn.Rate(),
aOut.Rate(),
SPEEX_RESAMPLER_QUALITY_DEFAULT,
&error);
if (error == RESAMPLER_ERR_SUCCESS) {
speex_resampler_skip_zeros(mResampler);
} else {
NS_WARNING("Failed to initialize resampler.");
mResampler = nullptr;
}
RecreateResampler();
}
}
@@ -60,6 +49,7 @@ bool
AudioConverter::CanWorkInPlace() const
{
bool needDownmix = mIn.Channels() > mOut.Channels();
bool needUpmix = mIn.Channels() < mOut.Channels();
bool canDownmixInPlace =
mIn.Channels() * AudioConfig::SampleSize(mIn.Format()) >=
mOut.Channels() * AudioConfig::SampleSize(mOut.Format());
@@ -68,7 +58,7 @@ AudioConverter::CanWorkInPlace() const
// We should be able to work in place if 1s of audio input takes less space
// than 1s of audio output. However, as we downmix before resampling we can't
// perform any upsampling in place (e.g. if incoming rate >= outgoing rate)
return (!needDownmix || canDownmixInPlace) &&
return !needUpmix && (!needDownmix || canDownmixInPlace) &&
(!needResample || canResampleInPlace);
}
@@ -77,8 +67,9 @@ AudioConverter::ProcessInternal(void* aOut, const void* aIn, size_t aFrames)
{
if (mIn.Channels() > mOut.Channels()) {
return DownmixAudio(aOut, aIn, aFrames);
} else if (mIn.Layout() != mOut.Layout() &&
CanReorderAudio()) {
} else if (mIn.Channels() < mOut.Channels()) {
return UpmixAudio(aOut, aIn, aFrames);
} else if (mIn.Layout() != mOut.Layout() && CanReorderAudio()) {
ReOrderInterleavedChannels(aOut, aIn, aFrames);
} else if (aIn != aOut) {
memmove(aOut, aIn, FramesOutToBytes(aFrames));
@@ -114,10 +105,7 @@ AudioConverter::ReOrderInterleavedChannels(void* aOut, const void* aIn,
{
MOZ_DIAGNOSTIC_ASSERT(mIn.Channels() == mOut.Channels());
if (mOut.Layout() == mIn.Layout()) {
return;
}
if (mOut.Channels() == 1) {
if (mOut.Channels() == 1 || mOut.Layout() == mIn.Layout()) {
// If channel count is 1, planar and non-planar formats are the same and
// there's nothing to reorder.
if (aOut != aIn) {
@@ -231,7 +219,7 @@ AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const
if (mIn.Format() == AudioConfig::FORMAT_FLT) {
const float* in = static_cast<const float*>(aIn);
float* out = static_cast<float*>(aOut);
for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
float sample = 0.0;
// The sample of the buffer would be interleaved.
sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
@@ -240,7 +228,7 @@ AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const
} else if (mIn.Format() == AudioConfig::FORMAT_S16) {
const int16_t* in = static_cast<const int16_t*>(aIn);
int16_t* out = static_cast<int16_t*>(aOut);
for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
int32_t sample = 0.0;
// The sample of the buffer would be interleaved.
sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
@@ -262,27 +250,126 @@ AudioConverter::ResampleAudio(void* aOut, const void* aIn, size_t aFrames)
uint32_t outframes = ResampleRecipientFrames(aFrames);
uint32_t inframes = aFrames;
int error;
if (mOut.Format() == AudioConfig::FORMAT_FLT) {
const float* in = reinterpret_cast<const float*>(aIn);
float* out = reinterpret_cast<float*>(aOut);
speex_resampler_process_interleaved_float(mResampler, in, &inframes,
out, &outframes);
error =
speex_resampler_process_interleaved_float(mResampler, in, &inframes,
out, &outframes);
} else if (mOut.Format() == AudioConfig::FORMAT_S16) {
const int16_t* in = reinterpret_cast<const int16_t*>(aIn);
int16_t* out = reinterpret_cast<int16_t*>(aOut);
speex_resampler_process_interleaved_int(mResampler, in, &inframes,
out, &outframes);
error =
speex_resampler_process_interleaved_int(mResampler, in, &inframes,
out, &outframes);
} else {
MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
}
MOZ_ASSERT(error == RESAMPLER_ERR_SUCCESS);
if (error != RESAMPLER_ERR_SUCCESS) {
speex_resampler_destroy(mResampler);
mResampler = nullptr;
return 0;
}
MOZ_ASSERT(inframes == aFrames, "Some frames will be dropped");
return outframes;
}
void
AudioConverter::RecreateResampler()
{
if (mResampler) {
speex_resampler_destroy(mResampler);
}
int error;
mResampler = speex_resampler_init(mOut.Channels(),
mIn.Rate(),
mOut.Rate(),
SPEEX_RESAMPLER_QUALITY_DEFAULT,
&error);
if (error == RESAMPLER_ERR_SUCCESS) {
speex_resampler_skip_zeros(mResampler);
} else {
NS_WARNING("Failed to initialize resampler.");
mResampler = nullptr;
}
}
size_t
AudioConverter::DrainResampler(void* aOut)
{
if (!mResampler) {
return 0;
}
int frames = speex_resampler_get_input_latency(mResampler);
AlignedByteBuffer buffer(FramesOutToBytes(frames));
if (!buffer) {
// OOM
return 0;
}
frames = ResampleAudio(aOut, buffer.Data(), frames);
// Tore down the resampler as it's easier than handling follow-up.
RecreateResampler();
return frames;
}
size_t
AudioConverter::UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const
{
MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
mIn.Format() == AudioConfig::FORMAT_FLT);
MOZ_ASSERT(mIn.Channels() < mOut.Channels());
MOZ_ASSERT(mIn.Channels() == 1, "Can only upmix mono for now");
MOZ_ASSERT(mOut.Channels() == 2, "Can only upmix to stereo for now");
if (mOut.Channels() != 2) {
return 0;
}
// Upmix mono to stereo.
// This is a very dumb mono to stereo upmixing, power levels are preserved
// following the calculation: left = right = -3dB*mono.
if (mIn.Format() == AudioConfig::FORMAT_FLT) {
const float m3db = std::sqrt(0.5); // -3dB = sqrt(1/2)
const float* in = static_cast<const float*>(aIn);
float* out = static_cast<float*>(aOut);
for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
float sample = in[fIdx] * m3db;
// The samples of the buffer would be interleaved.
*out++ = sample;
*out++ = sample;
}
} else if (mIn.Format() == AudioConfig::FORMAT_S16) {
const int16_t* in = static_cast<const int16_t*>(aIn);
int16_t* out = static_cast<int16_t*>(aOut);
for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
int16_t sample = ((int32_t)in[fIdx] * 11585) >> 14; // close enough to i*sqrt(0.5)
// The samples of the buffer would be interleaved.
*out++ = sample;
*out++ = sample;
}
} else {
MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
}
return aFrames;
}
size_t
AudioConverter::ResampleRecipientFrames(size_t aFrames) const
{
return (uint64_t)aFrames * mOut.Rate() / mIn.Rate() + 1;
if (!aFrames && mIn.Rate() != mOut.Rate()) {
// The resampler will be drained, account for frames currently buffered
// in the resampler.
if (!mResampler) {
return 0;
}
return speex_resampler_get_output_latency(mResampler);
} else {
return (uint64_t)aFrames * mOut.Rate() / mIn.Rate() + 1;
}
}
size_t
+14 -5
View File
@@ -123,6 +123,8 @@ public:
// Convert the AudioDataBuffer.
// Conversion will be done in place if possible. Otherwise a new buffer will
// be returned.
// Providing an empty buffer and resampling is expected, the resampler
// will be drained.
template <AudioConfig::SampleFormat Format, typename Value>
AudioDataBuffer<Format, Value> Process(AudioDataBuffer<Format, Value>&& aBuffer)
{
@@ -152,7 +154,7 @@ public:
return AudioDataBuffer<Format, Value>(Move(temp1));
}
frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames);
if (!frames || mIn.Rate() == mOut.Rate()) {
if (mIn.Rate() == mOut.Rate()) {
temp1.SetLength(FramesOutToSamples(frames));
return AudioDataBuffer<Format, Value>(Move(temp1));
}
@@ -161,13 +163,17 @@ public:
// If we are downsampling we can re-use it.
AlignedBuffer<Value>* outputBuffer = &temp1;
AlignedBuffer<Value> temp2;
if (mOut.Rate() > mIn.Rate()) {
// We are upsampling, we can't work in place. Allocate another temporary
// buffer where the upsampling will occur.
if (!frames || mOut.Rate() > mIn.Rate()) {
// We are upsampling or about to drain, we can't work in place.
// Allocate another temporary buffer where the upsampling will occur.
temp2.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)));
outputBuffer = &temp2;
}
frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
if (!frames) {
frames = DrainResampler(outputBuffer->Data());
} else {
frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
}
outputBuffer->SetLength(FramesOutToSamples(frames));
return AudioDataBuffer<Format, Value>(Move(*outputBuffer));
}
@@ -213,6 +219,7 @@ private:
size_t ProcessInternal(void* aOut, const void* aIn, size_t aFrames);
void ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aFrames) const;
size_t DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
size_t FramesOutToSamples(size_t aFrames) const;
size_t SamplesInToFrames(size_t aSamples) const;
@@ -222,6 +229,8 @@ private:
SpeexResamplerState* mResampler;
size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames);
size_t ResampleRecipientFrames(size_t aFrames) const;
void RecreateResampler();
size_t DrainResampler(void* aOut);
};
} // namespace mozilla
+6 -16
View File
@@ -128,7 +128,6 @@ AudioStream::AudioStream(DataSource& aSource)
, mTimeStretcher(nullptr)
, mDumpFile(nullptr)
, mState(INITIALIZED)
, mIsMonoAudioEnabled(gfxPrefs::MonoAudio())
, mDataSource(aSource)
{
}
@@ -330,7 +329,7 @@ AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
("%s channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this));
mInRate = mOutRate = aRate;
mChannels = aNumChannels;
mOutChannels = mIsMonoAudioEnabled ? 1 : aNumChannels;
mOutChannels = aNumChannels;
mDumpFile = OpenDumpFile(this);
@@ -352,11 +351,6 @@ AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
mAudioClock.Init();
if (mIsMonoAudioEnabled) {
AudioConfig inConfig(mChannels, mInRate);
AudioConfig outConfig(mOutChannels, mOutRate);
mAudioConverter = MakeUnique<AudioConverter>(inConfig, outConfig);
}
return OpenCubeb(params);
}
@@ -549,7 +543,7 @@ AudioStream::IsPaused()
}
bool
AudioStream::Downmix(Chunk* aChunk)
AudioStream::IsValidAudioFormat(Chunk* aChunk)
{
if (aChunk->Rate() != mInRate) {
LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mInRate);
@@ -560,10 +554,6 @@ AudioStream::Downmix(Chunk* aChunk)
return false;
}
if (mAudioConverter) {
mAudioConverter->Process(aChunk->GetWritable(), aChunk->Frames());
}
return true;
}
@@ -592,10 +582,10 @@ AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
break;
}
MOZ_ASSERT(c->Frames() <= aWriter.Available());
if (Downmix(c.get())) {
if (IsValidAudioFormat(c.get())) {
aWriter.Write(c->Data(), c->Frames());
} else {
// Write silence if downmixing fails.
// Write silence if invalid format.
aWriter.WriteZeros(c->Frames());
}
}
@@ -620,10 +610,10 @@ AudioStream::GetTimeStretched(AudioBufferWriter& aWriter)
break;
}
MOZ_ASSERT(c->Frames() <= toPopFrames);
if (Downmix(c.get())) {
if (IsValidAudioFormat(c.get())) {
mTimeStretcher->putSamples(c->Data(), c->Frames());
} else {
// Write silence if downmixing fails.
// Write silence if invalid format.
AutoTArray<AudioDataValue, 1000> buf;
buf.SetLength(mOutChannels * c->Frames());
memset(buf.Elements(), 0, buf.Length() * sizeof(AudioDataValue));
+8 -6
View File
@@ -286,6 +286,11 @@ public:
// Returns true when the audio stream is paused.
bool IsPaused();
static uint32_t GetPreferredRate()
{
CubebUtils::InitPreferredSampleRate();
return CubebUtils::PreferredSampleRate();
}
uint32_t GetRate() { return mOutRate; }
uint32_t GetChannels() { return mChannels; }
uint32_t GetOutChannels() { return mOutChannels; }
@@ -328,8 +333,9 @@ private:
nsresult EnsureTimeStretcherInitializedUnlocked();
// Return true if downmixing succeeds otherwise false.
bool Downmix(Chunk* aChunk);
// Return true if audio frames are valid (correct sampling rate and valid
// channel count) otherwise false.
bool IsValidAudioFormat(Chunk* aChunk);
void GetUnprocessed(AudioBufferWriter& aWriter);
void GetTimeStretched(AudioBufferWriter& aWriter);
@@ -369,12 +375,8 @@ private:
StreamState mState;
bool mIsFirst;
// Get this value from the preference, if true, we would downmix the stereo.
bool mIsMonoAudioEnabled;
DataSource& mDataSource;
UniquePtr<AudioConverter> mAudioConverter;
};
} // namespace mozilla
+3 -3
View File
@@ -364,7 +364,7 @@ MediaDecoderStateMachine::CreateAudioSink()
auto audioSinkCreator = [self] () {
MOZ_ASSERT(self->OnTaskQueue());
return new DecodedAudioDataSink(
self->mAudioQueue, self->GetMediaTime(),
self->mTaskQueue, self->mAudioQueue, self->GetMediaTime(),
self->mInfo.mAudio, self->mAudioChannel);
};
return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
@@ -428,7 +428,7 @@ void MediaDecoderStateMachine::DiscardStreamData()
const auto clockTime = GetClock();
while (true) {
const MediaData* a = AudioQueue().PeekFront();
RefPtr<MediaData> a = AudioQueue().PeekFront();
// If we discard audio samples fed to the stream immediately, we will
// keep decoding audio samples till the end and consume a lot of memory.
@@ -1983,7 +1983,7 @@ MediaDecoderStateMachine::SeekCompleted()
if (seekTime == Duration().ToMicroseconds()) {
newCurrentTime = seekTime;
} else if (HasAudio()) {
MediaData* audio = AudioQueue().PeekFront();
RefPtr<MediaData> audio = AudioQueue().PeekFront();
// Though we adjust the newCurrentTime in audio-based, and supplemented
// by video. For better UX, should NOT bind the slide position to
// the first audio data timestamp directly.
+7 -7
View File
@@ -36,7 +36,7 @@ public:
Reset();
}
inline size_t GetSize() {
inline size_t GetSize() const {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return nsDeque::GetSize();
}
@@ -65,12 +65,12 @@ public:
return rv.forget();
}
inline T* Peek() {
inline RefPtr<T> Peek() const {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return static_cast<T*>(nsDeque::Peek());
}
inline T* PeekFront() {
inline RefPtr<T> PeekFront() const {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return static_cast<T*>(nsDeque::PeekFront());
}
@@ -83,7 +83,7 @@ public:
mEndOfStream = false;
}
bool AtEndOfStream() {
bool AtEndOfStream() const {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return GetSize() == 0 && mEndOfStream;
}
@@ -91,7 +91,7 @@ public:
// Returns true if the media queue has had its last item added to it.
// This happens when the media stream has been completely decoded. Note this
// does not mean that the corresponding stream has finished playback.
bool IsFinished() {
bool IsFinished() const {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mEndOfStream;
}
@@ -109,8 +109,8 @@ public:
if (GetSize() == 0) {
return 0;
}
T* last = Peek();
T* first = PeekFront();
T* last = static_cast<T*>(nsDeque::Peek());
T* first = static_cast<T*>(nsDeque::PeekFront());
return last->GetEndTime() - first->mTime;
}
+2 -2
View File
@@ -31,10 +31,10 @@ namespace mozilla {
using layers::PlanarYCbCrImage;
static inline CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv) {
CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv) {
int64_t major = aValue / aDiv;
int64_t remainder = aValue % aDiv;
return CheckedInt64(remainder) * aMul / aDiv + major * aMul;
return CheckedInt64(remainder) * aMul / aDiv + CheckedInt64(major) * aMul;
}
// Converts from number of audio frames to microseconds, given the specified
+4
View File
@@ -131,6 +131,10 @@ CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
// Converts from number of audio frames (aFrames) TimeUnit, given
// the specified audio rate (aRate).
media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate);
// Perform aValue * aMul / aDiv, reducing the possibility of overflow due to
// aValue * aMul overflowing.
CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv);
// Converts from microseconds (aUsecs) to number of audio frames, given the
// specified audio rate (aRate). Stores the result in aOutFrames. Returns
// true if the operation succeeded, or false if there was an integer
+298 -90
View File
@@ -29,27 +29,49 @@ namespace media {
// The amount of audio frames that is used to fuzz rounding errors.
static const int64_t AUDIO_FUZZ_FRAMES = 1;
DecodedAudioDataSink::DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
// Amount of audio frames we will be processing ahead of use
static const int32_t LOW_AUDIO_USECS = 300000;
DecodedAudioDataSink::DecodedAudioDataSink(AbstractThread* aThread,
MediaQueue<MediaData>& aAudioQueue,
int64_t aStartTime,
const AudioInfo& aInfo,
dom::AudioChannel aChannel)
: AudioSink(aAudioQueue)
, mStartTime(aStartTime)
, mWritten(0)
, mLastGoodPosition(0)
, mInfo(aInfo)
, mChannel(aChannel)
, mPlaying(true)
, mMonitor("DecodedAudioDataSink")
, mWritten(0)
, mErrored(false)
, mPlaybackComplete(false)
, mOwnerThread(aThread)
, mProcessedQueueLength(0)
, mFramesParsed(0)
, mLastEndTime(0)
{
bool resampling = gfxPrefs::AudioSinkResampling();
uint32_t resamplingRate = gfxPrefs::AudioSinkResampleRate();
mConverter =
MakeUnique<AudioConverter>(
AudioConfig(mInfo.mChannels, mInfo.mRate),
AudioConfig(mInfo.mChannels > 2 && gfxPrefs::AudioSinkForceStereo()
? 2 : mInfo.mChannels,
resampling ? resamplingRate : mInfo.mRate));
if (resampling) {
mOutputRate = gfxPrefs::AudioSinkResampleRate();
} else if (mInfo.mRate == 44100 || mInfo.mRate == 48000) {
// The original rate is of good quality and we want to minimize unecessary
// resampling. The common scenario being that the sampling rate is one or
// the other, this allows to minimize audio quality regression and hoping
// content provider want change from those rates mid-stream.
mOutputRate = mInfo.mRate;
} else {
// We will resample all data to match cubeb's preferred sampling rate.
mOutputRate = AudioStream::GetPreferredRate();
}
MOZ_DIAGNOSTIC_ASSERT(mOutputRate, "output rate can't be 0.");
bool monoAudioEnabled = gfxPrefs::MonoAudio();
mOutputChannels = monoAudioEnabled
? 1 : (gfxPrefs::AudioSinkForceStereo() ? 2 : mInfo.mChannels);
}
DecodedAudioDataSink::~DecodedAudioDataSink()
@@ -59,6 +81,18 @@ DecodedAudioDataSink::~DecodedAudioDataSink()
RefPtr<GenericPromise>
DecodedAudioDataSink::Init(const PlaybackParams& aParams)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mAudioQueueListener = mAudioQueue.PushEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::OnAudioPushed);
mAudioQueueFinishListener = mAudioQueue.FinishEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::NotifyAudioNeeded);
mProcessedQueueListener = mProcessedQueue.PopEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::OnAudioPopped);
// To ensure at least one audio packet will be popped from AudioQueue and
// ready to be played.
NotifyAudioNeeded();
RefPtr<GenericPromise> p = mEndPromise.Ensure(__func__);
nsresult rv = InitializeAudioStream(aParams);
if (NS_FAILED(rv)) {
@@ -89,16 +123,30 @@ DecodedAudioDataSink::HasUnplayedFrames()
{
// Experimentation suggests that GetPositionInFrames() is zero-indexed,
// so we need to add 1 here before comparing it to mWritten.
return mAudioStream && mAudioStream->GetPositionInFrames() + 1 < mWritten;
int64_t total;
{
MonitorAutoLock mon(mMonitor);
total = mWritten + (mCursor.get() ? mCursor->Available() : 0);
}
return mProcessedQueue.GetSize() ||
(mAudioStream && mAudioStream->GetPositionInFrames() + 1 < total);
}
void
DecodedAudioDataSink::Shutdown()
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mAudioQueueListener.Disconnect();
mAudioQueueFinishListener.Disconnect();
mProcessedQueueListener.Disconnect();
if (mAudioStream) {
mAudioStream->Shutdown();
mAudioStream = nullptr;
}
mProcessedQueue.Reset();
mProcessedQueue.Finish();
mEndPromise.ResolveIfExists(true, __func__);
}
@@ -146,9 +194,7 @@ nsresult
DecodedAudioDataSink::InitializeAudioStream(const PlaybackParams& aParams)
{
mAudioStream = new AudioStream(*this);
nsresult rv = mAudioStream->Init(mConverter->OutputConfig().Channels(),
mConverter->OutputConfig().Rate(),
mChannel);
nsresult rv = mAudioStream->Init(mOutputChannels, mOutputRate, mChannel);
if (NS_FAILED(rv)) {
mAudioStream->Shutdown();
mAudioStream = nullptr;
@@ -168,13 +214,19 @@ DecodedAudioDataSink::InitializeAudioStream(const PlaybackParams& aParams)
int64_t
DecodedAudioDataSink::GetEndTime() const
{
CheckedInt64 playedUsecs =
FramesToUsecs(mWritten, mConverter->OutputConfig().Rate()) + mStartTime;
int64_t written;
{
MonitorAutoLock mon(mMonitor);
written = mWritten;
}
CheckedInt64 playedUsecs = FramesToUsecs(written, mOutputRate) + mStartTime;
if (!playedUsecs.isValid()) {
NS_WARNING("Int overflow calculating audio end time");
return -1;
}
return playedUsecs.value();
// As we may be resampling, rounding errors may occur. Ensure we never get
// past the original end time.
return std::min<int64_t>(mLastEndTime, playedUsecs.value());
}
UniquePtr<AudioStream::Chunk>
@@ -217,82 +269,29 @@ DecodedAudioDataSink::PopFrames(uint32_t aFrames)
UniquePtr<AudioDataValue[]> mData;
};
while (!mCurrentData) {
bool needPopping = false;
if (!mCurrentData) {
// No data in the queue. Return an empty chunk.
if (AudioQueue().GetSize() == 0) {
if (!mProcessedQueue.GetSize()) {
return MakeUnique<Chunk>();
}
AudioData* a = AudioQueue().PeekFront()->As<AudioData>();
// Ignore the element with 0 frames and try next.
if (a->mFrames == 0) {
RefPtr<MediaData> releaseMe = AudioQueue().PopFront();
continue;
// We need to update our values prior popping the processed queue in
// order to prevent the pop event to fire too early (prior
// mProcessedQueueLength being updated) or prevent HasUnplayedFrames
// to incorrectly return true during the time interval betweeen the
// when mProcessedQueue is read and mWritten is updated.
needPopping = true;
mCurrentData = mProcessedQueue.PeekFront();
{
MonitorAutoLock mon(mMonitor);
mCursor = MakeUnique<AudioBufferCursor>(mCurrentData->mAudioData.get(),
mCurrentData->mChannels,
mCurrentData->mFrames);
}
// Ignore invalid samples.
if (a->mRate != mInfo.mRate || a->mChannels != mInfo.mChannels) {
NS_WARNING(nsPrintfCString(
"mismatched sample format, data=%p rate=%u channels=%u frames=%u",
a->mAudioData.get(), a->mRate, a->mChannels, a->mFrames).get());
RefPtr<MediaData> releaseMe = AudioQueue().PopFront();
continue;
}
// See if there's a gap in the audio. If there is, push silence into the
// audio hardware, so we can play across the gap.
// Calculate the timestamp of the next chunk of audio in numbers of
// samples.
CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime,
mConverter->OutputConfig().Rate());
// Calculate the number of frames that have been pushed onto the audio hardware.
CheckedInt64 playedFrames = UsecsToFrames(mStartTime,
mConverter->OutputConfig().Rate()) +
static_cast<int64_t>(mWritten);
CheckedInt64 missingFrames = sampleTime - playedFrames;
if (!missingFrames.isValid() || !sampleTime.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return MakeUnique<Chunk>();
}
const uint32_t rate = mConverter->OutputConfig().Rate();
const uint32_t channels = mConverter->OutputConfig().Channels();
if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
// The next audio chunk begins some time after the end of the last chunk
// we pushed to the audio hardware. We must push silence into the audio
// hardware so that the next audio chunk begins playback at the correct
// time.
missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
auto framesToPop = std::min<uint32_t>(missingFrames.value(), aFrames);
mWritten += framesToPop;
return MakeUnique<SilentChunk>(framesToPop, channels, rate);
}
RefPtr<AudioData> data =
dont_AddRef(AudioQueue().PopFront().take()->As<AudioData>());
if (mConverter->InputConfig() != mConverter->OutputConfig()) {
AlignedAudioBuffer convertedData =
mConverter->Process(AudioSampleBuffer(Move(data->mAudioData))).Forget();
mCurrentData =
new AudioData(data->mOffset,
data->mTime,
data->mDuration,
convertedData.Length() / channels,
Move(convertedData),
channels,
rate);
} else {
mCurrentData = Move(data);
}
mCursor = MakeUnique<AudioBufferCursor>(mCurrentData->mAudioData.get(),
mCurrentData->mChannels,
mCurrentData->mFrames);
MOZ_ASSERT(mCurrentData->mFrames > 0);
mProcessedQueueLength -=
FramesToUsecs(mCurrentData->mFrames, mOutputRate).value();
}
auto framesToPop = std::min(aFrames, mCursor->Available());
@@ -303,15 +302,24 @@ DecodedAudioDataSink::PopFrames(uint32_t aFrames)
UniquePtr<AudioStream::Chunk> chunk =
MakeUnique<Chunk>(mCurrentData, framesToPop, mCursor->Ptr());
mWritten += framesToPop;
mCursor->Advance(framesToPop);
{
MonitorAutoLock mon(mMonitor);
mWritten += framesToPop;
mCursor->Advance(framesToPop);
}
// All frames are popped. Reset mCurrentData so we can pop new elements from
// the audio queue in next calls to PopFrames().
if (mCursor->Available() == 0) {
if (!mCursor->Available()) {
mCurrentData = nullptr;
}
if (needPopping) {
// We can now safely pop the audio packet from the processed queue.
// This will fire the popped event, triggering a call to NotifyAudioNeeded.
RefPtr<AudioData> releaseMe = mProcessedQueue.PopFront();
}
return chunk;
}
@@ -319,7 +327,7 @@ bool
DecodedAudioDataSink::Ended() const
{
// Return true when error encountered so AudioStream can start draining.
return AudioQueue().IsFinished() || mErrored;
return mProcessedQueue.IsFinished() || mErrored;
}
void
@@ -330,5 +338,205 @@ DecodedAudioDataSink::Drained()
mEndPromise.ResolveIfExists(true, __func__);
}
void
DecodedAudioDataSink::OnAudioPopped(const RefPtr<MediaData>& aSample)
{
SINK_LOG_V("AudioStream has used an audio packet.");
NotifyAudioNeeded();
}
void
DecodedAudioDataSink::OnAudioPushed(const RefPtr<MediaData>& aSample)
{
SINK_LOG_V("One new audio packet available.");
NotifyAudioNeeded();
}
void
DecodedAudioDataSink::NotifyAudioNeeded()
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn(),
"Not called from the owner's thread");
// Always ensure we have two processed frames pending to allow for processing
// latency.
while (AudioQueue().GetSize() && (AudioQueue().IsFinished() ||
mProcessedQueueLength < LOW_AUDIO_USECS ||
mProcessedQueue.GetSize() < 2)) {
RefPtr<AudioData> data =
dont_AddRef(AudioQueue().PopFront().take()->As<AudioData>());
// Ignore the element with 0 frames and try next.
if (!data->mFrames) {
continue;
}
if (!mConverter ||
(data->mRate != mConverter->InputConfig().Rate() ||
data->mChannels != mConverter->InputConfig().Channels())) {
SINK_LOG_V("Audio format changed from %u@%uHz to %u@%uHz",
mConverter? mConverter->InputConfig().Channels() : 0,
mConverter ? mConverter->InputConfig().Rate() : 0,
data->mChannels, data->mRate);
DrainConverter();
// mFramesParsed indicates the current playtime in frames at the current
// input sampling rate. Recalculate it per the new sampling rate.
if (mFramesParsed) {
// We minimize overflow.
uint32_t oldRate = mConverter->InputConfig().Rate();
uint32_t newRate = data->mRate;
CheckedInt64 result = SaferMultDiv(mFramesParsed, newRate, oldRate);
if (!result.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
mFramesParsed = result.value();
}
mConverter =
MakeUnique<AudioConverter>(
AudioConfig(data->mChannels, data->mRate),
AudioConfig(mOutputChannels, mOutputRate));
}
// See if there's a gap in the audio. If there is, push silence into the
// audio hardware, so we can play across the gap.
// Calculate the timestamp of the next chunk of audio in numbers of
// samples.
CheckedInt64 sampleTime = UsecsToFrames(data->mTime - mStartTime,
data->mRate);
// Calculate the number of frames that have been pushed onto the audio hardware.
CheckedInt64 missingFrames = sampleTime - mFramesParsed;
if (!missingFrames.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
// The next audio packet begins some time after the end of the last packet
// we pushed to the audio hardware. We must push silence into the audio
// hardware so that the next audio packet begins playback at the correct
// time.
missingFrames = std::min<int64_t>(INT32_MAX, missingFrames.value());
mFramesParsed += missingFrames.value();
// We need to calculate how many frames are missing at the output rate.
missingFrames =
SaferMultDiv(missingFrames.value(), mOutputRate, data->mRate);
if (!missingFrames.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
// We need to insert silence, first use drained frames if any.
missingFrames -= DrainConverter(missingFrames.value());
// Insert silence if still needed.
if (missingFrames.value()) {
AlignedAudioBuffer silenceData(missingFrames.value() * mOutputChannels);
if (!silenceData) {
NS_WARNING("OOM in DecodedAudioDataSink");
mErrored = true;
return;
}
RefPtr<AudioData> silence = CreateAudioFromBuffer(Move(silenceData), data);
PushProcessedAudio(silence);
}
}
mLastEndTime = data->GetEndTime();
mFramesParsed += data->mFrames;
if (mConverter->InputConfig() != mConverter->OutputConfig()) {
AlignedAudioBuffer convertedData =
mConverter->Process(AudioSampleBuffer(Move(data->mAudioData))).Forget();
data = CreateAudioFromBuffer(Move(convertedData), data);
}
if (PushProcessedAudio(data)) {
mLastProcessedPacket = Some(data);
}
}
if (AudioQueue().IsFinished()) {
// We have reached the end of the data, drain the resampler.
DrainConverter();
mProcessedQueue.Finish();
}
}
uint32_t
DecodedAudioDataSink::PushProcessedAudio(AudioData* aData)
{
if (!aData || !aData->mFrames) {
return 0;
}
mProcessedQueue.Push(aData);
mProcessedQueueLength += FramesToUsecs(aData->mFrames, mOutputRate).value();
return aData->mFrames;
}
already_AddRefed<AudioData>
DecodedAudioDataSink::CreateAudioFromBuffer(AlignedAudioBuffer&& aBuffer,
AudioData* aReference)
{
uint32_t frames = aBuffer.Length() / mOutputChannels;
if (!frames) {
return nullptr;
}
CheckedInt64 duration = FramesToUsecs(frames, mOutputRate);
if (!duration.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return nullptr;
}
RefPtr<AudioData> data =
new AudioData(aReference->mOffset,
aReference->mTime,
duration.value(),
frames,
Move(aBuffer),
mOutputChannels,
mOutputRate);
return data.forget();
}
uint32_t
DecodedAudioDataSink::DrainConverter(uint32_t aMaxFrames)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
if (!mConverter || !mLastProcessedPacket || !aMaxFrames) {
// nothing to drain.
return 0;
}
RefPtr<AudioData> lastPacket = mLastProcessedPacket.ref();
mLastProcessedPacket.reset();
// To drain we simply provide an empty packet to the audio converter.
AlignedAudioBuffer convertedData =
mConverter->Process(AudioSampleBuffer(AlignedAudioBuffer())).Forget();
uint32_t frames = convertedData.Length() / mOutputChannels;
if (!convertedData.SetLength(std::min(frames, aMaxFrames) * mOutputChannels)) {
// This can never happen as we were reducing the length of convertData.
mErrored = true;
return 0;
}
RefPtr<AudioData> data =
CreateAudioFromBuffer(Move(convertedData), lastPacket);
if (!data) {
return 0;
}
mProcessedQueue.Push(data);
return data->mFrames;
}
} // namespace media
} // namespace mozilla
+44 -6
View File
@@ -9,6 +9,7 @@
#include "AudioSink.h"
#include "AudioStream.h"
#include "MediaEventSource.h"
#include "MediaQueue.h"
#include "MediaInfo.h"
#include "mozilla/RefPtr.h"
#include "nsISupportsImpl.h"
@@ -17,7 +18,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Monitor.h"
namespace mozilla {
@@ -28,7 +29,8 @@ namespace media {
class DecodedAudioDataSink : public AudioSink,
private AudioStream::DataSource {
public:
DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
DecodedAudioDataSink(AbstractThread* aThread,
MediaQueue<MediaData>& aAudioQueue,
int64_t aStartTime,
const AudioInfo& aInfo,
dom::AudioChannel aChannel);
@@ -76,9 +78,6 @@ private:
// the current audio time.
const int64_t mStartTime;
// PCM frames written to the stream so far.
Atomic<int64_t> mWritten;
// Keep the last good position returned from the audio stream. Used to ensure
// position returned by GetPosition() is mono-increasing in spite of audio
// stream error. Used on the task queue of MDSM only.
@@ -99,15 +98,54 @@ private:
*/
// The AudioData at which AudioStream::DataSource is reading.
RefPtr<AudioData> mCurrentData;
// Monitor protecting access to mCursor and mWritten.
// mCursor is created/destroyed on the cubeb thread, while we must also
// ensure that mWritten and mCursor::Available() get modified simultaneously.
// (written on cubeb thread, and read on MDSM task queue).
mutable Monitor mMonitor;
// Keep track of the read position of mCurrentData.
UniquePtr<AudioBufferCursor> mCursor;
// PCM frames written to the stream so far.
int64_t mWritten;
// True if there is any error in processing audio data like overflow.
bool mErrored = false;
Atomic<bool> mErrored;
// Set on the callback thread of cubeb once the stream has drained.
Atomic<bool> mPlaybackComplete;
const RefPtr<AbstractThread> mOwnerThread;
// Audio Processing objects and methods
void OnAudioPopped(const RefPtr<MediaData>& aSample);
void OnAudioPushed(const RefPtr<MediaData>& aSample);
void NotifyAudioNeeded();
// Drain the converter and add the output to the processed audio queue.
// A maximum of aMaxFrames will be added.
uint32_t DrainConverter(uint32_t aMaxFrames = UINT32_MAX);
already_AddRefed<AudioData> CreateAudioFromBuffer(AlignedAudioBuffer&& aBuffer,
AudioData* aReference);
// Add data to the processsed queue, update mProcessedQueueLength and
// return the number of frames added.
uint32_t PushProcessedAudio(AudioData* aData);
UniquePtr<AudioConverter> mConverter;
MediaQueue<AudioData> mProcessedQueue;
// Length in microseconds of the ProcessedQueue
Atomic<int32_t> mProcessedQueueLength;
MediaEventListener mAudioQueueListener;
MediaEventListener mAudioQueueFinishListener;
MediaEventListener mProcessedQueueListener;
// Number of frames processed from AudioQueue(). Used to determine gaps in
// the input stream. It indicates the time in frames since playback started
// at the current input framerate.
int64_t mFramesParsed;
Maybe<RefPtr<AudioData>> mLastProcessedPacket;
int64_t mLastEndTime;
// Never modifed after construction.
uint32_t mOutputRate;
uint32_t mOutputChannels;
};
} // namespace media
+1 -1
View File
@@ -383,7 +383,7 @@ VideoSink::UpdateRenderedVideoFrames()
RefPtr<MediaData> currentFrame = VideoQueue().PopFront();
int32_t framesRemoved = 0;
while (VideoQueue().GetSize() > 0) {
MediaData* nextFrame = VideoQueue().PeekFront();
RefPtr<MediaData> nextFrame = VideoQueue().PeekFront();
if (nextFrame->mTime > clockTime) {
remainingTime = nextFrame->mTime - clockTime;
break;
+9 -14
View File
@@ -1358,7 +1358,7 @@ nsresult OggReader::SeekInBufferedRange(int64_t aTarget,
} while (!eof &&
mVideoQueue.GetSize() == 0);
VideoData* video = mVideoQueue.PeekFront();
RefPtr<VideoData> video = mVideoQueue.PeekFront();
if (video && !video->mKeyframe) {
// First decoded frame isn't a keyframe, seek back to previous keyframe,
// otherwise we'll get visual artifacts.
@@ -1490,7 +1490,7 @@ nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime)
// First, we must check to see if there's already a keyframe in the frames
// that we may have already decoded, and discard frames up to the
// keyframe.
VideoData* v;
RefPtr<VideoData> v;
while ((v = mVideoQueue.PeekFront()) && !v->mKeyframe) {
RefPtr<VideoData> releaseMe = mVideoQueue.PopFront();
}
@@ -1959,7 +1959,7 @@ media::TimeIntervals OggReader::GetBuffered()
#endif
}
VideoData* OggReader::FindStartTime(int64_t& aOutStartTime)
void OggReader::FindStartTime(int64_t& aOutStartTime)
{
MOZ_ASSERT(OnTaskQueue());
@@ -1967,17 +1967,16 @@ VideoData* OggReader::FindStartTime(int64_t& aOutStartTime)
// the duration.
int64_t videoStartTime = INT64_MAX;
int64_t audioStartTime = INT64_MAX;
VideoData* videoData = nullptr;
if (HasVideo()) {
videoData = SyncDecodeToFirstVideoData();
RefPtr<VideoData> videoData = SyncDecodeToFirstVideoData();
if (videoData) {
videoStartTime = videoData->mTime;
LOG(LogLevel::Debug, ("OggReader::FindStartTime() video=%lld", videoStartTime));
}
}
if (HasAudio()) {
AudioData* audioData = SyncDecodeToFirstAudioData();
RefPtr<AudioData> audioData = SyncDecodeToFirstAudioData();
if (audioData) {
audioStartTime = audioData->mTime;
LOG(LogLevel::Debug, ("OggReader::FindStartTime() audio=%lld", audioStartTime));
@@ -1988,11 +1987,9 @@ VideoData* OggReader::FindStartTime(int64_t& aOutStartTime)
if (startTime != INT64_MAX) {
aOutStartTime = startTime;
}
return videoData;
}
AudioData* OggReader::SyncDecodeToFirstAudioData()
RefPtr<AudioData> OggReader::SyncDecodeToFirstAudioData()
{
bool eof = false;
while (!eof && AudioQueue().GetSize() == 0) {
@@ -2004,11 +2001,10 @@ AudioData* OggReader::SyncDecodeToFirstAudioData()
if (eof) {
AudioQueue().Finish();
}
AudioData* d = nullptr;
return (d = AudioQueue().PeekFront()) ? d : nullptr;
return AudioQueue().PeekFront();
}
VideoData* OggReader::SyncDecodeToFirstVideoData()
RefPtr<VideoData> OggReader::SyncDecodeToFirstVideoData()
{
bool eof = false;
while (!eof && VideoQueue().GetSize() == 0) {
@@ -2021,8 +2017,7 @@ VideoData* OggReader::SyncDecodeToFirstVideoData()
if (eof) {
VideoQueue().Finish();
}
VideoData* d = nullptr;
return (d = VideoQueue().PeekFront()) ? d : nullptr;
return VideoQueue().PeekFront();
}
OggCodecStore::OggCodecStore()
+3 -3
View File
@@ -77,9 +77,9 @@ private:
// Stores the presentation time of the first frame we'd be able to play if
// we started playback at the current position. Returns the first video
// frame, if we have video.
VideoData* FindStartTime(int64_t& aOutStartTime);
AudioData* SyncDecodeToFirstAudioData();
VideoData* SyncDecodeToFirstVideoData();
void FindStartTime(int64_t& aOutStartTime);
RefPtr<AudioData> SyncDecodeToFirstAudioData();
RefPtr<VideoData> SyncDecodeToFirstVideoData();
// This monitor should be taken when reading or writing to mIsChained.
ReentrantMonitor mMonitor;
+1
View File
@@ -44,6 +44,7 @@ DIRS += [
'bluetooth',
'activities',
'archivereader',
'requestsync',
'bindings',
'battery',
'browser-element',
+5
View File
@@ -0,0 +1,5 @@
component {8ee5ab74-15c4-478f-9d32-67627b9f0f1a} RequestSyncScheduler.js
contract @mozilla.org/dom/request-sync-scheduler;1 {8ee5ab74-15c4-478f-9d32-67627b9f0f1a}
component {e6f55080-e549-4e30-9d00-15f240fb763c} RequestSyncManager.js
contract @mozilla.org/dom/request-sync-manager;1 {e6f55080-e549-4e30-9d00-15f240fb763c}
+48
View File
@@ -0,0 +1,48 @@
/* 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';
this.EXPORTED_SYMBOLS = ['RequestSyncApp'];
function debug(s) {
//dump('DEBUG RequestSyncApp: ' + s + '\n');
}
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
this.RequestSyncApp = function(aData) {
debug('created');
let keys = [ 'origin', 'manifestURL', 'isInBrowserElement' ];
for (let i = 0; i < keys.length; ++i) {
if (!(keys[i] in aData)) {
dump("ERROR - RequestSyncApp must receive a full app object: " + keys[i] + " missing.");
throw "ERROR!";
}
this["_" + keys[i]] = aData[keys[i]];
}
}
this.RequestSyncApp.prototype = {
classDescription: 'RequestSyncApp XPCOM Component',
classID: Components.ID('{5a0b64db-a2be-4f08-a6c5-8bf2e3ae0c57}'),
contractID: '@mozilla.org/dom/request-sync-manager;1',
QueryInterface: XPCOMUtils.generateQI([]),
get origin() {
return this._origin;
},
get manifestURL() {
return this._manifestURL;
},
get isInBrowserElement() {
return this._isInBrowserElement;
}
};
+134
View File
@@ -0,0 +1,134 @@
/* 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';
function debug(s) {
//dump('DEBUG RequestSyncManager: ' + s + '\n');
}
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/RequestSyncApp.jsm');
Cu.import('resource://gre/modules/RequestSyncTask.jsm');
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
function RequestSyncManager() {
debug('created');
}
RequestSyncManager.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: 'RequestSyncManager XPCOM Component',
classID: Components.ID('{e6f55080-e549-4e30-9d00-15f240fb763c}'),
contractID: '@mozilla.org/dom/request-sync-manager;1',
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
Ci.nsIObserver,
Ci.nsIDOMGlobalPropertyInitializer]),
_messages: [ "RequestSyncManager:Registrations:Return",
"RequestSyncManager:SetPolicy:Return",
"RequestSyncManager:RunTask:Return" ],
init: function(aWindow) {
debug("init");
// DOMRequestIpcHelper.initHelper sets this._window
this.initDOMRequestHelper(aWindow, this._messages);
},
sendMessage: function(aMsg, aObj) {
let self = this;
return this.createPromiseWithId(function(aResolverId) {
aObj.requestID = aResolverId;
cpmm.sendAsyncMessage(aMsg, aObj, null,
self._window.document.nodePrincipal);
});
},
registrations: function() {
debug('registrations');
return this.sendMessage("RequestSyncManager:Registrations", {});
},
setPolicy: function(aTask, aOrigin, aManifestURL, aIsInIsolatedMozBrowserElement,
aState, aOverwrittenMinInterval) {
debug('setPolicy');
return this.sendMessage("RequestSyncManager:SetPolicy",
{ task: aTask,
origin: aOrigin,
manifestURL: aManifestURL,
isInBrowserElement: aIsInIsolatedMozBrowserElement,
state: aState,
overwrittenMinInterval: aOverwrittenMinInterval });
},
runTask: function(aTask, aOrigin, aManifestURL, aIsInIsolatedMozBrowserElement) {
debug('runTask');
return this.sendMessage("RequestSyncManager:RunTask",
{ task: aTask,
origin: aOrigin,
manifestURL: aManifestURL,
isInBrowserElement: aIsInIsolatedMozBrowserElement });
},
registrationsResult: function(aData) {
debug("registrationsResult");
let results = new this._window.Array();
for (let i = 0; i < aData.length; ++i) {
if (!("app" in aData[i])) {
dump("ERROR - Serialization error in RequestSyncManager.\n");
continue;
}
let app = new RequestSyncApp(aData[i].app);
let exposedApp =
this._window.RequestSyncApp._create(this._window, app);
let task = new RequestSyncTask(this, this._window, exposedApp, aData[i]);
let exposedTask =
this._window.RequestSyncTask._create(this._window, task);
results.push(exposedTask);
}
return results;
},
receiveMessage: function(aMessage) {
debug('receiveMessage');
let req = this.getPromiseResolver(aMessage.data.requestID);
if (!req) {
return;
}
if ('error' in aMessage.data) {
req.reject(Cu.cloneInto(aMessage.data.error, this._window));
return;
}
if (aMessage.name == 'RequestSyncManager:Registrations:Return') {
req.resolve(this.registrationsResult(aMessage.data.results));
return;
}
if ('results' in aMessage.data) {
req.resolve(Cu.cloneInto(aMessage.data.results, this._window));
return;
}
req.resolve();
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RequestSyncManager]);
+100
View File
@@ -0,0 +1,100 @@
/* 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';
function debug(s) {
//dump('DEBUG RequestSyncScheduler: ' + s + '\n');
}
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/DOMRequestHelper.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
'@mozilla.org/childprocessmessagemanager;1',
'nsIMessageSender');
function RequestSyncScheduler() {
debug('created');
}
RequestSyncScheduler.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: 'RequestSyncScheduler XPCOM Component',
classID: Components.ID('{8ee5ab74-15c4-478f-9d32-67627b9f0f1a}'),
contractID: '@mozilla.org/dom/request-sync-scheduler;1',
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
Ci.nsIObserver,
Ci.nsIDOMGlobalPropertyInitializer]),
_messages: [ 'RequestSync:Register:Return',
'RequestSync:Unregister:Return',
'RequestSync:Registrations:Return',
'RequestSync:Registration:Return' ],
init: function(aWindow) {
debug('init');
// DOMRequestIpcHelper.initHelper sets this._window
this.initDOMRequestHelper(aWindow, this._messages);
},
register: function(aTask, aParams) {
debug('register');
return this.sendMessage('RequestSync:Register',
{ task: aTask, params: aParams });
},
unregister: function(aTask) {
debug('unregister');
return this.sendMessage('RequestSync:Unregister',
{ task: aTask });
},
registrations: function() {
debug('registrations');
return this.sendMessage('RequestSync:Registrations', {});
},
registration: function(aTask) {
debug('registration');
return this.sendMessage('RequestSync:Registration',
{ task: aTask });
},
sendMessage: function(aMsg, aObj) {
let self = this;
return this.createPromiseWithId(function(aResolverId) {
aObj.requestID = aResolverId;
cpmm.sendAsyncMessage(aMsg, aObj, null,
self._window.document.nodePrincipal);
});
},
receiveMessage: function(aMessage) {
debug('receiveMessage');
let req = this.getPromiseResolver(aMessage.data.requestID);
if (!req) {
return;
}
if ('error' in aMessage.data) {
req.reject(Cu.cloneInto(aMessage.data.error, this._window));
return;
}
if ('results' in aMessage.data) {
req.resolve(Cu.cloneInto(aMessage.data.results, this._window));
return;
}
req.resolve();
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RequestSyncScheduler]);
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
/* 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';
this.EXPORTED_SYMBOLS = ['RequestSyncTask'];
function debug(s) {
//dump('DEBUG RequestSyncTask: ' + s + '\n');
}
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
this.RequestSyncTask = function(aManager, aWindow, aApp, aData) {
debug('created');
this._manager = aManager;
this._window = aWindow;
this._app = aApp;
let keys = [ 'task', 'lastSync', 'oneShot', 'minInterval', 'wakeUpPage',
'wifiOnly', 'data', 'state', 'overwrittenMinInterval' ];
for (let i = 0; i < keys.length; ++i) {
if (!(keys[i] in aData)) {
dump("ERROR - RequestSyncTask must receive a fully app object: " + keys[i] + " missing.");
throw "ERROR!";
}
this["_" + keys[i]] = aData[keys[i]];
}
}
this.RequestSyncTask.prototype = {
classDescription: 'RequestSyncTask XPCOM Component',
classID: Components.ID('{a1e1c9c6-ce42-49d4-b8b4-fbd686d8fdd9}'),
contractID: '@mozilla.org/dom/request-sync-manager;1',
QueryInterface: XPCOMUtils.generateQI([]),
get app() {
return this._app;
},
get state() {
return this._state;
},
get overwrittenMinInterval() {
return this._overwrittenMinInterval;
},
get task() {
return this._task;
},
get lastSync() {
return this._lastSync;
},
get wakeUpPage() {
return this._wakeUpPage;
},
get oneShot() {
return this._oneShot;
},
get minInterval() {
return this._minInterval;
},
get wifiOnly() {
return this._wifiOnly;
},
get data() {
return this._data;
},
setPolicy: function(aState, aOverwrittenMinInterval) {
debug("setPolicy");
let self = this;
return new this._window.Promise(function(aResolve, aReject) {
let p = self._manager.setPolicy(self._task, self._app.origin,
self._app.manifestURL,
self._app.isInBrowserElement,
aState,
aOverwrittenMinInterval);
// Set the new value only when the promise is resolved.
p.then(function() {
self._state = aState;
self._overwrittenMinInterval = aOverwrittenMinInterval;
aResolve();
}, aReject);
});
},
runNow: function() {
debug("runNow");
return this._manager.runTask(this._task, this._app.origin,
this._app.manifestURL,
this._app.isInBrowserElement);
}
};
@@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "RequestSyncWifiService.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsIObserverService.h"
namespace mozilla {
namespace dom {
using namespace hal;
NS_IMPL_ISUPPORTS0(RequestSyncWifiService)
namespace {
StaticRefPtr<RequestSyncWifiService> sService;
} // namespace
/* static */ void
RequestSyncWifiService::Init()
{
RefPtr<RequestSyncWifiService> service = GetInstance();
if (!service) {
NS_WARNING("Failed to initialize RequestSyncWifiService.");
}
}
/* static */ already_AddRefed<RequestSyncWifiService>
RequestSyncWifiService::GetInstance()
{
if (!sService) {
sService = new RequestSyncWifiService();
hal::RegisterNetworkObserver(sService);
ClearOnShutdown(&sService);
}
RefPtr<RequestSyncWifiService> service = sService.get();
return service.forget();
}
void
RequestSyncWifiService::Notify(const hal::NetworkInformation& aNetworkInfo)
{
bool isWifi = aNetworkInfo.isWifi();
if (isWifi == mIsWifi) {
return;
}
mIsWifi = isWifi;
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "wifi-state-changed",
mIsWifi ? MOZ_UTF16("enabled") :
MOZ_UTF16("disabled"));
}
}
} // namespace dom
} // namespace mozilla
+43
View File
@@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_RequestSyncWifiService_h
#define mozilla_dom_RequestSyncWifiService_h
#include "mozilla/dom/network/Types.h"
#include "mozilla/Hal.h"
#include "nsIObserver.h"
namespace mozilla {
namespace dom {
class RequestSyncWifiService final : public nsISupports
, public NetworkObserver
{
public:
NS_DECL_ISUPPORTS
static void Init();
static already_AddRefed<RequestSyncWifiService> GetInstance();
void Notify(const hal::NetworkInformation& aNetworkInfo) override;
private:
RequestSyncWifiService()
: mIsWifi(false)
{}
~RequestSyncWifiService()
{}
bool mIsWifi;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_RequestSyncWifiService_h
+34
View File
@@ -0,0 +1,34 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
EXPORTS.mozilla.dom += [
'RequestSyncWifiService.h',
]
EXTRA_COMPONENTS += [
'RequestSync.manifest',
'RequestSyncManager.js',
'RequestSyncScheduler.js',
]
EXTRA_JS_MODULES += [
'RequestSyncApp.jsm',
'RequestSyncService.jsm',
'RequestSyncTask.jsm',
]
SOURCES += [
'RequestSyncWifiService.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wshadow']
+15
View File
@@ -0,0 +1,15 @@
function is(a, b, msg) {
alert((a === b ? 'OK' : 'KO') + ' ' + msg)
}
function ok(a, msg) {
alert((a ? 'OK' : 'KO')+ ' ' + msg)
}
function cbError() {
alert('KO error');
}
function finish() {
alert('DONE');
}
+188
View File
@@ -0,0 +1,188 @@
function test_registerFailure() {
ok("sync" in navigator, "navigator.sync exists");
navigator.sync.register().then(
function() {
ok(false, "navigator.sync.register() throws without a task name");
}, function() {
ok(true, "navigator.sync.register() throws without a task name");
})
.then(function() {
return navigator.sync.register(42);
}).then(function() {
ok(false, "navigator.sync.register() throws without a string task name");
}, function() {
ok(true, "navigator.sync.register() throws without a string task name");
})
.then(function() {
return navigator.sync.register('foobar');
}).then(function() {
ok(false, "navigator.sync.register() throws without a param dictionary");
}, function() {
ok(true, "navigator.sync.register() throws without a param dictionary");
})
.then(function() {
return navigator.sync.register('foobar', 42);
}).then(function() {
ok(false, "navigator.sync.register() throws without a real dictionary");
}, function() {
ok(true, "navigator.sync.register() throws without a real dictionary");
})
.then(function() {
return navigator.sync.register('foobar', {});
}).then(function() {
ok(false, "navigator.sync.register() throws without a minInterval and wakeUpPage");
}, function() {
ok(true, "navigator.sync.register() throws without a minInterval and wakeUpPage");
})
.then(function() {
return navigator.sync.register('foobar', { minInterval: 100 });
}).then(function() {
ok(false, "navigator.sync.register() throws without a wakeUpPage");
}, function() {
ok(true, "navigator.sync.register() throws without a wakeUpPage");
})
.then(function() {
return navigator.sync.register('foobar', { wakeUpPage: 100 });
}).then(function() {
ok(false, "navigator.sync.register() throws without a minInterval");
}, function() {
ok(true, "navigator.sync.register() throws without a minInterval");
})
.then(function() {
runTests();
});
}
function genericError(name, val) {
ok(false, "Promise from " + name + " rejected with value: " + val);
}
function test_register() {
navigator.sync.register('foobar', { minInterval: 5, wakeUpPage:'/' }).then(
function() {
ok(true, "navigator.sync.register() worked!");
runTests();
}, genericError.bind(null, 'register'));
}
function test_unregister() {
navigator.sync.unregister('foobar').then(
function() {
ok(true, "navigator.sync.unregister() worked!");
runTests();
}, genericError.bind(null, 'unregister'));
}
function test_unregisterDuplicate() {
navigator.sync.unregister('foobar').then(
genericError.bind(null, 'unregisterDuplicate'),
function(error) {
ok(true, "navigator.sync.unregister() should throw if the task doesn't exist.");
ok(error, "UnknownTaskError", "Duplicate unregistration error is correct");
runTests();
});
}
function test_registrationEmpty() {
navigator.sync.registration('bar').then(
function(results) {
is(results, null, "navigator.sync.registration() should return null.");
runTests();
},
genericError.bind(null, 'registrationEmpty'));
}
function test_registration() {
navigator.sync.registration('foobar').then(
function(results) {
is(results.task, 'foobar', "navigator.sync.registration().task is correct");
ok("lastSync" in results, "navigator.sync.registration().lastSync is correct");
is(results.oneShot, true, "navigator.sync.registration().oneShot is correct");
is(results.minInterval, 5, "navigator.sync.registration().minInterval is correct");
ok("wakeUpPage" in results, "navigator.sync.registration().wakeUpPage is correct");
ok("wifiOnly" in results, "navigator.sync.registration().wifiOnly is correct");
ok("data" in results, "navigator.sync.registration().data is correct");
ok(!("app" in results), "navigator.sync.registrations().app is correct");
runTests();
},
genericError.bind(null, 'registration'));
}
function test_registrationsEmpty() {
navigator.sync.registrations().then(
function(results) {
is(results.length, 0, "navigator.sync.registrations() should return an empty array.");
runTests();
},
genericError.bind(null, 'registrationEmpty'));
}
function test_registrations() {
navigator.sync.registrations().then(
function(results) {
is(results.length, 1, "navigator.sync.registrations() should not return an empty array.");
is(results[0].task, 'foobar', "navigator.sync.registrations()[0].task is correct");
ok("lastSync" in results[0], "navigator.sync.registrations()[0].lastSync is correct");
is(results[0].oneShot, true, "navigator.sync.registrations()[0].oneShot is correct");
is(results[0].minInterval, 5, "navigator.sync.registrations()[0].minInterval is correct");
ok("wakeUpPage" in results[0], "navigator.sync.registration()[0].wakeUpPage is correct");
ok("wifiOnly" in results[0], "navigator.sync.registrations()[0].wifiOnly is correct");
ok("data" in results[0], "navigator.sync.registrations()[0].data is correct");
ok(!("app" in results[0]), "navigator.sync.registrations()[0].app is correct");
runTests();
},
genericError.bind(null, 'registrations'));
}
function test_managerRegistrationsEmpty() {
navigator.syncManager.registrations().then(
function(results) {
is(results.length, 0, "navigator.syncManager.registrations() should return an empty array.");
runTests();
},
genericError.bind(null, 'managerRegistrationsEmpty'));
}
function test_managerRegistrations(state, overwrittenMinInterval) {
navigator.syncManager.registrations().then(
function(results) {
is(results.length, 1, "navigator.sync.registrations() should not return an empty array.");
is(results[0].task, 'foobar', "navigator.sync.registrations()[0].task is correct");
ok("lastSync" in results[0], "navigator.sync.registrations()[0].lastSync is correct");
is(results[0].oneShot, true, "navigator.sync.registrations()[0].oneShot is correct");
is(results[0].minInterval, 5, "navigator.sync.registrations()[0].minInterval is correct");
ok("wakeUpPage" in results[0], "navigator.sync.registration()[0].wakeUpPage is correct");
ok("wifiOnly" in results[0], "navigator.sync.registrations()[0].wifiOnly is correct");
ok("data" in results[0], "navigator.sync.registrations()[0].data is correct");
ok("app" in results[0], "navigator.sync.registrations()[0].app is correct");
ok("manifestURL" in results[0].app, "navigator.sync.registrations()[0].app.manifestURL is correct");
is(results[0].app.origin, 'http://mochi.test:8888', "navigator.sync.registrations()[0].app.origin is correct");
is(results[0].app.isInBrowserElement, false, "navigator.sync.registrations()[0].app.isInBrowserElement is correct");
is(results[0].state, state, "navigator.sync.registrations()[0].state is correct");
is(results[0].overwrittenMinInterval, overwrittenMinInterval, "navigator.sync.registrations()[0].overwrittenMinInterval is correct");
ok("setPolicy" in results[0], "navigator.sync.registrations()[0].setPolicy is correct");
ok("runNow" in results[0], "navigator.sync.registrations()[0].runNow is correct");
runTests();
},
genericError.bind(null, 'managerRegistrations'));
}
function test_managerSetPolicy(state, overwrittenMinInterval) {
navigator.syncManager.registrations().then(
function(results) {
results[0].setPolicy(state, overwrittenMinInterval).then(
function() {
ok(state, results[0].state, "State matches");
ok(overwrittenMinInterval, results[0].overwrittenMinInterval, "OverwrittenMinInterval matches");
runTests();
}, genericError.bind(null, 'managerSetPolicy'));
}).catch(genericError.bind(null, 'managerSetPolicy_catch'));
}
+52
View File
@@ -0,0 +1,52 @@
var gBasePath = "tests/dom/requestsync/tests/";
var gTemplate = "file_app.template.webapp";
function handleRequest(request, response) {
var query = getQuery(request);
var testToken = '';
if ('testToken' in query) {
testToken = query.testToken;
}
var template = gBasePath + gTemplate;
response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken));
}
// Copy-pasted incantations. There ought to be a better way to synchronously read
// a file into a string, but I guess we're trying to discourage that.
function readTemplate(path) {
var file = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("CurWorkD", Components.interfaces.nsILocalFile);
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
createInstance(Components.interfaces.nsIFileInputStream);
var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
var split = path.split("/");
for(var i = 0; i < split.length; ++i) {
file.append(split[i]);
}
fis.init(file, -1, -1, false);
cis.init(fis, "UTF-8", 0, 0);
var data = "";
let str = {};
let read = 0;
do {
read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
data += str.value;
} while (read != 0);
cis.close();
return data;
}
function getQuery(request) {
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
return query;
}
@@ -0,0 +1,6 @@
{
"name": "Really Rapid Release (hosted)",
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
"launch_path": "/tests/dom/requestsync/tests/TESTTOKEN",
"icons": { "128": "default_icon" }
}
+62
View File
@@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="common_app.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<meta charset="utf-8">
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
function test_sync_interface() {
ok("sync" in navigator, "navigator.sync should exist with permissions");
ok(!("syncManager" in navigator), "navigator.syncManager should not exist without permissions");
ok("register" in navigator.sync, "navigator.sync.register exists");
ok("unregister" in navigator.sync, "navigator.sync.unregister exists");
ok("registrations" in navigator.sync, "navigator.sync.registrations exists");
ok("registration" in navigator.sync, "navigator.sync.registration exists");
runTests();
}
var tests = [
test_sync_interface,
test_registrationsEmpty,
test_registerFailure,
test_register,
// overwrite the same registration.
test_register,
test_registrations,
test_registrationEmpty,
test_registration,
test_unregister,
test_unregisterDuplicate,
test_registrationsEmpty,
// Let's keep a registration active when the app is uninstall...
test_register,
test_registrations
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
runTests();
</script>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="common_app.js"></script>
<meta charset="utf-8">
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
ok("sync" in navigator, "navigator.sync should exist with permissions");
ok("register" in navigator.sync, "navigator.sync.register exists");
ok("unregister" in navigator.sync, "navigator.sync.unregister exists");
ok("registrations" in navigator.sync, "navigator.sync.registrations exists");
ok("registration" in navigator.sync, "navigator.sync.registration exists");
finish();
</script>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
[DEFAULT]
support-files =
file_app.template.webapp
file_app.sjs
file_basic_app.html
common_app.js
common_basic.js
system_message_chrome_script.js
[test_webidl.html]
skip-if = os == "android" || toolkit == "gonk"
[test_minInterval.html]
skip-if = os == "android" || toolkit == "gonk"
[test_basic.html]
skip-if = os == "android" || toolkit == "gonk"
[test_basic_app.html]
skip-if = buildapp != 'mulet' || e10s # mozapps
[test_wakeUp.html]
run-if = buildapp == 'b2g' && toolkit == 'gonk'
[test_runNow.html]
run-if = buildapp == 'b2g' && toolkit == 'gonk'
[test_promise.html]
skip-if = os == "android" || toolkit == "gonk"
[test_bug1151082.html]
skip-if = os == "android" || toolkit == "gonk"
@@ -0,0 +1,18 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
var { classes: Cc, interfaces: Ci } = Components;
const systemMessenger = Cc["@mozilla.org/system-message-internal;1"]
.getService(Ci.nsISystemMessagesInternal);
const ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
addMessageListener("trigger-register-page", function(aData) {
systemMessenger.registerPage(aData.type,
ioService.newURI(aData.pageURL, null, null),
ioService.newURI(aData.manifestURL, null, null));
sendAsyncMessage("page-registered");
});
+77
View File
@@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for RequestSync basic use</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1],
["dom.ignore_webidl_scope_checks", true]]}, runTests);
},
function() {
SpecialPowers.pushPermissions(
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
},
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
runTests();
},
test_managerRegistrationsEmpty,
test_registrationsEmpty,
test_registerFailure,
test_register,
// overwrite the same registration.
test_register,
function() { test_managerRegistrations('wifiOnly', 0); },
test_registrations,
test_registrationEmpty,
test_registration,
function() { test_managerSetPolicy('disabled', 123); },
function() { test_managerRegistrations('disabled', 123); },
function() { test_managerSetPolicy('enabled', 42); },
function() { test_managerRegistrations('enabled', 42); },
test_unregister,
test_unregisterDuplicate,
test_managerRegistrationsEmpty,
test_registrationsEmpty,
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
+130
View File
@@ -0,0 +1,130 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for requestSync - basic operations in app</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var gHostedManifestURL = 'http://test/tests/dom/requestsync/tests/file_app.sjs?testToken=file_basic_app.html';
var gApp;
function cbError() {
ok(false, "Error callback invoked");
finish();
}
function installApp() {
var request = navigator.mozApps.install(gHostedManifestURL);
request.onerror = cbError;
request.onsuccess = function() {
gApp = request.result;
runTests();
}
}
function uninstallApp() {
// Uninstall the app.
var request = navigator.mozApps.mgmt.uninstall(gApp);
request.onerror = cbError;
request.onsuccess = function() {
// All done.
info("All done");
runTests();
}
}
function testApp() {
var ifr = document.createElement('iframe');
ifr.setAttribute('mozbrowser', 'true');
ifr.setAttribute('mozapp', gApp.manifestURL);
ifr.setAttribute('src', gApp.manifest.launch_path);
var domParent = document.getElementById('container');
// Set us up to listen for messages from the app.
var listener = function(e) {
var message = e.detail.message;
if (/^OK/.exec(message)) {
ok(true, "Message from app: " + message);
} else if (/KO/.exec(message)) {
ok(false, "Message from app: " + message);
} else if (/DONE/.exec(message)) {
ok(true, "Messaging from app complete");
ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
domParent.removeChild(ifr);
runTests();
}
}
// This event is triggered when the app calls "alert".
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
domParent.appendChild(ifr);
}
var tests = [
// Permissions
function() {
SpecialPowers.pushPermissions(
[{ "type": "browser", "allow": 1, "context": document },
{ "type": "embed-apps", "allow": 1, "context": document },
{ "type": "requestsync-manager", "allow": 1, "context": document },
{ "type": "webapps-manage", "allow": 1, "context": document }], runTests);
},
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1],
["dom.ignore_webidl_scope_checks", true],
["dom.testing.ignore_ipc_principal", true]]}, runTests);
},
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
SpecialPowers.pushPrefEnv({"set":[["dom.mozBrowserFramesEnabled", true]]}, runTests);
},
// No confirmation needed when an app is installed
function() {
SpecialPowers.autoConfirmAppInstall(() =>
SpecialPowers.autoConfirmAppUninstall(runTests));
},
test_managerRegistrationsEmpty,
// Installing the app
installApp,
// Run tests in app
testApp,
// Uninstall the app
uninstallApp,
test_managerRegistrationsEmpty
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
@@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for RequestSync bug 1151082</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1],
["dom.ignore_webidl_scope_checks", true]]}, runTests);
},
function() {
SpecialPowers.pushPermissions(
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
},
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
runTests();
},
function() {
counter = 2;
function registerCb() {
if (!--counter) {
ok(true, "All the registrations are done.");
runTests();
}
}
navigator.sync.register('foobar', { minInterval: 5, wakeUpPage:'/' }).then(registerCb, genericError);
navigator.sync.register('barfoo', { minInterval: 5, wakeUpPage:'/' }).then(registerCb, genericError);
},
function() {
counter = 2;
function unregisterCb() {
if (!--counter) {
ok(true, "All the unregistrations are done.");
runTests();
}
}
navigator.sync.unregister('foobar').then(unregisterCb, genericError);
navigator.sync.unregister('barfoo').then(unregisterCb, genericError);
}
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
@@ -0,0 +1,69 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for RequestSync minInterval pref</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
function test_minInterval(expected) {
navigator.sync.register('foobar', { minInterval: 1, wakeUpPage: '/' }).then(
function() {
ok(expected, "MinInterval succeeded");
},
function(e) {
ok(!expected, "MinInterval failed");
is(e, "ParamsError", "Correct error received");
})
.then(function() {
if (expected) {
navigator.sync.unregister('foobar').then(runTests);
} else {
runTests();
}
});
}
var tests = [
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
runTests();
},
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.ignore_webidl_scope_checks", true],
["dom.requestSync.enabled", true]]}, runTests);
},
function() { test_minInterval(false); },
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.minInterval", 1]]}, runTests);
},
function() { test_minInterval(true); },
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for requestSync - promise</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["dom.sysmsg.enabled", true]]}, function() {
ok("mozSetMessageHandlerPromise" in navigator, "mozSetMessageHandlerPromise exists");
var status = false;
try {
navigator.mozSetMessageHandlerPromise();
} catch(e) {
status = true;
}
ok(status, "mozSetMessageHandlerPromise wants a promise 1");
status = false;
try {
navigator.mozSetMessageHandlerPromise(42);
} catch(e) {
status = true;
}
ok(status, "mozSetMessageHandlerPromise wants a promise 2");
status = false;
try {
navigator.mozSetMessageHandlerPromise("hello world");
} catch(e) {
status = true;
}
ok(status, "mozSetMessageHandlerPromise wants a promise 3");
status = false;
try {
navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {}));
} catch(e) {
info(e);
status = true;
}
ok(status, "mozSetMessageHandlerPromise cannot be called outside a messageHandler");
SimpleTest.finish();
});
</script>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for requestSync - runNow</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var taskExecuted = false;
function registerPage() {
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('system_message_chrome_script.js'));
gScript.addMessageListener("page-registered", function pageRegisteredHandler() {
gScript.removeMessageListener("page-registered", pageRegisteredHandler);
gScript.destroy();
runTests();
});
gScript.sendAsyncMessage("trigger-register-page",
{ type: "request-sync",
manifestURL: window.location.origin + "/manifest.webapp",
pageURL: window.location.href });
}
function setMessageHandler() {
navigator.mozSetMessageHandler('request-sync', function(e) {
ok(true, "One event has been received!");
if (e.task == "oneShot") {
is(e.data, 42, "e.data is correct");
is(e.lastSync, 0, "e.lastSync is correct");
is(e.oneShot, true, "e.oneShot is correct");
is(e.minInterval, 1024, "e.minInterval is correct");
is(e.wifiOnly, false, "e.wifiOnly is correct");
taskExecuted = true;
} else {
ok(false, "Unknown event has been received!");
}
});
runTests();
}
function test_register_oneShot() {
navigator.sync.register('oneShot', { minInterval: 1024,
oneShot: true,
data: 42,
wifiOnly: false,
wakeUpPage: location.href }).then(
function() {
ok(true, "navigator.sync.register() oneShot done");
runTests();
}, genericError);
}
function test_unregister_oneShot() {
navigator.sync.unregister('oneShot').then(
function() {
ok(true, "navigator.sync.unregister() oneShot done");
runTests();
}, genericError);
}
function test_runNow() {
navigator.syncManager.registrations().then(
function(array) {
for (var i = 0; i < array.length; ++i) { info(array[i].task); }
is(array.length, 1, "One registration found.");
array[0].runNow().then(function() {
ok(taskExecuted, "Task has been executed");
runTests();
});
}, genericError);
}
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.sysmsg.enabled", true],
["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1],
["dom.requestSync.maxTaskTimeout", 10000 /* 10 seconds */],
["dom.ignore_webidl_scope_checks", true]]}, runTests);
},
function() {
SpecialPowers.pushPermissions(
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
},
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
runTests();
},
registerPage,
setMessageHandler,
test_register_oneShot,
test_runNow,
test_unregister_oneShot,
];
function runTests() {
if (!tests.length) {
SimpleTest.finish();
return;
}
var test = tests.shift();
test();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
+187
View File
@@ -0,0 +1,187 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for requestSync - wakeUp</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common_basic.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var oneShotCounter = 0;
var multiShotCounter = 0;
function maybeDone() {
if (oneShotCounter == 1 && multiShotCounter == 5) {
runTests();
}
}
function registerPage() {
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('system_message_chrome_script.js'));
gScript.addMessageListener("page-registered", function pageRegisteredHandler() {
gScript.removeMessageListener("page-registered", pageRegisteredHandler);
gScript.destroy();
runTests();
});
gScript.sendAsyncMessage("trigger-register-page",
{ type: "request-sync",
manifestURL: window.location.origin + "/manifest.webapp",
pageURL: window.location.href });
}
function setMessageHandler() {
navigator.mozSetMessageHandler('request-sync', function(e) {
ok(true, "One event has been received!");
if (e.task == "oneShot") {
is(e.data, 42, "e.data is correct");
is(e.lastSync, 0, "e.lastSync is correct");
is(e.oneShot, true, "e.oneShot is correct");
is(e.minInterval, 2, "e.minInterval is correct");
is(e.wifiOnly, false, "e.wifiOnly is correct");
is(++oneShotCounter, 1, "Only 1 shot should be received here");
maybeDone();
}
else if (e.task == "multiShots") {
is(e.data, 'hello world!', "e.data is correct");
if (multiShotCounter == 0) {
is(e.lastSync, 0, "e.lastSync is correct");
} else {
isnot(e.lastSync, 0, "e.lastSync is correct");
}
is(e.oneShot, false, "e.oneShot is correct");
is(e.minInterval, 3, "e.minInterval is correct");
is(e.wifiOnly, false, "e.wifiOnly is correct");
++multiShotCounter;
if (multiShotCounter == 1) {
info("Setting a promise object.");
navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {
setTimeout(a, 0);
}));
} else if (multiShotCounter == 2) {
// The second time we don't reply at all.
info("Setting a promise object without resolving it.");
navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {}));
} else if (multiShotCounter == 3) {
info("Throwing an exception.");
// Now we throw an exception
SimpleTest.expectUncaughtException();
throw "Booom!";
} else {
info("Setting a promise object and reject it.");
navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {
setTimeout(b, 0);
}));
}
maybeDone();
}
else {
ok(false, "Unknown event has been received!");
}
});
runTests();
}
function test_register_oneShot() {
navigator.sync.register('oneShot', { minInterval: 2,
oneShot: true,
data: 42,
wifiOnly: false,
wakeUpPage: location.href }).then(
function() {
ok(true, "navigator.sync.register() oneShot done");
runTests();
}, genericError);
}
function test_register_multiShots() {
navigator.sync.register('multiShots', { minInterval: 3,
oneShot: false,
data: 'hello world!',
wifiOnly: false,
wakeUpPage: location.href }).then(
function() {
ok(true, "navigator.sync.register() multiShots done");
runTests();
}, genericError);
}
function test_unregister_oneShot() {
navigator.sync.unregister('oneShot').then(
function() {
ok(true, "navigator.sync.unregister() oneShot done");
runTests();
}, genericError);
}
function test_unregister_multiShots() {
navigator.sync.unregister('multiShots').then(
function() {
ok(true, "navigator.sync.unregister() multiShots done");
runTests();
}, genericError);
}
function test_wait() {
// nothing to do here.
}
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1],
["dom.requestSync.maxTaskTimeout", 10000 /* 10 seconds */],
["dom.ignore_webidl_scope_checks", true]]}, runTests);
},
function() {
SpecialPowers.pushPermissions(
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
},
function() {
SpecialPowers.importInMainProcess("resource://gre/modules/RequestSyncService.jsm");
runTests();
},
registerPage,
setMessageHandler,
test_register_oneShot,
test_register_multiShots,
test_wait,
test_unregister_oneShot,
test_unregister_multiShots,
];
function runTests() {
if (!tests.length) {
SimpleTest.finish();
return;
}
var test = tests.shift();
test();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
+104
View File
@@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for RequestSync interfaces</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
function makeIframe() {
var iframe = document.createElement('iframe');
return document.body.appendChild(iframe);
}
function destroyIframe(iframe) {
iframe.remove();
}
function test_no_interface() {
var iframe = makeIframe();
ok(!("sync" in iframe.contentWindow.navigator), "navigator.sync should not exist without permissions");
ok(!("syncManager" in iframe.contentWindow.navigator), "navigator.syncManager should not exist without permissions");
destroyIframe(iframe);
runTests();
}
function test_sync(win) {
ok("register" in win.navigator.sync, "navigator.sync.register exists");
ok("unregister" in win.navigator.sync, "navigator.sync.unregister exists");
ok("registrations" in win.navigator.sync, "navigator.sync.registrations exists");
ok("registration" in win.navigator.sync, "navigator.sync.registration exists");
}
function test_sync_interface() {
var iframe = makeIframe();
ok("sync" in iframe.contentWindow.navigator, "navigator.sync should exist with permissions");
ok(!("syncManager" in iframe.contentWindow.navigator), "navigator.syncManager should not exist without permissions");
test_sync(iframe.contentWindow);
destroyIframe(iframe);
runTests();
}
function test_sync_manager_interface() {
var iframe = makeIframe();
ok("sync" in iframe.contentWindow.navigator, "navigator.sync should exist with permissions");
ok("syncManager" in iframe.contentWindow.navigator, "navigator.syncManager should exist with permissions");
test_sync(iframe.contentWindow);
ok("registrations" in iframe.contentWindow.navigator.syncManager, "navigator.syncManager.registrations exists");
destroyIframe(iframe);
runTests();
}
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", false]]}, runTests);
},
test_no_interface,
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.ignore_webidl_scope_checks", true]]}, runTests);
},
test_no_interface,
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
["dom.requestSync.minInterval", 1]]}, runTests);
},
test_sync_interface,
// Permissions
function() {
SpecialPowers.pushPermissions(
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
},
test_sync_manager_interface,
];
function runTests() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTests();
</script>
</body>
</html>
@@ -1300,6 +1300,12 @@ var interfaceNamesInGlobalScope =
"SVGZoomAndPan",
// IMPORTANT: Do not change this list without review from a DOM peer!
"SVGZoomEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "RequestSyncManager", b2g: true, permission: ["requestsync-manager"] },
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "RequestSyncApp", b2g: true, permission: ["requestsync-manager"] },
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "RequestSyncTask", b2g: true, permission: ["requestsync-manager"] },
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "Telephony", b2g: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
+56
View File
@@ -0,0 +1,56 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
[AvailableIn=CertifiedApps,
Pref="dom.requestSync.enabled",
CheckAnyPermissions="requestsync-manager",
JSImplementation="@mozilla.org/dom/request-sync-task-app;1"]
interface RequestSyncApp {
readonly attribute USVString origin;
readonly attribute USVString manifestURL;
readonly attribute boolean isInBrowserElement;
};
enum RequestSyncTaskPolicyState { "enabled", "disabled", "wifiOnly" };
// Like a normal task, but with info about the app.
[AvailableIn=CertifiedApps,
Pref="dom.requestSync.enabled",
CheckAnyPermissions="requestsync-manager",
JSImplementation="@mozilla.org/dom/request-sync-task-manager;1"]
interface RequestSyncTask {
// This object describes the app that is owning the task.
readonly attribute RequestSyncApp app;
// Using setPolicy it's possible to owerwrite the state and the minInterval.
readonly attribute RequestSyncTaskPolicyState state;
readonly attribute long overwrittenMinInterval;
// These attributes are taken from the configuration of the task:
readonly attribute USVString task;
readonly attribute DOMTimeStamp lastSync;
readonly attribute USVString wakeUpPage;
readonly attribute boolean oneShot;
readonly attribute long minInterval;
readonly attribute boolean wifiOnly;
readonly attribute any data;
Promise<void> setPolicy(RequestSyncTaskPolicyState aState,
optional long ovewrittenMinInterval);
Promise<void> runNow();
};
[NavigatorProperty="syncManager",
AvailableIn=CertifiedApps,
Pref="dom.requestSync.enabled",
CheckAnyPermissions="requestsync-manager",
JSImplementation="@mozilla.org/dom/request-sync-manager;1"]
// This interface will be used only by the B2G SystemApp
interface RequestSyncManager {
Promise<sequence<RequestSyncTask>> registrations();
};
+38
View File
@@ -0,0 +1,38 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
// This is the dictionary for the creation of a new task.
dictionary RequestTaskParams {
required USVString wakeUpPage;
boolean oneShot = true;
required long minInterval; // in seconds >= dom.requestSync.minInterval or 100 secs
boolean wifiOnly = true;
any data = null;
};
// This is the dictionary you can have back from registration{s}().
dictionary RequestTaskFull : RequestTaskParams {
USVString task = "";
// Last synchonization date.. maybe it's useful to know.
DOMTimeStamp lastSync;
};
[NavigatorProperty="sync",
AvailableIn=CertifiedApps,
Pref="dom.requestSync.enabled",
JSImplementation="@mozilla.org/dom/request-sync-scheduler;1"]
interface RequestSyncScheduler {
Promise<void> register(USVString task,
optional RequestTaskParams params);
Promise<void> unregister(USVString task);
// Useful methods to get registrations
Promise<sequence<RequestTaskFull>> registrations();
Promise<RequestTaskFull> registration(USVString task);
};
+2
View File
@@ -394,6 +394,8 @@ WEBIDL_FILES = [
'Range.webidl',
'Rect.webidl',
'Request.webidl',
'RequestSyncManager.webidl',
'RequestSyncScheduler.webidl',
'ResourceStats.webidl',
'ResourceStatsManager.webidl',
'Response.webidl',
+5
View File
@@ -66,6 +66,7 @@
#include "ActiveLayerTracker.h"
#include "CounterStyleManager.h"
#include "FrameLayerBuilder.h"
#include "mozilla/dom/RequestSyncWifiService.h"
#include "AnimationCommon.h"
#include "LayerAnimationInfo.h"
@@ -307,6 +308,10 @@ nsLayoutStatics::Initialize()
ServiceWorkerRegistrar::Initialize();
#ifdef MOZ_B2G
RequestSyncWifiService::Init();
#endif
#ifdef DEBUG
nsStyleContext::Initialize();
mozilla::LayerAnimationInfo::Initialize();
@@ -0,0 +1,46 @@
diff --git a/media/libspeex_resampler/src/resample.c b/media/libspeex_resampler/src/resample.c
index 83ad119..a3859e3 100644
--- a/media/libspeex_resampler/src/resample.c
+++ b/media/libspeex_resampler/src/resample.c
@@ -811,6 +811,12 @@ EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
return NULL;
}
st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState));
+ if (!st)
+ {
+ if (err)
+ *err = RESAMPLER_ERR_ALLOC_FAILED;
+ return NULL;
+ }
st->initialised = 0;
st->started = 0;
st->in_rate = 0;
@@ -832,9 +838,12 @@ EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
st->buffer_size = 160;
/* Per channel data */
- st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(spx_int32_t));
- st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t));
- st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t));
+ if (!(st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(spx_int32_t))))
+ goto fail;
+ if (!(st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t))))
+ goto fail;
+ if (!(st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t))))
+ goto fail;
for (i=0;i<nb_channels;i++)
{
st->last_sample[i] = 0;
@@ -857,6 +866,12 @@ EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
*err = filter_err;
return st;
+
+fail:
+ if (err)
+ *err = RESAMPLER_ERR_ALLOC_FAILED;
+ speex_resampler_destroy(st);
+ return NULL;
}
EXPORT void speex_resampler_destroy(SpeexResamplerState *st)
+3
View File
@@ -5457,6 +5457,9 @@ pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\",\"special-powers@m
// the percentage of time addons are allowed to use without being labeled slow
pref("browser.addon-watch.percentage-limit", 5);
// RequestSync API is disabled by default.
pref("dom.requestSync.enabled", false);
// Search service settings
pref("browser.search.log", false);
pref("browser.search.update", true);
+2 -2
View File
@@ -300,7 +300,7 @@ nsDeque::PopFront()
* @return last item in container
*/
void*
nsDeque::Peek()
nsDeque::Peek() const
{
void* result = 0;
if (mSize > 0) {
@@ -316,7 +316,7 @@ nsDeque::Peek()
* @return last item in container
*/
void*
nsDeque::PeekFront()
nsDeque::PeekFront() const
{
void* result = 0;
if (mSize > 0) {
+2 -2
View File
@@ -124,13 +124,13 @@ public:
* @return the first item in container
*/
void* Peek();
void* Peek() const;
/**
* Return topmost item without removing it.
*
* @return the first item in container
*/
void* PeekFront();
void* PeekFront() const;
/**
* Retrieve a member from the deque without removing it.