Merge b2g-inbound to m-c.

This commit is contained in:
Ryan VanderMeulen 2014-01-02 16:14:38 -05:00
commit d0a9cd5005
42 changed files with 4324 additions and 247 deletions

View File

@ -1,4 +1,4 @@
{
"revision": "77a90d19d2c6feb621f894685068bd131c0a0fdb",
"revision": "b61e95024e07f0ad99e20802953fb8b62ae02505",
"repo_path": "/integration/gaia-central"
}

View File

@ -37,8 +37,6 @@ PRLogModuleInfo* gMediaEncoderLog;
namespace mozilla {
static nsIThread* sEncoderThread = nullptr;
void
MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
TrackID aID,
@ -75,11 +73,6 @@ MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
}
bool
MediaEncoder::OnEncoderThread()
{
return NS_GetCurrentThread() == sEncoderThread;
}
/* static */
already_AddRefed<MediaEncoder>
MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
@ -179,9 +172,7 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
nsAString& aMIMEType)
{
MOZ_ASSERT(!NS_IsMainThread());
if (!sEncoderThread) {
sEncoderThread = NS_GetCurrentThread();
}
aMIMEType = mMIMEType;
bool reloop = true;

View File

@ -94,10 +94,6 @@ public :
*/
static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
uint8_t aTrackTypes = ContainerWriter::HAS_AUDIO);
/**
* Check if run on Encoder thread
*/
static bool OnEncoderThread();
/**
* Encodes the raw track data and returns the final container data. Assuming
* it is called on a single worker thread. The buffer of container data is

View File

@ -18,7 +18,9 @@ public:
enum MetadataKind {
METADATA_OPUS, // Represent the Opus metadata
METADATA_VP8,
METADATA_UNKNOW // Metadata Kind not set
METADATA_AVC,
METADATA_AAC,
METADATA_UNKNOWN // Metadata Kind not set
};
virtual ~TrackMetadataBase() {}
// Return the specific metadata kind

View File

@ -0,0 +1,120 @@
/* -*- Mode: C++; 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/. */
#include <climits>
#include "ISOControl.h"
#include "ISOMediaBoxes.h"
#include "AVCBox.h"
namespace mozilla {
nsresult
VisualSampleEntry::Generate(uint32_t* aBoxSize)
{
// both fields occupy 16 bits defined in 14496-2 6.2.3.
width = mMeta.mVidMeta->Width;
height = mMeta.mVidMeta->Height;
uint32_t avc_box_size = 0;
nsresult rv;
rv = avcConfigBox->Generate(&avc_box_size);
NS_ENSURE_SUCCESS(rv, rv);
size += avc_box_size +
sizeof(reserved) +
sizeof(width) +
sizeof(height) +
sizeof(horizresolution) +
sizeof(vertresolution) +
sizeof(reserved2) +
sizeof(frame_count) +
sizeof(compressorName) +
sizeof(depth) +
sizeof(pre_defined);
*aBoxSize = size;
return NS_OK;
}
nsresult
VisualSampleEntry::Write()
{
BoxSizeChecker checker(mControl, size);
SampleEntryBox::Write();
mControl->Write(reserved, sizeof(reserved));
mControl->Write(width);
mControl->Write(height);
mControl->Write(horizresolution);
mControl->Write(vertresolution);
mControl->Write(reserved2);
mControl->Write(frame_count);
mControl->Write(compressorName, sizeof(compressorName));
mControl->Write(depth);
mControl->Write(pre_defined);
nsresult rv = avcConfigBox->Write();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
VisualSampleEntry::VisualSampleEntry(ISOControl* aControl)
: SampleEntryBox(NS_LITERAL_CSTRING("avc1"), Video_Track, aControl)
, width(0)
, height(0)
, horizresolution(resolution_72_dpi)
, vertresolution(resolution_72_dpi)
, reserved2(0)
, frame_count(1)
, depth(video_depth)
, pre_defined(-1)
{
memset(reserved, 0 , sizeof(reserved));
memset(compressorName, 0 , sizeof(compressorName));
avcConfigBox = new AVCConfigurationBox(aControl);
MOZ_COUNT_CTOR(VisualSampleEntry);
}
VisualSampleEntry::~VisualSampleEntry()
{
MOZ_COUNT_DTOR(VisualSampleEntry);
}
AVCConfigurationBox::AVCConfigurationBox(ISOControl* aControl)
: Box(NS_LITERAL_CSTRING("avcC"), aControl)
{
MOZ_COUNT_CTOR(AVCConfigurationBox);
}
AVCConfigurationBox::~AVCConfigurationBox()
{
MOZ_COUNT_DTOR(AVCConfigurationBox);
}
nsresult
AVCConfigurationBox::Generate(uint32_t* aBoxSize)
{
nsresult rv;
FragmentBuffer* frag = mControl->GetFragment(Video_Track);
rv = frag->GetCSD(avcConfig);
NS_ENSURE_SUCCESS(rv, rv);
size += avcConfig.Length();
*aBoxSize = size;
return NS_OK;
}
nsresult
AVCConfigurationBox::Write()
{
BoxSizeChecker checker(mControl, size);
Box::Write();
mControl->Write(avcConfig.Elements(), avcConfig.Length());
return NS_OK;
}
}

View File

@ -0,0 +1,73 @@
/* -*- Mode: C++; 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/. */
#ifndef AVCBox_h_
#define AVCBox_h_
#include "nsTArray.h"
#include "nsAutoPtr.h"
#include "ISOMediaBoxes.h"
namespace mozilla {
class ISOControl;
// 14496-12 8.5.2.2
#define resolution_72_dpi 0x00480000
#define video_depth 0x0018
// 14496-15 5.3.4.1 'Sample description name and format'
// Box type: 'avcC'
class AVCConfigurationBox : public Box {
public:
// ISO BMFF members
// avcConfig is CodecSpecificData from 14496-15 '5.3.4.1 Sample description
// name and format.
// These data are generated by encoder and we encapsulated the generated
// bitstream into box directly.
nsTArray<uint8_t> avcConfig;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// AVCConfigurationBox methods
AVCConfigurationBox(ISOControl* aControl);
~AVCConfigurationBox();
};
// 14496-15 5.3.4.1 'Sample description name and format'
// Box type: 'avc1'
class VisualSampleEntry : public SampleEntryBox {
public:
// ISO BMFF members
uint8_t reserved[16];
uint16_t width;
uint16_t height;
uint32_t horizresolution; // 72 dpi
uint32_t vertresolution; // 72 dpi
uint32_t reserved2;
uint16_t frame_count; // 1, defined in 14496-12 8.5.2.2
uint8_t compressorName[32];
uint16_t depth; // 0x0018, defined in 14496-12 8.5.2.2;
uint16_t pre_defined; // -1, defined in 14496-12 8.5.2.2;
nsRefPtr<AVCConfigurationBox> avcConfigBox;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// VisualSampleEntry methods
VisualSampleEntry(ISOControl* aControl);
~VisualSampleEntry();
};
}
#endif // AVCBox_h_

View File

@ -0,0 +1,385 @@
/* -*- Mode: C++; 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/. */
#include <time.h>
#include "nsAutoPtr.h"
#include "ISOControl.h"
#include "ISOMediaBoxes.h"
#include "EncodedFrameContainer.h"
namespace mozilla {
// For MP4 creation_time and modification_time offset from January 1, 1904 to
// January 1, 1970.
#define iso_time_offset 2082844800
const static uint32_t MUXING_BUFFER_SIZE = 512*1024;
FragmentBuffer::FragmentBuffer(uint32_t aTrackType, uint32_t aFragDuration,
TrackMetadataBase* aMetadata)
: mTrackType(aTrackType)
, mFragDuration(aFragDuration)
, mMediaStartTime(0)
, mFragmentNumber(0)
, mEOS(false)
{
mFragArray.AppendElement();
if (mTrackType == Audio_Track) {
nsRefPtr<AACTrackMetadata> audMeta = static_cast<AACTrackMetadata*>(aMetadata);
MOZ_ASSERT(audMeta);
} else {
nsRefPtr<AVCTrackMetadata> vidMeta = static_cast<AVCTrackMetadata*>(aMetadata);
MOZ_ASSERT(vidMeta);
}
MOZ_COUNT_CTOR(FragmentBuffer);
}
FragmentBuffer::~FragmentBuffer()
{
MOZ_COUNT_DTOR(FragmentBuffer);
}
bool
FragmentBuffer::HasEnoughData()
{
// Audio or video frame is enough to form a moof.
return (mFragArray.Length() > 1);
}
nsresult
FragmentBuffer::GetCSD(nsTArray<uint8_t>& aCSD)
{
if (!mCSDFrame) {
return NS_ERROR_FAILURE;
}
aCSD.AppendElements(mCSDFrame->GetFrameData().Elements(),
mCSDFrame->GetFrameData().Length());
return NS_OK;
}
nsresult
FragmentBuffer::AddFrame(EncodedFrame* aFrame)
{
// already EOS, it rejects all new data.
if (mEOS) {
MOZ_ASSERT(0);
return NS_OK;
}
EncodedFrame::FrameType type = aFrame->GetFrameType();
if (type == EncodedFrame::AAC_CSD || type == EncodedFrame::AVC_CSD) {
mCSDFrame = aFrame;
// Ue CSD's timestamp as the start time. Encoder should send CSD frame first
// and data frames.
mMediaStartTime = aFrame->GetTimeStamp();
mFragmentNumber = 1;
return NS_OK;
}
// if the timestamp is incorrect, abort it.
if (aFrame->GetTimeStamp() < mMediaStartTime) {
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
mFragArray.LastElement().AppendElement(aFrame);
// check if current fragment is reach the fragment duration.
if ((aFrame->GetTimeStamp() - mMediaStartTime) > (mFragDuration * mFragmentNumber)) {
mFragArray.AppendElement();
mFragmentNumber++;
}
return NS_OK;
}
nsresult
FragmentBuffer::GetFirstFragment(nsTArray<nsRefPtr<EncodedFrame>>& aFragment,
bool aFlush)
{
// It should be called only if there is a complete fragment in mFragArray.
if (mFragArray.Length() <= 1 && !mEOS) {
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
if (aFlush) {
aFragment.SwapElements(mFragArray.ElementAt(0));
mFragArray.RemoveElementAt(0);
} else {
aFragment.AppendElements(mFragArray.ElementAt(0));
}
return NS_OK;
}
uint32_t
FragmentBuffer::GetFirstFragmentSampleNumber()
{
return mFragArray.ElementAt(0).Length();
}
uint32_t
FragmentBuffer::GetFirstFragmentSampleSize()
{
uint32_t size = 0;
uint32_t len = mFragArray.ElementAt(0).Length();
for (uint32_t i = 0; i < len; i++) {
size += mFragArray.ElementAt(0).ElementAt(i)->GetFrameData().Length();
}
return size;
}
ISOControl::ISOControl()
: mAudioFragmentBuffer(nullptr)
, mVideoFragmentBuffer(nullptr)
, mFragNum(0)
, mOutputSize(0)
, mBitCount(0)
, mBit(0)
{
mOutBuffer.SetCapacity(MUXING_BUFFER_SIZE);
MOZ_COUNT_CTOR(ISOControl);
}
ISOControl::~ISOControl()
{
MOZ_COUNT_DTOR(ISOControl);
}
uint32_t
ISOControl::GetNextTrackID()
{
return (mMetaArray.Length() + 1);
}
uint32_t
ISOControl::GetTrackID(uint32_t aTrackType)
{
TrackMetadataBase::MetadataKind kind;
if (aTrackType == Audio_Track) {
kind = TrackMetadataBase::METADATA_AAC;
} else {
kind = TrackMetadataBase::METADATA_AVC;
}
for (uint32_t i = 0; i < mMetaArray.Length(); i++) {
if (mMetaArray[i]->GetKind() == kind) {
return (i + 1);
}
}
return 0;
}
nsresult
ISOControl::SetMetadata(TrackMetadataBase* aTrackMeta)
{
if (aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AAC ||
aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AVC) {
mMetaArray.AppendElement(aTrackMeta);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
nsresult
ISOControl::GetAudioMetadata(nsRefPtr<AACTrackMetadata>& aAudMeta)
{
for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AAC) {
aAudMeta = static_cast<AACTrackMetadata*>(mMetaArray[i].get());
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
nsresult
ISOControl::GetVideoMetadata(nsRefPtr<AVCTrackMetadata>& aVidMeta)
{
for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AVC) {
aVidMeta = static_cast<AVCTrackMetadata*>(mMetaArray[i].get());
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
bool
ISOControl::HasAudioTrack()
{
nsRefPtr<AACTrackMetadata> audMeta;
GetAudioMetadata(audMeta);
return audMeta;
}
bool
ISOControl::HasVideoTrack()
{
nsRefPtr<AVCTrackMetadata> vidMeta;
GetVideoMetadata(vidMeta);
return vidMeta;
}
nsresult
ISOControl::SetFragment(FragmentBuffer* aFragment)
{
if (aFragment->GetType() == Audio_Track) {
mAudioFragmentBuffer = aFragment;
} else {
mVideoFragmentBuffer = aFragment;
}
return NS_OK;
}
FragmentBuffer*
ISOControl::GetFragment(uint32_t aType)
{
if (aType == Audio_Track) {
return mAudioFragmentBuffer;
} else if (aType == Video_Track){
return mVideoFragmentBuffer;
}
MOZ_ASSERT(0);
return nullptr;
}
nsresult
ISOControl::GetBuf(nsTArray<uint8_t>& aOutBuf)
{
mOutputSize += mOutBuffer.Length();
aOutBuf.SwapElements(mOutBuffer);
return FlushBuf();
}
nsresult
ISOControl::FlushBuf()
{
mOutBuffer.SetCapacity(MUXING_BUFFER_SIZE);
mLastWrittenBoxPos = 0;
return NS_OK;
}
uint32_t
ISOControl::WriteBits(uint64_t aBits, size_t aNumBits)
{
uint8_t output_byte = 0;
MOZ_ASSERT(aNumBits <= 64);
// TODO: rewritten following with bitset?
for (size_t i = aNumBits; i > 0; i--) {
mBit |= (((aBits >> (i - 1)) & 1) << (8 - ++mBitCount));
if (mBitCount == 8) {
Write(&mBit, sizeof(uint8_t));
mBit = 0;
mBitCount = 0;
output_byte++;
}
}
return output_byte;
}
uint32_t
ISOControl::Write(uint8_t* aBuf, uint32_t aSize)
{
mOutBuffer.AppendElements(aBuf, aSize);
return aSize;
}
uint32_t
ISOControl::Write(uint8_t aData)
{
MOZ_ASSERT(!mBitCount);
Write((uint8_t*)&aData, sizeof(uint8_t));
return sizeof(uint8_t);
}
uint32_t
ISOControl::WriteFourCC(const char* aType)
{
// Bit operation should be aligned to byte before writing any byte data.
MOZ_ASSERT(!mBitCount);
uint32_t size = strlen(aType);
if (size == 4) {
return Write((uint8_t*)aType, size);
}
return 0;
}
nsresult
ISOControl::GenerateFtyp()
{
nsresult rv;
uint32_t size;
nsAutoPtr<FileTypeBox> type_box(new FileTypeBox(this));
rv = type_box->Generate(&size);
NS_ENSURE_SUCCESS(rv, rv);
rv = type_box->Write();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
ISOControl::GenerateMoov()
{
nsresult rv;
uint32_t size;
nsAutoPtr<MovieBox> moov_box(new MovieBox(this));
rv = moov_box->Generate(&size);
NS_ENSURE_SUCCESS(rv, rv);
rv = moov_box->Write();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
ISOControl::GenerateMoof(uint32_t aTrackType)
{
mFragNum++;
nsresult rv;
uint32_t size;
uint64_t first_sample_offset = mOutputSize + mLastWrittenBoxPos;
nsAutoPtr<MovieFragmentBox> moof_box(new MovieFragmentBox(aTrackType, this));
nsAutoPtr<MediaDataBox> mdat_box(new MediaDataBox(aTrackType, this));
rv = moof_box->Generate(&size);
NS_ENSURE_SUCCESS(rv, rv);
first_sample_offset += size;
rv = mdat_box->Generate(&size);
NS_ENSURE_SUCCESS(rv, rv);
first_sample_offset += mdat_box->FirstSampleOffsetInMediaDataBox();
// correct offset info
nsTArray<nsRefPtr<MuxerOperation>> tfhds;
rv = moof_box->Find(NS_LITERAL_CSTRING("tfhd"), tfhds);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t len = tfhds.Length();
for (uint32_t i = 0; i < len; i++) {
TrackFragmentHeaderBox* tfhd = (TrackFragmentHeaderBox*) tfhds.ElementAt(i).get();
rv = tfhd->UpdateBaseDataOffset(first_sample_offset);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = moof_box->Write();
NS_ENSURE_SUCCESS(rv, rv);
rv = mdat_box->Write();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
uint32_t
ISOControl::GetTime()
{
return (uint64_t)time(nullptr) + iso_time_offset;
}
}

View File

@ -0,0 +1,221 @@
/* -*- Mode: C++; 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/. */
#ifndef ISOCOMPOSITOR_H_
#define ISOCOMPOSITOR_H_
#include "mozilla/Endian.h"
#include "TrackMetadataBase.h"
#include "nsTArray.h"
#include "ISOTrackMetadata.h"
#include "EncodedFrameContainer.h"
namespace mozilla {
class Box;
class ISOControl;
/**
* This class collects elementary stream data to form a fragment.
* ISOMediaWriter will check if the data is enough; if yes, the corresponding
* moof will be created and write to ISOControl.
* Each audio and video has its own fragment and only one during the whole
* life cycle, when a fragment is formed in ISOControl, Flush() needs to
* be called to reset it.
*/
class FragmentBuffer {
public:
// aTrackType: it could be Audio_Track or Video_Track.
// aFragDuration: it is the fragment duration. (microsecond per unit)
// Audio and video have the same fragment duration.
FragmentBuffer(uint32_t aTrackType, uint32_t aFragDuration,
TrackMetadataBase* aMetadata);
~FragmentBuffer();
// Get samples of first fragment, that will swap all the elements in the
// mFragArray[0] when aFlush = true, and caller is responsible for drop
// EncodedFrame reference count.
nsresult GetFirstFragment(nsTArray<nsRefPtr<EncodedFrame>>& aFragment,
bool aFlush = false);
// Add sample frame to the last element fragment of mFragArray. If sample
// number is enough, it will append a new fragment element. And the new
// sample will be added to the new fragment element of mFragArray.
nsresult AddFrame(EncodedFrame* aFrame);
// Get total sample size of first complete fragment size.
uint32_t GetFirstFragmentSampleSize();
// Get sample number of first complete fragment.
uint32_t GetFirstFragmentSampleNumber();
// Check if it accumulates enough frame data.
// It returns true when data is enough to form a fragment.
bool HasEnoughData();
// Called by ISOMediaWriter when TrackEncoder has sent the last frame. The
// remains frame data will form the last moof and move the state machine to
// in ISOMediaWriter to last phrase.
nsresult SetEndOfStream() {
mEOS = true;
return NS_OK;
}
bool EOS() { return mEOS; }
// CSD (codec specific data), it is generated by encoder and the data depends
// on codec type. This data will be sent as a special frame from encoder to
// ISOMediaWriter and pass to this class via AddFrame().
nsresult GetCSD(nsTArray<uint8_t>& aCSD);
bool HasCSD() { return mCSDFrame; }
uint32_t GetType() { return mTrackType; }
private:
uint32_t mTrackType;
// Fragment duration, microsecond per unit.
uint32_t mFragDuration;
// Media start time, microsecond per unit.
// Together with mFragDuration, mFragmentNumber and EncodedFrame->GetTimeStamp(),
// when the difference between current frame time and mMediaStartTime is
// exceeded current fragment ceiling timeframe, that means current fragment has
// enough data and a new element in mFragArray will be added.
uint64_t mMediaStartTime;
// Current fragment number. It will be increase when a new element of
// mFragArray is created.
// Note:
// It only means the fragment number of current accumulated frames, not
// the current 'creating' fragment mFragNum in ISOControl.
uint32_t mFragmentNumber;
// Array of fragments, each element has enough samples to form a
// complete fragment.
nsTArray<nsTArray<nsRefPtr<EncodedFrame>>> mFragArray;
// Codec specific data frame, it will be generated by encoder and send to
// ISOMediaWriter through WriteEncodedTrack(). The data will be vary depends
// on codec type.
nsRefPtr<EncodedFrame> mCSDFrame;
// END_OF_STREAM from ContainerWriter
bool mEOS;
};
/**
* ISOControl will be carried to each box when box is created. It is the main
* bridge for box to output stream to ContainerWriter and retrieve information.
* ISOControl acts 3 different roles:
* 1. Holds the pointer of audio metadata, video metadata, fragment and
* pass them to boxes.
* 2. Provide the functions to generate the base structure of MP4; they are
* GenerateFtyp, GenerateMoov, GenerateMoof, and GenerateMfra.
* 3. The actually writer used by MuxOperation::Write() in each box. It provides
* writing methods for different kind of data; they are Write, WriteArray,
* WriteBits...etc.
*/
class ISOControl {
friend class Box;
public:
ISOControl();
~ISOControl();
nsresult GenerateFtyp();
nsresult GenerateMoov();
nsresult GenerateMoof(uint32_t aTrackType);
uint32_t Write(uint8_t* aBuf, uint32_t aSize);
uint32_t Write(uint8_t aData);
template <typename T>
uint32_t Write(T aData) {
MOZ_ASSERT(!mBitCount);
aData = NativeEndian::swapToNetworkOrder(aData);
Write((uint8_t*)&aData, sizeof(T));
return sizeof(T);
}
template <typename T>
uint32_t WriteArray(const T &aArray, uint32_t aSize) {
MOZ_ASSERT(!mBitCount);
uint32_t size = 0;
for (uint32_t i = 0; i < aSize; i++) {
size += Write(aArray[i]);
}
return size;
}
uint32_t WriteFourCC(const char* aType);
// Bit writing. Note: it needs to be byte-boundary before using
// others non-bit writing function.
uint32_t WriteBits(uint64_t aBits, size_t aNumBits);
// This is called by GetContainerData and swap the buffer to aOutBuf.
nsresult GetBuf(nsTArray<uint8_t>& aOutBuf);
// Presentation time in seconds since midnight, Jan. 1, 1904, in UTC time.
uint32_t GetTime();
// current fragment number
uint32_t GetCurFragmentNumber() { return mFragNum; }
nsresult SetFragment(FragmentBuffer* aFragment);
FragmentBuffer* GetFragment(uint32_t aType);
nsresult SetMetadata(TrackMetadataBase* aTrackMeta);
nsresult GetAudioMetadata(nsRefPtr<AACTrackMetadata>& aAudMeta);
nsresult GetVideoMetadata(nsRefPtr<AVCTrackMetadata>& aVidMeta);
// Track ID is the Metadata index in mMetaArray.
uint32_t GetTrackID(uint32_t aTrackType);
uint32_t GetNextTrackID();
bool HasAudioTrack();
bool HasVideoTrack();
private:
uint32_t GetBufPos() { return mOutBuffer.Length(); }
nsresult FlushBuf();
// Audio and video fragments are owned by ISOMediaWriter.
// They don't need to worry about pointer going stale.
FragmentBuffer* mAudioFragmentBuffer;
FragmentBuffer* mVideoFragmentBuffer;
// Generated fragment number
uint32_t mFragNum;
// The (index + 1) will be the track ID.
nsTArray<nsRefPtr<TrackMetadataBase>> mMetaArray;
// TODO:
// ContainerWriter accepts a array of uint8_t array so it is possible to
// create a serial of small box header + swap the raw data pointer from
// WriteEncodedTrack to another array without any memory copy.
nsTArray<uint8_t> mOutBuffer;
// Last written position of current box, it is for box checking purpose.
uint32_t mLastWrittenBoxPos;
// Accumulate size of output fragments.
uint64_t mOutputSize;
// Bit writing operation. Note: the mBitCount should be 0 before any
// byte-boundary writing method be called (Write(uint32_t), Write(uint16_t)...etc);
// otherwise, there will be assertion on these functions.
uint8_t mBitCount;
uint8_t mBit;
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,735 @@
/* -*- Mode: C++; 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/. */
#ifndef ISOMediaBoxes_h_
#define ISOMediaBoxes_h_
#include <bitset>
#include "nsString.h"
#include "nsTArray.h"
#include "nsAutoPtr.h"
#include "MuxerOperation.h"
#define WRITE_FULLBOX(_compositor, _size) \
BoxSizeChecker checker(_compositor, _size); \
FullBox::Write();
#define FOURCC(a, b, c, d) ( ((a) << 24) | ((b) << 16) | ((c) << 8) | (d) )
namespace mozilla {
class AACTrackMetadata;
class AVCTrackMetadata;
class ES_Descriptor;
class ISOControl;
/**
* This is the base class for all ISO media format boxes.
* It provides the fields of box type(four CC) and size.
* The data members in the beginning of a Box (or its descendants)
* are the 14496-12 defined member. Other members prefix with 'm'
* are private control data.
*
* This class is for inherited only, it shouldn't be instanced directly.
*/
class Box : public MuxerOperation {
protected:
// ISO BMFF members
uint32_t size; // 14496-12 4-2 'Object Structure'. Size of this box.
nsCString boxType; // four CC name, all table names are listed in
// 14496-12 table 1.
public:
// MuxerOperation methods
nsresult Write() MOZ_OVERRIDE;
nsresult Find(const nsACString& aType,
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
// A helper class to check box written bytes number; it will compare
// the size generated from Box::Generate() and the actually written length in
// Box::Write().
class MetaHelper {
public:
nsresult Init(ISOControl* aControl);
bool AudioOnly() {
if (mAudMeta && !mVidMeta) {
return true;
}
return false;
}
nsRefPtr<AACTrackMetadata> mAudMeta;
nsRefPtr<AVCTrackMetadata> mVidMeta;
};
// This helper class will compare the written size in Write() and the size in
// Generate(). If their are not equal, it will assert.
class BoxSizeChecker {
public:
BoxSizeChecker(ISOControl* aControl, uint32_t aSize);
~BoxSizeChecker();
uint32_t ori_size;
uint32_t box_size;
ISOControl* mControl;
};
protected:
Box() MOZ_DELETE;
Box(const nsACString& aType, ISOControl* aControl);
ISOControl* mControl;
};
/**
* FullBox (and its descendants) is the box which contains the 'real' data
* members. It is the edge in the ISO box structure and it doesn't contain
* any box.
*
* This class is for inherited only, it shouldn't be instanced directly.
*/
class FullBox : public Box {
public:
// ISO BMFF members
uint8_t version; // 14496-12 4.2 'Object Structure'
std::bitset<24> flags; //
// MuxerOperation methods
nsresult Write() MOZ_OVERRIDE;
protected:
// FullBox methods
FullBox(const nsACString& aType, uint8_t aVersion, uint32_t aFlags,
ISOControl* aControl);
FullBox() MOZ_DELETE;
};
/**
* The default implementation of the container box.
* Basically, the container box inherits this class and overrides the
* constructor only.
*
* According to 14496-12 3.1.1 'container box', a container box is
* 'box whose sole purpose is to contain and group a set of related boxes'
*
* This class is for inherited only, it shouldn't be instanced directly.
*/
class DefaultContainerImpl : public Box {
public:
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
nsresult Find(const nsACString& aType,
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
protected:
// DefaultContainerImpl methods
DefaultContainerImpl(const nsACString& aType, ISOControl* aControl);
DefaultContainerImpl() MOZ_DELETE;
nsTArray<nsRefPtr<MuxerOperation>> boxes;
};
// 14496-12 4.3 'File Type Box'
// Box type: 'ftyp'
class FileTypeBox : public Box {
public:
// ISO BMFF members
nsCString major_brand; // four chars
uint32_t minor_version;
nsTArray<nsCString> compatible_brands;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// FileTypeBox methods
FileTypeBox(ISOControl* aControl);
~FileTypeBox();
};
// 14496-12 8.2.1 'Movie Box'
// Box type: 'moov'
// MovieBox contains MovieHeaderBox, TrackBox and MovieExtendsBox.
class MovieBox : public DefaultContainerImpl {
public:
MovieBox(ISOControl* aControl);
~MovieBox();
};
// 14496-12 8.2.2 'Movie Header Box'
// Box type: 'mvhd'
class MovieHeaderBox : public FullBox {
public:
// ISO BMFF members
uint32_t creation_time;
uint32_t modification_time;
uint32_t timescale;
uint32_t duration;
uint32_t rate;
uint16_t volume;
uint16_t reserved16;
uint32_t reserved32[2];
uint32_t matrix[9];
uint32_t pre_defined[6];
uint32_t next_track_ID;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// MovieHeaderBox methods
MovieHeaderBox(ISOControl* aControl);
~MovieHeaderBox();
uint32_t GetTimeScale();
protected:
MetaHelper mMeta;
};
// 14496-12 8.4.2 'Media Header Box'
// Box type: 'mdhd'
class MediaHeaderBox : public FullBox {
public:
// ISO BMFF members
uint32_t creation_time;
uint32_t modification_time;
uint32_t timescale;
uint32_t duration;
std::bitset<1> pad;
std::bitset<5> lang1;
std::bitset<5> lang2;
std::bitset<5> lang3;
uint16_t pre_defined;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// MediaHeaderBox methods
MediaHeaderBox(uint32_t aType, ISOControl* aControl);
~MediaHeaderBox();
uint32_t GetTimeScale();
protected:
uint32_t mTrackType;
MetaHelper mMeta;
};
// 14496-12 8.3.1 'Track Box'
// Box type: 'trak'
// TrackBox contains TrackHeaderBox and MediaBox.
class TrackBox : public DefaultContainerImpl {
public:
TrackBox(uint32_t aTrackType, ISOControl* aControl);
~TrackBox();
};
// 14496-12 8.1.1 'Media Data Box'
// Box type: 'mdat'
class MediaDataBox : public Box {
public:
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// MediaDataBox methods
uint32_t GetAllSampleSize() { return mAllSampleSize; }
uint32_t FirstSampleOffsetInMediaDataBox() { return mFirstSampleOffset; }
MediaDataBox(uint32_t aTrackType, ISOControl* aControl);
~MediaDataBox();
protected:
uint32_t mAllSampleSize; // All audio and video sample size in this box.
uint32_t mFirstSampleOffset; // The offset of first sample in this box from
// the beginning of this mp4 file.
uint32_t mTrackType;
};
// flags for TrackRunBox::flags, 14496-12 8.8.8.1.
#define flags_data_offset_present 0x000001
#define flags_first_sample_flags_present 0x000002
#define flags_sample_duration_present 0x000100
#define flags_sample_size_present 0x000200
#define flags_sample_flags_present 0x000400
#define flags_sample_composition_time_offsets_present 0x000800
// flag for TrackRunBox::tbl::sample_flags and TrackExtendsBox::default_sample_flags
// which is defined in 14496-12 8.8.3.1.
uint32_t set_sample_flags(bool aSync);
// 14496-12 8.8.8 'Track Fragment Run Box'
// Box type: 'trun'
class TrackRunBox : public FullBox {
public:
// ISO BMFF members
typedef struct {
uint32_t sample_duration;
uint32_t sample_size;
uint32_t sample_flags;
uint32_t sample_composition_time_offset;
} tbl;
uint32_t sample_count;
// the following are optional fields
uint32_t data_offset; // data offset exists when audio/video are present in file.
uint32_t first_sample_flags;
nsAutoArrayPtr<tbl> sample_info_table;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// TrackRunBox methods
uint32_t GetAllSampleSize() { return mAllSampleSize; }
nsresult SetDataOffset(uint32_t aOffset);
TrackRunBox(uint32_t aType, uint32_t aFlags, ISOControl* aControl);
~TrackRunBox();
protected:
uint32_t fillSampleTable();
uint32_t mAllSampleSize;
uint32_t mTrackType;
};
// tf_flags in TrackFragmentHeaderBox, 14496-12 8.8.7.1.
#define base_data_offset_present 0x000001
#define sample_description_index_present 0x000002
#define default_sample_duration_present 0x000008
#define default_sample_size_present 0x000010
#define default_sample_flags_present 0x000020
#define duration_is_empty 0x010000
#define default_base_is_moof 0x020000
// 14496-12 8.8.7 'Track Fragment Header Box'
// Box type: 'tfhd'
class TrackFragmentHeaderBox : public FullBox {
public:
// ISO BMFF members
uint32_t track_ID;
uint64_t base_data_offset;
uint32_t default_sample_duration;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// TrackFragmentHeaderBox methods
nsresult UpdateBaseDataOffset(uint64_t aOffset); // The offset of the first
// sample in file.
TrackFragmentHeaderBox(uint32_t aType, ISOControl* aControl);
~TrackFragmentHeaderBox();
protected:
uint32_t mTrackType;
MetaHelper mMeta;
};
// 14496-12 8.8.6 'Track Fragment Box'
// Box type: 'traf'
// TrackFragmentBox cotains TrackFragmentHeaderBox and TrackRunBox.
class TrackFragmentBox : public DefaultContainerImpl {
public:
TrackFragmentBox(uint32_t aType, uint32_t aFlags, ISOControl* aControl);
~TrackFragmentBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.8.5 'Movie Fragment Header Box'
// Box type: 'mfhd'
class MovieFragmentHeaderBox : public FullBox {
public:
// ISO BMFF members
uint32_t sequence_number;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// MovieFragmentHeaderBox methods
MovieFragmentHeaderBox(uint32_t aType, ISOControl* aControl);
~MovieFragmentHeaderBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.8.4 'Movie Fragment Box'
// Box type: 'moof'
// MovieFragmentBox contains MovieFragmentHeaderBox and TrackFragmentBox.
class MovieFragmentBox : public DefaultContainerImpl {
public:
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
// MovieFragmentBox methods
MovieFragmentBox(uint32_t aType, ISOControl* aControl);
~MovieFragmentBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.8.3 'Track Extends Box'
// Box type: 'trex'
class TrackExtendsBox : public FullBox {
public:
// ISO BMFF members
uint32_t track_ID;
uint32_t default_sample_description_index;
uint32_t default_sample_duration;
uint32_t default_sample_size;
uint32_t default_sample_flags;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// TrackExtendsBox methods
TrackExtendsBox(uint32_t aType, ISOControl* aControl);
~TrackExtendsBox();
protected:
uint32_t mTrackType;
MetaHelper mMeta;
};
// 14496-12 8.8.1 'Movie Extends Box'
// Box type: 'mvex'
// MovieExtendsBox contains TrackExtendsBox.
class MovieExtendsBox : public DefaultContainerImpl {
public:
MovieExtendsBox(ISOControl* aControl);
~MovieExtendsBox();
protected:
MetaHelper mMeta;
};
// 14496-12 8.7.5 'Chunk Offset Box'
// Box type: 'stco'
class ChunkOffsetBox : public FullBox {
public:
// ISO BMFF members
typedef struct {
uint32_t chunk_offset;
} tbl;
uint32_t entry_count;
nsAutoArrayPtr<tbl> sample_tbl;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// ChunkOffsetBox methods
ChunkOffsetBox(uint32_t aType, ISOControl* aControl);
~ChunkOffsetBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.7.4 'Sample To Chunk Box'
// Box type: 'stsc'
class SampleToChunkBox : public FullBox {
public:
// ISO BMFF members
typedef struct {
uint32_t first_chunk;
uint32_t sample_per_chunk;
uint32_t sample_description_index;
} tbl;
uint32_t entry_count;
nsAutoArrayPtr<tbl> sample_tbl;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// SampleToChunkBox methods
SampleToChunkBox(uint32_t aType, ISOControl* aControl);
~SampleToChunkBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.6.1.2 'Decoding Time to Sample Box'
// Box type: 'stts'
class TimeToSampleBox : public FullBox {
public:
// ISO BMFF members
typedef struct {
uint32_t sample_count;
uint32_t sample_delta;
} tbl;
uint32_t entry_count;
nsAutoArrayPtr<tbl> sample_tbl;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// TimeToSampleBox methods
TimeToSampleBox(uint32_t aType, ISOControl* aControl);
~TimeToSampleBox();
protected:
uint32_t mTrackType;
};
/**
* 14496-12 8.5.2 'Sample Description Box'
* This is the base class for VisualSampleEntry and MP4AudioSampleEntry.
*
* This class is for inherited only, it shouldn't be instanced directly.
*/
class SampleEntryBox : public Box {
public:
// ISO BMFF members
uint8_t reserved[6];
uint16_t data_reference_index;
// SampleEntryBox methods
SampleEntryBox(const nsACString& aFormat, uint32_t aTrackType,
ISOControl* aControl);
// MuxerOperation methods
nsresult Write() MOZ_OVERRIDE;
protected:
SampleEntryBox() MOZ_DELETE;
uint32_t mTrackType;
MetaHelper mMeta;
};
// 14496-12 8.5.2 'Sample Description Box'
// Box type: 'stsd'
class SampleDescriptionBox : public FullBox {
public:
// ISO BMFF members
uint32_t entry_count;
nsRefPtr<SampleEntryBox> sample_entry_box;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// SampleDescriptionBox methods
SampleDescriptionBox(uint32_t aType, ISOControl* aControl);
~SampleDescriptionBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.7.3.2 'Sample Size Box'
// Box type: 'stsz'
class SampleSizeBox : public FullBox {
public:
// ISO BMFF members
uint32_t sample_size;
uint32_t sample_count;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// SampleSizeBox methods
SampleSizeBox(ISOControl* aControl);
~SampleSizeBox();
};
// 14496-12 8.5.1 'Sample Table Box'
// Box type: 'stbl'
//
// SampleTableBox contains SampleDescriptionBox,
// TimeToSampleBox,
// SampleToChunkBox,
// SampleSizeBox and
// ChunkOffsetBox.
class SampleTableBox : public DefaultContainerImpl {
public:
SampleTableBox(uint32_t aType, ISOControl* aControl);
~SampleTableBox();
};
// 14496-12 8.7.2 'Data Reference Box'
// Box type: 'url '
class DataEntryUrlBox : public FullBox {
public:
// ISO BMFF members
// flags in DataEntryUrlBox::flags
const static uint16_t flags_media_at_the_same_file = 0x0001;
nsCString location;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// DataEntryUrlBox methods
DataEntryUrlBox();
DataEntryUrlBox(ISOControl* aControl);
DataEntryUrlBox(const DataEntryUrlBox& aBox);
~DataEntryUrlBox();
};
// 14496-12 8.7.2 'Data Reference Box'
// Box type: 'dref'
class DataReferenceBox : public FullBox {
public:
// ISO BMFF members
uint32_t entry_count;
nsTArray<nsAutoPtr<DataEntryUrlBox>> urls;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// DataReferenceBox methods
DataReferenceBox(ISOControl* aControl);
~DataReferenceBox();
};
// 14496-12 8.7.1 'Data Information Box'
// Box type: 'dinf'
// DataInformationBox contains DataReferenceBox.
class DataInformationBox : public DefaultContainerImpl {
public:
DataInformationBox(ISOControl* aControl);
~DataInformationBox();
};
// 14496-12 8.4.5.2 'Video Media Header Box'
// Box type: 'vmhd'
class VideoMediaHeaderBox : public FullBox {
public:
// ISO BMFF members
uint16_t graphicsmode;
uint16_t opcolor[3];
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// VideoMediaHeaderBox methods
VideoMediaHeaderBox(ISOControl* aControl);
~VideoMediaHeaderBox();
};
// 14496-12 8.4.5.3 'Sound Media Header Box'
// Box type: 'smhd'
class SoundMediaHeaderBox : public FullBox {
public:
// ISO BMFF members
uint16_t balance;
uint16_t reserved;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// SoundMediaHeaderBox methods
SoundMediaHeaderBox(ISOControl* aControl);
~SoundMediaHeaderBox();
};
// 14496-12 8.4.4 'Media Information Box'
// Box type: 'minf'
// MediaInformationBox contains SoundMediaHeaderBox, DataInformationBox and
// SampleTableBox.
class MediaInformationBox : public DefaultContainerImpl {
public:
MediaInformationBox(uint32_t aType, ISOControl* aControl);
~MediaInformationBox();
protected:
uint32_t mTrackType;
};
// flags for TrackHeaderBox::flags.
#define flags_track_enabled 0x000001
#define flags_track_in_movie 0x000002
#define flags_track_in_preview 0x000004
// 14496-12 8.3.2 'Track Header Box'
// Box type: 'tkhd'
class TrackHeaderBox : public FullBox {
public:
// ISO BMFF members
// version = 0
uint32_t creation_time;
uint32_t modification_time;
uint32_t track_ID;
uint32_t reserved;
uint32_t duration;
uint32_t reserved2[2];
uint16_t layer;
uint16_t alternate_group;
uint16_t volume;
uint16_t reserved3;
uint32_t matrix[9];
uint32_t width;
uint32_t height;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// TrackHeaderBox methods
TrackHeaderBox(uint32_t aType, ISOControl* aControl);
~TrackHeaderBox();
protected:
uint32_t mTrackType;
MetaHelper mMeta;
};
// 14496-12 8.4.3 'Handler Reference Box'
// Box type: 'hdlr'
class HandlerBox : public FullBox {
public:
// ISO BMFF members
uint32_t pre_defined;
uint32_t handler_type;
uint32_t reserved[3];
nsCString name;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// HandlerBox methods
HandlerBox(uint32_t aType, ISOControl* aControl);
~HandlerBox();
protected:
uint32_t mTrackType;
};
// 14496-12 8.4.1 'Media Box'
// Box type: 'mdia'
// MediaBox contains MediaHeaderBox, HandlerBox, and MediaInformationBox.
class MediaBox : public DefaultContainerImpl {
public:
MediaBox(uint32_t aType, ISOControl* aControl);
~MediaBox();
protected:
uint32_t mTrackType;
};
}
#endif // ISOMediaBoxes_h_

View File

@ -0,0 +1,226 @@
/* -*- Mode: C++; 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/. */
#include "ISOMediaWriter.h"
#include "ISOControl.h"
#include "ISOMediaBoxes.h"
#include "ISOTrackMetadata.h"
#include "nsThreadUtils.h"
#include "MediaEncoder.h"
#undef LOG
#ifdef MOZ_WIDGET_GONK
#include <android/log.h>
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
#else
#define LOG(args, ...)
#endif
namespace mozilla {
const static uint32_t FRAG_DURATION = 2000000; // microsecond per unit
ISOMediaWriter::ISOMediaWriter(uint32_t aType)
: ContainerWriter()
, mState(MUXING_HEAD)
, mBlobReady(false)
, mType(0)
{
// TODO: replace Audio_Track/Video_Track with HAS_AUDIO/HAS_VIDEO
if (aType & HAS_AUDIO) {
mType |= Audio_Track;
}
if (aType & HAS_VIDEO) {
mType |= Video_Track;
}
mControl = new ISOControl();
MOZ_COUNT_CTOR(ISOMediaWriter);
}
ISOMediaWriter::~ISOMediaWriter()
{
MOZ_COUNT_DTOR(ISOMediaWriter);
}
nsresult
ISOMediaWriter::RunState()
{
nsresult rv;
switch (mState) {
case MUXING_HEAD:
{
rv = mControl->GenerateFtyp();
NS_ENSURE_SUCCESS(rv, rv);
rv = mControl->GenerateMoov();
NS_ENSURE_SUCCESS(rv, rv);
mState = MUXING_FRAG;
break;
}
case MUXING_FRAG:
{
rv = mControl->GenerateMoof(mType);
NS_ENSURE_SUCCESS(rv, rv);
bool EOS;
if (ReadyToRunState(EOS) && EOS) {
mState = MUXING_DONE;
}
break;
}
case MUXING_DONE:
{
break;
}
}
mBlobReady = true;
return NS_OK;
}
nsresult
ISOMediaWriter::WriteEncodedTrack(const EncodedFrameContainer& aData,
uint32_t aFlags)
{
// Muxing complete, it doesn't allowed to reentry again.
if (mState == MUXING_DONE) {
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
FragmentBuffer* frag = nullptr;
uint32_t len = aData.GetEncodedFrames().Length();
if (!len) {
// no frame? why bother to WriteEncodedTrack
return NS_OK;
}
for (uint32_t i = 0; i < len; i++) {
nsRefPtr<EncodedFrame> frame(aData.GetEncodedFrames()[i]);
EncodedFrame::FrameType type = frame->GetFrameType();
if (type == EncodedFrame::AUDIO_FRAME ||
type == EncodedFrame::AAC_CSD) {
frag = mAudioFragmentBuffer;
} else if (type == EncodedFrame::I_FRAME ||
type == EncodedFrame::P_FRAME ||
type == EncodedFrame::B_FRAME ||
type == EncodedFrame::AVC_CSD) {
frag = mVideoFragmentBuffer;
} else {
MOZ_ASSERT(0);
return NS_ERROR_FAILURE;
}
frag->AddFrame(frame);
}
// Encoder should send CSD (codec specific data) frame before sending the
// audio/video frames. When CSD data is ready, it is sufficient to generate a
// moov data. If encoder doesn't send CSD yet, muxer needs to wait before
// generating anything.
if (mType & Audio_Track && !mAudioFragmentBuffer->HasCSD()) {
return NS_OK;
}
if (mType & Video_Track && !mVideoFragmentBuffer->HasCSD()) {
return NS_OK;
}
// Only one FrameType in EncodedFrameContainer so it doesn't need to be
// inside the for-loop.
if (frag && (aFlags & END_OF_STREAM)) {
frag->SetEndOfStream();
}
nsresult rv;
bool EOS;
if (ReadyToRunState(EOS)) {
// TODO:
// The MediaEncoder doesn't use nsRunnable, so thread will be
// stocked on that part and the new added nsRunnable won't get to run
// before MediaEncoder completing. Before MediaEncoder change, it needs
// to call RunState directly.
// https://bugzilla.mozilla.org/show_bug.cgi?id=950429
rv = RunState();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
bool
ISOMediaWriter::ReadyToRunState(bool& aEOS)
{
aEOS = false;
bool bReadyToMux = true;
if ((mType & Audio_Track) && (mType & Video_Track)) {
if (!mAudioFragmentBuffer->HasEnoughData()) {
bReadyToMux = false;
}
if (!mVideoFragmentBuffer->HasEnoughData()) {
bReadyToMux = false;
}
if (mAudioFragmentBuffer->EOS() && mVideoFragmentBuffer->EOS()) {
aEOS = true;
bReadyToMux = true;
}
} else if (mType == Audio_Track) {
if (!mAudioFragmentBuffer->HasEnoughData()) {
bReadyToMux = false;
}
if (mAudioFragmentBuffer->EOS()) {
aEOS = true;
bReadyToMux = true;
}
} else if (mType == Video_Track) {
if (!mVideoFragmentBuffer->HasEnoughData()) {
bReadyToMux = false;
}
if (mAudioFragmentBuffer->EOS()) {
aEOS = true;
bReadyToMux = true;
}
}
return bReadyToMux;
}
nsresult
ISOMediaWriter::GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs,
uint32_t aFlags)
{
if (mBlobReady) {
if (mState == MUXING_DONE) {
mIsWritingComplete = true;
}
mBlobReady = false;
aOutputBufs->AppendElement();
return mControl->GetBuf(aOutputBufs->LastElement());
}
return NS_OK;
}
nsresult
ISOMediaWriter::SetMetadata(TrackMetadataBase* aMetadata)
{
if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AAC ) {
mControl->SetMetadata(aMetadata);
mAudioFragmentBuffer = new FragmentBuffer(Audio_Track,
FRAG_DURATION,
aMetadata);
mControl->SetFragment(mAudioFragmentBuffer);
return NS_OK;
}
if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AVC) {
mControl->SetMetadata(aMetadata);
mVideoFragmentBuffer = new FragmentBuffer(Video_Track,
FRAG_DURATION,
aMetadata);
mControl->SetFragment(mVideoFragmentBuffer);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
} // namespace mozilla

View File

@ -0,0 +1,89 @@
/* -*- Mode: C++; 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/. */
#ifndef ISOMediaWriter_h_
#define ISOMediaWriter_h_
#include "ContainerWriter.h"
#include "nsIRunnable.h"
namespace mozilla {
class ISOControl;
class FragmentBuffer;
class AACTrackMetadata;
class AVCTrackMetadata;
class ISOMediaWriterRunnable;
class ISOMediaWriter : public ContainerWriter
{
public:
nsresult WriteEncodedTrack(const EncodedFrameContainer &aData,
uint32_t aFlags = 0) MOZ_OVERRIDE;
nsresult GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs,
uint32_t aFlags = 0) MOZ_OVERRIDE;
nsresult SetMetadata(TrackMetadataBase* aMetadata) MOZ_OVERRIDE;
ISOMediaWriter(uint32_t aType);
~ISOMediaWriter();
protected:
/**
* The state of each state will generate one or more blob.
* Each blob will be a moov, moof, moof... until receiving EOS.
* The generated sequence is:
*
* moov -> moof -> moof -> ... -> moof -> moof
*
* Following is the details of each state.
* MUXING_HEAD:
* It collects the metadata to generate a moov. The state transits to
* MUXING_HEAD after output moov blob.
*
* MUXING_FRAG:
* It collects enough audio/video data to generate a fragment blob. This
* will be repeated until END_OF_STREAM and then transiting to MUXING_DONE.
*
* MUXING_DONE:
* End of ISOMediaWriter life cycle.
*/
enum MuxState {
MUXING_HEAD,
MUXING_FRAG,
MUXING_DONE,
};
private:
nsresult RunState();
// True if one of following conditions hold:
// 1. Audio/Video accumulates enough data to generate a moof.
// 2. Get EOS signal.
// aEOS will be assigned to true if it gets EOS signal.
bool ReadyToRunState(bool& aEOS);
// The main class to generate and iso box. Its life time is same as
// ISOMediaWriter and deleted only if ISOMediaWriter is destroyed.
nsAutoPtr<ISOControl> mControl;
// Buffers to keep audio/video data frames, they are created when metadata is
// received. Only one instance for each media type is allowed and they will be
// deleted only if ISOMediaWriter is destroyed.
nsAutoPtr<FragmentBuffer> mAudioFragmentBuffer;
nsAutoPtr<FragmentBuffer> mVideoFragmentBuffer;
MuxState mState;
// A flag to indicate the output buffer is ready to blob out.
bool mBlobReady;
// Combination of HAS_AUDIO or HAS_VIDEO.
uint32_t mType;
};
} // namespace mozilla
#endif // ISOMediaWriter_h_

View File

@ -0,0 +1,57 @@
/* -*- Mode: C++; 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/. */
#ifndef ISOTrackMetadata_h_
#define ISOTrackMetadata_h_
#include "TrackMetadataBase.h"
namespace mozilla {
/**
* track type from spec 8.4.3.3
*/
#define Audio_Track 0x01
#define Video_Track 0x02
class AACTrackMetadata : public TrackMetadataBase {
public:
uint32_t SampleRate; // From 14496-3 table 1.16, it could be 7350 ~ 96000.
uint32_t FrameDuration; // Audio frame duration based on SampleRate.
uint32_t FrameSize; // Audio frame size, 0 is variant size.
uint32_t Channels; // Channel number, it should be 1 or 2.
AACTrackMetadata()
: SampleRate(0)
, FrameDuration(0)
, FrameSize(0)
, Channels(0) {
MOZ_COUNT_CTOR(AACTrackMetadata);
}
~AACTrackMetadata() { MOZ_COUNT_DTOR(AACTrackMetadata); }
MetadataKind GetKind() const MOZ_OVERRIDE { return METADATA_AAC; }
};
class AVCTrackMetadata : public TrackMetadataBase {
public:
uint32_t Height;
uint32_t Width;
uint32_t VideoFrequency; // for AVC, it should be 90k Hz.
uint32_t FrameRate; // frames per second
AVCTrackMetadata()
: Height(0)
, Width(0)
, VideoFrequency(0)
, FrameRate(0) {
MOZ_COUNT_CTOR(AVCTrackMetadata);
}
~AVCTrackMetadata() { MOZ_COUNT_DTOR(AVCTrackMetadata); }
MetadataKind GetKind() const MOZ_OVERRIDE { return METADATA_AVC; }
};
}
#endif // ISOTrackMetadata_h_

View File

@ -0,0 +1,170 @@
/* -*- Mode: C++; 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/. */
#include <climits>
#include "ISOControl.h"
#include "ISOMediaBoxes.h"
#include "MP4ESDS.h"
namespace mozilla {
nsresult
MP4AudioSampleEntry::Generate(uint32_t* aBoxSize)
{
sound_version = 0;
// step reserved2
sample_size = 16;
channels = mMeta.mAudMeta->Channels;
compressionId = 0;
packet_size = 0;
timeScale = mMeta.mAudMeta->SampleRate << 16;
size += sizeof(sound_version) +
sizeof(reserved2) +
sizeof(sample_size) +
sizeof(channels) +
sizeof(packet_size) +
sizeof(compressionId) +
sizeof(timeScale);
uint32_t box_size;
nsresult rv = es->Generate(&box_size);
NS_ENSURE_SUCCESS(rv, rv);
size += box_size;
*aBoxSize = size;
return NS_OK;
}
nsresult
MP4AudioSampleEntry::Write()
{
BoxSizeChecker checker(mControl, size);
SampleEntryBox::Write();
mControl->Write(sound_version);
mControl->Write(reserved2, sizeof(reserved2));
mControl->Write(channels);
mControl->Write(sample_size);
mControl->Write(compressionId);
mControl->Write(packet_size);
mControl->Write(timeScale);
nsresult rv = es->Write();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
MP4AudioSampleEntry::MP4AudioSampleEntry(ISOControl* aControl)
: SampleEntryBox(NS_LITERAL_CSTRING("mp4a"), Audio_Track, aControl)
, sound_version(0)
, channels(2)
, sample_size(16)
, compressionId(0)
, packet_size(0)
, timeScale(0)
{
es = new ESDBox(aControl);
mMeta.Init(mControl);
memset(reserved2, 0 , sizeof(reserved2));
MOZ_COUNT_CTOR(MP4AudioSampleEntry);
}
MP4AudioSampleEntry::~MP4AudioSampleEntry()
{
MOZ_COUNT_DTOR(MP4AudioSampleEntry);
}
nsresult
ESDBox::Generate(uint32_t* aBoxSize)
{
uint32_t box_size;
es_descriptor->Generate(&box_size);
size += box_size;
*aBoxSize = size;
return NS_OK;
}
nsresult
ESDBox::Write()
{
WRITE_FULLBOX(mControl, size)
es_descriptor->Write();
return NS_OK;
}
ESDBox::ESDBox(ISOControl* aControl)
: FullBox(NS_LITERAL_CSTRING("esds"), 0, 0, aControl)
{
es_descriptor = new ES_Descriptor(aControl);
MOZ_COUNT_CTOR(ESDBox);
}
ESDBox::~ESDBox()
{
MOZ_COUNT_DTOR(ESDBox);
}
nsresult
ES_Descriptor::Find(const nsACString& aType,
nsTArray<nsRefPtr<MuxerOperation>>& aOperations)
{
// ES_Descriptor is not a real ISOMediaBox, so we return nothing here.
return NS_OK;
}
nsresult
ES_Descriptor::Write()
{
mControl->Write(tag);
mControl->Write(length);
mControl->Write(ES_ID);
mControl->WriteBits(streamDependenceFlag.to_ulong(), streamDependenceFlag.size());
mControl->WriteBits(URL_Flag.to_ulong(), URL_Flag.size());
mControl->WriteBits(reserved.to_ulong(), reserved.size());
mControl->WriteBits(streamPriority.to_ulong(), streamPriority.size());
mControl->Write(DecodeSpecificInfo.Elements(), DecodeSpecificInfo.Length());
return NS_OK;
}
nsresult
ES_Descriptor::Generate(uint32_t* aBoxSize)
{
nsresult rv;
// 14496-1 '8.3.4 DecoderConfigDescriptor'
// 14496-1 '10.2.3 SL Packet Header Configuration'
Box::MetaHelper meta;
meta.Init(mControl);
FragmentBuffer* frag = mControl->GetFragment(Audio_Track);
rv = frag->GetCSD(DecodeSpecificInfo);
NS_ENSURE_SUCCESS(rv, rv);
length = sizeof(ES_ID) + 1;
length += DecodeSpecificInfo.Length();
*aBoxSize = sizeof(tag) + sizeof(length) + length;
return NS_OK;
}
ES_Descriptor::ES_Descriptor(ISOControl* aControl)
: tag(ESDescrTag)
, length(0)
, ES_ID(0)
, streamDependenceFlag(0)
, URL_Flag(0)
, reserved(0)
, streamPriority(0)
, mControl(aControl)
{
MOZ_COUNT_CTOR(ES_Descriptor);
}
ES_Descriptor::~ES_Descriptor()
{
MOZ_COUNT_DTOR(ES_Descriptor);
}
}

View File

@ -0,0 +1,98 @@
/* -*- Mode: C++; 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/. */
#ifndef MP4ESDS_h_
#define MP4ESDS_h_
#include "nsTArray.h"
#include "MuxerOperation.h"
namespace mozilla {
class ISOControl;
/**
* ESDS tag
*/
#define ESDescrTag 0x03
/**
* 14496-1 '8.3.3 ES_Descriptor'.
* It will get DecoderConfigDescriptor and SLConfigDescriptor from
* AAC CSD data.
*/
class ES_Descriptor : public MuxerOperation {
public:
// ISO BMFF members
uint8_t tag; // ESDescrTag
uint8_t length;
uint16_t ES_ID;
std::bitset<1> streamDependenceFlag;
std::bitset<1> URL_Flag;
std::bitset<1> reserved;
std::bitset<5> streamPriority;
nsTArray<uint8_t> DecodeSpecificInfo;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
nsresult Find(const nsACString& aType,
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
// ES_Descriptor methods
ES_Descriptor(ISOControl* aControl);
~ES_Descriptor();
protected:
ISOControl* mControl;
};
// 14496-14 5.6 'Sample Description Boxes'
// Box type: 'esds'
class ESDBox : public FullBox {
public:
// ISO BMFF members
nsRefPtr<ES_Descriptor> es_descriptor;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// ESDBox methods
ESDBox(ISOControl* aControl);
~ESDBox();
};
// 14496-14 5.6 'Sample Description Boxes'
// Box type: 'mp4a'
class MP4AudioSampleEntry : public SampleEntryBox {
public:
// ISO BMFF members
uint16_t sound_version;
uint8_t reserved2[6];
uint16_t channels;
uint16_t sample_size;
uint16_t compressionId;
uint16_t packet_size;
uint32_t timeScale;
nsRefPtr<ESDBox> es;
// MuxerOperation methods
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
nsresult Write() MOZ_OVERRIDE;
// MP4AudioSampleEntry methods
MP4AudioSampleEntry(ISOControl* aControl);
~MP4AudioSampleEntry();
protected:
uint32_t mTrackType;
MetaHelper mMeta;
};
}
#endif // MP4ESDS_h_

View File

@ -0,0 +1,56 @@
/* -*- Mode: C++; 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/. */
#include "nsString.h"
#include "nsTArray.h"
#ifndef MuxerOperation_h_
#define MuxerOperation_h_
namespace mozilla {
/**
* The interface for ISO box. All Boxes inherit from this interface.
* Generate() and Write() are needed to be called to produce a complete box.
*
* Generate() will generate all the data structures and their size.
*
* Write() will write all data into muxing output stream (ISOControl actually)
* and update the data which can't be known at Generate() (for example, the
* offset of the video data in mp4 file).
*
* ISO base media format is composed of several container boxes and the contained
* boxes. The container boxes hold a list of MuxerOperation which is implemented
* by contained boxes. The contained boxes will be called via the list.
* For example:
* MovieBox (container) ---> boxes (array of MuxerOperation)
* |---> MovieHeaderBox (full box)
* |---> TrakBox (container)
* |---> MovieExtendsBox (container)
*
* The complete box structure can be found at 14496-12 E.2 "Theisombrand".
*/
class MuxerOperation {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MuxerOperation)
// Generate data of this box and its contained box, and calculate box size.
virtual nsresult Generate(uint32_t* aBoxSize) = 0;
// Write data to stream.
virtual nsresult Write() = 0;
// Find the box type via its name (name is the box type defined in 14496-12;
// for example, 'moov' is the name of MovieBox).
// It can only look child boxes including itself and the box in the boxes
// list if exists. It can't look parent boxes.
virtual nsresult Find(const nsACString& aType,
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) = 0;
virtual ~MuxerOperation() {}
};
}
#endif

View File

@ -0,0 +1,22 @@
# -*- 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/.
EXPORTS += [
'ISOMediaWriter.h',
'ISOTrackMetadata.h',
]
UNIFIED_SOURCES += [
'AVCBox.cpp',
'ISOControl.cpp',
'ISOMediaBoxes.cpp',
'ISOMediaWriter.cpp',
'MP4ESDS.cpp',
]
FAIL_ON_WARNINGS = True
FINAL_LIBRARY = 'gklayout'

View File

@ -4,6 +4,9 @@
# 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/.
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
PARALLEL_DIRS += ['fmp4_muxer']
EXPORTS += [
'ContainerWriter.h',
'EncodedFrameContainer.h',

View File

@ -75,6 +75,7 @@ this.Keyboard = {
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
mm.addMessageListener('Forms:SendKey:Result:OK', this);
mm.addMessageListener('Forms:SendKey:Result:Error', this);
mm.addMessageListener('Forms:SequenceError', this);
mm.addMessageListener('Forms:GetContext:Result:OK', this);
mm.addMessageListener('Forms:SetComposition:Result:OK', this);
@ -125,6 +126,7 @@ this.Keyboard = {
case 'Forms:SetSelectionRange:Result:OK':
case 'Forms:ReplaceSurroundingText:Result:OK':
case 'Forms:SendKey:Result:OK':
case 'Forms:SendKey:Result:Error':
case 'Forms:SequenceError':
case 'Forms:GetContext:Result:OK':
case 'Forms:SetComposition:Result:OK':

View File

@ -498,6 +498,7 @@ MozInputContext.prototype = {
"Keyboard:SetSelectionRange:Result:OK",
"Keyboard:ReplaceSurroundingText:Result:OK",
"Keyboard:SendKey:Result:OK",
"Keyboard:SendKey:Result:Error",
"Keyboard:SetComposition:Result:OK",
"Keyboard:EndComposition:Result:OK",
"Keyboard:SequenceError"]);
@ -543,6 +544,9 @@ MozInputContext.prototype = {
case "Keyboard:SendKey:Result:OK":
resolver.resolve();
break;
case "Keyboard:SendKey:Result:Error":
resolver.reject(json.error);
break;
case "Keyboard:GetText:Result:OK":
resolver.resolve(json.text);
break;

View File

@ -521,11 +521,17 @@ let FormAssistant = {
json.charCode, json.modifiers);
this._editing = false;
if (json.requestId) {
if (json.requestId && doKeypress) {
sendAsyncMessage("Forms:SendKey:Result:OK", {
requestId: json.requestId
});
}
else if (json.requestId && !doKeypress) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Keydown event got canceled"
});
}
break;
case "Forms:Select:Choice":

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<body>
<input id="test-input" type="text" value="Yolo"/>
<script type="application/javascript;version=1.7">
let input = document.getElementById('test-input');
input.focus();
input.addEventListener('keydown', function(e) {
e.preventDefault();
});
</script>
</body>
</html>

View File

@ -1,9 +1,10 @@
[DEFAULT]
support-files =
inputmethod_common.js
file_test_app.html
file_inputmethod.html
file_test_app.html
file_test_sendkey_cancel.html
[test_basic.html]
[test_bug944397.html]
[test_sendkey_cancel.html]

View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=952080
-->
<head>
<title>SendKey with canceled keydown test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952080">Mozilla Bug 952080</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sendkey_cancel.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test() {
gContext.sendKey(0, 'j', 0).then(function() {
ok(false, 'sendKey was incorrectly resolved');
inputmethod_cleanup();
}, function(e) {
ok(true, 'sendKey was rejected');
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>

View File

@ -14,14 +14,20 @@ interface nsIDOMMozNetworkStatsData : nsISupports
readonly attribute jsval date; // Date.
};
[scriptable, builtinclass, uuid(b6fc4b14-628d-4c99-bf4e-e4ed56916cbe)]
[scriptable, builtinclass, uuid(f1996e44-1057-4d4b-8ff8-919e76c4cfa9)]
interface nsIDOMMozNetworkStats : nsISupports
{
/**
* Manifest URL of an application for specifying the per-app
* stats of the specified app. If null, system stats are returned.
* App manifest URL of an application for specifying the per-app stats of the
* specified app.
*/
readonly attribute DOMString manifestURL;
readonly attribute DOMString appManifestURL;
/**
* Service type is used to retrieve the corresponding "system-only" stats.
* E.g., "Tethering", "OTA", etc.
*/
readonly attribute DOMString serviceType;
/**
* Network the returned data belongs to.

View File

@ -6,6 +6,22 @@
interface nsIDOMDOMRequest;
/**
* Provide the detailed options for specifying different kinds of data filtering
* in getSamples function.
*/
dictionary NetworkStatsGetOptions
{
/**
* App manifest URL is used to filter network stats by app, while service type
* is used to filter stats by system service.
* Note that, these two options cannot be specified at the same time for now;
* others, an NS_ERROR_NOT_IMPLMENTED exception will be thrown.
*/
DOMString appManifestURL;
DOMString serviceType;
};
/**
* Represents a data interface for which the manager is recording statistics.
*/
@ -35,7 +51,7 @@ interface nsIDOMMozNetworkStatsAlarm : nsISupports
readonly attribute jsval data;
};
[scriptable, uuid(50d109b8-0d7f-4208-81fe-5f07a759f159)]
[scriptable, uuid(8a66f4c1-0c25-4a66-9fc5-0106947b91f9)]
interface nsIDOMMozNetworkStatsManager : nsISupports
{
/**
@ -47,15 +63,15 @@ interface nsIDOMMozNetworkStatsManager : nsISupports
/**
* Find samples between two dates start and end, both included.
*
* If manifestURL is provided, per-app usage is retrieved,
* otherwise the target will be system usage.
* If options is provided, per-app or per-system service usage will be
* retrieved; otherwise the target will be overall system usage.
*
* If success, the request result will be an nsIDOMMozNetworkStats object.
*/
nsIDOMDOMRequest getSamples(in nsIDOMMozNetworkStatsInterface network,
in jsval start,
in jsval end,
[optional] in DOMString manifestURL);
[optional] in jsval options /* NetworkStatsGetOptions */);
/**
* Install an alarm on a network. The network must be in the return of
@ -107,6 +123,11 @@ interface nsIDOMMozNetworkStatsManager : nsISupports
*/
nsIDOMDOMRequest getAvailableNetworks(); // array of nsIDOMMozNetworkStatsInterface.
/**
* Return available service types that used to be saved in the database.
*/
nsIDOMDOMRequest getAvailableServiceTypes(); // array of string.
/**
* Minimum time in milliseconds between samples stored in the database.
*/

View File

@ -16,7 +16,7 @@ interface nsINetworkStatsServiceProxyCallback : nsISupports
void notify(in boolean aResult, in jsval aMessage);
};
[scriptable, uuid(facef032-3fd9-4509-a396-83d94c1a11ae)]
[scriptable, uuid(705c01d6-8574-464c-8ec9-ac1522a45546)]
interface nsINetworkStatsServiceProxy : nsISupports
{
/*
@ -26,6 +26,7 @@ interface nsINetworkStatsServiceProxy : nsISupports
* @param aTimeStamp time stamp
* @param aRxBytes received data amount
* @param aTxBytes transmitted data amount
* @param aIsAccumulative is stats accumulative
* @param aCallback an optional callback
*/
void saveAppStats(in unsigned long aAppId,
@ -33,5 +34,24 @@ interface nsINetworkStatsServiceProxy : nsISupports
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
in boolean aIsAccumulative,
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
/*
* An interface used to record per-system service traffic data.
* @param aServiceType system service type
* @param aNetworkInterface network
* @param aTimeStamp time stamp
* @param aRxBytes received data amount
* @param aTxBytes transmitted data amount
* @param aIsAccumulative is stats accumulative
* @param aCallback an optional callback
*/
void saveServiceStats(in string aServiceType,
in nsINetworkInterface aNetwork,
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
in boolean aIsAccumulative,
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
};

View File

@ -16,8 +16,9 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.importGlobalProperties(["indexedDB"]);
const DB_NAME = "net_stats";
const DB_VERSION = 5;
const STATS_STORE_NAME = "net_stats";
const DB_VERSION = 6;
const DEPRECATED_STORE_NAME = "net_stats";
const STATS_STORE_NAME = "net_stats_store";
const ALARMS_STORE_NAME = "net_alarm";
// Constant defining the maximum values allowed per interface. If more, older
@ -59,7 +60,7 @@ NetworkStatsDB.prototype = {
* Create the initial database schema.
*/
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
objectStore.createIndex("connectionType", "connectionType", { unique: false });
objectStore.createIndex("timestamp", "timestamp", { unique: false });
objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
@ -78,9 +79,9 @@ NetworkStatsDB.prototype = {
// to modify the keyPath is mandatory to delete the object store
// and create it again. Old data is going to be deleted because the
// networkId for each sample can not be set.
db.deleteObjectStore(STATS_STORE_NAME);
db.deleteObjectStore(DEPRECATED_STORE_NAME);
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
objectStore.createIndex("appId", "appId", { unique: false });
objectStore.createIndex("network", "network", { unique: false });
objectStore.createIndex("networkType", "networkType", { unique: false });
@ -95,7 +96,7 @@ NetworkStatsDB.prototype = {
}
} else if (currVersion == 3) {
// Delete redundent indexes (leave "network" only).
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
if (objectStore.indexNames.contains("appId")) {
objectStore.deleteIndex("appId");
}
@ -124,7 +125,7 @@ NetworkStatsDB.prototype = {
} else if (currVersion == 4) {
// In order to manage alarms, it is necessary to use a global counter
// (totalBytes) that will increase regardless of the system reboot.
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
// Now, systemBytes will hold the old totalBytes and totalBytes will
// keep the increasing counter. |counters| will keep the track of
@ -183,12 +184,42 @@ NetworkStatsDB.prototype = {
if (DEBUG) {
debug("Created alarms store for version 5");
}
} else if (currVersion == 5) {
// In contrast to "per-app" traffic data, "system-only" traffic data
// refers to data which can not be identified by any applications.
// To further support "system-only" data storage, the data can be
// saved by service type (e.g., Tethering, OTA). Thus it's needed to
// have a new key ("serviceType") for the ojectStore.
let newObjectStore;
newObjectStore = db.createObjectStore(STATS_STORE_NAME,
{ keyPath: ["appId", "serviceType", "network", "timestamp"] });
newObjectStore.createIndex("network", "network", { unique: false });
// Copy the data from the original objectStore to the new objectStore.
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (!cursor) {
db.deleteObjectStore(DEPRECATED_STORE_NAME);
return;
}
let newStats = cursor.value;
newStats.serviceType = "";
newObjectStore.put(newStats);
cursor.continue();
};
if (DEBUG) {
debug("Added new key 'serviceType' for version 6");
}
}
}
},
importData: function importData(aStats) {
let stats = { appId: aStats.appId,
serviceType: aStats.serviceType,
network: [aStats.networkId, aStats.networkType],
timestamp: aStats.timestamp,
rxBytes: aStats.rxBytes,
@ -203,6 +234,7 @@ NetworkStatsDB.prototype = {
exportData: function exportData(aStats) {
let stats = { appId: aStats.appId,
serviceType: aStats.serviceType,
networkId: aStats.network[0],
networkType: aStats.network[1],
timestamp: aStats.timestamp,
@ -223,18 +255,20 @@ NetworkStatsDB.prototype = {
},
saveStats: function saveStats(aStats, aResultCb) {
let isAccumulative = aStats.isAccumulative;
let timestamp = this.normalizeDate(aStats.date);
let stats = { appId: aStats.appId,
serviceType: aStats.serviceType,
networkId: aStats.networkId,
networkType: aStats.networkType,
timestamp: timestamp,
rxBytes: (aStats.appId == 0) ? 0 : aStats.rxBytes,
txBytes: (aStats.appId == 0) ? 0 : aStats.txBytes,
rxSystemBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
txSystemBytes: (aStats.appId == 0) ? aStats.txBytes : 0,
rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
rxBytes: (isAccumulative) ? 0 : aStats.rxBytes,
txBytes: (isAccumulative) ? 0 : aStats.txBytes,
rxSystemBytes: (isAccumulative) ? aStats.rxBytes : 0,
txSystemBytes: (isAccumulative) ? aStats.txBytes : 0,
rxTotalBytes: (isAccumulative) ? aStats.rxBytes : 0,
txTotalBytes: (isAccumulative) ? aStats.txBytes : 0 };
stats = this.importData(stats);
@ -244,7 +278,7 @@ NetworkStatsDB.prototype = {
debug("New stats: " + JSON.stringify(stats));
}
let request = aStore.index("network").openCursor(stats.network, "prev");
let request = aStore.index("network").openCursor(stats.network, "prev");
request.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
if (!cursor) {
@ -254,7 +288,7 @@ NetworkStatsDB.prototype = {
// interface comes up and the point when the database is initialized.
// In this short interval some traffic data are generated but are not
// registered by the first sample.
if (stats.appId == 0) {
if (isAccumulative) {
stats.rxBytes = stats.rxTotalBytes;
stats.txBytes = stats.txTotalBytes;
}
@ -263,21 +297,24 @@ NetworkStatsDB.prototype = {
return;
}
if (stats.appId != cursor.value.appId) {
let value = cursor.value;
if (stats.appId != value.appId ||
(stats.appId == 0 && stats.serviceType != value.serviceType)) {
cursor.continue();
return;
}
// There are old samples
if (DEBUG) {
debug("Last value " + JSON.stringify(cursor.value));
debug("Last value " + JSON.stringify(value));
}
// Remove stats previous to now - VALUE_MAX_LENGTH
this._removeOldStats(aTxn, aStore, stats.appId, stats.network, stats.timestamp);
this._removeOldStats(aTxn, aStore, stats.appId, stats.serviceType,
stats.network, stats.timestamp);
// Process stats before save
this._processSamplesDiff(aTxn, aStore, cursor, stats);
this._processSamplesDiff(aTxn, aStore, cursor, stats, isAccumulative);
}.bind(this);
}.bind(this), aResultCb);
},
@ -286,7 +323,11 @@ NetworkStatsDB.prototype = {
* This function check that stats are saved in the database following the sample rate.
* In this way is easier to find elements when stats are requested.
*/
_processSamplesDiff: function _processSamplesDiff(aTxn, aStore, aLastSampleCursor, aNewSample) {
_processSamplesDiff: function _processSamplesDiff(aTxn,
aStore,
aLastSampleCursor,
aNewSample,
aIsAccumulative) {
let lastSample = aLastSampleCursor.value;
// Get difference between last and new sample.
@ -303,14 +344,14 @@ NetworkStatsDB.prototype = {
lastSample.timestamp + " - diff: " + diff);
}
// If the incoming data is obtained from netd (|newSample.appId| is 0),
// the new |txBytes|/|rxBytes| is assigend by the differnce between the new
// If the incoming data has a accumulation feature, the new
// |txBytes|/|rxBytes| is assigend by differnces between the new
// |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
// Else, the incoming data is per-app data (|newSample.appId| is not 0),
// the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
// Else, if incoming data is non-accumulative, the |txBytes|/|rxBytes|
// is the new |txBytes|/|rxBytes|.
let rxDiff = 0;
let txDiff = 0;
if (aNewSample.appId == 0) {
if (aIsAccumulative) {
rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
if (rxDiff < 0 || txDiff < 0) {
@ -330,10 +371,10 @@ NetworkStatsDB.prototype = {
if (diff == 1) {
// New element.
// If the incoming data is per-app data, new |rxTotalBytes|/|txTotalBytes|
// needs to be obtained by adding new |rxBytes|/|txBytes| to last
// |rxTotalBytes|/|txTotalBytes|.
if (aNewSample.appId != 0) {
// If the incoming data is non-accumulative, the new
// |rxTotalBytes|/|txTotalBytes| needs to be updated by adding new
// |rxBytes|/|txBytes| to the last |rxTotalBytes|/|txTotalBytes|.
if (!aIsAccumulative) {
aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
}
@ -353,6 +394,7 @@ NetworkStatsDB.prototype = {
for (let i = diff - 2; i >= 0; i--) {
let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
let sample = { appId: aNewSample.appId,
serviceType: aNewSample.serviceType,
network: aNewSample.network,
timestamp: time,
rxBytes: 0,
@ -406,11 +448,12 @@ NetworkStatsDB.prototype = {
}
},
_removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aNetwork, aDate) {
_removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aServiceType,
aNetwork, aDate) {
// Callback function to remove old items when new ones are added.
let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
let lowerFilter = [aAppId, aNetwork, 0];
let upperFilter = [aAppId, aNetwork, filterDate];
let lowerFilter = [aAppId, aServiceType, aNetwork, 0];
let upperFilter = [aAppId, aServiceType, aNetwork, filterDate];
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
let lastSample = null;
let self = this;
@ -469,6 +512,7 @@ NetworkStatsDB.prototype = {
timestamp = self.normalizeDate(timestamp);
sample.timestamp = timestamp;
sample.appId = 0;
sample.serviceType = "";
sample.rxBytes = 0;
sample.txBytes = 0;
@ -528,22 +572,24 @@ NetworkStatsDB.prototype = {
}.bind(this), aResultCb);
},
find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
find: function find(aResultCb, aAppId, aServiceType, aNetwork,
aStart, aEnd, aAppManifestURL) {
let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
let start = this.normalizeDate(aStart);
let end = this.normalizeDate(aEnd);
if (DEBUG) {
debug("Find samples for appId: " + aAppId + " network " +
JSON.stringify(aNetwork) + " from " + start + " until " + end);
debug("Find samples for appId: " + aAppId + " serviceType: " +
aServiceType + " network: " + JSON.stringify(aNetwork) + " from " +
start + " until " + end);
debug("Start time: " + new Date(start));
debug("End time: " + new Date(end));
}
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
let network = [aNetwork.id, aNetwork.type];
let lowerFilter = [aAppId, network, start];
let upperFilter = [aAppId, network, end];
let lowerFilter = [aAppId, aServiceType, network, start];
let upperFilter = [aAppId, aServiceType, network, end];
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
let data = [];
@ -566,7 +612,8 @@ NetworkStatsDB.prototype = {
// now - VALUES_MAX_LENGTH, fill with empty samples.
this.fillResultSamples(start + offset, end + offset, data);
aTxn.result.manifestURL = aManifestURL;
aTxn.result.appManifestURL = aAppManifestURL;
aTxn.result.serviceType = aServiceType;
aTxn.result.network = aNetwork;
aTxn.result.start = aStart;
aTxn.result.end = aEnd;
@ -634,6 +681,24 @@ NetworkStatsDB.prototype = {
}, aResultCb);
},
getAvailableServiceTypes: function getAvailableServiceTypes(aResultCb) {
this.dbNewTxn("readonly", function(aTxn, aStore) {
if (!aTxn.result) {
aTxn.result = [];
}
let request = aStore.index("serviceType").openKeyCursor(null, "nextunique");
request.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
if (cursor) {
aTxn.result.push({ serviceType: cursor.key });
cursor.continue();
return;
}
};
}, aResultCb);
},
get sampleRate () {
return SAMPLE_RATE;
},

View File

@ -86,14 +86,15 @@ NetworkStatsInterface.prototype = {
// NetworkStats
const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
const NETWORKSTATS_CID = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
const NETWORKSTATS_CID = Components.ID("{f1996e44-1057-4d4b-8ff8-919e76c4cfa9}");
const nsIDOMMozNetworkStats = Ci.nsIDOMMozNetworkStats;
function NetworkStats(aWindow, aStats) {
if (DEBUG) {
debug("NetworkStats Constructor");
}
this.manifestURL = aStats.manifestURL || null;
this.appManifestURL = aStats.appManifestURL || null;
this.serviceType = aStats.serviceType || null;
this.network = new NetworkStatsInterface(aStats.network);
this.start = aStats.start || null;
this.end = aStats.end || null;
@ -106,7 +107,8 @@ function NetworkStats(aWindow, aStats) {
NetworkStats.prototype = {
__exposedProps__: {
manifestURL: 'r',
appManifestURL: 'r',
serviceType: 'r',
network: 'r',
start: 'r',
end: 'r',
@ -157,7 +159,7 @@ NetworkStatsAlarm.prototype = {
// NetworkStatsManager
const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
const NETWORKSTATSMANAGER_CID = Components.ID("{50d109b8-0d7f-4208-81fe-5f07a759f159}");
const NETWORKSTATSMANAGER_CID = Components.ID("{8a66f4c1-0c25-4a66-9fc5-0106947b91f9}");
const nsIDOMMozNetworkStatsManager = Ci.nsIDOMMozNetworkStatsManager;
function NetworkStatsManager() {
@ -175,7 +177,7 @@ NetworkStatsManager.prototype = {
}
},
getSamples: function getSamples(aNetwork, aStart, aEnd, aManifestURL) {
getSamples: function getSamples(aNetwork, aStart, aEnd, aOptions) {
this.checkPrivileges();
if (aStart.constructor.name !== "Date" ||
@ -184,6 +186,16 @@ NetworkStatsManager.prototype = {
throw Components.results.NS_ERROR_INVALID_ARG;
}
let appManifestURL = null;
let serviceType = null;
if (aOptions) {
if (aOptions.appManifestURL && aOptions.serviceType) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
appManifestURL = aOptions.appManifestURL;
serviceType = aOptions.serviceType;
}
// TODO Bug 929410 Date object cannot correctly pass through cpmm/ppmm IPC
// This is just a work-around by passing timestamp numbers.
aStart = aStart.getTime();
@ -194,7 +206,8 @@ NetworkStatsManager.prototype = {
{ network: aNetwork,
start: aStart,
end: aEnd,
manifestURL: aManifestURL,
appManifestURL: appManifestURL,
serviceType: serviceType,
id: this.getRequestId(request) });
return request;
},
@ -273,6 +286,15 @@ NetworkStatsManager.prototype = {
return request;
},
getAvailableServiceTypes: function getAvailableServiceTypes() {
this.checkPrivileges();
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes",
{ id: this.getRequestId(request) });
return request;
},
get sampleRate() {
this.checkPrivileges();
return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
@ -325,6 +347,20 @@ NetworkStatsManager.prototype = {
Services.DOMRequest.fireSuccess(req, networks);
break;
case "NetworkStats:GetAvailableServiceTypes:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let serviceTypes = Cu.createArrayIn(this._window);
for (let i = 0; i < msg.result.length; i++) {
serviceTypes.push(msg.result[i]);
}
Services.DOMRequest.fireSuccess(req, serviceTypes);
break;
case "NetworkStats:Clear:Return":
case "NetworkStats:ClearAll:Return":
if (msg.error) {
@ -391,6 +427,7 @@ NetworkStatsManager.prototype = {
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
"NetworkStats:GetAvailableNetworks:Return",
"NetworkStats:GetAvailableServiceTypes:Return",
"NetworkStats:Clear:Return",
"NetworkStats:ClearAll:Return",
"NetworkStats:SetAlarm:Return",

View File

@ -1,8 +1,8 @@
component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
component {f1996e44-1057-4d4b-8ff8-919e76c4cfa9} NetworkStatsManager.js
contract @mozilla.org/networkStats;1 {f1996e44-1057-4d4b-8ff8-919e76c4cfa9}
component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
@ -10,6 +10,6 @@ contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5
component {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c} NetworkStatsManager.js
contract @mozilla.org/networkstatsalarm;1 {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}
component {50d109b8-0d7f-4208-81fe-5f07a759f159} NetworkStatsManager.js
contract @mozilla.org/networkStatsManager;1 {50d109b8-0d7f-4208-81fe-5f07a759f159}
component {8a66f4c1-0c25-4a66-9fc5-0106947b91f9} NetworkStatsManager.js
contract @mozilla.org/networkStatsManager;1 {8a66f4c1-0c25-4a66-9fc5-0106947b91f9}
category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1

View File

@ -29,7 +29,7 @@ const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
// The maximum traffic amount can be saved in the |cachedAppStats|.
// The maximum traffic amount can be saved in the |cachedStats|.
const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
@ -98,6 +98,7 @@ this.NetworkStatsService = {
"NetworkStats:GetAlarms",
"NetworkStats:RemoveAlarms",
"NetworkStats:GetAvailableNetworks",
"NetworkStats:GetAvailableServiceTypes",
"NetworkStats:SampleRate",
"NetworkStats:MaxStorageAge"];
@ -111,9 +112,9 @@ this.NetworkStatsService = {
this.timer.initWithCallback(this, this._db.sampleRate,
Ci.nsITimer.TYPE_REPEATING_PRECISE);
// App stats are firstly stored in the cached.
this.cachedAppStats = Object.create(null);
this.cachedAppStatsDate = new Date();
// Stats not from netd are firstly stored in the cached.
this.cachedStats = Object.create(null);
this.cachedStatsDate = new Date();
this.updateQueue = [];
this.isQueueRunning = false;
@ -154,6 +155,9 @@ this.NetworkStatsService = {
case "NetworkStats:GetAvailableNetworks":
this.getAvailableNetworks(mm, msg);
break;
case "NetworkStats:GetAvailableServiceTypes":
this.getAvailableServiceTypes(mm, msg);
break;
case "NetworkStats:SampleRate":
// This message is sync.
return this._db.sampleRate;
@ -307,6 +311,13 @@ this.NetworkStatsService = {
});
},
getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
{ id: msg.id, error: aError, result: aResult });
});
},
initAlarms: function initAlarms() {
debug("Init usage alarms");
let self = this;
@ -340,17 +351,20 @@ this.NetworkStatsService = {
let netId = this.getNetworkId(network.id, network.type);
let appId = 0;
let manifestURL = msg.manifestURL;
if (manifestURL) {
appId = appsService.getAppLocalIdByManifestURL(manifestURL);
let appManifestURL = msg.appManifestURL;
if (appManifestURL) {
appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
if (!appId) {
mm.sendAsyncMessage("NetworkStats:Get:Return",
{ id: msg.id, error: "Invalid manifestURL", result: null });
{ id: msg.id,
error: "Invalid appManifestURL", result: null });
return;
}
}
let serviceType = msg.serviceType || "";
let start = new Date(msg.start);
let end = new Date(msg.end);
@ -359,13 +373,13 @@ this.NetworkStatsService = {
if (this._networks[netId]) {
this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
debug("getstats for network " + network.id + " of type " + network.type);
debug("appId: " + appId + " from manifestURL: " + manifestURL);
debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
self.updateCachedAppStats(function onAppStatsUpdated(aResult, aMessage) {
self.updateCachedStats(function onStatsUpdated(aResult, aMessage) {
self._db.find(function onStatsFound(aError, aResult) {
mm.sendAsyncMessage("NetworkStats:Get:Return",
{ id: msg.id, error: aError, result: aResult });
}, network, start, end, appId, manifestURL);
}, appId, serviceType, network, start, end, appManifestURL);
});
});
return;
@ -394,7 +408,7 @@ this.NetworkStatsService = {
self._db.find(function onStatsFound(aError, aResult) {
mm.sendAsyncMessage("NetworkStats:Get:Return",
{ id: msg.id, error: aError, result: aResult });
}, network, start, end, appId, manifestURL);
}, appId, serviceType, network, start, end, appManifestURL);
return;
}
@ -453,8 +467,8 @@ this.NetworkStatsService = {
},
updateAllStats: function updateAllStats(aCallback) {
// Update |cachedAppStats|.
this.updateCachedAppStats();
// Update |cachedStats|.
this.updateCachedStats();
let elements = [];
let lastElement;
@ -601,12 +615,14 @@ this.NetworkStatsService = {
return;
}
let stats = { appId: 0,
networkId: this._networks[aNetId].network.id,
networkType: this._networks[aNetId].network.type,
date: aDate,
rxBytes: aTxBytes,
txBytes: aRxBytes };
let stats = { appId: 0,
serviceType: "",
networkId: this._networks[aNetId].network.id,
networkType: this._networks[aNetId].network.type,
date: aDate,
rxBytes: aTxBytes,
txBytes: aRxBytes,
isAccumulative: true };
debug("Update stats for: " + JSON.stringify(stats));
@ -623,9 +639,11 @@ this.NetworkStatsService = {
},
/*
* Function responsible for receiving per-app stats.
* Function responsible for receiving stats which are not from netd.
*/
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
saveStats: function saveStats(aAppId, aServiceType, aNetwork, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback) {
let netId = this.convertNetworkInterface(aNetwork);
if (!netId) {
if (aCallback) {
@ -634,36 +652,43 @@ this.NetworkStatsService = {
return;
}
debug("saveAppStats: " + aAppId + " " + netId + " " +
debug("saveStats: " + aAppId + " " + aServiceType + " " + netId + " " +
aTimeStamp + " " + aRxBytes + " " + aTxBytes);
// Check if |aAppId| and |aConnectionType| are valid.
if (!aAppId || !this._networks[netId]) {
debug("Invalid appId or network interface");
// Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
// There are two invalid cases for the combination of |aAppId| and
// |aServiceType|:
// a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
// b. Both |aAppId| is zero and |aServiceType| is empty.
if (!this._networks[netId] || (aAppId && aServiceType) ||
(!aAppId && !aServiceType)) {
debug("Invalid network interface, appId or serviceType");
return;
}
let stats = { appId: aAppId,
networkId: this._networks[netId].network.id,
networkType: this._networks[netId].network.type,
date: new Date(aTimeStamp),
rxBytes: aRxBytes,
txBytes: aTxBytes };
let stats = { appId: aAppId,
serviceType: aServiceType,
networkId: this._networks[netId].network.id,
networkType: this._networks[netId].network.type,
date: new Date(aTimeStamp),
rxBytes: aRxBytes,
txBytes: aTxBytes,
isAccumulative: aIsAccumulative };
// Generate an unique key from |appId| and |connectionType|,
// which is used to retrieve data in |cachedAppStats|.
let key = stats.appId + "" + netId;
// Generate an unique key from |appId|, |serviceType| and |netId|,
// which is used to retrieve data in |cachedStats|.
let key = stats.appId + "" + stats.serviceType + "" + netId;
// |cachedAppStats| only keeps the data with the same date.
// If the incoming date is different from |cachedAppStatsDate|,
// both |cachedAppStats| and |cachedAppStatsDate| will get updated.
// |cachedStats| only keeps the data with the same date.
// If the incoming date is different from |cachedStatsDate|,
// both |cachedStats| and |cachedStatsDate| will get updated.
let diff = (this._db.normalizeDate(stats.date) -
this._db.normalizeDate(this.cachedAppStatsDate)) /
this._db.normalizeDate(this.cachedStatsDate)) /
this._db.sampleRate;
if (diff != 0) {
this.updateCachedAppStats(function onUpdated(success, message) {
this.cachedAppStatsDate = stats.date;
this.cachedAppStats[key] = stats;
this.updateCachedStats(function onUpdated(success, message) {
this.cachedStatsDate = stats.date;
this.cachedStats[key] = stats;
if (!aCallback) {
return;
@ -682,36 +707,36 @@ this.NetworkStatsService = {
// Try to find the matched row in the cached by |appId| and |connectionType|.
// If not found, save the incoming data into the cached.
let appStats = this.cachedAppStats[key];
if (!appStats) {
this.cachedAppStats[key] = stats;
let cachedStats = this.cachedStats[key];
if (!cachedStats) {
this.cachedStats[key] = stats;
return;
}
// Find matched row, accumulate the traffic amount.
appStats.rxBytes += stats.rxBytes;
appStats.txBytes += stats.txBytes;
cachedStats.rxBytes += stats.rxBytes;
cachedStats.txBytes += stats.txBytes;
// If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
// the corresponding row will be saved to indexedDB.
// Then, the row will be removed from the cached.
if (appStats.rxBytes > MAX_CACHED_TRAFFIC ||
appStats.txBytes > MAX_CACHED_TRAFFIC) {
this._db.saveStats(appStats,
if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
this._db.saveStats(cachedStats,
function (error, result) {
debug("Application stats inserted in indexedDB");
}
);
delete this.cachedAppStats[key];
delete this.cachedStats[key];
}
},
updateCachedAppStats: function updateCachedAppStats(aCallback) {
debug("updateCachedAppStats: " + this.cachedAppStatsDate);
updateCachedStats: function updateCachedStats(aCallback) {
debug("updateCachedStats: " + this.cachedStatsDate);
let stats = Object.keys(this.cachedAppStats);
let stats = Object.keys(this.cachedStats);
if (stats.length == 0) {
// |cachedAppStats| is empty, no need to update.
// |cachedStats| is empty, no need to update.
if (aCallback) {
aCallback(true, "no need to update");
}
@ -720,15 +745,15 @@ this.NetworkStatsService = {
}
let index = 0;
this._db.saveStats(this.cachedAppStats[stats[index]],
this._db.saveStats(this.cachedStats[stats[index]],
function onSavedStats(error, result) {
if (DEBUG) {
debug("Application stats inserted in indexedDB");
}
// Clean up the |cachedAppStats| after updating.
// Clean up the |cachedStats| after updating.
if (index == stats.length - 1) {
this.cachedAppStats = Object.create(null);
this.cachedStats = Object.create(null);
if (!aCallback) {
return;
@ -745,7 +770,7 @@ this.NetworkStatsService = {
// Update is not finished, keep updating.
index += 1;
this._db.saveStats(this.cachedAppStats[stats[index]],
this._db.saveStats(this.cachedStats[stats[index]],
onSavedStats.bind(this, error, result));
}.bind(this));
},

View File

@ -15,7 +15,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
const NETWORKSTATSSERVICEPROXY_CONTRACTID = "@mozilla.org/networkstatsServiceProxy;1";
const NETWORKSTATSSERVICEPROXY_CID = Components.ID("8fbd115d-f590-474c-96dc-e2b6803ca975");
const NETWORKSTATSSERVICEPROXY_CID = Components.ID("705c01d6-8574-464c-8ec9-ac1522a45546");
const nsINetworkStatsServiceProxy = Ci.nsINetworkStatsServiceProxy;
function NetworkStatsServiceProxy() {
@ -30,7 +30,8 @@ NetworkStatsServiceProxy.prototype = {
* to pass the per-app stats to NetworkStatsService.
*/
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp,
aRxBytes, aTxBytes, aCallback) {
aRxBytes, aTxBytes, aIsAccumulative,
aCallback) {
if (!aNetwork) {
if (DEBUG) {
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
@ -39,12 +40,38 @@ NetworkStatsServiceProxy.prototype = {
}
if (DEBUG) {
debug("saveAppStats: " + aAppId + " connectionType " + aNetwork.type +
" " + aTimeStamp + " " + aRxBytes + " " + aTxBytes);
debug("saveAppStats: " + aAppId + " " + aNetwork.type + " " + aTimeStamp +
" " + aRxBytes + " " + aTxBytes + " " + aIsAccumulative);
}
NetworkStatsService.saveAppStats(aAppId, aNetwork, aTimeStamp,
aRxBytes, aTxBytes, aCallback);
NetworkStatsService.saveStats(aAppId, "", aNetwork, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback);
},
/*
* Function called in the points of different system services
* to pass the per-serive stats to NetworkStatsService.
*/
saveServiceStats: function saveServiceStats(aServiceType, aNetwork,
aTimeStamp, aRxBytes, aTxBytes,
aIsAccumulative, aCallback) {
if (!aNetwork) {
if (DEBUG) {
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
}
return;
}
if (DEBUG) {
debug("saveServiceStats: " + aServiceType + " " + aNetwork.type + " " +
aTimeStamp + " " + aRxBytes + " " + aTxBytes + " " +
aIsAccumulative);
}
NetworkStatsService.saveStats(0, aServiceType ,aNetwork, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback);
},
classID : NETWORKSTATSSERVICEPROXY_CID,

View File

@ -1,2 +1,2 @@
component {8fbd115d-f590-474c-96dc-e2b6803ca975} NetworkStatsServiceProxy.js
contract @mozilla.org/networkstatsServiceProxy;1 {8fbd115d-f590-474c-96dc-e2b6803ca975}
component {705c01d6-8574-464c-8ec9-ac1522a45546} NetworkStatsServiceProxy.js
contract @mozilla.org/networkstatsServiceProxy;1 {705c01d6-8574-464c-8ec9-ac1522a45546}

View File

@ -361,7 +361,7 @@ TCPSocket.prototype = {
return;
}
nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(),
this._rxBytes, this._txBytes);
this._rxBytes, this._txBytes, false);
// Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
this._txBytes = this._rxBytes = 0;

View File

@ -22,7 +22,7 @@ function clearStore(store, callback) {
add_test(function prepareDatabase() {
// Clear whole database to avoid starting tests with unknown state
// due to the previous tests.
clearStore('net_stats', function() {
clearStore('net_stats_store', function() {
clearStore('net_alarm', function() {
run_next_test();
});
@ -139,6 +139,7 @@ add_test(function test_internalSaveStats_singleSample() {
var networks = getNetworks();
var stats = { appId: 0,
serviceType: "",
network: [networks[0].id, networks[0].type],
timestamp: Date.now(),
rxBytes: 0,
@ -148,7 +149,7 @@ add_test(function test_internalSaveStats_singleSample() {
rxTotalBytes: 1234,
txTotalBytes: 1234 };
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
@ -157,6 +158,7 @@ add_test(function test_internalSaveStats_singleSample() {
do_check_eq(error, null);
do_check_eq(result.length, 1);
do_check_eq(result[0].appId, stats.appId);
do_check_eq(result[0].serviceType, stats.serviceType);
do_check_true(compareNetworks(result[0].network, stats.network));
do_check_eq(result[0].timestamp, stats.timestamp);
do_check_eq(result[0].rxBytes, stats.rxBytes);
@ -182,6 +184,7 @@ add_test(function test_internalSaveStats_arraySamples() {
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: 0,
serviceType: "",
network: network,
timestamp: Date.now() + (10 * i),
rxBytes: 0,
@ -192,7 +195,7 @@ add_test(function test_internalSaveStats_arraySamples() {
txTotalBytes: 1234 });
}
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
@ -207,6 +210,7 @@ add_test(function test_internalSaveStats_arraySamples() {
var success = true;
for (var i = 1; i < samples; i++) {
if (result[i].appId != stats[i].appId ||
result[i].serviceType != stats[i].serviceType ||
!compareNetworks(result[i].network, stats[i].network) ||
result[i].timestamp != stats[i].timestamp ||
result[i].rxBytes != stats[i].rxBytes ||
@ -236,24 +240,24 @@ add_test(function test_internalRemoveOldStats() {
var samples = 10;
var stats = [];
for (var i = 0; i < samples - 1; i++) {
stats.push({ appId: 0,
stats.push({ appId: 0, serviceType: "",
network: network, timestamp: Date.now() + (10 * i),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 1234, txTotalBytes: 1234 });
}
stats.push({ appId: 0,
stats.push({ appId: 0, serviceType: "",
network: network, timestamp: Date.now() + (10 * samples),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 1234, txTotalBytes: 1234 });
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
var date = stats[stats.length - 1].timestamp
+ (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
netStatsDb._removeOldStats(txn, store, 0, network, date);
netStatsDb._removeOldStats(txn, store, 0, "", network, date);
}, function(error, result) {
do_check_eq(error, null);
@ -270,15 +274,15 @@ add_test(function test_internalRemoveOldStats() {
function processSamplesDiff(networks, lastStat, newStat, callback) {
netStatsDb.clearStats(networks, function (error, result){
do_check_eq(error, null);
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, lastStat);
}, function(error, result) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
let request = store.index("network").openCursor(newStat.network, "prev");
request.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
do_check_neq(cursor, null);
netStatsDb._processSamplesDiff(txn, store, cursor, newStat);
netStatsDb._processSamplesDiff(txn, store, cursor, newStat, true);
};
}, function(error, result) {
do_check_eq(error, null);
@ -298,13 +302,13 @@ add_test(function test_processSamplesDiffSameSample() {
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
var lastStat = { appId: 0, serviceType: "",
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
var newStat = { appId: 0, serviceType: "",
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 2234, txSystemBytes: 2234,
@ -313,6 +317,7 @@ add_test(function test_processSamplesDiffSameSample() {
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 1);
do_check_eq(result[0].appId, newStat.appId);
do_check_eq(result[0].serviceType, newStat.serviceType);
do_check_true(compareNetworks(result[0].network, newStat.network));
do_check_eq(result[0].timestamp, newStat.timestamp);
do_check_eq(result[0].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
@ -332,13 +337,13 @@ add_test(function test_processSamplesDiffNextSample() {
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
var lastStat = { appId: 0, serviceType: "",
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
var newStat = { appId: 0, serviceType: "",
network: network, timestamp: date + sampleRate,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1734, txSystemBytes: 1734,
@ -347,6 +352,7 @@ add_test(function test_processSamplesDiffNextSample() {
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 2);
do_check_eq(result[1].appId, newStat.appId);
do_check_eq(result[1].serviceType, newStat.serviceType);
do_check_true(compareNetworks(result[1].network, newStat.network));
do_check_eq(result[1].timestamp, newStat.timestamp);
do_check_eq(result[1].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
@ -365,13 +371,13 @@ add_test(function test_processSamplesDiffSamplesLost() {
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
var lastStat = { appId: 0, serviceType: "",
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234};
var newStat = { appId: 0,
var newStat = { appId: 0, serviceType: "",
network: network, timestamp: date + (sampleRate * samples),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 2234, txSystemBytes: 2234,
@ -380,6 +386,7 @@ add_test(function test_processSamplesDiffSamplesLost() {
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, samples + 1);
do_check_eq(result[0].appId, newStat.appId);
do_check_eq(result[0].serviceType, newStat.serviceType);
do_check_true(compareNetworks(result[samples].network, newStat.network));
do_check_eq(result[samples].timestamp, newStat.timestamp);
do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
@ -396,20 +403,23 @@ add_test(function test_saveStats() {
var networks = getNetworks();
var network = [networks[0].id, networks[0].type];
var stats = { appId: 0,
networkId: networks[0].id,
networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
txBytes: 2234};
var stats = { appId: 0,
serviceType: "",
networkId: networks[0].id,
networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
txBytes: 2234,
isAccumulative: true };
clearStore('net_stats', function() {
clearStore('net_stats_store', function() {
netStatsDb.saveStats(stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.logAllRecords(function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
do_check_eq(result[0].appId, stats.appId);
do_check_eq(result[0].serviceType, stats.serviceType);
do_check_true(compareNetworks(result[0].network, network));
let timestamp = filterTimestamp(stats.date);
do_check_eq(result[0].timestamp, timestamp);
@ -429,12 +439,14 @@ add_test(function test_saveAppStats() {
var networks = getNetworks();
var network = [networks[0].id, networks[0].type];
var stats = { appId: 1,
networkId: networks[0].id,
networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
txBytes: 2234};
var stats = { appId: 1,
serviceType: "",
networkId: networks[0].id,
networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
txBytes: 2234,
isAccumulative: false };
netStatsDb.clearStats(networks, function (error, result) {
do_check_eq(error, null);
@ -449,6 +461,47 @@ add_test(function test_saveAppStats() {
// past tests and the new one for appId 1
do_check_eq(result.length, 2);
do_check_eq(result[1].appId, stats.appId);
do_check_eq(result[1].serviceType, stats.serviceType);
do_check_true(compareNetworks(result[1].network, network));
let timestamp = filterTimestamp(stats.date);
do_check_eq(result[1].timestamp, timestamp);
do_check_eq(result[1].rxBytes, stats.rxBytes);
do_check_eq(result[1].txBytes, stats.txBytes);
do_check_eq(result[1].rxSystemBytes, 0);
do_check_eq(result[1].txSystemBytes, 0);
do_check_eq(result[1].rxTotalBytes, 0);
do_check_eq(result[1].txTotalBytes, 0);
run_next_test();
});
});
});
});
add_test(function test_saveServiceStats() {
var networks = getNetworks();
var network = [networks[0].id, networks[0].type];
var stats = { appId: 0,
serviceType: "FakeType",
networkId: networks[0].id,
networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
txBytes: 2234,
isAccumulative: false };
netStatsDb.clearStats(networks, function (error, result) {
do_check_eq(error, null);
netStatsDb.saveStats(stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.logAllRecords(function(error, result) {
do_check_eq(error, null);
// Again, at this point, we have two records, one for the appId 0 and
// empty serviceType used in past tests and the new one for appId 0 and
// non-empty serviceType.
do_check_eq(result.length, 2);
do_check_eq(result[1].appId, stats.appId);
do_check_eq(result[1].serviceType, stats.serviceType);
do_check_true(compareNetworks(result[1].network, network));
let timestamp = filterTimestamp(stats.date);
do_check_eq(result[1].timestamp, timestamp);
@ -467,7 +520,7 @@ add_test(function test_saveAppStats() {
function prepareFind(network, stats, callback) {
netStatsDb.clearStats(network, function (error, result) {
do_check_eq(error, null);
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
callback(error, result);
@ -480,6 +533,7 @@ add_test(function test_find () {
var networkWifi = [networks[0].id, networks[0].type];
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
var appId = 0;
var serviceType = "";
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
@ -489,24 +543,25 @@ add_test(function test_find () {
start = new Date(start - sampleRate);
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: appId,
stats.push({ appId: appId, serviceType: serviceType,
network: networkWifi, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxSystemBytes: 0, txSystemBytes: 0,
rxTotalBytes: 0, txTotalBytes: 0});
rxTotalBytes: 0, txTotalBytes: 0 });
stats.push({ appId: appId,
stats.push({ appId: appId, serviceType: serviceType,
network: networkMobile, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxSystemBytes: 0, txSystemBytes: 0,
rxTotalBytes: 0, txTotalBytes: 0});
rxTotalBytes: 0, txTotalBytes: 0 });
}
prepareFind(networks[0], stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.find(function (error, result) {
do_check_eq(error, null);
do_check_eq(result.serviceType, serviceType);
do_check_eq(result.network.id, networks[0].id);
do_check_eq(result.network.type, networks[0].type);
do_check_eq(result.start.getTime(), start.getTime());
@ -516,7 +571,7 @@ add_test(function test_find () {
do_check_eq(result.data[1].rxBytes, 0);
do_check_eq(result.data[samples].rxBytes, 0);
run_next_test();
}, networks[0], start, end, appId);
}, appId, serviceType, networks[0], start, end);
});
});
@ -524,6 +579,8 @@ add_test(function test_findAppStats () {
var networks = getNetworks();
var networkWifi = [networks[0].id, networks[0].type];
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
var appId = 1;
var serviceType = "";
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
@ -533,12 +590,12 @@ add_test(function test_findAppStats () {
start = new Date(start - sampleRate);
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: 1,
stats.push({ appId: appId, serviceType: serviceType,
network: networkWifi, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
stats.push({ appId: 1,
stats.push({ appId: appId, serviceType: serviceType,
network: networkMobile, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
@ -548,6 +605,7 @@ add_test(function test_findAppStats () {
do_check_eq(error, null);
netStatsDb.find(function (error, result) {
do_check_eq(error, null);
do_check_eq(result.serviceType, serviceType);
do_check_eq(result.network.id, networks[0].id);
do_check_eq(result.network.type, networks[0].type);
do_check_eq(result.start.getTime(), start.getTime());
@ -557,7 +615,51 @@ add_test(function test_findAppStats () {
do_check_eq(result.data[1].rxBytes, 0);
do_check_eq(result.data[samples].rxBytes, 0);
run_next_test();
}, networks[0], start, end, 1);
}, appId, serviceType, networks[0], start, end);
});
});
add_test(function test_findServiceStats () {
var networks = getNetworks();
var networkWifi = [networks[0].id, networks[0].type];
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
var appId = 0;
var serviceType = "FakeType";
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
var start = Date.now();
var saveDate = filterTimestamp(new Date());
var end = new Date(start + (sampleRate * (samples - 1)));
start = new Date(start - sampleRate);
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: appId, serviceType: serviceType,
network: networkWifi, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
stats.push({ appId: appId, serviceType: serviceType,
network: networkMobile, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
}
prepareFind(networks[0], stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.find(function (error, result) {
do_check_eq(error, null);
do_check_eq(result.serviceType, serviceType);
do_check_eq(result.network.id, networks[0].id);
do_check_eq(result.network.type, networks[0].type);
do_check_eq(result.start.getTime(), start.getTime());
do_check_eq(result.end.getTime(), end.getTime());
do_check_eq(result.data.length, samples + 1);
do_check_eq(result.data[0].rxBytes, null);
do_check_eq(result.data[1].rxBytes, 0);
do_check_eq(result.data[samples].rxBytes, 0);
run_next_test();
}, appId, serviceType, networks[0], start, end);
});
});
@ -568,22 +670,51 @@ add_test(function test_saveMultipleAppStats () {
var saveDate = filterTimestamp(new Date());
var cached = Object.create(null);
var serviceType = "FakeType";
var wifiNetId = networkWifi.id + '' + networkWifi.type;
var mobileNetId = networkMobile.id + '' + networkMobile.type;
cached['1wifi'] = { appId: 1, date: new Date(),
networkId: networkWifi.id, networkType: networkWifi.type,
rxBytes: 0, txBytes: 10 };
cached[0 + '' + serviceType + wifiNetId] = {
appId: 0, date: new Date(),
networkId: networkWifi.id, networkType: networkWifi.type,
rxBytes: 0, txBytes: 10,
serviceType: serviceType, isAccumulative: false
};
cached['1mobile'] = { appId: 1, date: new Date(),
networkId: networkMobile.id, networkType: networkMobile.type,
rxBytes: 0, txBytes: 10 };
cached[0 + '' + serviceType + mobileNetId] = {
appId: 0, date: new Date(),
networkId: networkMobile.id, networkType: networkMobile.type,
rxBytes: 0, txBytes: 10,
serviceType: serviceType, isAccumulative: false
};
cached['2wifi'] = { appId: 2, date: new Date(),
networkId: networkWifi.id, networkType: networkWifi.type,
rxBytes: 0, txBytes: 10 };
cached[1 + '' + wifiNetId] = {
appId: 1, date: new Date(),
networkId: networkWifi.id, networkType: networkWifi.type,
rxBytes: 0, txBytes: 10,
serviceType: "", isAccumulative: false
};
cached['2mobile'] = { appId: 2, date: new Date(),
networkId: networkMobile.id, networkType: networkMobile.type,
rxBytes: 0, txBytes: 10 };
cached[1 + '' + mobileNetId] = {
appId: 1, date: new Date(),
networkId: networkMobile.id, networkType: networkMobile.type,
rxBytes: 0, txBytes: 10,
serviceType: "", isAccumulative: false
};
cached[2 + '' + wifiNetId] = {
appId: 2, date: new Date(),
networkId: networkWifi.id, networkType: networkWifi.type,
rxBytes: 0, txBytes: 10,
serviceType: "", isAccumulative: false
};
cached[2 + '' + mobileNetId] = {
appId: 2, date: new Date(),
networkId: networkMobile.id, networkType: networkMobile.type,
rxBytes: 0, txBytes: 10,
serviceType: "", isAccumulative: false
};
let keys = Object.keys(cached);
let index = 0;
@ -605,8 +736,9 @@ add_test(function test_saveMultipleAppStats () {
result.shift();
do_check_eq(error, null);
do_check_eq(result.length, 4);
do_check_eq(result[0].appId, 1);
do_check_eq(result.length, 6);
do_check_eq(result[0].serviceType, serviceType);
do_check_eq(result[3].appId, 1);
do_check_true(compareNetworks(result[0].network, [networkWifi.id, networkWifi.type]));
do_check_eq(result[0].rxBytes, 0);
do_check_eq(result[0].txBytes, 10);

View File

@ -34,8 +34,8 @@ function mokConvertNetworkInterface() {
}
add_test(function test_saveAppStats() {
var cachedAppStats = NetworkStatsService.cachedAppStats;
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
var cachedStats = NetworkStatsService.cachedStats;
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
var samples = 5;
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
@ -47,101 +47,160 @@ add_test(function test_saveAppStats() {
// Insert fake mobile network interface in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedAppStats).length, 0);
do_check_eq(Object.keys(cachedStats).length, 0);
for (var i = 0; i < samples; i++) {
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false);
nssProxy.saveAppStats(1, mobile, timestamp, 10, 20);
nssProxy.saveAppStats(1, mobile, timestamp, 10, 20, false);
}
var key1 = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key2 = 1 + mobileNetId;
var key1 = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key2 = 1 + "" + mobileNetId + "";
do_check_eq(Object.keys(cachedAppStats).length, 2);
do_check_eq(cachedAppStats[key1].appId, 1);
do_check_eq(cachedAppStats[key1].networkId, wifi.id);
do_check_eq(cachedAppStats[key1].networkType, wifi.type);
do_check_eq(new Date(cachedAppStats[key1].date).getTime() / 1000,
do_check_eq(Object.keys(cachedStats).length, 2);
do_check_eq(cachedStats[key1].appId, 1);
do_check_eq(cachedStats[key1].serviceType.length, 0);
do_check_eq(cachedStats[key1].networkId, wifi.id);
do_check_eq(cachedStats[key1].networkType, wifi.type);
do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
Math.floor(timestamp / 1000));
do_check_eq(cachedAppStats[key1].rxBytes, 50);
do_check_eq(cachedAppStats[key1].txBytes, 100);
do_check_eq(cachedAppStats[key2].appId, 1);
do_check_eq(cachedAppStats[key2].networkId, mobile.id);
do_check_eq(cachedAppStats[key2].networkType, mobile.type);
do_check_eq(new Date(cachedAppStats[key2].date).getTime() / 1000,
do_check_eq(cachedStats[key1].rxBytes, 50);
do_check_eq(cachedStats[key1].txBytes, 100);
do_check_eq(cachedStats[key2].appId, 1);
do_check_eq(cachedStats[key1].serviceType.length, 0);
do_check_eq(cachedStats[key2].networkId, mobile.id);
do_check_eq(cachedStats[key2].networkType, mobile.type);
do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
Math.floor(timestamp / 1000));
do_check_eq(cachedAppStats[key2].rxBytes, 50);
do_check_eq(cachedAppStats[key2].txBytes, 100);
do_check_eq(cachedStats[key2].rxBytes, 50);
do_check_eq(cachedStats[key2].txBytes, 100);
run_next_test();
});
add_test(function test_saveAppStatsWithDifferentDates() {
var today = NetworkStatsService.cachedAppStatsDate;
add_test(function test_saveServiceStats() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
var samples = 5;
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
// be instantiated, these two vars will emulate it by filling the properties
// that will be used.
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network interface in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
NetworkStatsService.updateCachedStats(
function (success, msg) {
do_check_eq(success, true);
var cachedStats = NetworkStatsService.cachedStats;
do_check_eq(Object.keys(cachedStats).length, 0);
var serviceType = 'FakeType';
for (var i = 0; i < samples; i++) {
nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false);
nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false);
}
var key1 = 0 + "" + serviceType +
NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key2 = 0 + "" + serviceType + mobileNetId + "";
do_check_eq(Object.keys(cachedStats).length, 2);
do_check_eq(cachedStats[key1].appId, 0);
do_check_eq(cachedStats[key1].serviceType, serviceType);
do_check_eq(cachedStats[key1].networkId, wifi.id);
do_check_eq(cachedStats[key1].networkType, wifi.type);
do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
Math.floor(timestamp / 1000));
do_check_eq(cachedStats[key1].rxBytes, 50);
do_check_eq(cachedStats[key1].txBytes, 100);
do_check_eq(cachedStats[key2].appId, 0);
do_check_eq(cachedStats[key1].serviceType, serviceType);
do_check_eq(cachedStats[key2].networkId, mobile.id);
do_check_eq(cachedStats[key2].networkType, mobile.type);
do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
Math.floor(timestamp / 1000));
do_check_eq(cachedStats[key2].rxBytes, 50);
do_check_eq(cachedStats[key2].txBytes, 100);
}
);
run_next_test();
});
add_test(function test_saveStatsWithDifferentDates() {
var today = NetworkStatsService.cachedStatsDate;
var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
var key = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
NetworkStatsService.updateCachedAppStats(
NetworkStatsService.updateCachedStats(
function (success, msg) {
do_check_eq(success, true);
do_check_eq(Object.keys(NetworkStatsService.cachedAppStats).length, 0);
do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0);
nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20);
nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20, false);
nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20);
nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20, false);
var saveAppStatsCb = {
var saveStatsCb = {
notify: function notify(success, message) {
do_check_eq(success, true);
var cachedAppStats = NetworkStatsService.cachedAppStats;
var key = 2 + NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedAppStats).length, 1);
do_check_eq(cachedAppStats[key].appId, 2);
do_check_eq(cachedAppStats[key].networkId, mobile.id);
do_check_eq(cachedAppStats[key].networkType, mobile.type);
do_check_eq(new Date(cachedAppStats[key].date).getTime() / 1000,
var cachedStats = NetworkStatsService.cachedStats;
var key = 2 + "" +
NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedStats).length, 1);
do_check_eq(cachedStats[key].appId, 2);
do_check_eq(cachedStats[key].networkId, mobile.id);
do_check_eq(cachedStats[key].networkType, mobile.type);
do_check_eq(new Date(cachedStats[key].date).getTime() / 1000,
Math.floor(tomorrow.getTime() / 1000));
do_check_eq(cachedAppStats[key].rxBytes, 30);
do_check_eq(cachedAppStats[key].txBytes, 40);
do_check_eq(cachedStats[key].rxBytes, 30);
do_check_eq(cachedStats[key].txBytes, 40);
run_next_test();
}
};
nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, saveAppStatsCb);
nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, false,
saveStatsCb);
}
);
});
add_test(function test_saveAppStatsWithMaxCachedTraffic() {
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
add_test(function test_saveStatsWithMaxCachedTraffic() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
var maxtraffic = NetworkStatsService.maxCachedTraffic;
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
NetworkStatsService.updateCachedAppStats(
NetworkStatsService.updateCachedStats(
function (success, msg) {
do_check_eq(success, true);
var cachedAppStats = NetworkStatsService.cachedAppStats;
do_check_eq(Object.keys(cachedAppStats).length, 0);
var cachedStats = NetworkStatsService.cachedStats;
do_check_eq(Object.keys(cachedStats).length, 0);
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false);
do_check_eq(Object.keys(cachedAppStats).length, 1);
do_check_eq(Object.keys(cachedStats).length, 1);
nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20);
nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20, false);
do_check_eq(Object.keys(cachedAppStats).length, 0);
do_check_eq(Object.keys(cachedStats).length, 0);
run_next_test();
});
}
);
});
function run_test() {

View File

@ -2873,6 +2873,12 @@ RadioInterface.prototype = {
}
let callback = (function(response) {
if (response.errorMsg) {
// Request fails. Rollback to the original radiostate.
let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_DISABLED
: RIL.GECKO_DETAILED_RADIOSTATE_ENABLED;
this.handleDetailedRadioStateChanged(state);
}
this.setRadioEnabledResponse(target, response);
return false;
}).bind(this);

View File

@ -5265,6 +5265,7 @@ RIL[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
return;
}
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
this.sendChromeMessage(options);
};
RIL[REQUEST_DTMF] = null;

View File

@ -2386,6 +2386,7 @@ NS_IsSrcdocChannel(nsIChannel *aChannel)
// The following members are used for network per-app metering.
const static uint64_t NETWORK_STATS_THRESHOLD = 65536;
const static char NETWORK_STATS_NO_SERVICE_TYPE[] = "";
#ifdef MOZ_WIDGET_GONK
inline nsresult

View File

@ -2225,9 +2225,13 @@ nsFtpState::SaveNetworkStats(bool enforce)
return rv;
}
networkStatsServiceProxy->SaveAppStats(appId, mActiveNetwork,
PR_Now() / 1000, mCountRecv,
0, nullptr);
networkStatsServiceProxy->SaveAppStats(appId,
mActiveNetwork,
PR_Now() / 1000,
mCountRecv,
0,
false,
nullptr);
// Reset the counters after saving.
mCountRecv = 0;

View File

@ -756,6 +756,7 @@ public:
PR_Now() / 1000,
mCountRecv,
mCountSent,
false,
nullptr);
return NS_OK;

View File

@ -3304,6 +3304,7 @@ public:
PR_Now() / 1000,
mCountRecv,
mCountSent,
false,
nullptr);
return NS_OK;