mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-29 10:08:38 +00:00
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:
@@ -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");
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -44,6 +44,7 @@ DIRS += [
|
||||
'bluetooth',
|
||||
'activities',
|
||||
'archivereader',
|
||||
'requestsync',
|
||||
'bindings',
|
||||
'battery',
|
||||
'browser-element',
|
||||
|
||||
@@ -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}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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]);
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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']
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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!
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -394,6 +394,8 @@ WEBIDL_FILES = [
|
||||
'Range.webidl',
|
||||
'Rect.webidl',
|
||||
'Request.webidl',
|
||||
'RequestSyncManager.webidl',
|
||||
'RequestSyncScheduler.webidl',
|
||||
'ResourceStats.webidl',
|
||||
'ResourceStatsManager.webidl',
|
||||
'Response.webidl',
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user