merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-12-09 12:02:12 +01:00
commit 0eda110956
139 changed files with 12719 additions and 1411 deletions

View File

@ -1503,7 +1503,8 @@ nsObjectLoadingContent::IsYoutubeEmbed()
// See if requester is planning on using the JS API.
nsAutoCString uri;
mURI->GetSpec(uri);
if (uri.Find("enablejsapi=1", true, 0, -1) == kNotFound) {
// Only log urls that are rewritable, e.g. not using enablejsapi=1
if (uri.Find("enablejsapi=1", true, 0, -1) != kNotFound) {
return false;
}
return true;
@ -2160,7 +2161,7 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
// Check whether this is a youtube embed.
if (IsYoutubeEmbed()) {
Telemetry::Accumulate(Telemetry::YOUTUBE_EMBED_SEEN, 1);
Telemetry::Accumulate(Telemetry::YOUTUBE_REWRITABLE_EMBED_SEEN, 1);
}
//

View File

@ -17,13 +17,15 @@ interface nsIPrincipal;
* endpoint.
*/
[scriptable, uuid(dc201064-8e5c-4a26-bd37-d1e33558a903)]
[scriptable, uuid(d83e398f-9920-4451-b23a-6d5a5ad2fa26)]
interface nsIPushEndpointCallback : nsISupports
{
void onPushEndpoint(in nsresult status,
in DOMString endpoint,
in uint32_t keyLen,
[array, size_is(keyLen)] in octet key);
[array, size_is(keyLen)] in octet key,
in uint32_t authSecretLen,
[array, size_is(authSecretLen)] in octet authSecret);
};
/**

View File

@ -504,11 +504,13 @@ StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream,
AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl)
: GraphDriver(aGraphImpl)
, mSampleRate(0)
, mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
, mStarted(false)
, mAudioChannel(aGraphImpl->AudioChannel())
, mInCallback(false)
, mPauseRequested(false)
, mMicrophoneActive(false)
#ifdef XP_MACOSX
, mCallbackReceivedWhileSwitching(0)
#endif

View File

@ -27,7 +27,10 @@ UNIFIED_SOURCES += [
'wrappers/H264Converter.cpp'
]
DIRS += ['agnostic/gmp']
DIRS += [
'agnostic/gmp',
'omx'
]
if CONFIG['MOZ_WMF']:
DIRS += [ 'wmf' ];

View File

@ -0,0 +1,353 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "OmxDataDecoder.h"
#include "OmxPromiseLayer.h"
#include "GonkOmxPlatformLayer.h"
#include "MediaInfo.h"
#include <binder/MemoryDealer.h>
#include <media/IOMX.h>
#include <utils/List.h>
#include <media/stagefright/OMXCodec.h>
extern mozilla::LogModule* GetPDMLog();
#ifdef LOG
#undef LOG
#endif
#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("GonkOmxPlatformLayer:: " arg, ##__VA_ARGS__))
using namespace android;
namespace mozilla {
extern void GetPortIndex(nsTArray<uint32_t>& aPortIndex);
bool IsSoftwareCodec(const char* aComponentName) {
nsAutoCString str(aComponentName);
return (str.Find(NS_LITERAL_CSTRING("OMX.google.")) == -1 ? false : true);
}
class GonkOmxObserver : public BnOMXObserver {
public:
void onMessage(const omx_message& aMsg)
{
switch (aMsg.type) {
case omx_message::EVENT:
{
sp<GonkOmxObserver> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aMsg] () {
if (self->mClient && self->mClient->Event(aMsg.u.event_data.event,
aMsg.u.event_data.data1,
aMsg.u.event_data.data2))
{
return;
}
});
mTaskQueue->Dispatch(r.forget());
break;
}
case omx_message::EMPTY_BUFFER_DONE:
{
sp<GonkOmxObserver> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aMsg] () {
if (!self->mPromiseLayer) {
return;
}
BufferData::BufferID id = (BufferData::BufferID)aMsg.u.buffer_data.buffer;
self->mPromiseLayer->EmptyFillBufferDone(OMX_DirInput, id);
});
mTaskQueue->Dispatch(r.forget());
break;
}
case omx_message::FILL_BUFFER_DONE:
{
sp<GonkOmxObserver> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aMsg] () {
if (!self->mPromiseLayer) {
return;
}
// TODO: these codes look a little ugly, it'd be better to improve them.
RefPtr<BufferData> buf;
BufferData::BufferID id = (BufferData::BufferID)aMsg.u.extended_buffer_data.buffer;
buf = self->mPromiseLayer->FindAndRemoveBufferHolder(OMX_DirOutput, id);
MOZ_RELEASE_ASSERT(buf);
GonkBufferData* gonkBuffer = static_cast<GonkBufferData*>(buf.get());
// Copy the critical information to local buffer.
if (gonkBuffer->IsLocalBuffer()) {
gonkBuffer->mBuffer->nOffset = aMsg.u.extended_buffer_data.range_offset;
gonkBuffer->mBuffer->nFilledLen = aMsg.u.extended_buffer_data.range_length;
gonkBuffer->mBuffer->nFlags = aMsg.u.extended_buffer_data.flags;
gonkBuffer->mBuffer->nTimeStamp = aMsg.u.extended_buffer_data.timestamp;
}
self->mPromiseLayer->EmptyFillBufferDone(OMX_DirOutput, buf);
});
mTaskQueue->Dispatch(r.forget());
break;
}
default:
{
LOG("Unhandle event %d", aMsg.type);
}
}
}
void Shutdown()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
mPromiseLayer = nullptr;
mClient = nullptr;
}
GonkOmxObserver(TaskQueue* aTaskQueue, OmxPromiseLayer* aPromiseLayer, OmxDataDecoder* aDataDecoder)
: mTaskQueue(aTaskQueue)
, mPromiseLayer(aPromiseLayer)
, mClient(aDataDecoder)
{}
protected:
RefPtr<TaskQueue> mTaskQueue;
// TODO:
// we should combination both event handlers into one. And we should provide
// an unified way for event handling in OmxPlatforLayer class.
RefPtr<OmxPromiseLayer> mPromiseLayer;
RefPtr<OmxDataDecoder> mClient;
};
GonkBufferData::GonkBufferData(android::IOMX::buffer_id aId, bool aLiveInLocal, android::IMemory* aMemory)
: BufferData((OMX_BUFFERHEADERTYPE*)aId)
, mId(aId)
{
if (!aLiveInLocal) {
mLocalBuffer = new OMX_BUFFERHEADERTYPE;
PodZero(mLocalBuffer.get());
// aMemory is a IPC memory, it is safe to use it here.
mLocalBuffer->pBuffer = (OMX_U8*)aMemory->pointer();
mBuffer = mLocalBuffer.get();
}
}
GonkOmxPlatformLayer::GonkOmxPlatformLayer(OmxDataDecoder* aDataDecoder,
OmxPromiseLayer* aPromiseLayer,
TaskQueue* aTaskQueue)
: mTaskQueue(aTaskQueue)
, mNode(0)
, mQuirks(0)
, mUsingHardwareCodec(false)
{
mOmxObserver = new GonkOmxObserver(mTaskQueue, aPromiseLayer, aDataDecoder);
}
nsresult
GonkOmxPlatformLayer::AllocateOmxBuffer(OMX_DIRTYPE aType,
BUFFERLIST* aBufferList)
{
MOZ_ASSERT(!mMemoryDealer[aType].get());
// Get port definition.
OMX_PARAM_PORTDEFINITIONTYPE def;
nsTArray<uint32_t> portindex;
GetPortIndex(portindex);
for (auto idx : portindex) {
InitOmxParameter(&def);
def.nPortIndex = idx;
OMX_ERRORTYPE err = GetParameter(OMX_IndexParamPortDefinition,
&def,
sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
if (err != OMX_ErrorNone) {
return NS_ERROR_FAILURE;
} else if (def.eDir == aType) {
LOG("Get OMX_IndexParamPortDefinition: port: %d, type: %d", def.nPortIndex, def.eDir);
break;
}
}
size_t t = def.nBufferCountActual * def.nBufferSize;
LOG("Buffer count %d, buffer size %d", def.nBufferCountActual, def.nBufferSize);
bool liveinlocal = mOmx->livesLocally(mNode, getpid());
// MemoryDealer is a IPC buffer allocator in Gonk because IOMX is actually
// lives in mediaserver.
mMemoryDealer[aType] = new MemoryDealer(t, "Gecko-OMX");
for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
sp<IMemory> mem = mMemoryDealer[aType]->allocate(def.nBufferSize);
MOZ_ASSERT(mem.get());
IOMX::buffer_id bufferID;
status_t st;
if ((mQuirks & OMXCodec::kRequiresAllocateBufferOnInputPorts && aType == OMX_DirInput) ||
(mQuirks & OMXCodec::kRequiresAllocateBufferOnOutputPorts && aType == OMX_DirOutput)) {
st = mOmx->allocateBufferWithBackup(mNode, aType, mem, &bufferID);
} else {
st = mOmx->useBuffer(mNode, aType, mem, &bufferID);
}
if (st != OK) {
return NS_ERROR_FAILURE;
}
aBufferList->AppendElement(new GonkBufferData(bufferID, liveinlocal, mem.get()));
}
return NS_OK;
}
nsresult
GonkOmxPlatformLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType,
BUFFERLIST* aBufferList)
{
status_t st;
for (uint32_t i = 0; i < aBufferList->Length(); i++) {
IOMX::buffer_id id = (OMX_BUFFERHEADERTYPE*) aBufferList->ElementAt(i)->ID();
st = mOmx->freeBuffer(mNode, aType, id);
if (st != OK) {
return NS_ERROR_FAILURE;
}
}
aBufferList->Clear();
mMemoryDealer[aType].clear();
return NS_OK;
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::GetState(OMX_STATETYPE* aType)
{
return (OMX_ERRORTYPE)mOmx->getState(mNode, aType);
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::GetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize)
{
return (OMX_ERRORTYPE)mOmx->getParameter(mNode,
aParamIndex,
aComponentParameterStructure,
aComponentParameterSize);
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::SetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize)
{
return (OMX_ERRORTYPE)mOmx->setParameter(mNode,
aParamIndex,
aComponentParameterStructure,
aComponentParameterSize);
}
nsresult
GonkOmxPlatformLayer::Shutdown()
{
mOmx->freeNode(mNode);
mOmxObserver->Shutdown();
mOmxObserver = nullptr;
mOmxClient.disconnect();
return NS_OK;
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::InitOmxToStateLoaded(const TrackInfo* aInfo)
{
status_t err = mOmxClient.connect();
if (err != OK) {
return OMX_ErrorUndefined;
}
mOmx = mOmxClient.interface();
if (!mOmx.get()) {
return OMX_ErrorUndefined;
}
// In Gonk, the software compoment name has prefix "OMX.google". It needs to
// have a way to use hardware codec first.
android::Vector<OMXCodec::CodecNameAndQuirks> matchingCodecs;
const char* swcomponent = nullptr;
OMXCodec::findMatchingCodecs(aInfo->mMimeType.Data(),
0,
nullptr,
0,
&matchingCodecs);
for (uint32_t i = 0; i < matchingCodecs.size(); i++) {
const char* componentName = matchingCodecs.itemAt(i).mName.string();
if (IsSoftwareCodec(componentName)) {
swcomponent = componentName;
} else {
// Try to use hardware codec first.
if (LoadComponent(componentName)) {
mUsingHardwareCodec = true;
return OMX_ErrorNone;
}
}
}
// TODO: in android ICS, the software codec is allocated in mediaserver by
// default, it may be necessay to allocate it in local process.
//
// fallback to sw codec
if (LoadComponent(swcomponent)) {
return OMX_ErrorNone;
}
LOG("no component is loaded");
return OMX_ErrorUndefined;
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::EmptyThisBuffer(BufferData* aData)
{
return (OMX_ERRORTYPE)mOmx->emptyBuffer(mNode,
(IOMX::buffer_id)aData->ID(),
aData->mBuffer->nOffset,
aData->mBuffer->nFilledLen,
aData->mBuffer->nFlags,
aData->mBuffer->nTimeStamp);
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::FillThisBuffer(BufferData* aData)
{
return (OMX_ERRORTYPE)mOmx->fillBuffer(mNode, (IOMX::buffer_id)aData->mBuffer);
}
OMX_ERRORTYPE
GonkOmxPlatformLayer::SendCommand(OMX_COMMANDTYPE aCmd,
OMX_U32 aParam1,
OMX_PTR aCmdData)
{
return (OMX_ERRORTYPE)mOmx->sendCommand(mNode, aCmd, aParam1);
}
bool
GonkOmxPlatformLayer::LoadComponent(const char* aName)
{
status_t err = mOmx->allocateNode(aName, mOmxObserver, &mNode);
if (err == OK) {
OMXCodec::findCodecQuirks(aName, &mQuirks);
LOG("Load OpenMax component %s, quirks %x, live locally %d",
aName, mQuirks, mOmx->livesLocally(mNode, getpid()));
return true;
}
return false;
}
template<class T> void
GonkOmxPlatformLayer::InitOmxParameter(T* aParam)
{
PodZero(aParam);
aParam->nSize = sizeof(T);
aParam->nVersion.s.nVersionMajor = 1;
}
} // mozilla

View File

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(GonkOmxPlatformLayer_h_)
#define GonkOmxPlatformLayer_h_
#pragma GCC visibility push(default)
#include "OmxPlatformLayer.h"
#include "OMX_Component.h"
#include <utils/RefBase.h>
#include <media/stagefright/OMXClient.h>
namespace android {
class MemoryDealer;
class IMemory;
}
namespace mozilla {
class GonkOmxObserver;
/*
* Due to Android's omx node could live in local process (client) or remote
* process (mediaserver).
*
* When it is in local process, the IOMX::buffer_id is OMX_BUFFERHEADERTYPE
* pointer actually, it is safe to use it directly.
*
* When it is in remote process, the OMX_BUFFERHEADERTYPE pointer is 'IN' the
* remote process. It can't be used in local process, so here it allocates a
* local OMX_BUFFERHEADERTYPE.
*/
class GonkBufferData : public OmxPromiseLayer::BufferData {
protected:
virtual ~GonkBufferData() {}
public:
// aMemory is an IPC based memory which will be used as the pBuffer in
// mLocalBuffer.
GonkBufferData(android::IOMX::buffer_id aId, bool aLiveInLocal, android::IMemory* aMemory);
BufferID ID() override
{
return mId;
}
bool IsLocalBuffer()
{
return !!mLocalBuffer.get();
}
// Android OMX uses this id to pass the buffer between OMX component and
// client.
android::IOMX::buffer_id mId;
// mLocalBuffer are used only when the omx node is in mediaserver.
// Due to IPC problem, the mId is the OMX_BUFFERHEADERTYPE address in mediaserver.
// It can't mapping to client process, so we need a local OMX_BUFFERHEADERTYPE
// here.
nsAutoPtr<OMX_BUFFERHEADERTYPE> mLocalBuffer;
};
class GonkOmxPlatformLayer : public OmxPlatformLayer {
public:
GonkOmxPlatformLayer(OmxDataDecoder* aDataDecoder,
OmxPromiseLayer* aPromiseLayer,
TaskQueue* aTaskQueue);
nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override;
nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override;
OMX_ERRORTYPE GetState(OMX_STATETYPE* aType) override;
OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize) override;
OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize) override;
OMX_ERRORTYPE InitOmxToStateLoaded(const TrackInfo* aInfo) override;
OMX_ERRORTYPE EmptyThisBuffer(BufferData* aData) override;
OMX_ERRORTYPE FillThisBuffer(BufferData* aData) override;
OMX_ERRORTYPE SendCommand(OMX_COMMANDTYPE aCmd,
OMX_U32 aParam1,
OMX_PTR aCmdData) override;
nsresult Shutdown() override;
// TODO:
// There is another InitOmxParameter in OmxDataDecoder. They need to combinate
// to one function.
template<class T> void InitOmxParameter(T* aParam);
protected:
bool LoadComponent(const char* aName);
friend class GonkOmxObserver;
RefPtr<TaskQueue> mTaskQueue;
// OMX_DirInput is 0, OMX_DirOutput is 1.
android::sp<android::MemoryDealer> mMemoryDealer[2];
android::sp<GonkOmxObserver> mOmxObserver;
android::sp<android::IOMX> mOmx;
android::IOMX::node_id mNode;
android::OMXClient mOmxClient;
uint32_t mQuirks;
bool mUsingHardwareCodec;
};
}
#pragma GCC visibility pop
#endif // GonkOmxPlatformLayer_h_

View File

@ -0,0 +1,861 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "OmxDataDecoder.h"
#include "OMX_Types.h"
#include "OMX_Component.h"
#include "OMX_Audio.h"
extern mozilla::LogModule* GetPDMLog();
#ifdef LOG
#undef LOG
#endif
#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("OmxDataDecoder::%s: " arg, __func__, ##__VA_ARGS__))
#define CHECK_OMX_ERR(err) \
if (err != OMX_ErrorNone) { \
NotifyError(err, __func__);\
return; \
} \
namespace mozilla {
static const char*
StateTypeToStr(OMX_STATETYPE aType)
{
MOZ_ASSERT(aType == OMX_StateLoaded ||
aType == OMX_StateIdle ||
aType == OMX_StateExecuting ||
aType == OMX_StatePause ||
aType == OMX_StateWaitForResources ||
aType == OMX_StateInvalid);
switch (aType) {
case OMX_StateLoaded:
return "OMX_StateLoaded";
case OMX_StateIdle:
return "OMX_StateIdle";
case OMX_StateExecuting:
return "OMX_StateExecuting";
case OMX_StatePause:
return "OMX_StatePause";
case OMX_StateWaitForResources:
return "OMX_StateWaitForResources";
case OMX_StateInvalid:
return "OMX_StateInvalid";
default:
return "Unknown";
}
}
// There should be 2 ports and port number start from 0.
void GetPortIndex(nsTArray<uint32_t>& aPortIndex) {
aPortIndex.AppendElement(0);
aPortIndex.AppendElement(1);
}
OmxDataDecoder::OmxDataDecoder(const TrackInfo& aTrackInfo,
MediaDataDecoderCallback* aCallback)
: mMonitor("OmxDataDecoder")
, mOmxTaskQueue(CreateMediaDecodeTaskQueue())
, mWatchManager(this, mOmxTaskQueue)
, mOmxState(OMX_STATETYPE::OMX_StateInvalid, "OmxDataDecoder::mOmxState")
, mTrackInfo(aTrackInfo.Clone())
, mFlushing(false)
, mShutdown(false)
, mCheckingInputExhausted(false)
, mPortSettingsChanged(-1, "OmxDataDecoder::mPortSettingsChanged")
, mAudioCompactor(mAudioQueue)
, mCallback(aCallback)
{
LOG("(%p)", this);
mOmxLayer = new OmxPromiseLayer(mOmxTaskQueue, this);
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &OmxDataDecoder::InitializationTask);
mOmxTaskQueue->Dispatch(r.forget());
}
OmxDataDecoder::~OmxDataDecoder()
{
LOG("(%p)", this);
mWatchManager.Shutdown();
mOmxTaskQueue->AwaitShutdownAndIdle();
}
void
OmxDataDecoder::InitializationTask()
{
mWatchManager.Watch(mOmxState, &OmxDataDecoder::OmxStateRunner);
mWatchManager.Watch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged);
}
void
OmxDataDecoder::EndOfStream()
{
LOG("(%p)", this);
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
RefPtr<OmxDataDecoder> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self] () {
self->mCallback->DrainComplete();
});
mReaderTaskQueue->Dispatch(r.forget());
}
RefPtr<MediaDataDecoder::InitPromise>
OmxDataDecoder::Init()
{
LOG("(%p)", this);
mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue();
MOZ_ASSERT(mReaderTaskQueue);
RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
RefPtr<OmxDataDecoder> self = this;
// TODO: it needs to get permission from resource manager before allocating
// Omx component.
InvokeAsync(mOmxTaskQueue, mOmxLayer.get(), __func__, &OmxPromiseLayer::Init,
mOmxTaskQueue, mTrackInfo.get())
->Then(mReaderTaskQueue, __func__,
[self] () {
// Omx state should be OMX_StateIdle.
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self] () {
self->mOmxState = self->mOmxLayer->GetState();
MOZ_ASSERT(self->mOmxState != OMX_StateIdle);
});
self->mOmxTaskQueue->Dispatch(r.forget());
},
[self] () {
self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__);
});
return p;
}
nsresult
OmxDataDecoder::Input(MediaRawData* aSample)
{
LOG("(%p) sample %p", this, aSample);
MOZ_ASSERT(mInitPromise.IsEmpty());
RefPtr<OmxDataDecoder> self = this;
RefPtr<MediaRawData> sample = aSample;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self, sample] () {
self->mMediaRawDatas.AppendElement(sample);
// Start to fill/empty buffers.
if (self->mOmxState == OMX_StateIdle ||
self->mOmxState == OMX_StateExecuting) {
self->FillAndEmptyBuffers();
}
});
mOmxTaskQueue->Dispatch(r.forget());
return NS_OK;
}
nsresult
OmxDataDecoder::Flush()
{
LOG("(%p)", this);
mFlushing = true;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &OmxDataDecoder::DoFlush);
mOmxTaskQueue->Dispatch(r.forget());
// According to the definition of Flush() in PDM:
// "the decoder must be ready to accept new input for decoding".
// So it needs to wait for the Omx to complete the flush command.
MonitorAutoLock lock(mMonitor);
while (mFlushing) {
lock.Wait();
}
return NS_OK;
}
nsresult
OmxDataDecoder::Drain()
{
LOG("(%p)", this);
// TODO: For video decoding, it needs to copy the latest video frame to yuv
// and output to layer again, because all video buffers will be released
// later.
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &OmxDataDecoder::SendEosBuffer);
mOmxTaskQueue->Dispatch(r.forget());
return NS_OK;
}
nsresult
OmxDataDecoder::Shutdown()
{
LOG("(%p)", this);
mShutdown = true;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &OmxDataDecoder::DoAsyncShutdown);
mOmxTaskQueue->Dispatch(r.forget());
return NS_OK;
}
void
OmxDataDecoder::DoAsyncShutdown()
{
LOG("(%p)", this);
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
MOZ_ASSERT(mFlushing);
mWatchManager.Unwatch(mOmxState, &OmxDataDecoder::OmxStateRunner);
mWatchManager.Unwatch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged);
// Do flush so all port can be returned to client.
RefPtr<OmxDataDecoder> self = this;
mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr)
->Then(mOmxTaskQueue, __func__,
[self] () -> RefPtr<OmxCommandPromise> {
LOG("DoAsyncShutdown: flush complete, collecting buffers...");
self->CollectBufferPromises(OMX_DirMax)
->Then(self->mOmxTaskQueue, __func__,
[self] () {
LOG("DoAsyncShutdown: releasing all buffers.");
self->ReleaseBuffers(OMX_DirInput);
self->ReleaseBuffers(OMX_DirOutput);
},
[self] () {
self->mOmxLayer->Shutdown();
});
return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr);
},
[self] () {
self->mOmxLayer->Shutdown();
})
->CompletionPromise()
->Then(mOmxTaskQueue, __func__,
[self] () -> RefPtr<OmxCommandPromise> {
LOG("DoAsyncShutdown: OMX_StateIdle");
return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr);
},
[self] () {
self->mOmxLayer->Shutdown();
})
->CompletionPromise()
->Then(mOmxTaskQueue, __func__,
[self] () {
LOG("DoAsyncShutdown: OMX_StateLoaded, it is safe to shutdown omx");
self->mOmxLayer->Shutdown();
},
[self] () {
self->mOmxLayer->Shutdown();
});
}
void
OmxDataDecoder::CheckIfInputExhausted()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
MOZ_ASSERT(!mCheckingInputExhausted);
mCheckingInputExhausted = false;
if (mMediaRawDatas.Length()) {
return;
}
// When all input buffers are not in omx component, it means all samples have
// been fed into OMX component.
for (auto buf : mInPortBuffers) {
if (buf->mStatus == BufferData::BufferStatus::OMX_COMPONENT) {
return;
}
}
// When all output buffers are held by component, it means client is waiting for output.
for (auto buf : mOutPortBuffers) {
if (buf->mStatus != BufferData::BufferStatus::OMX_COMPONENT) {
return;
}
}
LOG("Call InputExhausted()");
mCallback->InputExhausted();
}
void
OmxDataDecoder::OutputAudio(BufferData* aBufferData)
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
OMX_BUFFERHEADERTYPE* buf = aBufferData->mBuffer;
AudioInfo* info = mTrackInfo->GetAsAudioInfo();
if (buf->nFilledLen) {
uint64_t offset = 0;
uint32_t frames = buf->nFilledLen / (2 * info->mChannels);
if (aBufferData->mRawData) {
offset = aBufferData->mRawData->mOffset;
}
typedef AudioCompactor::NativeCopy OmxCopy;
mAudioCompactor.Push(offset,
buf->nTimeStamp,
info->mRate,
frames,
info->mChannels,
OmxCopy(buf->pBuffer + buf->nOffset,
buf->nFilledLen,
info->mChannels));
RefPtr<AudioData> audio = mAudioQueue.PopFront();
mCallback->Output(audio);
}
aBufferData->mStatus = BufferData::BufferStatus::FREE;
}
void
OmxDataDecoder::FillBufferDone(BufferData* aData)
{
MOZ_ASSERT(!aData || aData->mStatus == BufferData::BufferStatus::OMX_CLIENT);
if (mTrackInfo->IsAudio()) {
OutputAudio(aData);
} else {
MOZ_ASSERT(0);
}
if (aData->mBuffer->nFlags & OMX_BUFFERFLAG_EOS) {
EndOfStream();
} else {
FillAndEmptyBuffers();
// If the latest decoded sample's MediaRawData is also the latest input
// sample, it means there is no input data in queue and component, calling
// CheckIfInputExhausted().
if (aData->mRawData == mLatestInputRawData && !mCheckingInputExhausted) {
mCheckingInputExhausted = true;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &OmxDataDecoder::CheckIfInputExhausted);
mOmxTaskQueue->Dispatch(r.forget());
}
}
}
void
OmxDataDecoder::FillBufferFailure(OmxBufferFailureHolder aFailureHolder)
{
NotifyError(aFailureHolder.mError, __func__);
}
void
OmxDataDecoder::EmptyBufferDone(BufferData* aData)
{
MOZ_ASSERT(!aData || aData->mStatus == BufferData::BufferStatus::OMX_CLIENT);
// Nothing to do when status of input buffer is OMX_CLIENT.
aData->mStatus = BufferData::BufferStatus::FREE;
FillAndEmptyBuffers();
}
void
OmxDataDecoder::EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder)
{
NotifyError(aFailureHolder.mError, __func__);
}
void
OmxDataDecoder::NotifyError(OMX_ERRORTYPE aError, const char* aLine)
{
LOG("NotifyError %d at %s", aError, aLine);
mCallback->Error();
}
void
OmxDataDecoder::FillAndEmptyBuffers()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
MOZ_ASSERT(mOmxState == OMX_StateExecuting);
// During the port setting changed, it is forbided to do any buffer operations.
if (mPortSettingsChanged != -1 || mShutdown) {
return;
}
if (mFlushing) {
return;
}
// Trigger input port.
while (!!mMediaRawDatas.Length()) {
// input buffer must be usedi by component if there is data available.
RefPtr<BufferData> inbuf = FindAvailableBuffer(OMX_DirInput);
if (!inbuf) {
LOG("no input buffer!");
break;
}
RefPtr<MediaRawData> data = mMediaRawDatas[0];
memcpy(inbuf->mBuffer->pBuffer, data->Data(), data->Size());
inbuf->mBuffer->nFilledLen = data->Size();
inbuf->mBuffer->nOffset = 0;
// TODO: the frame size could larger than buffer size in video case.
inbuf->mBuffer->nFlags = inbuf->mBuffer->nAllocLen > data->Size() ?
OMX_BUFFERFLAG_ENDOFFRAME : 0;
inbuf->mBuffer->nTimeStamp = data->mTime;
if (data->Size()) {
inbuf->mRawData = mMediaRawDatas[0];
} else {
LOG("send EOS buffer");
inbuf->mBuffer->nFlags |= OMX_BUFFERFLAG_EOS;
}
LOG("feed sample %p to omx component, len %d, flag %X", data.get(),
inbuf->mBuffer->nFilledLen, inbuf->mBuffer->nFlags);
mOmxLayer->EmptyBuffer(inbuf)->Then(mOmxTaskQueue, __func__, this,
&OmxDataDecoder::EmptyBufferDone,
&OmxDataDecoder::EmptyBufferFailure);
mLatestInputRawData.swap(mMediaRawDatas[0]);
mMediaRawDatas.RemoveElementAt(0);
}
// Trigger output port.
while (true) {
RefPtr<BufferData> outbuf = FindAvailableBuffer(OMX_DirOutput);
if (!outbuf) {
break;
}
mOmxLayer->FillBuffer(outbuf)->Then(mOmxTaskQueue, __func__, this,
&OmxDataDecoder::FillBufferDone,
&OmxDataDecoder::FillBufferFailure);
}
}
OmxPromiseLayer::BufferData*
OmxDataDecoder::FindAvailableBuffer(OMX_DIRTYPE aType)
{
BUFFERLIST* buffers = GetBuffers(aType);
for (uint32_t i = 0; i < buffers->Length(); i++) {
BufferData* buf = buffers->ElementAt(i);
if (buf->mStatus == BufferData::BufferStatus::FREE) {
return buf;
}
}
return nullptr;
}
nsresult
OmxDataDecoder::AllocateBuffers(OMX_DIRTYPE aType)
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
return mOmxLayer->AllocateOmxBuffer(aType, GetBuffers(aType));
}
nsresult
OmxDataDecoder::ReleaseBuffers(OMX_DIRTYPE aType)
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
return mOmxLayer->ReleaseOmxBuffer(aType, GetBuffers(aType));
}
nsTArray<RefPtr<OmxPromiseLayer::BufferData>>*
OmxDataDecoder::GetBuffers(OMX_DIRTYPE aType)
{
MOZ_ASSERT(aType == OMX_DIRTYPE::OMX_DirInput ||
aType == OMX_DIRTYPE::OMX_DirOutput);
if (aType == OMX_DIRTYPE::OMX_DirInput) {
return &mInPortBuffers;
}
return &mOutPortBuffers;
}
void
OmxDataDecoder::ResolveInitPromise(const char* aMethodName)
{
LOG("Resolved InitPromise");
RefPtr<OmxDataDecoder> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self, aMethodName] () {
MOZ_ASSERT(self->mReaderTaskQueue->IsCurrentThreadIn());
self->mInitPromise.ResolveIfExists(self->mTrackInfo->GetType(), aMethodName);
});
mReaderTaskQueue->Dispatch(r.forget());
}
void
OmxDataDecoder::RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName)
{
RefPtr<OmxDataDecoder> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self, aReason, aMethodName] () {
MOZ_ASSERT(self->mReaderTaskQueue->IsCurrentThreadIn());
self->mInitPromise.RejectIfExists(aReason, aMethodName);
});
mReaderTaskQueue->Dispatch(r.forget());
}
void
OmxDataDecoder::OmxStateRunner()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
LOG("OMX state: %s", StateTypeToStr(mOmxState));
// TODO: maybe it'd be better to use promise CompletionPromise() to replace
// this state machine.
if (mOmxState == OMX_StateLoaded) {
// Config codec parameters by minetype.
if (mTrackInfo->IsAudio()) {
ConfigAudioCodec();
}
// Send OpenMax state commane to OMX_StateIdle.
RefPtr<OmxDataDecoder> self = this;
mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr)
->Then(mOmxTaskQueue, __func__,
[self] () {
// Current state should be OMX_StateIdle.
self->mOmxState = self->mOmxLayer->GetState();
MOZ_ASSERT(self->mOmxState == OMX_StateIdle);
},
[self] () {
self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__);
});
// Allocate input and output buffers.
OMX_DIRTYPE types[] = {OMX_DIRTYPE::OMX_DirInput, OMX_DIRTYPE::OMX_DirOutput};
for(const auto id : types) {
if (NS_FAILED(AllocateBuffers(id))) {
LOG("Failed to allocate buffer on port %d", id);
RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__);
break;
}
}
} else if (mOmxState == OMX_StateIdle) {
RefPtr<OmxDataDecoder> self = this;
mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateExecuting, nullptr)
->Then(mOmxTaskQueue, __func__,
[self] () {
self->mOmxState = self->mOmxLayer->GetState();
MOZ_ASSERT(self->mOmxState == OMX_StateExecuting);
self->ResolveInitPromise(__func__);
},
[self] () {
self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__);
});
} else if (mOmxState == OMX_StateExecuting) {
// Config codec once it gets OMX_StateExecuting state.
FillCodecConfigDataToOmx();
} else {
MOZ_ASSERT(0);
}
}
void
OmxDataDecoder::ConfigAudioCodec()
{
const AudioInfo* audioInfo = mTrackInfo->GetAsAudioInfo();
OMX_ERRORTYPE err;
// TODO: it needs to handle other formats like mp3, amr-nb...etc.
if (audioInfo->mMimeType.EqualsLiteral("audio/mp4a-latm")) {
OMX_AUDIO_PARAM_AACPROFILETYPE aac_profile;
InitOmxParameter(&aac_profile);
err = mOmxLayer->GetParameter(OMX_IndexParamAudioAac, &aac_profile, sizeof(aac_profile));
CHECK_OMX_ERR(err);
aac_profile.nSampleRate = audioInfo->mRate;
aac_profile.nChannels = audioInfo->mChannels;
aac_profile.eAACProfile = (OMX_AUDIO_AACPROFILETYPE)audioInfo->mProfile;
err = mOmxLayer->SetParameter(OMX_IndexParamAudioAac, &aac_profile, sizeof(aac_profile));
CHECK_OMX_ERR(err);
LOG("Config OMX_IndexParamAudioAac, channel %d, sample rate %d, profile %d",
audioInfo->mChannels, audioInfo->mRate, audioInfo->mProfile);
}
}
void
OmxDataDecoder::FillCodecConfigDataToOmx()
{
// Codec config data should be the first sample running on Omx TaskQueue.
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
MOZ_ASSERT(!mMediaRawDatas.Length());
MOZ_ASSERT(mOmxState == OMX_StateIdle || mOmxState == OMX_StateExecuting);
RefPtr<BufferData> inbuf = FindAvailableBuffer(OMX_DirInput);
if (mTrackInfo->IsAudio()) {
AudioInfo* audio_info = mTrackInfo->GetAsAudioInfo();
memcpy(inbuf->mBuffer->pBuffer,
audio_info->mCodecSpecificConfig->Elements(),
audio_info->mCodecSpecificConfig->Length());
inbuf->mBuffer->nFilledLen = audio_info->mCodecSpecificConfig->Length();
inbuf->mBuffer->nOffset = 0;
inbuf->mBuffer->nFlags = (OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG);
} else {
MOZ_ASSERT(0);
}
LOG("Feed codec configure data to OMX component");
mOmxLayer->EmptyBuffer(inbuf)->Then(mOmxTaskQueue, __func__, this,
&OmxDataDecoder::EmptyBufferDone,
&OmxDataDecoder::EmptyBufferFailure);
}
bool
OmxDataDecoder::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2)
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
if (mOmxLayer->Event(aEvent, aData1, aData2)) {
return true;
}
switch (aEvent) {
case OMX_EventPortSettingsChanged:
{
// According to spec: "To prevent the loss of any input data, the
// component issuing the OMX_EventPortSettingsChanged event on its input
// port should buffer all input port data that arrives between the
// emission of the OMX_EventPortSettingsChanged event and the arrival of
// the command to disable the input port."
//
// So client needs to disable port and reallocate buffers.
MOZ_ASSERT(mPortSettingsChanged == -1);
mPortSettingsChanged = aData1;
LOG("Got OMX_EventPortSettingsChanged event");
break;
}
default:
{
LOG("WARNING: got none handle event: %d, aData1: %d, aData2: %d",
aEvent, aData1, aData2);
return false;
}
}
return true;
}
template<class T> void
OmxDataDecoder::InitOmxParameter(T* aParam)
{
PodZero(aParam);
aParam->nSize = sizeof(T);
aParam->nVersion.s.nVersionMajor = 1;
}
bool
OmxDataDecoder::BuffersCanBeReleased(OMX_DIRTYPE aType)
{
BUFFERLIST* buffers = GetBuffers(aType);
uint32_t len = buffers->Length();
for (uint32_t i = 0; i < len; i++) {
BufferData::BufferStatus buf_status = buffers->ElementAt(i)->mStatus;
if (buf_status == BufferData::BufferStatus::OMX_COMPONENT ||
buf_status == BufferData::BufferStatus::OMX_CLIENT_OUTPUT) {
return false;
}
}
return true;
}
OMX_DIRTYPE
OmxDataDecoder::GetPortDirection(uint32_t aPortIndex)
{
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOmxParameter(&def);
def.nPortIndex = mPortSettingsChanged;
OMX_ERRORTYPE err = mOmxLayer->GetParameter(OMX_IndexParamPortDefinition,
&def,
sizeof(def));
if (err != OMX_ErrorNone) {
return OMX_DirMax;
}
return def.eDir;
}
RefPtr<OmxPromiseLayer::OmxBufferPromise::AllPromiseType>
OmxDataDecoder::CollectBufferPromises(OMX_DIRTYPE aType)
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
nsTArray<RefPtr<OmxBufferPromise>> promises;
OMX_DIRTYPE types[] = {OMX_DIRTYPE::OMX_DirInput, OMX_DIRTYPE::OMX_DirOutput};
for (const auto type : types) {
if ((aType == type) || (aType == OMX_DirMax)) {
// find the buffer which has promise.
BUFFERLIST* buffers = GetBuffers(type);
for (uint32_t i = 0; i < buffers->Length(); i++) {
BufferData* buf = buffers->ElementAt(i);
if (!buf->mPromise.IsEmpty()) {
// OmxBufferPromise is not exclusive, it can be multiple "Then"s, so it
// is safe to call "Ensure" here.
promises.AppendElement(buf->mPromise.Ensure(__func__));
}
}
}
}
LOG("CollectBufferPromises: type %d, total %d promiese", aType, promises.Length());
if (promises.Length()) {
return OmxBufferPromise::All(mOmxTaskQueue, promises);
}
nsTArray<BufferData*> headers;
return OmxBufferPromise::AllPromiseType::CreateAndResolve(headers, __func__);
}
void
OmxDataDecoder::PortSettingsChanged()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
if (mPortSettingsChanged == -1 || mOmxState == OMX_STATETYPE::OMX_StateInvalid) {
return;
}
// The PortSettingsChanged algorithm:
//
// 1. disable port.
// 2. wait for port buffers return to client and then release these buffers.
// 3. enable port.
// 4. allocate port buffers.
//
// Disable port. Get port definition if the target port is enable.
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOmxParameter(&def);
def.nPortIndex = mPortSettingsChanged;
OMX_ERRORTYPE err = mOmxLayer->GetParameter(OMX_IndexParamPortDefinition,
&def,
sizeof(def));
CHECK_OMX_ERR(err);
RefPtr<OmxDataDecoder> self = this;
if (def.bEnabled) {
// 1. disable port.
LOG("PortSettingsChanged: disable port %d", def.nPortIndex);
mOmxLayer->SendCommand(OMX_CommandPortDisable, mPortSettingsChanged, nullptr)
->Then(mOmxTaskQueue, __func__,
[self, def] () -> RefPtr<OmxCommandPromise> {
// 3. enable port.
// Send enable port command.
RefPtr<OmxCommandPromise> p =
self->mOmxLayer->SendCommand(OMX_CommandPortEnable,
self->mPortSettingsChanged,
nullptr);
// 4. allocate port buffers.
// Allocate new port buffers.
nsresult rv = self->AllocateBuffers(def.eDir);
if (NS_FAILED(rv)) {
self->NotifyError(OMX_ErrorUndefined, __func__);
}
return p;
},
[self] () {
self->NotifyError(OMX_ErrorUndefined, __func__);
})
->CompletionPromise()
->Then(mOmxTaskQueue, __func__,
[self] () {
LOG("PortSettingsChanged: port settings changed complete");
// finish port setting changed.
self->mPortSettingsChanged = -1;
self->FillAndEmptyBuffers();
},
[self] () {
self->NotifyError(OMX_ErrorUndefined, __func__);
});
// 2. wait for port buffers return to client and then release these buffers.
//
// Port buffers will be returned to client soon once OMX_CommandPortDisable
// command is sent. Then releasing these buffers.
CollectBufferPromises(def.eDir)
->Then(mOmxTaskQueue, __func__,
[self, def] () {
MOZ_ASSERT(self->BuffersCanBeReleased(def.eDir));
nsresult rv = self->ReleaseBuffers(def.eDir);
if (NS_FAILED(rv)) {
MOZ_RELEASE_ASSERT(0);
self->NotifyError(OMX_ErrorUndefined, __func__);
}
},
[self] () {
self->NotifyError(OMX_ErrorUndefined, __func__);
});
}
}
void
OmxDataDecoder::SendEosBuffer()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
// There is no 'Drain' API in OpenMax, so it needs to wait for output sample
// with EOS flag. However, MediaRawData doesn't provide EOS information,
// so here it generates an empty BufferData with eos OMX_BUFFERFLAG_EOS in queue.
// This behaviour should be compliant with spec, I think...
RefPtr<MediaRawData> eos_data = new MediaRawData();
mMediaRawDatas.AppendElement(eos_data);
FillAndEmptyBuffers();
}
void
OmxDataDecoder::DoFlush()
{
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
// 1. Call OMX command OMX_CommandFlush in Omx TaskQueue.
// 2. Remove all elements in mMediaRawDatas when flush is completed.
RefPtr<OmxDataDecoder> self = this;
mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr)
->Then(mOmxTaskQueue, __func__, this,
&OmxDataDecoder::FlushComplete,
&OmxDataDecoder::FlushFailure);
}
void
OmxDataDecoder::FlushComplete(OMX_COMMANDTYPE aCommandType)
{
mMediaRawDatas.Clear();
mFlushing = false;
MonitorAutoLock lock(mMonitor);
mMonitor.Notify();
LOG("Flush complete");
}
void OmxDataDecoder::FlushFailure(OmxCommandFailureHolder aFailureHolder)
{
NotifyError(OMX_ErrorUndefined, __func__);
mFlushing = false;
MonitorAutoLock lock(mMonitor);
mMonitor.Notify();
}
}

View File

@ -0,0 +1,204 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(OmxDataDecoder_h_)
#define OmxDataDecoder_h_
#include "mozilla/Monitor.h"
#include "PlatformDecoderModule.h"
#include "OmxPromiseLayer.h"
#include "MediaInfo.h"
#include "AudioCompactor.h"
namespace mozilla {
typedef OmxPromiseLayer::OmxCommandPromise OmxCommandPromise;
typedef OmxPromiseLayer::OmxBufferPromise OmxBufferPromise;
typedef OmxPromiseLayer::OmxBufferFailureHolder OmxBufferFailureHolder;
typedef OmxPromiseLayer::OmxCommandFailureHolder OmxCommandFailureHolder;
typedef OmxPromiseLayer::BufferData BufferData;
typedef OmxPromiseLayer::BUFFERLIST BUFFERLIST;
/* OmxDataDecoder is the major class which performs followings:
* 1. Translate PDM function into OMX commands.
* 2. Keeping the buffers between client and component.
* 3. Manage the OMX state.
*
* From the definiton in OpenMax spec. "2.2.1", there are 3 major roles in
* OpenMax IL.
*
* IL client:
* "The IL client may be a layer below the GUI application, such as GStreamer,
* or may be several layers below the GUI layer."
*
* OmxDataDecoder acts as the IL client.
*
* OpenMAX IL component:
* "A component that is intended to wrap functionality that is required in the
* target system."
*
* OmxPromiseLayer acts as the OpenMAX IL component.
*
* OpenMAX IL core:
* "Platform-specific code that has the functionality necessary to locate and
* then load an OpenMAX IL component into main memory."
*
* OmxPlatformLayer acts as the OpenMAX IL core.
*/
class OmxDataDecoder : public MediaDataDecoder {
protected:
virtual ~OmxDataDecoder();
public:
OmxDataDecoder(const TrackInfo& aTrackInfo,
MediaDataDecoderCallback* aCallback);
RefPtr<InitPromise> Init() override;
nsresult Input(MediaRawData* aSample) override;
nsresult Flush() override;
nsresult Drain() override;
nsresult Shutdown() override;
// Return true if event is handled.
bool Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2);
protected:
void InitializationTask();
void ResolveInitPromise(const char* aMethodName);
void RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName);
void OmxStateRunner();
void FillAndEmptyBuffers();
void FillBufferDone(BufferData* aData);
void FillBufferFailure(OmxBufferFailureHolder aFailureHolder);
void EmptyBufferDone(BufferData* aData);
void EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder);
void NotifyError(OMX_ERRORTYPE aError, const char* aLine);
// Config audio codec.
// Some codec may just ignore this and rely on codec specific data in
// FillCodecConfigDataToOmx().
void ConfigAudioCodec();
// Sending codec specific data to OMX component. OMX component could send a
// OMX_EventPortSettingsChanged back to client. And then client needs to
// disable port and reallocate buffer.
void FillCodecConfigDataToOmx();
void SendEosBuffer();
void EndOfStream();
// It could be called after codec specific data is sent and component found
// the port format is changed due to different codec specific.
void PortSettingsChanged();
void OutputAudio(BufferData* aBufferData);
// Notify InputExhausted when:
// 1. all input buffers are not held by component.
// 2. all output buffers are waiting for filling complete.
void CheckIfInputExhausted();
// Buffer can be released if its status is not OMX_COMPONENT or
// OMX_CLIENT_OUTPUT.
bool BuffersCanBeReleased(OMX_DIRTYPE aType);
OMX_DIRTYPE GetPortDirection(uint32_t aPortIndex);
void DoAsyncShutdown();
void DoFlush();
void FlushComplete(OMX_COMMANDTYPE aCommandType);
void FlushFailure(OmxCommandFailureHolder aFailureHolder);
BUFFERLIST* GetBuffers(OMX_DIRTYPE aType);
nsresult AllocateBuffers(OMX_DIRTYPE aType);
nsresult ReleaseBuffers(OMX_DIRTYPE aType);
BufferData* FindAvailableBuffer(OMX_DIRTYPE aType);
template<class T> void InitOmxParameter(T* aParam);
// aType could be OMX_DirMax for all types.
RefPtr<OmxPromiseLayer::OmxBufferPromise::AllPromiseType>
CollectBufferPromises(OMX_DIRTYPE aType);
Monitor mMonitor;
// The Omx TaskQueue.
RefPtr<TaskQueue> mOmxTaskQueue;
RefPtr<TaskQueue> mReaderTaskQueue;
WatchManager<OmxDataDecoder> mWatchManager;
// It is accessed in omx TaskQueue.
Watchable<OMX_STATETYPE> mOmxState;
RefPtr<OmxPromiseLayer> mOmxLayer;
UniquePtr<TrackInfo> mTrackInfo;
// It is accessed in both omx and reader TaskQueue.
Atomic<bool> mFlushing;
// It is accessed in Omx/reader TaskQeueu.
Atomic<bool> mShutdown;
// It is accessed in Omx TaskQeueu.
bool mCheckingInputExhausted;
// It is accessed in reader TaskQueue.
MozPromiseHolder<InitPromise> mInitPromise;
// It is written in Omx TaskQeueu. Read in Omx TaskQueue.
// It value means the port index which port settings is changed.
// -1 means no port setting changed.
//
// Note: when port setting changed, there should be no buffer operations
// via EmptyBuffer or FillBuffer.
Watchable<int32_t> mPortSettingsChanged;
// It is access in Omx TaskQueue.
nsTArray<RefPtr<MediaRawData>> mMediaRawDatas;
// It is access in Omx TaskQueue. The latest input MediaRawData.
RefPtr<MediaRawData> mLatestInputRawData;
BUFFERLIST mInPortBuffers;
BUFFERLIST mOutPortBuffers;
// For audio output.
// TODO: because this class is for both video and audio decoding, so there
// should be some kind of abstract things to these members.
MediaQueue<AudioData> mAudioQueue;
AudioCompactor mAudioCompactor;
MediaDataDecoderCallback* mCallback;
};
}
#endif /* OmxDataDecoder_h_ */

View File

@ -0,0 +1,49 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "OmxDecoderModule.h"
#include "OmxDataDecoder.h"
namespace mozilla {
already_AddRefed<MediaDataDecoder>
OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
mozilla::layers::LayersBackend aLayersBackend,
mozilla::layers::ImageContainer* aImageContainer,
FlushableTaskQueue* aVideoTaskQueue,
MediaDataDecoderCallback* aCallback)
{
return nullptr;
}
already_AddRefed<MediaDataDecoder>
OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
FlushableTaskQueue* aAudioTaskQueue,
MediaDataDecoderCallback* aCallback)
{
RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback);
return decoder.forget();
}
void
OmxDecoderModule::Init()
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
}
PlatformDecoderModule::ConversionRequired
OmxDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
{
return kNeedNone;
}
bool
OmxDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
{
return aMimeType.EqualsLiteral("audio/mp4a-latm");
}
}

View File

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(OmxDecoderModule_h_)
#define OmxDecoderModule_h_
#include "PlatformDecoderModule.h"
namespace mozilla {
class OmxDecoderModule : public PlatformDecoderModule {
public:
already_AddRefed<MediaDataDecoder>
CreateVideoDecoder(const VideoInfo& aConfig,
mozilla::layers::LayersBackend aLayersBackend,
mozilla::layers::ImageContainer* aImageContainer,
FlushableTaskQueue* aVideoTaskQueue,
MediaDataDecoderCallback* aCallback) override;
already_AddRefed<MediaDataDecoder>
CreateAudioDecoder(const AudioInfo& aConfig,
FlushableTaskQueue* aAudioTaskQueue,
MediaDataDecoderCallback* aCallback) override;
static void Init();
bool SupportsMimeType(const nsACString& aMimeType) const override;
ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override;
};
} // namespace mozilla
#endif // OmxDecoderModule_h_

View File

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(OmxPlatformLayer_h_)
#define OmxPlatformLayer_h_
#include "OMX_Core.h"
#include "OMX_Types.h"
#include "mozilla/MozPromise.h"
#include "mozilla/TaskQueue.h"
#include "OmxPromiseLayer.h"
namespace mozilla {
class TrackInfo;
/*
* This class the the abstract layer of the platform OpenMax IL implementation.
*
* For some platform like andoird, it exposures its OpenMax IL via IOMX which
* is definitions are different comparing to standard.
* For other platforms like Raspberry Pi, it will be easy to implement this layer
* with the standard OpenMax IL api.
*/
class OmxPlatformLayer {
public:
typedef OmxPromiseLayer::BUFFERLIST BUFFERLIST;
typedef OmxPromiseLayer::BufferData BufferData;
virtual OMX_ERRORTYPE InitOmxToStateLoaded(const TrackInfo* aInfo) = 0;
virtual OMX_ERRORTYPE EmptyThisBuffer(BufferData* aData) = 0;
virtual OMX_ERRORTYPE FillThisBuffer(BufferData* aData) = 0;
virtual OMX_ERRORTYPE SendCommand(OMX_COMMANDTYPE aCmd,
OMX_U32 aParam1,
OMX_PTR aCmdData) = 0;
// Buffer could be platform dependent; for example, video decoding needs gralloc
// on Gonk. Therefore, derived class needs to implement its owned buffer
// allocate/release API according to its platform type.
virtual nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) = 0;
virtual nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) = 0;
virtual OMX_ERRORTYPE GetState(OMX_STATETYPE* aType) = 0;
virtual OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize) = 0;
virtual OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize) = 0;
virtual nsresult Shutdown() = 0;
virtual ~OmxPlatformLayer() {}
};
}
#endif // OmxPlatformLayer_h_

View File

@ -0,0 +1,335 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "OmxPromiseLayer.h"
#include "OmxPlatformLayer.h"
#include "OmxDataDecoder.h"
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION < 21
#include "GonkOmxPlatformLayer.h"
#endif
extern mozilla::LogModule* GetPDMLog();
#ifdef LOG
#undef LOG
#endif
#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("OmxPromiseLayer:: " arg, ##__VA_ARGS__))
namespace mozilla {
OmxPromiseLayer::OmxPromiseLayer(TaskQueue* aTaskQueue, OmxDataDecoder* aDataDecoder)
: mTaskQueue(aTaskQueue)
, mFlushPortIndex(0)
{
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION < 21
mPlatformLayer = new GonkOmxPlatformLayer(aDataDecoder, this, aTaskQueue);
#endif
MOZ_ASSERT(!!mPlatformLayer);
}
RefPtr<OmxPromiseLayer::OmxCommandPromise>
OmxPromiseLayer::Init(TaskQueue* aTaskQueue, const TrackInfo* aInfo)
{
mTaskQueue = aTaskQueue;
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
OMX_ERRORTYPE err = mPlatformLayer->InitOmxToStateLoaded(aInfo);
if (err != OMX_ErrorNone) {
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet);
return OmxCommandPromise::CreateAndReject(failure, __func__);
}
OMX_STATETYPE state = GetState();
if (state == OMX_StateLoaded) {
return OmxCommandPromise::CreateAndResolve(OMX_CommandStateSet, __func__);
} if (state == OMX_StateIdle) {
return SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr);
}
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet);
return OmxCommandPromise::CreateAndReject(failure, __func__);
}
RefPtr<OmxPromiseLayer::OmxBufferPromise>
OmxPromiseLayer::FillBuffer(BufferData* aData)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
LOG("FillBuffer: buffer %p", aData->mBuffer);
RefPtr<OmxBufferPromise> p = aData->mPromise.Ensure(__func__);
OMX_ERRORTYPE err = mPlatformLayer->FillThisBuffer(aData);
if (err != OMX_ErrorNone) {
OmxBufferFailureHolder failure(err, aData);
aData->mPromise.Reject(Move(failure), __func__);
} else {
aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT;
GetBufferHolders(OMX_DirOutput)->AppendElement(aData);
}
return p;
}
RefPtr<OmxPromiseLayer::OmxBufferPromise>
OmxPromiseLayer::EmptyBuffer(BufferData* aData)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
LOG("EmptyBuffer: buffer %p, size %d", aData->mBuffer, aData->mBuffer->nFilledLen);
RefPtr<OmxBufferPromise> p = aData->mPromise.Ensure(__func__);
OMX_ERRORTYPE err = mPlatformLayer->EmptyThisBuffer(aData);
if (err != OMX_ErrorNone) {
OmxBufferFailureHolder failure(err, aData);
aData->mPromise.Reject(Move(failure), __func__);
} else {
if (aData->mRawData) {
mRawDatas.AppendElement(Move(aData->mRawData));
}
aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT;
GetBufferHolders(OMX_DirInput)->AppendElement(aData);
}
return p;
}
OmxPromiseLayer::BUFFERLIST*
OmxPromiseLayer::GetBufferHolders(OMX_DIRTYPE aType)
{
MOZ_ASSERT(aType == OMX_DirInput || aType == OMX_DirOutput);
if (aType == OMX_DirInput) {
return &mInbufferHolders;
}
return &mOutbufferHolders;
}
already_AddRefed<MediaRawData>
OmxPromiseLayer::FindAndRemoveRawData(OMX_TICKS aTimecode)
{
for (auto raw : mRawDatas) {
if (raw->mTimecode == aTimecode) {
mRawDatas.RemoveElement(raw);
return raw.forget();
}
}
return nullptr;
}
already_AddRefed<BufferData>
OmxPromiseLayer::FindAndRemoveBufferHolder(OMX_DIRTYPE aType,
BufferData::BufferID aId)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<BufferData> holder;
BUFFERLIST* holders = GetBufferHolders(aType);
for (uint32_t i = 0; i < holders->Length(); i++) {
if (holders->ElementAt(i)->ID() == aId) {
holder = holders->ElementAt(i);
holders->RemoveElementAt(i);
return holder.forget();
}
}
return nullptr;
}
already_AddRefed<BufferData>
OmxPromiseLayer::FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<BufferData> holder;
BUFFERLIST* holders = GetBufferHolders(aType);
for (uint32_t i = 0; i < holders->Length(); i++) {
if (holders->ElementAt(i)->ID() == aId) {
holder = holders->ElementAt(i);
return holder.forget();
}
}
return nullptr;
}
void
OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData)
{
MOZ_ASSERT(!!aData);
LOG("EmptyFillBufferDone: type %d, buffer %p", aType, aData->mBuffer);
if (aData) {
if (aType == OMX_DirOutput) {
aData->mRawData = nullptr;
aData->mRawData = FindAndRemoveRawData(aData->mBuffer->nTimeStamp);
}
aData->mStatus = BufferData::BufferStatus::OMX_CLIENT;
aData->mPromise.Resolve(aData, __func__);
}
}
void
OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID)
{
RefPtr<BufferData> holder = FindAndRemoveBufferHolder(aType, aID);
MOZ_ASSERT(!!holder);
LOG("EmptyFillBufferDone: type %d, buffer %p", aType, holder->mBuffer);
if (holder) {
if (aType == OMX_DirOutput) {
holder->mRawData = nullptr;
holder->mRawData = FindAndRemoveRawData(holder->mBuffer->nTimeStamp);
}
holder->mStatus = BufferData::BufferStatus::OMX_CLIENT;
holder->mPromise.Resolve(holder, __func__);
}
}
RefPtr<OmxPromiseLayer::OmxCommandPromise>
OmxPromiseLayer::SendCommand(OMX_COMMANDTYPE aCmd, OMX_U32 aParam1, OMX_PTR aCmdData)
{
// No need to issue flush because of buffers are in client already.
//
// Some components fail to respond flush event when all of buffers are in
// client.
if (aCmd == OMX_CommandFlush) {
bool needFlush = false;
if ((aParam1 & OMX_DirInput && mInbufferHolders.Length()) ||
(aParam1 & OMX_DirOutput && mOutbufferHolders.Length())) {
needFlush = true;
}
if (!needFlush) {
LOG("SendCommand: buffers are in client already, no need to flush");
mRawDatas.Clear();
return OmxCommandPromise::CreateAndResolve(OMX_CommandFlush, __func__);
}
}
OMX_ERRORTYPE err = mPlatformLayer->SendCommand(aCmd, aParam1, aCmdData);
if (err != OMX_ErrorNone) {
OmxCommandFailureHolder failure(OMX_ErrorNotReady, aCmd);
return OmxCommandPromise::CreateAndReject(failure, __func__);
}
RefPtr<OmxCommandPromise> p;
if (aCmd == OMX_CommandStateSet) {
p = mCommandStatePromise.Ensure(__func__);
} else if (aCmd == OMX_CommandFlush) {
p = mFlushPromise.Ensure(__func__);
mFlushPortIndex = aParam1;
// Clear all buffered raw data.
mRawDatas.Clear();
} else if (aCmd == OMX_CommandPortEnable) {
p = mPortEnablePromise.Ensure(__func__);
} else if (aCmd == OMX_CommandPortDisable) {
p = mPortDisablePromise.Ensure(__func__);
} else {
LOG("SendCommand: error unsupport command");
MOZ_ASSERT(0);
}
return p;
}
bool
OmxPromiseLayer::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2)
{
OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE) aData1;
switch (aEvent) {
case OMX_EventCmdComplete:
{
if (cmd == OMX_CommandStateSet) {
mCommandStatePromise.Resolve(OMX_CommandStateSet, __func__);
} else if (cmd == OMX_CommandFlush && mFlushPortIndex == aData2) {
mFlushPromise.Resolve(OMX_CommandFlush, __func__);
} else if (cmd == OMX_CommandPortDisable) {
mPortDisablePromise.Resolve(OMX_CommandPortDisable, __func__);
} else if (cmd == OMX_CommandPortEnable) {
mPortEnablePromise.Resolve(OMX_CommandPortEnable, __func__);
}
break;
}
case OMX_EventError:
{
if (cmd == OMX_CommandStateSet) {
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet);
mCommandStatePromise.Reject(failure, __func__);
} else if (cmd == OMX_CommandFlush && mFlushPortIndex == aData2) {
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandFlush);
mFlushPromise.Reject(failure, __func__);
} else if (cmd == OMX_CommandPortDisable) {
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandPortDisable);
mPortDisablePromise.Reject(failure, __func__);
} else if (cmd == OMX_CommandPortEnable) {
OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandPortEnable);
mPortEnablePromise.Reject(failure, __func__);
}
break;
}
default:
{
return false;
}
}
return true;
}
nsresult
OmxPromiseLayer::AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers)
{
return mPlatformLayer->AllocateOmxBuffer(aType, aBuffers);
}
nsresult
OmxPromiseLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers)
{
return mPlatformLayer->ReleaseOmxBuffer(aType, aBuffers);
}
OMX_STATETYPE
OmxPromiseLayer::GetState()
{
OMX_STATETYPE state;
OMX_ERRORTYPE err = mPlatformLayer->GetState(&state);
return err == OMX_ErrorNone ? state : OMX_StateInvalid;
}
OMX_ERRORTYPE
OmxPromiseLayer::GetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize)
{
return mPlatformLayer->GetParameter(aParamIndex,
aComponentParameterStructure,
aComponentParameterSize);
}
OMX_ERRORTYPE
OmxPromiseLayer::SetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize)
{
return mPlatformLayer->SetParameter(aParamIndex,
aComponentParameterStructure,
aComponentParameterSize);
}
nsresult
OmxPromiseLayer::Shutdown()
{
LOG("Shutdown");
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
MOZ_ASSERT(!GetBufferHolders(OMX_DirInput)->Length());
MOZ_ASSERT(!GetBufferHolders(OMX_DirOutput)->Length());
return mPlatformLayer->Shutdown();
}
}

View File

@ -0,0 +1,221 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(OmxPromiseLayer_h_)
#define OmxPromiseLayer_h_
#include "OMX_Core.h"
#include "OMX_Types.h"
#include "mozilla/MozPromise.h"
#include "mozilla/TaskQueue.h"
namespace mozilla {
class TrackInfo;
class OmxPlatformLayer;
class OmxDataDecoder;
/* This class acts as a middle layer between OmxDataDecoder and the underlying
* OmxPlatformLayer.
*
* This class has two purposes:
* 1. Using promise instead of OpenMax async callback function.
* For example, OmxCommandPromise is used for OpenMax IL SendCommand.
* 2. Manage the buffer exchanged between client and component.
* Because omx buffer works crossing threads, so each omx buffer has its own
* promise, it is defined in BufferData.
*
* All of functions and members in this class should be run in the same
* TaskQueue.
*/
class OmxPromiseLayer {
protected:
virtual ~OmxPromiseLayer() {}
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OmxPromiseLayer)
OmxPromiseLayer(TaskQueue* aTaskQueue, OmxDataDecoder* aDataDecoder);
class BufferData;
typedef nsTArray<RefPtr<BufferData>> BUFFERLIST;
class OmxBufferFailureHolder {
public:
OmxBufferFailureHolder(OMX_ERRORTYPE aError, BufferData* aBuffer)
: mError(aError)
, mBuffer(aBuffer)
{}
OMX_ERRORTYPE mError;
BufferData* mBuffer;
};
typedef MozPromise<BufferData*, OmxBufferFailureHolder, /* IsExclusive = */ false> OmxBufferPromise;
class OmxCommandFailureHolder {
public:
OmxCommandFailureHolder(OMX_ERRORTYPE aErrorType,
OMX_COMMANDTYPE aCommandType)
: mErrorType(aErrorType)
, mCommandType(aCommandType)
{}
OMX_ERRORTYPE mErrorType;
OMX_COMMANDTYPE mCommandType;
};
typedef MozPromise<OMX_COMMANDTYPE, OmxCommandFailureHolder, /* IsExclusive = */ true> OmxCommandPromise;
typedef MozPromise<uint32_t, bool, /* IsExclusive = */ true> OmxPortConfigPromise;
// TODO: maybe a generic promise is good enough for this case?
RefPtr<OmxCommandPromise> Init(TaskQueue* aQueue, const TrackInfo* aInfo);
RefPtr<OmxBufferPromise> FillBuffer(BufferData* aData);
RefPtr<OmxBufferPromise> EmptyBuffer(BufferData* aData);
RefPtr<OmxCommandPromise> SendCommand(OMX_COMMANDTYPE aCmd,
OMX_U32 aParam1,
OMX_PTR aCmdData);
nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers);
nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers);
OMX_STATETYPE GetState();
OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize);
OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex,
OMX_PTR aComponentParameterStructure,
OMX_U32 aComponentParameterSize);
nsresult Shutdown();
// BufferData maintains the status of OMX buffer (OMX_BUFFERHEADERTYPE).
// mStatus tracks the buffer owner.
// And a promise because OMX buffer working among different threads.
class BufferData {
protected:
virtual ~BufferData() {}
public:
explicit BufferData(OMX_BUFFERHEADERTYPE* aBuffer)
: mEos(false)
, mStatus(BufferStatus::FREE)
, mBuffer(aBuffer)
{}
typedef void* BufferID;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BufferData)
// In most cases, the ID of this buffer is the pointer address of mBuffer.
// However, in platform like gonk, it is another value.
virtual BufferID ID()
{
return mBuffer;
}
// The buffer could be used by several objects. And only one object owns the
// buffer the same time.
// FREE:
// nobody uses it.
//
// OMX_COMPONENT:
// buffer is used by OMX component (OmxPlatformLayer).
//
// OMX_CLIENT:
// buffer is used by client which is wait for audio/video playing
// (OmxDataDecoder)
//
// OMX_CLIENT_OUTPUT:
// used by client to output decoded data (for example, Gecko layer in
// this case)
//
// For output port buffer, the status transition is:
// FREE -> OMX_COMPONENT -> OMX_CLIENT -> OMX_CLIENT_OUTPUT -> FREE
//
// For input port buffer, the status transition is:
// FREE -> OMX_COMPONENT -> OMX_CLIENT -> FREE
//
enum BufferStatus {
FREE,
OMX_COMPONENT,
OMX_CLIENT,
OMX_CLIENT_OUTPUT,
INVALID
};
bool mEos;
// The raw keeps in OmxPromiseLayer after EmptyBuffer and then passing to
// output decoded buffer in EmptyFillBufferDone. It is used to keep the
// records of the original data from demuxer, like duration, stream offset...etc.
RefPtr<MediaRawData> mRawData;
// Because OMX buffer works acorssing threads, so it uses a promise
// for each buffer when the buffer is used by Omx component.
MozPromiseHolder<OmxBufferPromise> mPromise;
BufferStatus mStatus;
OMX_BUFFERHEADERTYPE* mBuffer;
};
void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID);
void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData);
already_AddRefed<BufferData>
FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId);
already_AddRefed<BufferData>
FindAndRemoveBufferHolder(OMX_DIRTYPE aType, BufferData::BufferID aId);
// Return truen if event is handled.
bool Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2);
protected:
BUFFERLIST* GetBufferHolders(OMX_DIRTYPE aType);
already_AddRefed<MediaRawData> FindAndRemoveRawData(OMX_TICKS aTimecode);
RefPtr<TaskQueue> mTaskQueue;
MozPromiseHolder<OmxCommandPromise> mCommandStatePromise;
MozPromiseHolder<OmxCommandPromise> mPortDisablePromise;
MozPromiseHolder<OmxCommandPromise> mPortEnablePromise;
MozPromiseHolder<OmxCommandPromise> mFlushPromise;
OMX_U32 mFlushPortIndex;
nsAutoPtr<OmxPlatformLayer> mPlatformLayer;
private:
// Elements are added to holders when FillBuffer() or FillBuffer(). And
// removing elelments when the promise is resolved. Buffers in these lists
// should NOT be used by other component; for example, output it to audio
// output. These list should be empty when engine is about to shutdown.
//
// Note:
// There bufferlist should not be used by other class directly.
BUFFERLIST mInbufferHolders;
BUFFERLIST mOutbufferHolders;
nsTArray<RefPtr<MediaRawData>> mRawDatas;
};
}
#endif /* OmxPromiseLayer_h_ */

View File

@ -0,0 +1,46 @@
# -*- 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 += [
'OmxDecoderModule.h',
]
UNIFIED_SOURCES += [
'OmxDataDecoder.cpp',
'OmxDecoderModule.cpp',
'OmxPromiseLayer.cpp',
]
LOCAL_INCLUDES += [
'/media/openmax_il/il112',
]
include('/ipc/chromium/chromium-config.mozbuild')
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] == '19' or CONFIG['ANDROID_VERSION'] == '20'):
# Suppress some GCC/clang warnings being treated as errors:
# - about attributes on forward declarations for types that are already
# defined, which complains about an important MOZ_EXPORT for android::AString
# - about multi-character constants which are used in codec-related code
if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
CXXFLAGS += [
'-Wno-error=attributes',
'-Wno-error=multichar'
]
CXXFLAGS += [
'-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
'frameworks/base/include/binder',
'frameworks/base/include/utils',
]
]
UNIFIED_SOURCES += [
'GonkOmxPlatformLayer.cpp',
]
EXTRA_DSO_LDOPTS += [
'-libbinder',
]
FINAL_LIBRARY = 'xul'

View File

@ -428,8 +428,13 @@ static void clearIdentifiers()
static void addRange(InstanceData* instanceData, const char* range)
{
char rangestr[16];
strncpy(rangestr, range, sizeof(rangestr));
/*
increased rangestr size from 16 to 17, the 17byte is only for
null terminated value, maybe for actual capacity it needs 16 bytes
*/
char rangestr[17];
memset(rangestr, 0, sizeof(rangestr));
strncpy(rangestr, range, sizeof(rangestr) - sizeof(char));
const char* str1 = strtok(rangestr, ",");
const char* str2 = str1 ? strtok(nullptr, ",") : nullptr;
if (str1 && str2) {

View File

@ -186,7 +186,8 @@ function PushEndpointCallback(pushManager, resolve, reject) {
PushEndpointCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushEndpointCallback]),
onPushEndpoint: function(ok, endpoint, keyLen, key) {
onPushEndpoint: function(ok, endpoint, keyLen, key,
authSecretLen, authSecretIn) {
let {pushManager} = this;
if (!Components.isSuccessCode(ok)) {
this.reject(new pushManager._window.DOMException(
@ -208,9 +209,17 @@ PushEndpointCallback.prototype = {
keyView.set(key);
}
let authSecret = null;
if (authSecretLen) {
authSecret = new ArrayBuffer(authSecretLen);
let secretView = new Uint8Array(authSecret);
secretView.set(authSecretIn);
}
let sub = new pushManager._window.PushSubscription(endpoint,
pushManager._scope,
publicKey);
publicKey,
authSecret);
sub.setPrincipal(pushManager._principal);
this.resolve(sub);
},

View File

@ -101,19 +101,26 @@ PushClient.prototype = {
_deliverPushEndpoint: function(request, registration) {
if (!registration) {
request.onPushEndpoint(Cr.NS_OK, "", 0, null);
return;
}
if (registration.p256dhKey) {
let key = new Uint8Array(registration.p256dhKey);
request.onPushEndpoint(Cr.NS_OK,
registration.pushEndpoint,
key.length,
key);
request.onPushEndpoint(Cr.NS_OK, "", 0, null, 0, null);
return;
}
request.onPushEndpoint(Cr.NS_OK, registration.pushEndpoint, 0, null);
let key;
if (registration.p256dhKey) {
key = new Uint8Array(registration.p256dhKey);
}
let authSecret;
if (registration.authSecret) {
authSecret = new Uint8Array(registration.authSecret);
}
request.onPushEndpoint(Cr.NS_OK,
registration.pushEndpoint,
key ? key.length : 0,
key,
authSecret ? authSecret.length : 0,
authSecret);
},
receiveMessage: function(aMessage) {
@ -135,7 +142,7 @@ PushClient.prototype = {
case "PushService:Register:KO":
case "PushService:Registration:KO":
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null);
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null, 0, null);
break;
case "PushService:Unregister:OK":

View File

@ -10,30 +10,70 @@ const Cu = Components.utils;
Cu.importGlobalProperties(['crypto']);
this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
'getEncryptionKeyParams', 'getEncryptionParams',
'getCryptoParams',
'base64UrlDecode'];
var ENCRYPT_INFO = new TextEncoder('utf-8').encode('Content-Encoding: aesgcm128');
var NONCE_INFO = new TextEncoder('utf-8').encode('Content-Encoding: nonce');
var UTF8 = new TextEncoder('utf-8');
var ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
var P256DH_INFO = UTF8.encode('P-256\0');
var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' };
// A default keyid with a name that won't conflict with a real keyid.
var DEFAULT_KEYID = '';
this.getEncryptionKeyParams = function(encryptKeyField) {
function getEncryptionKeyParams(encryptKeyField) {
if (!encryptKeyField) {
return null;
}
var params = encryptKeyField.split(',');
return params.reduce((m, p) => {
var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
if (pmap.keyid && pmap.dh) {
m[pmap.keyid] = pmap.dh;
}
if (!m[DEFAULT_KEYID] && pmap.dh) {
m[DEFAULT_KEYID] = pmap.dh;
}
return m;
}, {});
};
}
this.getEncryptionParams = function(encryptField) {
function getEncryptionParams(encryptField) {
var p = encryptField.split(',', 1)[0];
if (!p) {
return null;
}
return p.split(';').reduce(parseHeaderFieldParams, {});
};
}
this.getCryptoParams = function(headers) {
if (!headers) {
return null;
}
var requiresAuthenticationSecret = true;
var keymap = getEncryptionKeyParams(headers.crypto_key);
if (!keymap) {
requiresAuthenticationSecret = false;
keymap = getEncryptionKeyParams(headers.encryption_key);
if (!keymap) {
return null;
}
}
var enc = getEncryptionParams(headers.encryption);
if (!enc) {
return null;
}
var dh = keymap[enc.keyid || DEFAULT_KEYID];
var salt = enc.salt;
var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
return null;
}
return {dh, salt, rs, auth: requiresAuthenticationSecret};
}
var parseHeaderFieldParams = (m, v) => {
var i = v.indexOf('=');
@ -41,7 +81,7 @@ var parseHeaderFieldParams = (m, v) => {
// A quoted string with internal quotes is invalid for all the possible
// values of this header field.
m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
.replace(/^"(.*)"$/, '$1');
.replace(/^"(.*)"$/, '$1');
}
return m;
};
@ -115,7 +155,7 @@ function hkdf(salt, ikm) {
.then(prk => new hmac(prk));
}
hkdf.prototype.generate = function(info, len) {
hkdf.prototype.extract = function(info, len) {
var input = concatArray([info, new Uint8Array([1])]);
return this.prkhPromise
.then(prkh => prkh.hash(input))
@ -127,10 +167,10 @@ hkdf.prototype.generate = function(info, len) {
});
};
/* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
/* generate a 96-bit nonce for use in GCM, 48-bits of which are populated */
function generateNonce(base, index) {
if (index >= Math.pow(2, 48)) {
throw new Error('Error generating IV - index is too large.');
throw new Error('Error generating nonce - index is too large.');
}
var nonce = base.slice(0, 12);
nonce = new Uint8Array(nonce);
@ -142,19 +182,21 @@ function generateNonce(base, index) {
this.PushCrypto = {
generateKeys: function() {
return crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256'},
true,
['deriveBits'])
generateAuthenticationSecret() {
return crypto.getRandomValues(new Uint8Array(12));
},
generateKeys() {
return crypto.subtle.generateKey(ECDH_KEY, true, ['deriveBits'])
.then(cryptoKey =>
Promise.all([
crypto.subtle.exportKey('raw', cryptoKey.publicKey),
// TODO: change this when bug 1048931 lands.
crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
]));
},
decodeMsg: function(aData, aPrivateKey, aRemotePublicKey, aSalt, aRs) {
decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey,
aSalt, aRs, aAuthenticationSecret) {
if (aData.byteLength === 0) {
// Zero length messages will be passed as null.
@ -167,28 +209,21 @@ this.PushCrypto = {
return Promise.reject(new Error('Data truncated'));
}
let senderKey = base64UrlDecode(aSenderPublicKey)
return Promise.all([
crypto.subtle.importKey('raw', base64UrlDecode(aRemotePublicKey),
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits']),
crypto.subtle.importKey('jwk', aPrivateKey,
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits'])
crypto.subtle.importKey('raw', senderKey, ECDH_KEY,
false, ['deriveBits']),
crypto.subtle.importKey('jwk', aPrivateKey, ECDH_KEY,
false, ['deriveBits'])
])
.then(keys =>
crypto.subtle.deriveBits({ name: 'ECDH', public: keys[0] }, keys[1], 256))
.then(rawKey => {
var kdf = new hkdf(base64UrlDecode(aSalt), new Uint8Array(rawKey));
return Promise.all([
kdf.generate(ENCRYPT_INFO, 16)
.then(gcmBits =>
crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
['decrypt'])),
kdf.generate(NONCE_INFO, 12)
])
})
.then(([appServerKey, subscriptionPrivateKey]) =>
crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
subscriptionPrivateKey, 256))
.then(ikm => this._deriveKeyAndNonce(new Uint8Array(ikm),
base64UrlDecode(aSalt),
aPublicKey,
senderKey,
aAuthenticationSecret))
.then(r =>
// AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
@ -196,11 +231,57 @@ this.PushCrypto = {
.then(r => concatArray(r));
},
_decodeChunk: function(aSlice, aIndex, aNonce, aKey) {
return crypto.subtle.decrypt({name: 'AES-GCM',
iv: generateNonce(aNonce, aIndex)
},
aKey, aSlice)
_deriveKeyAndNonce(ikm, salt, receiverKey, senderKey, authenticationSecret) {
var kdfPromise;
var context;
// The authenticationSecret, when present, is mixed with the ikm using HKDF.
// This is its primary purpose. However, since the authentication secret
// was added at the same time that the info string was changed, we also use
// its presence to change how the final info string is calculated:
//
// 1. When there is no authenticationSecret, the context string is simply
// "Content-Encoding: <blah>". This corresponds to old, deprecated versions
// of the content encoding. This should eventually be removed: bug 1230038.
//
// 2. When there is an authenticationSecret, the context string is:
// "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
// receiver key and sender key.
if (authenticationSecret) {
// Since we are using an authentication secret, we need to run an extra
// round of HKDF with the authentication secret as salt.
var authKdf = new hkdf(authenticationSecret, ikm);
kdfPromise = authKdf.extract(AUTH_INFO, 32)
.then(ikm2 => new hkdf(salt, ikm2));
// We also use the presence of the authentication secret to indicate that
// we have extra context to add to the info parameter.
context = concatArray([
new Uint8Array([0]), P256DH_INFO,
this._encodeLength(receiverKey), receiverKey,
this._encodeLength(senderKey), senderKey
]);
} else {
kdfPromise = Promise.resolve(new hkdf(salt, ikm));
context = new Uint8Array(0);
}
return kdfPromise.then(kdf => Promise.all([
kdf.extract(concatArray([ENCRYPT_INFO, context]), 16)
.then(gcmBits => crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
['decrypt'])),
kdf.extract(concatArray([NONCE_INFO, context]), 12)
]));
},
_encodeLength(buffer) {
return new Uint8Array([0, buffer.byteLength]);
},
_decodeChunk(aSlice, aIndex, aNonce, aKey) {
let params = {
name: 'AES-GCM',
iv: generateNonce(aNonce, aIndex)
};
return crypto.subtle.decrypt(params, aKey, aSlice)
.then(decoded => {
decoded = new Uint8Array(decoded);
if (decoded.length == 0) {

View File

@ -8,6 +8,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "mozilla/dom/PushManagerBinding.h"
#include "mozilla/dom/PushSubscriptionBinding.h"
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
@ -124,11 +125,13 @@ PushSubscription::Unsubscribe(ErrorResult& aRv)
PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aRawP256dhKey)
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret)
: mGlobal(aGlobal)
, mEndpoint(aEndpoint)
, mScope(aScope)
, mRawP256dhKey(aRawP256dhKey)
, mAuthSecret(aAuthSecret)
{
}
@ -145,14 +148,18 @@ PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
void
PushSubscription::GetKey(JSContext* aCx,
PushEncryptionKeyName aType,
JS::MutableHandle<JSObject*> aP256dhKey)
JS::MutableHandle<JSObject*> aKey)
{
if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
aP256dhKey.set(ArrayBuffer::Create(aCx,
mRawP256dhKey.Length(),
mRawP256dhKey.Elements()));
aKey.set(ArrayBuffer::Create(aCx,
mRawP256dhKey.Length(),
mRawP256dhKey.Elements()));
} else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
aKey.set(ArrayBuffer::Create(aCx,
mAuthSecret.Length(),
mAuthSecret.Elements()));
} else {
aP256dhKey.set(nullptr);
aKey.set(nullptr);
}
}
@ -169,6 +176,7 @@ PushSubscription::Constructor(GlobalObject& aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
const Nullable<ArrayBuffer>& aAuthSecret,
ErrorResult& aRv)
{
MOZ_ASSERT(!aEndpoint.IsEmpty());
@ -180,13 +188,20 @@ PushSubscription::Constructor(GlobalObject& aGlobal,
if (!aP256dhKey.IsNull()) {
const ArrayBuffer& key = aP256dhKey.Value();
key.ComputeLengthAndData();
rawKey.SetLength(key.Length());
rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
rawKey.InsertElementsAt(0, key.Data(), key.Length());
}
nsTArray<uint8_t> authSecret;
if (!aAuthSecret.IsNull()) {
const ArrayBuffer& sekrit = aAuthSecret.Value();
sekrit.ComputeLengthAndData();
authSecret.InsertElementsAt(0, sekrit.Data(), sekrit.Length());
}
RefPtr<PushSubscription> sub = new PushSubscription(global,
aEndpoint,
aScope,
rawKey);
aEndpoint,
aScope,
rawKey,
authSecret);
return sub.forget();
}
@ -259,8 +274,12 @@ NS_INTERFACE_MAP_END
WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aRawP256dhKey)
: mEndpoint(aEndpoint), mScope(aScope), mRawP256dhKey(aRawP256dhKey)
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret)
: mEndpoint(aEndpoint)
, mScope(aScope)
, mRawP256dhKey(aRawP256dhKey)
, mAuthSecret(aAuthSecret)
{
MOZ_ASSERT(!aScope.IsEmpty());
MOZ_ASSERT(!aEndpoint.IsEmpty());
@ -281,6 +300,7 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
const Nullable<ArrayBuffer>& aAuthSecret,
ErrorResult& aRv)
{
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
@ -294,9 +314,19 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
rawKey.SetLength(key.Length());
rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
}
nsTArray<uint8_t> authSecret;
if (!aAuthSecret.IsNull()) {
const ArrayBuffer& sekrit = aAuthSecret.Value();
sekrit.ComputeLengthAndData();
authSecret.SetLength(sekrit.Length());
authSecret.ReplaceElementsAt(0, sekrit.Length(),
sekrit.Data(), sekrit.Length());
}
RefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(aEndpoint,
aScope,
rawKey);
aScope,
rawKey,
authSecret);
return sub.forget();
}
@ -304,15 +334,20 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
void
WorkerPushSubscription::GetKey(JSContext* aCx,
PushEncryptionKeyName aType,
JS::MutableHandle<JSObject*> aP256dhKey)
JS::MutableHandle<JSObject*> aKey)
{
if (aType == mozilla::dom::PushEncryptionKeyName::P256dh &&
!mRawP256dhKey.IsEmpty()) {
aP256dhKey.set(ArrayBuffer::Create(aCx,
mRawP256dhKey.Length(),
mRawP256dhKey.Elements()));
aKey.set(ArrayBuffer::Create(aCx,
mRawP256dhKey.Length(),
mRawP256dhKey.Elements()));
} else if (aType == mozilla::dom::PushEncryptionKeyName::Auth &&
!mAuthSecret.IsEmpty()) {
aKey.set(ArrayBuffer::Create(aCx,
mAuthSecret.Length(),
mAuthSecret.Elements()));
} else {
aP256dhKey.set(nullptr);
aKey.set(nullptr);
}
}
@ -500,13 +535,15 @@ public:
nsresult aStatus,
const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aRawP256dhKey)
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret)
: WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
, mProxy(aProxy)
, mStatus(aStatus)
, mEndpoint(aEndpoint)
, mScope(aScope)
, mRawP256dhKey(aRawP256dhKey)
, mAuthSecret(aAuthSecret)
{ }
bool
@ -518,7 +555,8 @@ public:
promise->MaybeResolve(JS::NullHandleValue);
} else {
RefPtr<WorkerPushSubscription> sub =
new WorkerPushSubscription(mEndpoint, mScope, mRawP256dhKey);
new WorkerPushSubscription(mEndpoint, mScope,
mRawP256dhKey, mAuthSecret);
promise->MaybeResolve(sub);
}
} else {
@ -537,6 +575,7 @@ private:
nsString mEndpoint;
nsString mScope;
nsTArray<uint8_t> mRawP256dhKey;
nsTArray<uint8_t> mAuthSecret;
};
class GetSubscriptionCallback final : public nsIPushEndpointCallback
@ -554,7 +593,9 @@ public:
OnPushEndpoint(nsresult aStatus,
const nsAString& aEndpoint,
uint32_t aKeyLen,
uint8_t* aKey) override
uint8_t* aKey,
uint32_t aAuthSecretLen,
uint8_t* aAuthSecret) override
{
AssertIsOnMainThread();
MOZ_ASSERT(mProxy, "OnPushEndpoint() called twice?");
@ -572,16 +613,30 @@ public:
nsTArray<uint8_t> rawP256dhKey(aKeyLen);
rawP256dhKey.ReplaceElementsAt(0, aKeyLen, aKey, aKeyLen);
nsTArray<uint8_t> authSecret(aAuthSecretLen);
authSecret.ReplaceElementsAt(0, aAuthSecretLen,
aAuthSecret, aAuthSecretLen);
RefPtr<GetSubscriptionResultRunnable> r =
new GetSubscriptionResultRunnable(proxy,
aStatus,
aEndpoint,
mScope,
rawP256dhKey);
rawP256dhKey,
authSecret);
r->Dispatch(jsapi.cx());
return NS_OK;
}
// Convenience method for use in this file.
void
OnPushEndpointError(nsresult aStatus)
{
Unused << NS_WARN_IF(NS_FAILED(
OnPushEndpoint(aStatus, EmptyString(), 0, nullptr, 0, nullptr)));
}
protected:
~GetSubscriptionCallback()
{}
@ -619,23 +674,23 @@ public:
PushPermissionState state;
nsresult rv = GetPermissionState(principal, state);
if (NS_FAILED(rv)) {
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
callback->OnPushEndpointError(NS_ERROR_FAILURE);
return NS_OK;
}
if (state != PushPermissionState::Granted) {
if (mAction == WorkerPushManager::GetSubscriptionAction) {
callback->OnPushEndpoint(NS_OK, EmptyString(), 0, nullptr);
callback->OnPushEndpointError(NS_OK);
return NS_OK;
}
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
callback->OnPushEndpointError(NS_ERROR_FAILURE);
return NS_OK;
}
nsCOMPtr<nsIPushClient> client =
do_CreateInstance("@mozilla.org/push/PushClient;1");
if (!client) {
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
callback->OnPushEndpointError(NS_ERROR_FAILURE);
return NS_OK;
}
@ -647,7 +702,7 @@ public:
}
if (NS_WARN_IF(NS_FAILED(rv))) {
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
callback->OnPushEndpointError(NS_ERROR_FAILURE);
return NS_OK;
}

View File

@ -67,7 +67,8 @@ public:
explicit PushSubscription(nsIGlobalObject* aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aP256dhKey);
const nsTArray<uint8_t>& aP256dhKey,
const nsTArray<uint8_t>& aAuthSecret);
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@ -87,13 +88,14 @@ public:
void
GetKey(JSContext* cx,
PushEncryptionKeyName aType,
JS::MutableHandle<JSObject*> aP256dhKey);
JS::MutableHandle<JSObject*> aKey);
static already_AddRefed<PushSubscription>
Constructor(GlobalObject& aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
const Nullable<ArrayBuffer>& aAuthSecret,
ErrorResult& aRv);
void
@ -111,6 +113,7 @@ private:
nsString mEndpoint;
nsString mScope;
nsTArray<uint8_t> mRawP256dhKey;
nsTArray<uint8_t> mAuthSecret;
};
class PushManager final : public nsISupports
@ -161,7 +164,8 @@ public:
explicit WorkerPushSubscription(const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aRawP256dhKey);
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret);
nsIGlobalObject*
GetParentObject() const
@ -177,6 +181,7 @@ public:
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
const Nullable<ArrayBuffer>& aAuthSecret,
ErrorResult& aRv);
void
@ -199,6 +204,7 @@ private:
nsString mEndpoint;
nsString mScope;
nsTArray<uint8_t> mRawP256dhKey;
nsTArray<uint8_t> mAuthSecret;
};
class WorkerPushManager final : public nsISupports

View File

@ -36,6 +36,7 @@ function PushRecord(props) {
this.lastPush = props.lastPush || 0;
this.p256dhPublicKey = props.p256dhPublicKey;
this.p256dhPrivateKey = props.p256dhPrivateKey;
this.authenticationSecret = props.authenticationSecret;
this.setQuota(props.quota);
this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
}
@ -220,6 +221,7 @@ PushRecord.prototype = {
lastPush: this.lastPush,
pushCount: this.pushCount,
p256dhKey: this.p256dhPublicKey,
authenticationSecret: this.authenticationSecret,
};
},
};

View File

@ -787,17 +787,29 @@ this.PushService = {
});
},
ensureP256dhKey: function(record) {
if (record.p256dhPublicKey && record.p256dhPrivateKey) {
ensureCrypto: function(record) {
if (record.authenticationSecret &&
record.p256dhPublicKey &&
record.p256dhPrivateKey) {
return Promise.resolve(record);
}
let keygen = Promise.resolve([]);
if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
keygen = PushCrypto.generateKeys();
}
// We do not have a encryption key. so we need to generate it. This
// is only going to happen on db upgrade from version 4 to higher.
return PushCrypto.generateKeys()
.then(exportedKeys => {
return keygen
.then(([pubKey, privKey]) => {
return this.updateRecordAndNotifyApp(record.keyID, record => {
record.p256dhPublicKey = exportedKeys[0];
record.p256dhPrivateKey = exportedKeys[1];
if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
record.p256dhPublicKey = pubKey;
record.p256dhPrivateKey = privKey;
}
if (!record.authenticationSecret) {
record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
}
return record;
});
}, error => {
@ -873,9 +885,11 @@ this.PushService = {
decodedPromise = PushCrypto.decodeMsg(
message,
record.p256dhPrivateKey,
record.p256dhPublicKey,
cryptoParams.dh,
cryptoParams.salt,
cryptoParams.rs
cryptoParams.rs,
cryptoParams.auth ? record.authenticationSecret : null
);
} else {
decodedPromise = Promise.resolve(null);
@ -889,6 +903,8 @@ this.PushService = {
setTimeout(() => this._updateQuota(keyID),
prefs.get("quotaUpdateDelay"));
return notified;
}, error => {
console.error("receivedPushMessage: Error decrypting message", error);
});
}).catch(error => {
console.error("receivedPushMessage: Error notifying app", error);

View File

@ -22,8 +22,7 @@ Cu.import("resource://gre/modules/Promise.jsm");
const {
PushCrypto,
concatArray,
getEncryptionKeyParams,
getEncryptionParams,
getCryptoParams,
} = Cu.import("resource://gre/modules/PushCrypto.jsm");
this.EXPORTED_SYMBOLS = ["PushServiceHttp2"];
@ -151,48 +150,25 @@ PushChannelListener.prototype = {
if (Components.isSuccessCode(aStatusCode) &&
this._mainListener &&
this._mainListener._pushService) {
var keymap = encryptKeyFieldParser(aRequest);
if (!keymap) {
return;
}
var enc = encryptFieldParser(aRequest);
if (!enc || !enc.keyid) {
return;
}
var dh = keymap[enc.keyid];
var salt = enc.salt;
var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
return;
}
var msg = concatArray(this._message);
let headers = {
encryption_key: getHeaderField(aRequest, "Encryption-Key"),
crypto_key: getHeaderField(aRequest, "Crypto-Key"),
encryption: getHeaderField(aRequest, "Encryption"),
};
let cryptoParams = getCryptoParams(headers);
let msg = concatArray(this._message);
this._mainListener._pushService._pushChannelOnStop(this._mainListener.uri,
this._ackUri,
msg,
dh,
salt,
rs);
cryptoParams);
}
}
};
function encryptKeyFieldParser(aRequest) {
function getHeaderField(aRequest, name) {
try {
var encryptKeyField = aRequest.getRequestHeader("Encryption-Key");
return getEncryptionKeyParams(encryptKeyField);
} catch(e) {
// getRequestHeader can throw.
return null;
}
}
function encryptFieldParser(aRequest) {
try {
var encryptField = aRequest.getRequestHeader("Encryption");
return getEncryptionParams(encryptField);
return aRequest.getRequestHeader(name);
} catch(e) {
// getRequestHeader can throw.
return null;
@ -490,9 +466,10 @@ this.PushServiceHttp2 = {
})
.then(result =>
PushCrypto.generateKeys()
.then(exportedKeys => {
result.p256dhPublicKey = exportedKeys[0];
result.p256dhPrivateKey = exportedKeys[1];
.then(([publicKey, privateKey]) => {
result.p256dhPublicKey = publicKey;
result.p256dhPrivateKey = privateKey;
result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
this._conns[result.subscriptionUri] = {
channel: null,
listener: null,
@ -673,7 +650,7 @@ this.PushServiceHttp2 = {
for (let i = 0; i < aSubscriptions.length; i++) {
let record = aSubscriptions[i];
this._mainPushService.ensureP256dhKey(record).then(record => {
this._mainPushService.ensureCrypto(record).then(record => {
this._startSingleConnection(record);
}, error => {
console.error("startConnections: Error updating record",
@ -800,14 +777,9 @@ this.PushServiceHttp2 = {
}
},
_pushChannelOnStop: function(aUri, aAckUri, aMessage, dh, salt, rs) {
_pushChannelOnStop: function(aUri, aAckUri, aMessage, cryptoParams) {
console.debug("pushChannelOnStop()");
let cryptoParams = {
dh: dh,
salt: salt,
rs: rs,
};
this._mainPushService.receivedPushMessage(
aUri, aMessage, cryptoParams, record => {
// Always update the stored record.

View File

@ -22,8 +22,7 @@ const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
const {
PushCrypto,
base64UrlDecode,
getEncryptionKeyParams,
getEncryptionParams,
getCryptoParams,
} = Cu.import("resource://gre/modules/PushCrypto.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
@ -59,27 +58,6 @@ XPCOMUtils.defineLazyGetter(this, "console", () => {
});
});
function getCryptoParams(headers) {
if (!headers) {
return null;
}
var keymap = getEncryptionKeyParams(headers.encryption_key);
if (!keymap) {
return null;
}
var enc = getEncryptionParams(headers.encryption);
if (!enc || !enc.keyid) {
return null;
}
var dh = keymap[enc.keyid];
var salt = enc.salt;
var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
return null;
}
return {dh, salt, rs};
}
/**
* A proxy between the PushService and the WebSocket. The listener is used so
* that the PushService can silence messages from the WebSocket by setting
@ -802,7 +780,7 @@ this.PushServiceWebSocket = {
if (this._dataEnabled) {
this._mainPushService.getAllUnexpired().then(records =>
Promise.all(records.map(record =>
this._mainPushService.ensureP256dhKey(record).catch(error => {
this._mainPushService.ensureCrypto(record).catch(error => {
console.error("finishHandshake: Error updating record",
record.keyID, error);
})
@ -1014,6 +992,7 @@ this.PushServiceWebSocket = {
.then(([publicKey, privateKey]) => {
record.p256dhPublicKey = publicKey;
record.p256dhPrivateKey = privateKey;
record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
return record;
});
});

View File

@ -4,6 +4,7 @@
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
let db;
let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d';
@ -16,7 +17,7 @@ function run_test() {
run_next_test();
}
function putRecord(channelID, scope, publicKey, privateKey) {
function putRecord(channelID, scope, publicKey, privateKey, authSecret) {
return db.put({
channelID: channelID,
pushEndpoint: 'https://example.org/push/' + channelID,
@ -25,12 +26,15 @@ function putRecord(channelID, scope, publicKey, privateKey) {
lastPush: 0,
originAttributes: '',
quota: Infinity,
p256dhPublicKey: publicKey,
p256dhPublicKey: base64UrlDecode(publicKey),
p256dhPrivateKey: privateKey,
authenticationSecret: base64UrlDecode(authSecret),
});
}
add_task(function* test_notification_ack_data() {
let ackDone;
let server;
add_task(function* test_notification_ack_data_setup() {
db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
@ -46,7 +50,8 @@ add_task(function* test_notification_ack_data() {
kty: "EC",
x: '8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM',
y: '26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA'
}
},
'c_sGN6uCv9Hu7JOQT34jAQ'
);
yield putRecord(
'subscription2',
@ -60,7 +65,8 @@ add_task(function* test_notification_ack_data() {
kty: 'EC',
x: '-dbJSjvIye4yXIq0RG4t9YTxrT1212MdJbaWkL38GpE',
y: '5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E'
}
},
't3P246Gj9vjKDHHRYaY6hw'
);
yield putRecord(
'subscription3',
@ -74,22 +80,13 @@ add_task(function* test_notification_ack_data() {
kty: 'EC',
x: 'OFQchNJ5WtZjJsWdvvKVVMIMMs91BYyl_yBeFxbC9po',
y: 'Ja6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI'
}
},
'E0qiXGWvFSR0PS352ES1_Q'
);
let updates = [];
let notifyPromise = promiseObserverNotification('push-notification', function(subject, data) {
let notification = subject.QueryInterface(Ci.nsIPushObserverNotification);
updates.push({
scope: data,
data: notification.data,
});
return updates.length == 3;
});
let setupDone;
let setupDonePromise = new Promise(r => setupDone = r);
let acks = 0;
let ackDone;
let ackPromise = new Promise(resolve => ackDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@ -98,96 +95,158 @@ add_task(function* test_notification_ack_data() {
return new MockWebSocket(uri, {
onHello(request) {
equal(request.uaid, userAgentID,
'Should send matching device IDs in handshake');
'Should send matching device IDs in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
uaid: userAgentID,
status: 200,
use_webpush: true,
}));
// subscription1 will send a message with no rs and padding
// length 1.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription1',
headers: {
encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
},
data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo',
version: 'v1',
}));
server = this;
setupDone();
},
onACK(request) {
switch (++acks) {
case 1:
deepEqual([{
channelID: 'subscription1',
version: 'v1',
}], request.updates, 'Wrong updates for acknowledgement 1');
// subscription2 will send a message with no rs and padding
// length 16.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription2',
headers: {
encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
},
data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU',
version: 'v2',
}));
break;
case 2:
deepEqual([{
channelID: 'subscription2',
version: 'v2',
}], request.updates, 'Wrong updates for acknowledgement 2');
// subscription3 will send a message with rs equal 24 and
// padding length 16.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription3',
headers: {
encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
},
data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA',
version: 'v3',
}));
break;
case 3:
deepEqual([{
channelID: 'subscription3',
version: 'v3',
}], request.updates, 'Wrong updates for acknowledgement 3');
ackDone();
break;
default:
ok(false, 'Unexpected acknowledgement ' + acks);
if (ackDone) {
ackDone(request.updates);
}
}
});
}
});
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for multiple acknowledgements');
updates.sort((a, b) => a.scope < b.scope ? -1 : a.scope > b.scope ? 1 : 0);
deepEqual([{
scope: 'https://example.com/page/1',
data: 'Some message',
}, {
scope: 'https://example.com/page/2',
data: 'Some message',
}, {
scope: 'https://example.com/page/3',
data: 'Some message',
}], updates, 'Wrong data for notifications');
yield waitForPromise(setupDonePromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
});
add_task(function* test_notification_ack_data() {
let allTestData = [
{
channelID: 'subscription1',
version: 'v1',
send: {
headers: {
encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
},
data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo',
version: 'v1',
},
receive: {
scope: 'https://example.com/page/1',
data: 'Some message'
}
},
{
channelID: 'subscription2',
version: 'v2',
send: {
headers: {
encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
},
data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU',
},
receive: {
scope: 'https://example.com/page/2',
data: 'Some message'
}
},
{
channelID: 'subscription3',
version: 'v3',
send: {
headers: {
encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
},
data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA',
},
receive: {
scope: 'https://example.com/page/3',
data: 'Some message'
}
},
// This message uses the newer, authenticated form based on the crypto-key
// header field. No padding or record size changes.
{
channelID: 'subscription1',
version: 'v4',
send: {
headers: {
crypto_key: 'keyid=v4;dh="BHqG01j7rOfp12BEDzxWXxlCaU4cdOx2DZAwCt3QuzEsnXN9lCna9QmZCkVpXsx7sAlaEmtl_VfF1lHlFS7XWcA"',
encryption: 'keyid="v4";salt="X5-iy5rzhm4naNmMHdSYJw"',
},
data: '7YlxyNlZsNX4UNknHxzTqFrcrzz58W95uXBa0iY',
},
receive: {
scope: 'https://example.com/page/1',
data: 'Some message'
}
},
// A message with 17 bytes of padding and rs of 24
{
channelID: 'subscription2',
version: 'v5',
send: {
headers: {
crypto_key: 'keyid="v5"; dh="BJhyKIH5P30YUKn1bolj_LMnael1-KZT_aGXgD2CRspBfv9gcUhVAmpxToZrw7QQEKl9K83b3zcqNY6G_dFhEsI"',
encryption: 'keyid=v5;salt="bLmqCy550eK1Ao41tD7orA";rs=24',
},
data: 'SQDlDg1ftLkM_ruZlmyB2bk9L78HYtkcbA-y4-uAxwL-G4KtOA-J-A_rJ007Vi6NUkQe9K4kSZeIBrIUpmGv',
},
receive: {
scope: 'https://example.com/page/2',
data: 'Some message'
}
},
// A message without key identifiers.
{
channelID: 'subscription3',
version: 'v6',
send: {
headers: {
crypto_key: 'dh="BEgnDmVw9Gcn1fWA5t53Jtpsgfewk_pzsjSc_PBPpPmROWGQA2v8ESrSsQgosNXx0o-uMMhi9tDAUeks3380kd8"',
encryption: 'salt=T9DM8bNxuMHRVTn4LzkJDQ',
},
data: '7KUCi0dBBJbWmsYTqEqhFrgTv4ZOo_BmQRQ_2kY',
},
receive: {
scope: 'https://example.com/page/3',
data: 'Some message'
}
},
];
let sendAndReceive = testData => {
let messageReceived = promiseObserverNotification('push-notification', (subject, data) => {
let notification = subject.QueryInterface(Ci.nsIPushObserverNotification);
equal(notification.data, testData.receive.data,
'Check data for notification ' + testData.version);
equal(data, testData.receive.scope,
'Check scope for notification ' + testData.version);
return true;
});
let ackReceived = new Promise(resolve => ackDone = resolve)
.then(ackData => {
deepEqual([{
channelID: testData.channelID,
version: testData.version
}], ackData, 'Check updates for acknowledgment ' + testData.version);
});
let msg = JSON.parse(JSON.stringify(testData.send));
msg.messageType = 'notification';
msg.channelID = testData.channelID;
msg.version = testData.version;
server.serverSendMsg(JSON.stringify(msg));
return Promise.all([messageReceived, ackReceived]);
};
yield waitForPromise(
allTestData.reduce((p, testData) => {
return p.then(_ => sendAndReceive(testData));
}, Promise.resolve()),
DEFAULT_TIMEOUT,
'Timed out waiting for message exchange to complete');
});

View File

@ -11,11 +11,13 @@ interface Principal;
enum PushEncryptionKeyName
{
"p256dh"
"p256dh",
"auth"
};
[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
ChromeConstructor(DOMString pushEndpoint, DOMString scope, ArrayBuffer? key)]
ChromeConstructor(DOMString pushEndpoint, DOMString scope,
ArrayBuffer? key, ArrayBuffer? authSecret)]
interface PushSubscription
{
readonly attribute USVString endpoint;

View File

@ -132,7 +132,7 @@ ContentHostTexture::Composite(LayerComposite* aLayer,
bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1);
do {
if (iterOnWhite) {
if (iterOnWhite && bigImgIter) {
MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(),
"component alpha textures should be the same size.");
}

View File

@ -208,9 +208,10 @@ void
gfxASurface::Init(cairo_surface_t* surface, bool existingSurface)
{
SetSurfaceWrapper(surface, this);
MOZ_ASSERT(surface, "surface should be a valid pointer");
mSurface = surface;
mSurfaceValid = surface && !cairo_surface_status(surface);
mSurfaceValid = !cairo_surface_status(surface);
if (!mSurfaceValid) {
gfxWarning() << "ASurface Init failed with Cairo status " << cairo_surface_status(surface) << " on " << hexa(surface);
}

View File

@ -183,11 +183,11 @@ private:
DECL_GFX_PREF(Live, "apz.use_paint_duration", APZUsePaintDuration, bool, true);
DECL_GFX_PREF(Live, "apz.velocity_bias", APZVelocityBias, float, 1.0f);
DECL_GFX_PREF(Live, "apz.velocity_relevance_time_ms", APZVelocityRelevanceTime, uint32_t, 150);
DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier", APZXSkateSizeMultiplier, float, 1.5f);
DECL_GFX_PREF(Live, "apz.x_skate_highmem_adjust", APZXSkateHighMemAdjust, float, 0.0f);
DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier", APZXSkateSizeMultiplier, float, 1.5f);
DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier", APZXStationarySizeMultiplier, float, 3.0f);
DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier", APZYSkateSizeMultiplier, float, 2.5f);
DECL_GFX_PREF(Live, "apz.y_skate_highmem_adjust", APZYSkateHighMemAdjust, float, 0.0f);
DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier", APZYSkateSizeMultiplier, float, 2.5f);
DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier", APZYStationarySizeMultiplier, float, 3.5f);
DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms", APZZoomAnimationDuration, int32_t, 250);

View File

@ -411,7 +411,11 @@ class HeapAccess
void offsetInsnOffsetBy(uint32_t offset) { insnOffset_ += offset; }
};
#elif defined(JS_CODEGEN_NONE)
class HeapAccess { };
class HeapAccess {
public:
void offsetInsnOffsetBy(uint32_t) { MOZ_CRASH(); }
uint32_t insnOffset() const { MOZ_CRASH(); }
};
#endif
typedef Vector<HeapAccess, 0, SystemAllocPolicy> HeapAccessVector;

View File

@ -5375,9 +5375,9 @@ BytecodeEmitter::emitIterator()
return false;
if (!emit1(JSOP_SWAP)) // ITERFN OBJ
return false;
if (!emitCall(JSOP_CALL, 0)) // ITER
if (!emitCall(JSOP_CALLITER, 0)) // ITER
return false;
checkTypeSet(JSOP_CALL);
checkTypeSet(JSOP_CALLITER);
return true;
}

View File

@ -0,0 +1,39 @@
function assertThrowsMsg(f, msg) {
try {
f();
assertEq(0, 1);
} catch(e) {
assertEq(e instanceof TypeError, true);
assertEq(e.message, msg);
}
}
// For-of
function testForOf(val) {
for (var x of val) {}
}
for (v of [{}, Math, new Proxy({}, {})]) {
assertThrowsMsg(() => testForOf(v), "val is not iterable");
}
assertThrowsMsg(() => testForOf(null), "val is null");
assertThrowsMsg(() => { for (var x of () => 1) {}}, "() => 1 is not iterable");
// Destructuring
function testDestr(val) {
var [a, b] = val;
}
for (v of [{}, Math, new Proxy({}, {})]) {
assertThrowsMsg(() => testDestr(v), "val is not iterable");
}
assertThrowsMsg(() => testDestr(null), "val is null");
assertThrowsMsg(() => { [a, b] = () => 1; }, "() => 1 is not iterable");
// Spread
function testSpread(val) {
[...val];
}
for (v of [{}, Math, new Proxy({}, {})]) {
assertThrowsMsg(() => testSpread(v), "val is not iterable");
}
assertThrowsMsg(() => testSpread(null), "val is null");
assertThrowsMsg(() => { [...() => 1]; }, "() => 1 is not iterable");

View File

@ -3098,6 +3098,12 @@ BaselineCompiler::emit_JSOP_CALL()
return emitCall();
}
bool
BaselineCompiler::emit_JSOP_CALLITER()
{
return emitCall();
}
bool
BaselineCompiler::emit_JSOP_NEW()
{

View File

@ -156,6 +156,7 @@ namespace jit {
_(JSOP_INITALIASEDLEXICAL) \
_(JSOP_UNINITIALIZED) \
_(JSOP_CALL) \
_(JSOP_CALLITER) \
_(JSOP_FUNCALL) \
_(JSOP_FUNAPPLY) \
_(JSOP_NEW) \

View File

@ -6142,10 +6142,16 @@ DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint
res.set(vp[0]);
} else {
MOZ_ASSERT(op == JSOP_CALL ||
op == JSOP_CALLITER ||
op == JSOP_FUNCALL ||
op == JSOP_FUNAPPLY ||
op == JSOP_EVAL ||
op == JSOP_STRICTEVAL);
if (op == JSOP_CALLITER && callee.isPrimitive()) {
MOZ_ASSERT(argc == 0, "thisv must be on top of the stack");
ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, thisv, nullptr);
return false;
}
if (!Invoke(cx, thisv, callee, argc, args, res))
return false;
}

View File

@ -1880,6 +1880,7 @@ IonBuilder::inspectOpcode(JSOp op)
return jsop_funapply(GET_ARGC(pc));
case JSOP_CALL:
case JSOP_CALLITER:
case JSOP_NEW:
case JSOP_SUPERCALL:
return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL);

View File

@ -130,7 +130,7 @@ js::StackUses(JSScript* script, jsbytecode* pc)
return 2 + GET_ARGC(pc) + 1;
default:
/* stack: fun, this, [argc arguments] */
MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL ||
MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL || op == JSOP_CALLITER ||
op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
return 2 + GET_ARGC(pc);
}
@ -1167,6 +1167,7 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc)
case JSOP_NEWTARGET:
return write("new.target");
case JSOP_CALL:
case JSOP_CALLITER:
case JSOP_FUNCALL:
return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
write("(...)");

View File

@ -76,7 +76,7 @@ enum {
JOF_CHECKSLOPPY = 1 << 19, /* Op can only be generated in sloppy mode */
JOF_CHECKSTRICT = 1 << 20, /* Op can only be generated in strict mode */
JOF_INVOKE = 1 << 21, /* JSOP_CALL, JSOP_FUNCALL, JSOP_FUNAPPLY,
JSOP_NEW, JSOP_EVAL */
JSOP_NEW, JSOP_EVAL, JSOP_CALLITER */
/* 1 << 22 is unused */
/* 1 << 23 is unused */
/* 1 << 24 is unused */

View File

@ -1,7 +1,7 @@
// The decompiler can handle the implicit call to @@iterator in a for-of loop.
var x;
function check(code) {
function check(code, msg) {
var s = "no exception thrown";
try {
eval(code);
@ -9,19 +9,19 @@ function check(code) {
s = exc.message;
}
assertEq(s, `x[Symbol.iterator] is not a function`);
assertEq(s, msg);
}
x = {};
check("for (var v of x) throw fit;");
check("[...x]");
check("Math.hypot(...x)");
check("for (var v of x) throw fit;", "x is not iterable");
check("[...x]", "x is not iterable");
check("Math.hypot(...x)", "x is not iterable");
x[Symbol.iterator] = "potato";
check("for (var v of x) throw fit;");
check("for (var v of x) throw fit;", "x is not iterable");
x[Symbol.iterator] = {};
check("for (var v of x) throw fit;");
check("for (var v of x) throw fit;", "x[Symbol.iterator] is not a function");
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View File

@ -291,7 +291,7 @@ js::ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip, MaybeConstr
unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
ReportValueError3(cx, error, spIndex, v, nullptr, nullptr, nullptr);
ReportValueError(cx, error, spIndex, v, nullptr);
return false;
}
@ -1740,7 +1740,6 @@ CASE(JSOP_NOP)
CASE(JSOP_UNUSED14)
CASE(JSOP_UNUSED65)
CASE(JSOP_BACKPATCH)
CASE(JSOP_UNUSED145)
CASE(JSOP_UNUSED163)
CASE(JSOP_UNUSED177)
CASE(JSOP_UNUSED178)
@ -2737,6 +2736,7 @@ CASE(JSOP_FUNAPPLY)
CASE(JSOP_NEW)
CASE(JSOP_CALL)
CASE(JSOP_CALLITER)
CASE(JSOP_SUPERCALL)
CASE(JSOP_FUNCALL)
{
@ -2760,6 +2760,11 @@ CASE(JSOP_FUNCALL)
if (!ConstructFromStack(cx, args))
goto error;
} else {
if (*REGS.pc == JSOP_CALLITER && args.calleev().isPrimitive()) {
MOZ_ASSERT(args.length() == 0, "thisv must be on top of the stack");
ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, args.thisv(), nullptr);
goto error;
}
if (!Invoke(cx, args))
goto error;
}

View File

@ -1474,7 +1474,16 @@
* Stack: val => val
*/ \
macro(JSOP_SETINTRINSIC, 144, "setintrinsic", NULL, 5, 1, 1, JOF_ATOM|JOF_NAME|JOF_SET|JOF_DETECTING) \
macro(JSOP_UNUSED145, 145, "unused145", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Like JSOP_CALL, but used as part of for-of and destructuring bytecode
* to provide better error messages.
* Category: Statements
* Type: Function
* Operands: uint16_t argc (must be 0)
* Stack: callee, this => rval
* nuses: 2
*/ \
macro(JSOP_CALLITER, 145, "calliter", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \
/*
* Initialize a non-configurable, non-writable, non-enumerable data-property on an object.
*

View File

@ -29,7 +29,7 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 325;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 326;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);

View File

@ -7895,6 +7895,13 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer,
*aDestroyedFramesFor = aChild;
}
if (aChild->IsHTMLElement(nsGkAtoms::body) ||
(!aContainer && aChild->IsElement())) {
// This might be the element we propagated viewport scrollbar
// styles from. Recompute those.
mPresShell->GetPresContext()->UpdateViewportScrollbarStylesOverride();
}
// XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and
// the :empty pseudo-class?

View File

@ -4195,11 +4195,18 @@ static bool GetAbsoluteCoord(const nsStyleCoord& aStyle, nscoord& aResult)
return true;
}
static nscoord
GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing,
nsIFrame* aFrame,
bool aHorizontalAxis,
bool aIgnorePadding);
// Only call on style coords for which GetAbsoluteCoord returned false.
static bool
GetPercentBSize(const nsStyleCoord& aStyle,
nsIFrame* aFrame,
nscoord& aResult)
nsIFrame* aFrame,
bool aHorizontalAxis,
nscoord& aResult)
{
if (eStyleUnit_Percent != aStyle.GetUnit() &&
!aStyle.IsCalcUnit())
@ -4225,7 +4232,7 @@ GetPercentBSize(const nsStyleCoord& aStyle,
const nsStyleCoord& bSizeCoord = pos->BSize(wm);
nscoord h;
if (!GetAbsoluteCoord(bSizeCoord, h) &&
!GetPercentBSize(bSizeCoord, f, h)) {
!GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto ||
bSizeCoord.HasPercent(),
"unknown block-size unit");
@ -4255,7 +4262,7 @@ GetPercentBSize(const nsStyleCoord& aStyle,
nscoord maxh;
if (GetAbsoluteCoord(maxBSizeCoord, maxh) ||
GetPercentBSize(maxBSizeCoord, f, maxh)) {
GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) {
if (maxh < h)
h = maxh;
} else {
@ -4268,7 +4275,7 @@ GetPercentBSize(const nsStyleCoord& aStyle,
nscoord minh;
if (GetAbsoluteCoord(minBSizeCoord, minh) ||
GetPercentBSize(minBSizeCoord, f, minh)) {
GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) {
if (minh > h)
h = minh;
} else {
@ -4277,6 +4284,14 @@ GetPercentBSize(const nsStyleCoord& aStyle,
"unknown min block-size unit");
}
// Now adjust h for box-sizing styles on the parent. We never ignore padding
// here. That could conceivably cause some problems with fieldsets (which are
// the one place that wants to ignore padding), but solving that here without
// hardcoding a check for f being a fieldset-content frame is a bit of a pain.
nscoord bSizeTakenByBoxSizing =
GetBSizeTakenByBoxSizing(pos->mBoxSizing, f, aHorizontalAxis, false);
h = std::max(0, h - bSizeTakenByBoxSizing);
if (aStyle.IsCalcUnit()) {
aResult = std::max(nsRuleNode::ComputeComputedCalc(aStyle, h), 0);
return true;
@ -4286,6 +4301,59 @@ GetPercentBSize(const nsStyleCoord& aStyle,
return true;
}
// Get the amount of vertical space taken out of aFrame's content area due to
// its borders and paddings given the box-sizing value in aBoxSizing. We don't
// get aBoxSizing from the frame because some callers want to compute this for
// specific box-sizing values. aHorizontalAxis is true if our inline direction
// is horisontal and our block direction is vertical. aIgnorePadding is true if
// padding should be ignored.
static nscoord
GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing,
nsIFrame* aFrame,
bool aHorizontalAxis,
bool aIgnorePadding)
{
nscoord bSizeTakenByBoxSizing = 0;
switch (aBoxSizing) {
case StyleBoxSizing::Border: {
const nsStyleBorder* styleBorder = aFrame->StyleBorder();
bSizeTakenByBoxSizing +=
aHorizontalAxis ? styleBorder->GetComputedBorder().TopBottom()
: styleBorder->GetComputedBorder().LeftRight();
// fall through
}
case StyleBoxSizing::Padding: {
if (!aIgnorePadding) {
const nsStyleSides& stylePadding =
aFrame->StylePadding()->mPadding;
const nsStyleCoord& paddingStart =
stylePadding.Get(aHorizontalAxis ? NS_SIDE_TOP : NS_SIDE_LEFT);
const nsStyleCoord& paddingEnd =
stylePadding.Get(aHorizontalAxis ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT);
nscoord pad;
// XXXbz Calling GetPercentBSize on padding values looks bogus, since
// percent padding is always a percentage of the inline-size of the
// containing block. We should perhaps just treat non-absolute paddings
// here as 0 instead, except that in some cases the width may in fact be
// known. See bug 1231059.
if (GetAbsoluteCoord(paddingStart, pad) ||
GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) {
bSizeTakenByBoxSizing += pad;
}
if (GetAbsoluteCoord(paddingEnd, pad) ||
GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) {
bSizeTakenByBoxSizing += pad;
}
}
// fall through
}
case StyleBoxSizing::Content:
default:
break;
}
return bSizeTakenByBoxSizing;
}
// Handles only -moz-max-content and -moz-min-content, and
// -moz-fit-content for min-width and max-width, since the others
// (-moz-fit-content for width, and -moz-available) have no effect on
@ -4646,49 +4714,19 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis,
AddStateBitToAncestors(aFrame,
NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
nscoord bSizeTakenByBoxSizing = 0;
switch (boxSizing) {
case StyleBoxSizing::Border: {
const nsStyleBorder* styleBorder = aFrame->StyleBorder();
bSizeTakenByBoxSizing +=
horizontalAxis ? styleBorder->GetComputedBorder().TopBottom()
: styleBorder->GetComputedBorder().LeftRight();
// fall through
}
case StyleBoxSizing::Padding: {
if (!(aFlags & IGNORE_PADDING)) {
const nsStyleSides& stylePadding =
aFrame->StylePadding()->mPadding;
const nsStyleCoord& paddingStart =
stylePadding.Get(horizontalAxis ? NS_SIDE_TOP : NS_SIDE_LEFT);
const nsStyleCoord& paddingEnd =
stylePadding.Get(horizontalAxis ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT);
nscoord pad;
if (GetAbsoluteCoord(paddingStart, pad) ||
GetPercentBSize(paddingStart, aFrame, pad)) {
bSizeTakenByBoxSizing += pad;
}
if (GetAbsoluteCoord(paddingEnd, pad) ||
GetPercentBSize(paddingEnd, aFrame, pad)) {
bSizeTakenByBoxSizing += pad;
}
}
// fall through
}
case StyleBoxSizing::Content:
default:
break;
}
nscoord bSizeTakenByBoxSizing =
GetBSizeTakenByBoxSizing(boxSizing, aFrame, horizontalAxis,
aFlags & IGNORE_PADDING);
nscoord h;
if (GetAbsoluteCoord(styleBSize, h) ||
GetPercentBSize(styleBSize, aFrame, h)) {
GetPercentBSize(styleBSize, aFrame, horizontalAxis, h)) {
h = std::max(0, h - bSizeTakenByBoxSizing);
result = NSCoordMulDiv(h, ratioISize, ratioBSize);
}
if (GetAbsoluteCoord(styleMaxBSize, h) ||
GetPercentBSize(styleMaxBSize, aFrame, h)) {
GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h)) {
h = std::max(0, h - bSizeTakenByBoxSizing);
nscoord maxISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
if (maxISize < result)
@ -4696,7 +4734,7 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis,
}
if (GetAbsoluteCoord(styleMinBSize, h) ||
GetPercentBSize(styleMinBSize, aFrame, h)) {
GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h)) {
h = std::max(0, h - bSizeTakenByBoxSizing);
nscoord minISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
if (minISize > result)

View File

@ -1207,6 +1207,23 @@ public:
nsFontMetrics** aFontMetrics,
float aSizeInflation = 1.0f);
/**
* Get the font metrics of emphasis marks corresponding to the given
* style data. The result is same as GetFontMetricsForStyleContext
* except that the font size is scaled down to 50%.
* @param aStyleContext the style data
* @param aFontMetrics the font metrics result
* @param aInflation number to multiple font size by
* @return success or failure code
*/
static nsresult GetFontMetricsOfEmphasisMarks(nsStyleContext* aStyleContext,
nsFontMetrics** aFontMetrics,
float aInflation)
{
return GetFontMetricsForStyleContext(aStyleContext, aFontMetrics,
aInflation * 0.5f);
}
/**
* Find the immediate child of aParent whose frame subtree contains
* aDescendantFrame. Returns null if aDescendantFrame is not a descendant

View File

@ -1634,6 +1634,11 @@ GetPropagatedScrollbarStylesForViewport(nsPresContext* aPresContext,
nsIDocument* document = aPresContext->Document();
Element* docElement = document->GetRootElement();
// docElement might be null if we're doing this after removing it.
if (!docElement) {
return nullptr;
}
// Check the style on the document root element
nsStyleSet *styleSet = aPresContext->StyleSet();
RefPtr<nsStyleContext> rootStyle;

View File

@ -1676,10 +1676,18 @@ nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
}
}
static nscoord
GetBSizeOfEmphasisMarks(nsIFrame* aSpanFrame, float aInflation)
{
RefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
aSpanFrame->StyleContext(), getter_AddRefs(fm), aInflation);
return fm->MaxHeight();
}
void
nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
const nsStyleText* aStyleText,
nsFontMetrics* aFontMetrics,
const nsStyleText* aStyleText, float aInflation,
bool* aZeroEffectiveSpanBox)
{
MOZ_ASSERT(spanFrame == psd->mFrame->mFrame);
@ -1696,17 +1704,14 @@ nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
requiredEndLeading += endLeading;
}
if (aStyleText->HasTextEmphasis()) {
// Emphasis marks are symbols rendered using the same font settings
// as the element with its size scaled down to 50%, so we add half
// height of the font metrics to the specified side as leading.
nscoord halfHeight = aFontMetrics->MaxHeight() / 2;
nscoord bsize = GetBSizeOfEmphasisMarks(spanFrame, aInflation);
LogicalSide side = aStyleText->TextEmphasisSide(mRootSpan->mWritingMode);
if (side == eLogicalSideBStart) {
requiredStartLeading += halfHeight;
requiredStartLeading += bsize;
} else {
MOZ_ASSERT(side == eLogicalSideBEnd,
"emphasis marks must be in block axis");
requiredEndLeading += halfHeight;
requiredEndLeading += bsize;
}
}
@ -1920,7 +1925,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
psd->mBStartLeading = leading / 2;
psd->mBEndLeading = leading - psd->mBStartLeading;
psd->mLogicalBSize = logicalBSize;
AdjustLeadings(spanFrame, psd, styleText, fm, &zeroEffectiveSpanBox);
AdjustLeadings(spanFrame, psd, styleText, inflation,
&zeroEffectiveSpanBox);
if (zeroEffectiveSpanBox) {
// When the span-box is to be ignored, zero out the initial
@ -2323,6 +2329,32 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
lineWM.IsLineInverted());
nscoord blockEnd = blockStart + minimumLineBSize;
if (mStyleText->HasTextEmphasis()) {
nscoord fontMaxHeight = fm->MaxHeight();
nscoord emphasisHeight =
GetBSizeOfEmphasisMarks(spanFrame, inflation);
nscoord delta = fontMaxHeight + emphasisHeight - minimumLineBSize;
if (delta > 0) {
if (minimumLineBSize < fontMaxHeight) {
// If the leadings are negative, fill them first.
nscoord ascent = fm->MaxAscent();
nscoord descent = fm->MaxDescent();
if (lineWM.IsLineInverted()) {
Swap(ascent, descent);
}
blockStart = -ascent;
blockEnd = descent;
delta = emphasisHeight;
}
LogicalSide side = mStyleText->TextEmphasisSide(lineWM);
if (side == eLogicalSideBStart) {
blockStart -= delta;
} else {
blockEnd += delta;
}
}
}
if (blockStart < minBCoord) minBCoord = blockStart;
if (blockEnd > maxBCoord) maxBCoord = blockEnd;

View File

@ -663,8 +663,7 @@ protected:
nsHTMLReflowMetrics& aMetrics);
void AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
const nsStyleText* aStyleText,
nsFontMetrics* aFontMetrics,
const nsStyleText* aStyleText, float aInflation,
bool* aZeroEffectiveSpanBox);
void VerticalAlignFrames(PerSpanData* psd);

View File

@ -5117,27 +5117,6 @@ GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
}
static already_AddRefed<nsFontMetrics>
GetFontMetricsOfEmphasisMarks(nsStyleContext* aStyleContext, float aInflation)
{
nsPresContext* pc = aStyleContext->PresContext();
WritingMode wm(aStyleContext);
gfxFont::Orientation orientation = wm.IsVertical() && !wm.IsSideways() ?
gfxFont::eVertical : gfxFont::eHorizontal;
const nsStyleFont* styleFont = aStyleContext->StyleFont();
nsFont font = styleFont->mFont;
font.size = NSToCoordRound(font.size * aInflation * 0.5f);
RefPtr<nsFontMetrics> fm;
pc->DeviceContext()->GetMetricsFor(font, styleFont->mLanguage,
styleFont->mExplicitLanguage,
orientation, pc->GetUserFontSet(),
pc->GetTextPerfMetrics(),
*getter_AddRefs(fm));
return fm.forget();
}
static gfxTextRun*
GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame, nsFontMetrics* aFontMetrics,
WritingMode aWM, const nsStyleText* aStyleText)
@ -5178,8 +5157,9 @@ nsTextFrame::UpdateTextEmphasis(WritingMode aWM, PropertyProvider& aProvider)
return nsRect();
}
RefPtr<nsFontMetrics> fm =
GetFontMetricsOfEmphasisMarks(StyleContext(), GetFontSizeInflation());
RefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
StyleContext(), getter_AddRefs(fm), GetFontSizeInflation());
EmphasisMarkInfo* info = new EmphasisMarkInfo;
info->textRun =
GenerateTextRunForEmphasisMarks(this, fm, aWM, styleText);

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 40px transparent solid">
<img src="lime100x100.png"
style="height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: padding-box; height: 120px;
border: 40px transparent solid">
<img src="lime100x100.png"
style="height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 25px transparent solid">
<div style="height: 100%; box-sizing: border-box;
border: 15px transparent solid;">
<img src="lime100x100.png"
style="height: 100%; display: block; visibility: hidden;">
</div>
</div>
</body>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 40px transparent solid">
<!-- We need to be taller than intrinsic height, so use a min-height -->
<img src="lime100x100.png"
style="min-height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: padding-box; height: 120px;
border: 40px transparent solid">
<!-- We need to be taller than intrinsic height, so use a min-height -->
<img src="lime100x100.png"
style="min-height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 25px transparent solid">
<div style="height: 100%; box-sizing: border-box;
border: 15px transparent solid;">
<!-- We need to be taller than intrinsic height, so use a min-height -->
<img src="lime100x100.png"
style="min-height: 100%; display: block; visibility: hidden;">
</div>
</div>
</body>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 80px transparent solid">
<!-- We need to be shorter than intrinsic height, so use a max-height -->
<img src="lime100x100.png"
style="max-height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: padding-box; height: 40px;
border: 80px transparent solid">
<!-- We need to be shorter than intrinsic height, so use a max-height -->
<img src="lime100x100.png"
style="max-height: 100%; display: block; visibility: hidden;">
</div>
</body>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<body>
<div style="display: inline-block; background: blue;
box-sizing: border-box; height: 200px;
border: 50px transparent solid">
<div style="height: 100%; box-sizing: border-box;
border: 30px transparent solid;">
<!-- We need to be shorter than intrinsic height, so use a max-height -->
<img src="lime100x100.png"
style="max-height: 100%; display: block; visibility: hidden;">
</div>
</div>
</body>

View File

@ -4,4 +4,13 @@
== intrinsic-1d.html intrinsic-1-ref.html
== intrinsic-1e.html intrinsic-1-ref.html
== intrinsic-1f.html intrinsic-1-ref.html
== intrinsic-1g.html intrinsic-1-ref.html
== intrinsic-1h.html intrinsic-1-ref.html
== intrinsic-1i.html intrinsic-1-ref.html
== intrinsic-1j.html intrinsic-1-ref.html
== intrinsic-1k.html intrinsic-1-ref.html
== intrinsic-1l.html intrinsic-1-ref.html
== intrinsic-1m.html intrinsic-1-ref.html
== intrinsic-1n.html intrinsic-1-ref.html
== intrinsic-1o.html intrinsic-1-ref.html
== computed-size-reporting.html computed-size-reporting-ref.html

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<script>
var b = document.createElement("body");
b.style.overflow = "scroll";
var h = document.documentElement;
h.insertBefore(b, h.firstChild);
b.offsetWidth;
b.remove();
</script>
</html>

View File

@ -1943,3 +1943,4 @@ fuzzy(1,74) fuzzy-if(gtkWidget,6,79) == 1174332-1.html 1174332-1-ref.html
== 1209994-4.html 1209994-4-ref.html
== 1222226-1.html 1222226-1-ref.html
pref(layout.css.overflow-clip-box.enabled,true) == 1226278.html 1226278-ref.html
== 1230466.html about:blank

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="ja">
<meta charset="UTF-8">
<title>Bug 1229278 - Dynamic change to text-emphasis-style</title>
<style>
div {
line-height: 5;
text-emphasis-style: filled triangle;
}
</style>
<div>テスト</div>
</html>

View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="ja" class="reftest-wait">
<meta charset="UTF-8">
<title>Bug 1229278 - Dynamic change to text-emphasis-style</title>
<style>
div {
line-height: 5;
text-emphasis-style: filled triangle;
}
.reftest-wait div {
text-emphasis-style: filled circle;
}
</style>
<div>テスト</div>
<script>
document.addEventListener("MozReftestInvalidate", function() {
document.documentElement.className = "";
});
</script>
</html>

View File

@ -109,3 +109,4 @@ fuzzy-if(OSX==1010,1,2) == underline-button-2.html underline-button-2-ref.html
== underline-select-2.html underline-select-2-ref.html
== 1133392.html 1133392-ref.html
!= 1159729-offset-adjustment.html 1159729-offset-adjustment-notref.html
pref(layout.css.text-emphasis.enabled,true) == emphasis-style-dynamic.html emphasis-style-dynamic-ref.html

View File

@ -115,3 +115,20 @@ skip-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) == text-emphasis-style-proper
== text-emphasis-ruby-004.html text-emphasis-ruby-004-ref.html
== text-emphasis-ruby-004a.html text-emphasis-ruby-004-ref.html
# END tests from support/generate-text-emphasis-ruby-tests.py
# text-emphasis line height
# START tests from support/generate-text-emphasis-line-height-tests.py
== text-emphasis-line-height-001a.html text-emphasis-line-height-001-ref.html
== text-emphasis-line-height-001b.html text-emphasis-line-height-001-ref.html
== text-emphasis-line-height-002a.html text-emphasis-line-height-002-ref.html
== text-emphasis-line-height-002b.html text-emphasis-line-height-002-ref.html
== text-emphasis-line-height-003a.html text-emphasis-line-height-003-ref.html
== text-emphasis-line-height-003b.html text-emphasis-line-height-003-ref.html
== text-emphasis-line-height-003c.html text-emphasis-line-height-003-ref.html
== text-emphasis-line-height-003d.html text-emphasis-line-height-003-ref.html
== text-emphasis-line-height-004a.html text-emphasis-line-height-004-ref.html
== text-emphasis-line-height-004b.html text-emphasis-line-height-004-ref.html
== text-emphasis-line-height-004c.html text-emphasis-line-height-004-ref.html
== text-emphasis-line-height-004d.html text-emphasis-line-height-004-ref.html
# END tests from support/generate-text-emphasis-line-height-tests.py
== text-emphasis-line-height-001z.html text-emphasis-line-height-001-ref.html

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
# - * - coding: UTF-8 - * -
"""
This script generates tests text-emphasis-line-height-001 ~ 004 except
001z. They test the line height expansion in different directions. This
script outputs a list of all tests it generated in the format of Mozilla
reftest.list to the stdout.
"""
from __future__ import unicode_literals
TEST_FILE = 'text-emphasis-line-height-{:03}{}.html'
TEST_TEMPLATE = '''<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, {pos}, {wm}, {tag}</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-{index:03}-ref.html">
<p>Pass if the emphasis marks are {dir} the black line:</p>
{start}試験テスト{end}
'''
REF_FILE = 'text-emphasis-line-height-{:03}-ref.html'
REF_TEMPLATE='''<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Reference: text-emphasis line height, {pos}</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style> rt {{ font-variant-east-asian: inherit; }} </style>
<p>Pass if the emphasis marks are {dir} the black line:</p>
<div style="line-height: 1; border-{pos}: 1px solid black; writing-mode: {wm}; ruby-position: {posval}"><ruby><rt>&#x25CF;</rt>験<rt>&#x25CF;</rt>テ<rt>&#x25CF;</rt>ス<rt>&#x25CF;</rt>ト<rt>&#x25CF;</rt></ruby></div>
'''
STYLE1 = 'line-height: 1; border-{pos}: 1px solid black; ' + \
'writing-mode: {wm}; text-emphasis-position: {posval};'
STYLE2 = 'text-emphasis: circle;'
TAGS = [
# (tag, start, end)
('div', '<div style="{style1}{style2}">', '</div>'),
('span', '<div style="{style1}"><span style="{style2}">', '</span></div>'),
]
POSITIONS = [
# pos, text-emphasis-position, ruby-position,
# writing-modes, dir text
('top', 'over right', 'over',
['horizontal-tb'], 'below'),
('bottom', 'under right', 'under',
['horizontal-tb'], 'over'),
('right', 'over right', 'over',
['vertical-rl', 'vertical-lr'], 'to the left of'),
('left', 'over left', 'under',
['vertical-rl', 'vertical-lr'], 'to the right of'),
]
import string
def write_file(filename, content):
with open(filename, 'wb') as f:
f.write(content.encode('UTF-8'))
print("# START tests from {}".format(__file__))
idx = 0
for (pos, emphasis_pos, ruby_pos, wms, dir) in POSITIONS:
idx += 1
ref_file = REF_FILE.format(idx)
content = REF_TEMPLATE.format(pos=pos, dir=dir, wm=wms[0], posval=ruby_pos)
write_file(ref_file, content)
suffix = iter(string.ascii_lowercase)
for wm in wms:
style1 = STYLE1.format(pos=pos, wm=wm, posval=emphasis_pos)
for (tag, start, end) in TAGS:
test_file = TEST_FILE.format(idx, next(suffix))
content = TEST_TEMPLATE.format(
pos=pos, wm=wm, tag=tag, index=idx, dir=dir,
start=start.format(style1=style1, style2=STYLE2), end=end)
write_file(test_file, content)
print("== {} {}".format(test_file, ref_file))
print("# END tests from {}".format(__file__))

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Reference: text-emphasis line height, top</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style> rt { font-variant-east-asian: inherit; } </style>
<p>Pass if the emphasis marks are below the black line:</p>
<div style="line-height: 1; border-top: 1px solid black; writing-mode: horizontal-tb; ruby-position: over"><ruby><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt></ruby></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, top, horizontal-tb, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-001-ref.html">
<p>Pass if the emphasis marks are below the black line:</p>
<div style="line-height: 1; border-top: 1px solid black; writing-mode: horizontal-tb; text-emphasis-position: over right;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, top, horizontal-tb, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-001-ref.html">
<p>Pass if the emphasis marks are below the black line:</p>
<div style="line-height: 1; border-top: 1px solid black; writing-mode: horizontal-tb; text-emphasis-position: over right;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, top, textarea</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-001-ref.html">
<style>
textarea {
all: inherit;
width: 100%;
box-sizing: border-box;
border: 0 none; margin: 0; padding: 0;
}
</style>
<p>Pass if the emphasis marks are below the black line:</p>
<textarea style="line-height: 1; border-top: 1px solid black; text-emphasis: circle;">試験テスト</textarea>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Reference: text-emphasis line height, bottom</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style> rt { font-variant-east-asian: inherit; } </style>
<p>Pass if the emphasis marks are over the black line:</p>
<div style="line-height: 1; border-bottom: 1px solid black; writing-mode: horizontal-tb; ruby-position: under"><ruby><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt></ruby></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, bottom, horizontal-tb, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-002-ref.html">
<p>Pass if the emphasis marks are over the black line:</p>
<div style="line-height: 1; border-bottom: 1px solid black; writing-mode: horizontal-tb; text-emphasis-position: under right;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, bottom, horizontal-tb, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-002-ref.html">
<p>Pass if the emphasis marks are over the black line:</p>
<div style="line-height: 1; border-bottom: 1px solid black; writing-mode: horizontal-tb; text-emphasis-position: under right;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Reference: text-emphasis line height, right</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style> rt { font-variant-east-asian: inherit; } </style>
<p>Pass if the emphasis marks are to the left of the black line:</p>
<div style="line-height: 1; border-right: 1px solid black; writing-mode: vertical-rl; ruby-position: over"><ruby><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt></ruby></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, right, vertical-rl, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-003-ref.html">
<p>Pass if the emphasis marks are to the left of the black line:</p>
<div style="line-height: 1; border-right: 1px solid black; writing-mode: vertical-rl; text-emphasis-position: over right;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, right, vertical-rl, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-003-ref.html">
<p>Pass if the emphasis marks are to the left of the black line:</p>
<div style="line-height: 1; border-right: 1px solid black; writing-mode: vertical-rl; text-emphasis-position: over right;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, right, vertical-lr, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-003-ref.html">
<p>Pass if the emphasis marks are to the left of the black line:</p>
<div style="line-height: 1; border-right: 1px solid black; writing-mode: vertical-lr; text-emphasis-position: over right;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, right, vertical-lr, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-003-ref.html">
<p>Pass if the emphasis marks are to the left of the black line:</p>
<div style="line-height: 1; border-right: 1px solid black; writing-mode: vertical-lr; text-emphasis-position: over right;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Reference: text-emphasis line height, left</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style> rt { font-variant-east-asian: inherit; } </style>
<p>Pass if the emphasis marks are to the right of the black line:</p>
<div style="line-height: 1; border-left: 1px solid black; writing-mode: vertical-rl; ruby-position: under"><ruby><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt><rt>&#x25CF;</rt></ruby></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, left, vertical-rl, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-004-ref.html">
<p>Pass if the emphasis marks are to the right of the black line:</p>
<div style="line-height: 1; border-left: 1px solid black; writing-mode: vertical-rl; text-emphasis-position: over left;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, left, vertical-rl, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-004-ref.html">
<p>Pass if the emphasis marks are to the right of the black line:</p>
<div style="line-height: 1; border-left: 1px solid black; writing-mode: vertical-rl; text-emphasis-position: over left;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, left, vertical-lr, div</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-004-ref.html">
<p>Pass if the emphasis marks are to the right of the black line:</p>
<div style="line-height: 1; border-left: 1px solid black; writing-mode: vertical-lr; text-emphasis-position: over left;text-emphasis: circle;">試験テスト</div>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: text-emphasis line height, left, vertical-lr, span</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-position-property">
<meta name="assert" content="text emphasis marks should expand the line height like ruby if necessary">
<link rel="match" href="text-emphasis-position-property-004-ref.html">
<p>Pass if the emphasis marks are to the right of the black line:</p>
<div style="line-height: 1; border-left: 1px solid black; writing-mode: vertical-lr; text-emphasis-position: over left;"><span style="text-emphasis: circle;">試験テスト</span></div>

View File

@ -313,26 +313,6 @@ CSS_PROP_ALIAS(-webkit-border-image,
border_image,
WebkitBorderImage,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-border-image-outset,
border_image_outset,
WebkitBorderImageOutset,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-border-image-repeat,
border_image_repeat,
WebkitBorderImageRepeat,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-border-image-slice,
border_image_slice,
WebkitBorderImageSlice,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-border-image-source,
border_image_source,
WebkitBorderImageSource,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-border-image-width,
border_image_width,
WebkitBorderImageWidth,
WEBKIT_PREFIX_PREF)
CSS_PROP_ALIAS(-webkit-box-shadow,
box_shadow,

View File

@ -3632,16 +3632,18 @@ nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aOther) const
(mTabSize != aOther.mTabSize))
return NS_STYLE_HINT_REFLOW;
if (!AreShadowArraysEqual(mTextShadow, aOther.mTextShadow)) {
return nsChangeHint_UpdateSubtreeOverflow |
nsChangeHint_SchedulePaint |
if (HasTextEmphasis() != aOther.HasTextEmphasis() ||
(HasTextEmphasis() &&
mTextEmphasisPosition != aOther.mTextEmphasisPosition)) {
// Text emphasis position change could affect line height calculation.
return nsChangeHint_AllReflowHints |
nsChangeHint_RepaintFrame;
}
if (mTextEmphasisPosition != aOther.mTextEmphasisPosition ||
if (!AreShadowArraysEqual(mTextShadow, aOther.mTextShadow) ||
mTextEmphasisStyle != aOther.mTextEmphasisStyle ||
mTextEmphasisStyleString != aOther.mTextEmphasisStyleString) {
return nsChangeHint_UpdateOverflow |
return nsChangeHint_UpdateSubtreeOverflow |
nsChangeHint_SchedulePaint |
nsChangeHint_RepaintFrame;
}
@ -3657,6 +3659,10 @@ nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aOther) const
nsChangeHint_RepaintFrame;
}
if (mTextEmphasisPosition != aOther.mTextEmphasisPosition) {
return nsChangeHint_NeutralChange;
}
return NS_STYLE_HINT_NONE;
}

View File

@ -6884,41 +6884,6 @@ if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) {
alias_for: "border-image",
subproperties: [ "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat" ],
};
gCSSProperties["-webkit-border-image-outset"] = {
domProp: "webkitBorderImageOutset",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
alias_for: "border-image-outset",
subproperties: [ "border-image-outset" ],
};
gCSSProperties["-webkit-border-image-repeat"] = {
domProp: "webkitBorderImageRepeat",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
alias_for: "border-image-repeat",
subproperties: [ "border-image-repeat" ],
};
gCSSProperties["-webkit-border-image-slice"] = {
domProp: "webkitBorderImageSlice",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
alias_for: "border-image-slice",
subproperties: [ "border-image-slice" ],
};
gCSSProperties["-webkit-border-image-source"] = {
domProp: "webkitBorderImageSource",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
alias_for: "border-image-source",
subproperties: [ "border-image-source" ],
};
gCSSProperties["-webkit-border-image-width"] = {
domProp: "webkitBorderImageWidth",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
alias_for: "border-image-width",
subproperties: [ "border-image-width" ],
};
gCSSProperties["-webkit-box-shadow"] = {
domProp: "webkitBoxShadow",
inherited: false,

View File

@ -314,15 +314,44 @@ nsresult NrIceMediaStream::GetCandidatePairs(std::vector<NrIceCandidatePair>*
return NS_ERROR_FAILURE;
}
nr_ice_cand_pair *p1;
nr_ice_cand_pair *p1, *p2;
out_pairs->clear();
TAILQ_FOREACH(p1, &peer_stream->check_list, entry) {
TAILQ_FOREACH(p1, &peer_stream->check_list, check_queue_entry) {
MOZ_ASSERT(p1);
MOZ_ASSERT(p1->local);
MOZ_ASSERT(p1->remote);
NrIceCandidatePair pair;
p2 = TAILQ_FIRST(&peer_stream->check_list);
while (p2) {
if (p1 == p2) {
/* Don't compare with our self. */
p2=TAILQ_NEXT(p2, check_queue_entry);
continue;
}
if (strncmp(p1->codeword,p2->codeword,sizeof(p1->codeword))==0) {
/* In case of duplicate pairs we only report the one winning pair */
if (
((p2->remote->component && (p2->remote->component->active == p2)) &&
!(p1->remote->component && (p1->remote->component->active == p1))) ||
((p2->peer_nominated || p2->nominated) &&
!(p1->peer_nominated || p1->nominated)) ||
(p2->priority > p1->priority) ||
((p2->state == NR_ICE_PAIR_STATE_SUCCEEDED) &&
(p1->state != NR_ICE_PAIR_STATE_SUCCEEDED))
) {
/* p2 is a better pair. */
break;
}
}
p2=TAILQ_NEXT(p2, check_queue_entry);
}
if (p2) {
/* p2 points to a duplicate but better pair so skip this one */
continue;
}
switch (p1->state) {
case NR_ICE_PAIR_STATE_FROZEN:
pair.state = NrIceCandidatePair::State::STATE_FROZEN;

View File

@ -909,10 +909,15 @@ class IceTestPeer : public sigslot::has_slots<> {
DumpCandidatePair(pairs[p]);
return false;
} else if (priority == pairs[p].priority) {
std::cerr << "Duplicate priority in subseqent pairs:" << std::endl;
DumpCandidatePair(pairs[p-1]);
DumpCandidatePair(pairs[p]);
return false;
if (!IceCandidatePairCompare()(pairs[p], pairs[p-1]) &&
!IceCandidatePairCompare()(pairs[p-1], pairs[p])) {
std::cerr << "Ignoring identical pair from trigger check" << std::endl;
} else {
std::cerr << "Duplicate priority in subseqent pairs:" << std::endl;
DumpCandidatePair(pairs[p-1]);
DumpCandidatePair(pairs[p]);
return false;
}
}
priority = pairs[p].priority;
}
@ -2120,7 +2125,7 @@ TEST_F(IceConnectTest, TestLoopbackOnlySortOf) {
Init(true, false);
AddStream("first", 1);
SetCandidateFilter(IsLoopbackCandidate);
ASSERT_TRUE(Gather());
ASSERT_TRUE(Gather(kDefaultTimeout, false));
SetExpectedRemoteCandidateAddr("127.0.0.1");
Connect();
}

View File

@ -247,7 +247,7 @@ static void nr_ice_candidate_pair_stun_cb(NR_SOCKET s, int how, void *cb_arg)
else if(!nr_transport_addr_cmp(&pair->local->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_SUCCEEDED);
}
else{
else if(pair->stun_client->state == NR_STUN_CLIENT_STATE_DONE) {
/* OK, this didn't correspond to a pair on the check list, but
it probably matches one of our candidates */
@ -394,32 +394,82 @@ int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
return(_status);
}
static int nr_ice_candidate_copy_for_triggered_check(nr_ice_cand_pair *pair)
{
int r,_status;
nr_ice_cand_pair *copy;
if(r=nr_ice_candidate_pair_create(pair->pctx, pair->local, pair->remote, &copy))
ABORT(r);
/* Preserve nomination status */
copy->peer_nominated= pair->peer_nominated;
copy->nominated = pair->nominated;
r_log(LOG_ICE,LOG_INFO,"CAND-PAIR(%s): Adding pair to check list and trigger check queue: %s",pair->codeword,pair->as_string);
if(r=nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,copy))
ABORT(r);
nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,copy);
copy->triggered = 1;
nr_ice_candidate_pair_set_state(copy->pctx,copy,NR_ICE_PAIR_STATE_WAITING);
_status=0;
abort:
return(_status);
}
int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
{
int r,_status;
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): triggered check on %s",pctx->label,pair->codeword,pair->as_string);
switch(pair->state){
case NR_ICE_PAIR_STATE_FROZEN:
nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING);
/* Fall through */
case NR_ICE_PAIR_STATE_WAITING:
/* Start the checks */
if(r=nr_ice_candidate_pair_start(pctx,pair))
ABORT(r);
break;
case NR_ICE_PAIR_STATE_IN_PROGRESS:
if(r=nr_stun_client_force_retransmit(pair->stun_client))
ABORT(r);
break;
default:
break;
if(pair->state==NR_ICE_PAIR_STATE_CANCELLED) {
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Ignoring matching but canceled pair",pctx->label,pair->codeword);
return(0);
} else if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED) {
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): No new trigger check for succeeded pair",pctx->label,pair->codeword);
return(0);
}
/* Activate the media stream if required */
if(pair->remote->stream->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_FROZEN){
/* Do not run this logic more than once on a given pair */
if(!pair->triggered){
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): triggered check on %s",pctx->label,pair->codeword,pair->as_string);
pair->triggered=1;
switch(pair->state){
case NR_ICE_PAIR_STATE_FAILED:
/* OK, there was a pair, it's just invalid: According to Section
* 7.2.1.4, we need to resurrect it */
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on failed pair, resurrecting: %s",pctx->label,pair->codeword,pair->as_string);
/* fall through */
case NR_ICE_PAIR_STATE_FROZEN:
nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING);
/* fall through even further */
case NR_ICE_PAIR_STATE_WAITING:
/* Append it additionally to the trigger check queue */
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): Inserting pair to trigger check queue: %s",pctx->label,pair->codeword,pair->as_string);
nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,pair);
break;
case NR_ICE_PAIR_STATE_IN_PROGRESS:
/* Instead of trying to maintain two stun contexts on the same pair,
* and handling heterogenous responses and error conditions, we instead
* create a second pair that is identical except that it has the
* |triggered| bit set. We also cancel the original pair, but it can
* still succeed on its own in the special waiting state. */
if(r=nr_ice_candidate_copy_for_triggered_check(pair))
ABORT(r);
nr_ice_candidate_pair_cancel(pair->pctx,pair,1);
break;
default:
/* all states are handled - a new/unknown state should not
* automatically enter the start_checks() below */
assert(0);
break;
}
/* Ensure that the timers are running to start checks on the topmost entry
* of the triggered check queue. */
if(r=nr_ice_media_stream_start_checks(pair->pctx,pair->remote->stream))
ABORT(r);
}
@ -429,12 +479,16 @@ int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_
return(_status);
}
int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair)
int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state)
{
if(pair->state != NR_ICE_PAIR_STATE_FAILED){
/* If it's already running we need to terminate the stun */
if(pair->state==NR_ICE_PAIR_STATE_IN_PROGRESS){
nr_stun_client_cancel(pair->stun_client);
if(move_to_wait_state) {
nr_stun_client_wait(pair->stun_client);
} else {
nr_stun_client_cancel(pair->stun_client);
}
}
nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_CANCELLED);
}
@ -506,7 +560,8 @@ int nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pai
pair->state=state;
if(pair->state==NR_ICE_PAIR_STATE_FAILED){
if(pair->state==NR_ICE_PAIR_STATE_FAILED ||
pair->state==NR_ICE_PAIR_STATE_CANCELLED){
if(r=nr_ice_component_failed_pair(pair->remote->component, pair))
ABORT(r);
}
@ -524,6 +579,17 @@ int nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, FILE *out)
}
int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
{
if(pair->triggered_check_queue_entry.tqe_next ||
pair->triggered_check_queue_entry.tqe_prev)
return(0);
TAILQ_INSERT_TAIL(head,pair,triggered_check_queue_entry);
return(0);
}
int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
{
nr_ice_cand_pair *c1;
@ -531,13 +597,13 @@ int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *p
c1=TAILQ_FIRST(head);
while(c1){
if(c1->priority < pair->priority){
TAILQ_INSERT_BEFORE(c1,pair,entry);
TAILQ_INSERT_BEFORE(c1,pair,check_queue_entry);
break;
}
c1=TAILQ_NEXT(c1,entry);
c1=TAILQ_NEXT(c1,check_queue_entry);
}
if(!c1) TAILQ_INSERT_TAIL(head,pair,entry);
if(!c1) TAILQ_INSERT_TAIL(head,pair,check_queue_entry);
return(0);
}

View File

@ -56,6 +56,8 @@ struct nr_ice_cand_pair_ {
on this check */
UCHAR nominated; /* Is this nominated or not */
UCHAR triggered; /* Ignore further trigger check requests */
UINT8 priority; /* The priority for this pair */
nr_ice_candidate *local; /* The local candidate */
nr_ice_candidate *remote; /* The remote candidate */
@ -68,7 +70,8 @@ struct nr_ice_cand_pair_ {
void *restart_role_change_cb_timer;
void *restart_nominated_cb_timer;
TAILQ_ENTRY(nr_ice_cand_pair_) entry;
TAILQ_ENTRY(nr_ice_cand_pair_) check_queue_entry; /* the check list */
TAILQ_ENTRY(nr_ice_cand_pair_) triggered_check_queue_entry; /* the trigger check queue */
};
int nr_ice_candidate_pair_create(nr_ice_peer_ctx *pctx, nr_ice_candidate *lcand,nr_ice_candidate *rcand,nr_ice_cand_pair **pairp);
@ -76,10 +79,11 @@ int nr_ice_candidate_pair_unfreeze(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair
int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair, int state);
int nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, FILE *out);
int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state);
int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg);
int nr_ice_candidate_pair_destroy(nr_ice_cand_pair **pairp);
void nr_ice_candidate_pair_role_change(nr_ice_cand_pair *pair);

View File

@ -704,6 +704,57 @@ int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *co
return 0;
}
static int nr_ice_component_pair_matches_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_transport_addr *local_addr, nr_stun_server_request *req)
{
if(pair->remote->component->component_id!=comp->component_id)
return(0);
if(nr_transport_addr_cmp(&pair->local->base,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
return(0);
if(nr_transport_addr_cmp(&pair->remote->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
return(0);
return(1);
}
static int nr_ice_component_handle_triggered_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_stun_server_request *req, int *error)
{
nr_stun_message *sreq=req->request;
int r=0,_status;
if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_USE_CANDIDATE,0)){
if(comp->stream->pctx->controlling){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND_PAIR(%s): Peer sent USE-CANDIDATE but is controlled",comp->stream->pctx->label, pair->codeword);
}
else{
/* If this is the first time we've noticed this is nominated...*/
pair->peer_nominated=1;
if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED && !pair->nominated){
pair->nominated=1;
if(r=nr_ice_component_nominated_pair(pair->remote->component, pair)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
}
}
}
/* Note: the RFC says to trigger first and then nominate. But in that case
* the canceled trigger pair would get nominated and the cloned trigger pair
* would not get the nomination status cloned with it.*/
if(r=nr_ice_candidate_pair_do_triggered_check(comp->stream->pctx,pair)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
_status=0;
abort:
return(r);
}
/* Section 7.2.1 */
static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_transport_addr *local_addr, nr_stun_server_request *req, int *error)
{
@ -711,12 +762,8 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr
nr_ice_candidate *pcand=0;
nr_stun_message *sreq=req->request;
nr_stun_message_attribute *attr;
int component_id_matched;
int local_addr_matched;
int remote_addr_matched;
nr_ice_cand_pair *found_invalid=0;
int r=0,_status;
int new_pcand_created=0;
int found_valid=0;
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): received request from %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,req->src_addr.as_string);
@ -767,157 +814,84 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr
pair=TAILQ_FIRST(&comp->stream->check_list);
while(pair){
component_id_matched = 0;
local_addr_matched = 0;
remote_addr_matched = 0;
if(pair->remote->component->component_id!=comp->component_id)
goto next_pair;
component_id_matched = 1;
if(nr_transport_addr_cmp(&pair->local->base,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
goto next_pair;
local_addr_matched=1;
if(nr_transport_addr_cmp(&pair->remote->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
goto next_pair;
remote_addr_matched = 1;
if(pair->state==NR_ICE_PAIR_STATE_FAILED){
found_invalid=pair;
goto next_pair;
}
if (local_addr_matched && remote_addr_matched){
/* Since triggered checks create duplicate pairs (in this implementation)
* we are willing to handle multiple matches here. */
if(nr_ice_component_pair_matches_check(comp, pair, local_addr, req)){
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Found a matching pair for received check: %s",comp->stream->pctx->label,pair->codeword,pair->as_string);
break; /* OK, this is a known pair */
}
next_pair:
pair=TAILQ_NEXT(pair,entry);
}
if(!pair){
if(!found_invalid){
/* First find our local component candidate */
nr_ice_candidate *cand;
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no matching pair",comp->stream->pctx->label);
cand=TAILQ_FIRST(&comp->local_component->candidates);
while(cand){
if(!nr_transport_addr_cmp(&cand->addr,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
break;
cand=TAILQ_NEXT(cand,entry_comp);
}
/* Well, this really shouldn't happen, but it's an error from the
other side, so we just throw an error and keep going */
if(!cand){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): stun request to unknown local address %s, discarding",comp->stream->pctx->label,local_addr->as_string);
*error=400;
ABORT(R_NOT_FOUND);
}
/* Try to find matching peer active tcp candidate */
pcand=TAILQ_FIRST(&comp->candidates);
while(pcand){
if(pcand->tcp_type == TCP_TYPE_ACTIVE) {
if(!nr_transport_addr_cmp(&pcand->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
break;
}
pcand=TAILQ_NEXT(pcand,entry_comp);
}
if (!pcand){
/* We now need to make a peer reflexive */
if(r=nr_ice_peer_peer_rflx_candidate_create(comp->stream->pctx->ctx,"prflx",comp,&req->src_addr,&pcand)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
new_pcand_created=1;
if(!nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_PRIORITY,&attr)){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Rejecting stun request without priority",comp->stream->pctx->label);
*error=487;
ABORT(R_BAD_DATA);
}
pcand->priority=attr->u.priority;
}
pcand->state=NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
if(r=nr_ice_candidate_pair_create(comp->stream->pctx,cand,pcand,
&pair)) {
*error=(r==R_NO_MEMORY)?500:400;
if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error))
ABORT(r);
}
nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN);
if(r=nr_ice_component_insert_pair(comp,pair)) {
*error=(r==R_NO_MEMORY)?500:400;
nr_ice_candidate_pair_destroy(&pair);
ABORT(r);
}
/* Do this last, since any call to ABORT will destroy pcand */
if (new_pcand_created){
TAILQ_INSERT_TAIL(&comp->candidates,pcand,entry_comp);
pcand=0;
}
}
else{
/* OK, there was a pair, it's just invalid: According to Section
7.2.1.4, we need to resurrect it
*/
if(found_invalid->state == NR_ICE_PAIR_STATE_FAILED){
pair=found_invalid;
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on invalid pair, resurrecting: %s",comp->stream->pctx->label,pair->codeword,pair->as_string);
nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_WAITING);
}
else{
/* This shouldn't happen */
r_log(LOG_ICE,LOG_ERR,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on invalid pair that was not in state FAILED; this should not happen: %s",comp->stream->pctx->label,pair->codeword,pair->as_string);
*error=500;
ABORT(R_BAD_DATA);
}
++found_valid;
}
pair=TAILQ_NEXT(pair,check_queue_entry);
}
/* OK, we've got a pair to work with. Turn it on */
assert(pair);
if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_USE_CANDIDATE,0)){
if(comp->stream->pctx->controlling){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND_PAIR(%s): Peer sent USE-CANDIDATE but is controlled",comp->stream->pctx->label, pair->codeword);
if(!found_valid){
/* There were no matching pairs, so we need to create a new peer
* reflexive candidate pair. */
if(!nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_PRIORITY,&attr)){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Rejecting stun request without priority",comp->stream->pctx->label);
*error=400;
ABORT(R_BAD_DATA);
}
else{
/* If this is the first time we've noticed this is nominated...*/
pair->peer_nominated=1;
if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED && !pair->nominated){
pair->nominated=1;
/* Find our local component candidate */
nr_ice_candidate *cand;
if(r=nr_ice_component_nominated_pair(pair->remote->component, pair)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
}
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no matching pair",comp->stream->pctx->label);
cand=TAILQ_FIRST(&comp->local_component->candidates);
while(cand){
if(!nr_transport_addr_cmp(&cand->addr,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
break;
cand=TAILQ_NEXT(cand,entry_comp);
}
}
if(r=nr_ice_candidate_pair_do_triggered_check(comp->stream->pctx,pair)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
/* Well, this really shouldn't happen, but it's an error from the
other side, so we just throw an error and keep going */
if(!cand){
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): stun request to unknown local address %s, discarding",comp->stream->pctx->label,local_addr->as_string);
*error=400;
ABORT(R_NOT_FOUND);
}
/* Now make a peer reflexive (remote) candidate */
if(r=nr_ice_peer_peer_rflx_candidate_create(comp->stream->pctx->ctx,"prflx",comp,&req->src_addr,&pcand)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
pcand->priority=attr->u.priority;
pcand->state=NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
/* Finally, create the candidate pair, insert into the check list, and
* apply the incoming check to it. */
if(r=nr_ice_candidate_pair_create(comp->stream->pctx,cand,pcand,
&pair)) {
*error=(r==R_NO_MEMORY)?500:400;
ABORT(r);
}
nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN);
if(r=nr_ice_component_insert_pair(comp,pair)) {
*error=(r==R_NO_MEMORY)?500:400;
nr_ice_candidate_pair_destroy(&pair);
ABORT(r);
}
/* Do this last, since any call to ABORT will destroy pcand */
TAILQ_INSERT_TAIL(&comp->candidates,pcand,entry_comp);
pcand=0;
/* Finally start the trigger check if needed */
if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error))
ABORT(r);
}
_status=0;
abort:
if(_status){
if (new_pcand_created)
nr_ice_candidate_destroy(&pcand);
nr_ice_candidate_destroy(&pcand);
assert(*error != 0);
if(r!=R_NO_MEMORY) assert(*error != 500);
}
@ -1013,6 +987,14 @@ int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pco
continue;
if(pcand->addr.ip_version != lcand->addr.ip_version)
continue;
/* This prevents our ice_unittest from pairing a loopback with a host
* candidate. */
if(nr_transport_addr_is_loopback(&lcand->addr) &&
!nr_transport_addr_is_loopback(&pcand->addr))
continue;
if(!nr_transport_addr_is_loopback(&lcand->addr) &&
nr_transport_addr_is_loopback(&pcand->addr))
continue;
/*
Two modes, depending on |pair_all_remote|
@ -1124,15 +1106,11 @@ static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_c
int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair)
{
int r,_status;
int fire_cb=0;
nr_ice_cand_pair *p2;
if(!comp->nominated)
fire_cb=1;
/* Are we changing what the nominated pair is? */
if(comp->nominated){
if(comp->nominated->priority > pair->priority)
if(comp->nominated->priority >= pair->priority)
return(0);
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): replacing pair %s with CAND-PAIR(%s)",comp->stream->pctx->label,comp->stream->label,comp->component_id,comp->nominated->codeword,comp->nominated->as_string,pair->codeword);
}
@ -1146,19 +1124,33 @@ int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pa
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling all pairs but %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->codeword,pair->as_string);
/* Cancel checks in WAITING and FROZEN per ICE S 8.1.2 */
p2=TAILQ_FIRST(&comp->stream->trigger_check_queue);
while(p2){
if((p2 != pair) &&
(p2->remote->component->component_id == comp->component_id)) {
assert(p2->state == NR_ICE_PAIR_STATE_WAITING ||
p2->state == NR_ICE_PAIR_STATE_CANCELLED);
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s in trigger check queue because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword);
if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2,0))
ABORT(r);
}
p2=TAILQ_NEXT(p2,triggered_check_queue_entry);
}
p2=TAILQ_FIRST(&comp->stream->check_list);
while(p2){
if((p2 != pair) &&
(p2->remote->component->component_id == comp->component_id) &&
((p2->state == NR_ICE_PAIR_STATE_FROZEN) ||
(p2->state == NR_ICE_PAIR_STATE_WAITING))) {
(p2->state == NR_ICE_PAIR_STATE_WAITING))) {
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword);
if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2))
if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2,0))
ABORT(r);
}
p2=TAILQ_NEXT(p2,entry);
p2=TAILQ_NEXT(p2,check_queue_entry);
}
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): cancelling done",comp->stream->pctx->label,comp->stream->label,comp->component_id);
@ -1193,7 +1185,7 @@ static int nr_ice_component_have_all_pairs_failed(nr_ice_component *comp)
}
}
p2=TAILQ_NEXT(p2,entry);
p2=TAILQ_NEXT(p2,check_queue_entry);
}
return(1);
@ -1233,7 +1225,7 @@ int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp)
if (comp->component_id == pair->local->component_id)
ct++;
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
/* Make and fill the array */
@ -1246,7 +1238,7 @@ int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp)
if (comp->component_id == pair->local->component_id)
pairs[ct++]=pair;
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
if (pctx->handler) {

View File

@ -72,6 +72,7 @@ int nr_ice_media_stream_create(nr_ice_ctx *ctx,char *label,int components, nr_ic
}
TAILQ_INIT(&stream->check_list);
TAILQ_INIT(&stream->trigger_check_queue);
stream->component_ct=components;
stream->ice_state = NR_ICE_MEDIA_STREAM_UNPAIRED;
@ -101,8 +102,10 @@ int nr_ice_media_stream_destroy(nr_ice_media_stream **streamp)
nr_ice_component_destroy(&c1);
}
TAILQ_FOREACH_SAFE(p1, &stream->check_list, entry, p2){
TAILQ_REMOVE(&stream->check_list,p1,entry);
/* Note: all the entries from the trigger check queue are held in here as
* well, so we only clean up the super set. */
TAILQ_FOREACH_SAFE(p1, &stream->check_list, check_queue_entry, p2){
TAILQ_REMOVE(&stream->check_list,p1,check_queue_entry);
nr_ice_candidate_pair_destroy(&p1);
}
@ -308,46 +311,66 @@ int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ic
return(_status);
}
/* S 5.8 -- run the highest priority WAITING pair or if not available
FROZEN pair */
/* S 5.8 -- run the first pair from the triggered check queue (even after
* checks have completed S 8.1.2) or run the highest priority WAITING pair or
* if not available FROZEN pair from the check queue */
static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg)
{
int r,_status;
nr_ice_media_stream *stream=cb_arg;
nr_ice_cand_pair *pair;
nr_ice_cand_pair *pair = 0;
int timer_val;
int timer_multiplier;
assert(stream->pctx->active_streams!=0);
timer_val=stream->pctx->ctx->Ta*stream->pctx->active_streams;
timer_multiplier=stream->pctx->active_streams;
/* Once the checks are completed we don't have an active streams any more,
* but we still need to process triggered checks. */
if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) {
r_log(LOG_ICE,LOG_ERR,"ICE-PEER(%s): (bug) bogus state for stream %s",stream->pctx->label,stream->label);
assert(timer_multiplier==0);
timer_multiplier=1;
}
assert(stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED);
assert(timer_multiplier!=0);
timer_val=stream->pctx->ctx->Ta*timer_multiplier;
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): check timer expired for media stream %s",stream->pctx->label,stream->label);
stream->timer=0;
/* Find the highest priority WAITING check and move it to RUNNING */
pair=TAILQ_FIRST(&stream->check_list);
/* The trigger check queue has the highest priority */
pair=TAILQ_FIRST(&stream->trigger_check_queue);
while(pair){
if(pair->state==NR_ICE_PAIR_STATE_WAITING)
if(pair->state==NR_ICE_PAIR_STATE_WAITING){
/* Remove the pair from he trigger check queue */
r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): Removing pair from trigger check queue %s",stream->pctx->label,pair->as_string);
TAILQ_REMOVE(&stream->trigger_check_queue,pair,triggered_check_queue_entry);
break;
pair=TAILQ_NEXT(pair,entry);
}
pair=TAILQ_NEXT(pair,triggered_check_queue_entry);
}
/* Hmmm... No WAITING. Let's look for FROZEN */
if(!pair){
pair=TAILQ_FIRST(&stream->check_list);
while(pair){
if(pair->state==NR_ICE_PAIR_STATE_FROZEN){
if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair))
ABORT(r);
break;
if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) {
if(!pair){
/* Find the highest priority WAITING check and move it to RUNNING */
pair=TAILQ_FIRST(&stream->check_list);
while(pair){
if(pair->state==NR_ICE_PAIR_STATE_WAITING)
break;
pair=TAILQ_NEXT(pair,check_queue_entry);
}
}
/* Hmmm... No WAITING. Let's look for FROZEN */
if(!pair){
pair=TAILQ_FIRST(&stream->check_list);
while(pair){
if(pair->state==NR_ICE_PAIR_STATE_FROZEN){
if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair))
ABORT(r);
break;
}
pair=TAILQ_NEXT(pair,check_queue_entry);
}
pair=TAILQ_NEXT(pair,entry);
}
}
@ -364,20 +387,25 @@ static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg)
return;
}
/* Start checks for this media stream (aka check list) */
int nr_ice_media_stream_start_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream)
{
int r,_status;
/* Don't start the check timer if the stream is done (failed/completed) */
if (stream->ice_state > NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE) {
/* Don't start the check timer if the stream is failed */
if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FAILED) {
assert(0);
ABORT(R_INTERNAL);
}
if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE))
ABORT(r);
/* Even if the stream is completed already remote can still create a new
* triggered check request which needs to fire, but not change our stream
* state. */
if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) {
if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)) {
ABORT(r);
}
}
if (!stream->timer) {
r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): Starting check timer for stream.",pctx->label,stream->label);
@ -419,7 +447,7 @@ int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_strea
}
/* Already exists... fall through */
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
_status=0;
@ -442,7 +470,7 @@ static int nr_ice_media_stream_unfreeze_pairs_match(nr_ice_media_stream *stream,
ABORT(r);
unfroze++;
}
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
if(!unfroze)
@ -532,7 +560,7 @@ int nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *s
while(pair){
nr_ice_candidate_pair_dump_state(pair,out);
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
return(0);
@ -625,10 +653,10 @@ int nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_comp
/* OK, we need to cancel off everything on this component */
p2=TAILQ_FIRST(&stream->check_list);
while(p2){
if(r=nr_ice_candidate_pair_cancel(p2->pctx,p2))
if(r=nr_ice_candidate_pair_cancel(p2->pctx,p2,0))
ABORT(r);
p2=TAILQ_NEXT(p2,entry);
p2=TAILQ_NEXT(p2,check_queue_entry);
}
/* Cancel our timer */
@ -823,7 +851,7 @@ int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice
ABORT(r);
_status=0;
abort:
abort:
return(_status);
}
@ -846,7 +874,7 @@ int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int compo
comp->state = NR_ICE_COMPONENT_DISABLED;
_status=0;
abort:
abort:
return(_status);
}
@ -858,7 +886,7 @@ void nr_ice_media_stream_role_change(nr_ice_media_stream *stream)
pair=TAILQ_FIRST(&stream->check_list);
while(pair){
nr_ice_candidate_pair_role_change(pair);
pair=TAILQ_NEXT(pair,entry);
pair=TAILQ_NEXT(pair,check_queue_entry);
}
}

View File

@ -63,6 +63,7 @@ struct nr_ice_media_stream_ {
#define NR_ICE_MEDIA_STREAM_CHECKS_FAILED 5
nr_ice_cand_pair_head check_list;
nr_ice_cand_pair_head trigger_check_queue;
void *timer; /* Check list periodic timer */
/* nr_ice_cand_pair_head valid_list; */

View File

@ -501,13 +501,15 @@ static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len,
sock->pending += len;
}
if (sock->pending && !already_armed) {
if (sock->pending) {
if (!already_armed) {
if ((r=nr_socket_buffered_stun_arm_writable_cb(sock)))
ABORT(r);
}
r_log(LOG_GENERIC, LOG_INFO, "Write buffer not empty for %s %u - %s armed (@%p)",
sock->remote_addr.as_string, (uint32_t)sock->pending,
already_armed ? "already" : "", &sock->pending);
}
r_log(LOG_GENERIC, LOG_INFO, "Write buffer not empty for %s %u - %s armed (@%p)",
sock->remote_addr.as_string, (uint32_t)sock->pending,
already_armed ? "already" : "", &sock->pending);
*written = original_len;

View File

@ -460,9 +460,8 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len
ATTACH_DATA(hmac_key, hmac_key_d);
if(ctx->state==NR_STUN_CLIENT_STATE_CANCELLED)
ABORT(R_REJECTED);
if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING)
if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) &&
(ctx->state != NR_STUN_CLIENT_STATE_WAITING))
ABORT(R_REJECTED);
r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Inspecting STUN response (my_addr=%s, peer_addr=%s)",ctx->label,ctx->my_addr.as_string,peer_addr->as_string);
@ -750,7 +749,8 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len
r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): Error processing response: %s, stun error code %d.", ctx->label, nr_strerror(_status), (int)ctx->error_code);
}
if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) &&
(ctx->state != NR_STUN_CLIENT_STATE_WAITING)) {
/* Cancel the timer firing */
if (ctx->timer_handle) {
NR_async_timer_cancel(ctx->timer_handle);
@ -798,6 +798,17 @@ int nr_stun_client_cancel(nr_stun_client_ctx *ctx)
return(0);
}
int nr_stun_client_wait(nr_stun_client_ctx *ctx)
{
nr_stun_client_cancel(ctx);
ctx->state=NR_STUN_CLIENT_STATE_WAITING;
ctx->request_ct = ctx->maximum_transmits;
ctx->timeout_ms = ctx->maximum_transmits_timeout_ms;
NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle);
return(0);
}
int nr_stun_client_failed(nr_stun_client_ctx *ctx)
{

View File

@ -137,6 +137,7 @@ struct nr_stun_client_ctx_ {
#define NR_STUN_CLIENT_STATE_FAILED 3
#define NR_STUN_CLIENT_STATE_TIMED_OUT 4
#define NR_STUN_CLIENT_STATE_CANCELLED 5
#define NR_STUN_CLIENT_STATE_WAITING 6
int mode;
#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH 1
@ -191,6 +192,7 @@ int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp);
int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 mask);
int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr);
int nr_stun_client_cancel(nr_stun_client_ctx *ctx);
int nr_stun_client_wait(nr_stun_client_ctx *ctx);
int nr_stun_client_failed(nr_stun_client_ctx *ctx);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,579 @@
/*
* Copyright (c) 2008 The Khronos Group Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject
* to the following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
/** OMX_Component.h - OpenMax IL version 1.1.2
* The OMX_Component header file contains the definitions used to define
* the public interface of a component. This header file is intended to
* be used by both the application and the component.
*/
#ifndef OMX_Component_h
#define OMX_Component_h
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Each OMX header must include all required header files to allow the
* header to compile without errors. The includes below are required
* for this header file to compile successfully
*/
#include <OMX_Audio.h>
#include <OMX_Video.h>
#include <OMX_Image.h>
#include <OMX_Other.h>
/** @ingroup comp */
typedef enum OMX_PORTDOMAINTYPE {
OMX_PortDomainAudio,
OMX_PortDomainVideo,
OMX_PortDomainImage,
OMX_PortDomainOther,
OMX_PortDomainKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_PortDomainVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_PortDomainMax = 0x7ffffff
} OMX_PORTDOMAINTYPE;
/** @ingroup comp */
typedef struct OMX_PARAM_PORTDEFINITIONTYPE {
OMX_U32 nSize; /**< Size of the structure in bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U32 nPortIndex; /**< Port number the structure applies to */
OMX_DIRTYPE eDir; /**< Direction (input or output) of this port */
OMX_U32 nBufferCountActual; /**< The actual number of buffers allocated on this port */
OMX_U32 nBufferCountMin; /**< The minimum number of buffers this port requires */
OMX_U32 nBufferSize; /**< Size, in bytes, for buffers to be used for this channel */
OMX_BOOL bEnabled; /**< Ports default to enabled and are enabled/disabled by
OMX_CommandPortEnable/OMX_CommandPortDisable.
When disabled a port is unpopulated. A disabled port
is not populated with buffers on a transition to IDLE. */
OMX_BOOL bPopulated; /**< Port is populated with all of its buffers as indicated by
nBufferCountActual. A disabled port is always unpopulated.
An enabled port is populated on a transition to OMX_StateIdle
and unpopulated on a transition to loaded. */
OMX_PORTDOMAINTYPE eDomain; /**< Domain of the port. Determines the contents of metadata below. */
union {
OMX_AUDIO_PORTDEFINITIONTYPE audio;
OMX_VIDEO_PORTDEFINITIONTYPE video;
OMX_IMAGE_PORTDEFINITIONTYPE image;
OMX_OTHER_PORTDEFINITIONTYPE other;
} format;
OMX_BOOL bBuffersContiguous;
OMX_U32 nBufferAlignment;
} OMX_PARAM_PORTDEFINITIONTYPE;
/** @ingroup comp */
typedef struct OMX_PARAM_U32TYPE {
OMX_U32 nSize; /**< Size of this structure, in Bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U32 nPortIndex; /**< port that this structure applies to */
OMX_U32 nU32; /**< U32 value */
} OMX_PARAM_U32TYPE;
/** @ingroup rpm */
typedef enum OMX_SUSPENSIONPOLICYTYPE {
OMX_SuspensionDisabled, /**< No suspension; v1.0 behavior */
OMX_SuspensionEnabled, /**< Suspension allowed */
OMX_SuspensionPolicyKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_SuspensionPolicyStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_SuspensionPolicyMax = 0x7fffffff
} OMX_SUSPENSIONPOLICYTYPE;
/** @ingroup rpm */
typedef struct OMX_PARAM_SUSPENSIONPOLICYTYPE {
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_SUSPENSIONPOLICYTYPE ePolicy;
} OMX_PARAM_SUSPENSIONPOLICYTYPE;
/** @ingroup rpm */
typedef enum OMX_SUSPENSIONTYPE {
OMX_NotSuspended, /**< component is not suspended */
OMX_Suspended, /**< component is suspended */
OMX_SuspensionKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_SuspensionVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_SuspendMax = 0x7FFFFFFF
} OMX_SUSPENSIONTYPE;
/** @ingroup rpm */
typedef struct OMX_PARAM_SUSPENSIONTYPE {
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_SUSPENSIONTYPE eType;
} OMX_PARAM_SUSPENSIONTYPE ;
typedef struct OMX_CONFIG_BOOLEANTYPE {
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_BOOL bEnabled;
} OMX_CONFIG_BOOLEANTYPE;
/* Parameter specifying the content uri to use. */
/** @ingroup cp */
typedef struct OMX_PARAM_CONTENTURITYPE
{
OMX_U32 nSize; /**< size of the structure in bytes, including
actual URI name */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U8 contentURI[1]; /**< The URI name */
} OMX_PARAM_CONTENTURITYPE;
/* Parameter specifying the pipe to use. */
/** @ingroup cp */
typedef struct OMX_PARAM_CONTENTPIPETYPE
{
OMX_U32 nSize; /**< size of the structure in bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_HANDLETYPE hPipe; /**< The pipe handle*/
} OMX_PARAM_CONTENTPIPETYPE;
/** @ingroup rpm */
typedef struct OMX_RESOURCECONCEALMENTTYPE {
OMX_U32 nSize; /**< size of the structure in bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_BOOL bResourceConcealmentForbidden; /**< disallow the use of resource concealment
methods (like degrading algorithm quality to
lower resource consumption or functional bypass)
on a component as a resolution to resource conflicts. */
} OMX_RESOURCECONCEALMENTTYPE;
/** @ingroup metadata */
typedef enum OMX_METADATACHARSETTYPE {
OMX_MetadataCharsetUnknown = 0,
OMX_MetadataCharsetASCII,
OMX_MetadataCharsetBinary,
OMX_MetadataCharsetCodePage1252,
OMX_MetadataCharsetUTF8,
OMX_MetadataCharsetJavaConformantUTF8,
OMX_MetadataCharsetUTF7,
OMX_MetadataCharsetImapUTF7,
OMX_MetadataCharsetUTF16LE,
OMX_MetadataCharsetUTF16BE,
OMX_MetadataCharsetGB12345,
OMX_MetadataCharsetHZGB2312,
OMX_MetadataCharsetGB2312,
OMX_MetadataCharsetGB18030,
OMX_MetadataCharsetGBK,
OMX_MetadataCharsetBig5,
OMX_MetadataCharsetISO88591,
OMX_MetadataCharsetISO88592,
OMX_MetadataCharsetISO88593,
OMX_MetadataCharsetISO88594,
OMX_MetadataCharsetISO88595,
OMX_MetadataCharsetISO88596,
OMX_MetadataCharsetISO88597,
OMX_MetadataCharsetISO88598,
OMX_MetadataCharsetISO88599,
OMX_MetadataCharsetISO885910,
OMX_MetadataCharsetISO885913,
OMX_MetadataCharsetISO885914,
OMX_MetadataCharsetISO885915,
OMX_MetadataCharsetShiftJIS,
OMX_MetadataCharsetISO2022JP,
OMX_MetadataCharsetISO2022JP1,
OMX_MetadataCharsetISOEUCJP,
OMX_MetadataCharsetSMS7Bit,
OMX_MetadataCharsetKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_MetadataCharsetVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_MetadataCharsetTypeMax= 0x7FFFFFFF
} OMX_METADATACHARSETTYPE;
/** @ingroup metadata */
typedef enum OMX_METADATASCOPETYPE
{
OMX_MetadataScopeAllLevels,
OMX_MetadataScopeTopLevel,
OMX_MetadataScopePortLevel,
OMX_MetadataScopeNodeLevel,
OMX_MetadataScopeKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_MetadataScopeVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_MetadataScopeTypeMax = 0x7fffffff
} OMX_METADATASCOPETYPE;
/** @ingroup metadata */
typedef enum OMX_METADATASEARCHMODETYPE
{
OMX_MetadataSearchValueSizeByIndex,
OMX_MetadataSearchItemByIndex,
OMX_MetadataSearchNextItemByKey,
OMX_MetadataSearchKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_MetadataSearchVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_MetadataSearchTypeMax = 0x7fffffff
} OMX_METADATASEARCHMODETYPE;
/** @ingroup metadata */
typedef struct OMX_CONFIG_METADATAITEMCOUNTTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_METADATASCOPETYPE eScopeMode;
OMX_U32 nScopeSpecifier;
OMX_U32 nMetadataItemCount;
} OMX_CONFIG_METADATAITEMCOUNTTYPE;
/** @ingroup metadata */
typedef struct OMX_CONFIG_METADATAITEMTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_METADATASCOPETYPE eScopeMode;
OMX_U32 nScopeSpecifier;
OMX_U32 nMetadataItemIndex;
OMX_METADATASEARCHMODETYPE eSearchMode;
OMX_METADATACHARSETTYPE eKeyCharset;
OMX_U8 nKeySizeUsed;
OMX_U8 nKey[128];
OMX_METADATACHARSETTYPE eValueCharset;
OMX_STRING sLanguageCountry;
OMX_U32 nValueMaxSize;
OMX_U32 nValueSizeUsed;
OMX_U8 nValue[1];
} OMX_CONFIG_METADATAITEMTYPE;
/* @ingroup metadata */
typedef struct OMX_CONFIG_CONTAINERNODECOUNTTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_BOOL bAllKeys;
OMX_U32 nParentNodeID;
OMX_U32 nNumNodes;
} OMX_CONFIG_CONTAINERNODECOUNTTYPE;
/** @ingroup metadata */
typedef struct OMX_CONFIG_CONTAINERNODEIDTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_BOOL bAllKeys;
OMX_U32 nParentNodeID;
OMX_U32 nNodeIndex;
OMX_U32 nNodeID;
OMX_STRING cNodeName;
OMX_BOOL bIsLeafType;
} OMX_CONFIG_CONTAINERNODEIDTYPE;
/** @ingroup metadata */
typedef struct OMX_PARAM_METADATAFILTERTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_BOOL bAllKeys; /* if true then this structure refers to all keys and
* the three key fields below are ignored */
OMX_METADATACHARSETTYPE eKeyCharset;
OMX_U32 nKeySizeUsed;
OMX_U8 nKey [128];
OMX_U32 nLanguageCountrySizeUsed;
OMX_U8 nLanguageCountry[128];
OMX_BOOL bEnabled; /* if true then key is part of filter (e.g.
* retained for query later). If false then
* key is not part of filter */
} OMX_PARAM_METADATAFILTERTYPE;
/** The OMX_HANDLETYPE structure defines the component handle. The component
* handle is used to access all of the component's public methods and also
* contains pointers to the component's private data area. The component
* handle is initialized by the OMX core (with help from the component)
* during the process of loading the component. After the component is
* successfully loaded, the application can safely access any of the
* component's public functions (although some may return an error because
* the state is inappropriate for the access).
*
* @ingroup comp
*/
typedef struct OMX_COMPONENTTYPE
{
/** The size of this structure, in bytes. It is the responsibility
of the allocator of this structure to fill in this value. Since
this structure is allocated by the GetHandle function, this
function will fill in this value. */
OMX_U32 nSize;
/** nVersion is the version of the OMX specification that the structure
is built against. It is the responsibility of the creator of this
structure to initialize this value and every user of this structure
should verify that it knows how to use the exact version of
this structure found herein. */
OMX_VERSIONTYPE nVersion;
/** pComponentPrivate is a pointer to the component private data area.
This member is allocated and initialized by the component when the
component is first loaded. The application should not access this
data area. */
OMX_PTR pComponentPrivate;
/** pApplicationPrivate is a pointer that is a parameter to the
OMX_GetHandle method, and contains an application private value
provided by the IL client. This application private data is
returned to the IL Client by OMX in all callbacks */
OMX_PTR pApplicationPrivate;
/** refer to OMX_GetComponentVersion in OMX_core.h or the OMX IL
specification for details on the GetComponentVersion method.
*/
OMX_ERRORTYPE (*GetComponentVersion)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_STRING pComponentName,
OMX_OUT OMX_VERSIONTYPE* pComponentVersion,
OMX_OUT OMX_VERSIONTYPE* pSpecVersion,
OMX_OUT OMX_UUIDTYPE* pComponentUUID);
/** refer to OMX_SendCommand in OMX_core.h or the OMX IL
specification for details on the SendCommand method.
*/
OMX_ERRORTYPE (*SendCommand)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_COMMANDTYPE Cmd,
OMX_IN OMX_U32 nParam1,
OMX_IN OMX_PTR pCmdData);
/** refer to OMX_GetParameter in OMX_core.h or the OMX IL
specification for details on the GetParameter method.
*/
OMX_ERRORTYPE (*GetParameter)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nParamIndex,
OMX_INOUT OMX_PTR pComponentParameterStructure);
/** refer to OMX_SetParameter in OMX_core.h or the OMX IL
specification for details on the SetParameter method.
*/
OMX_ERRORTYPE (*SetParameter)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentParameterStructure);
/** refer to OMX_GetConfig in OMX_core.h or the OMX IL
specification for details on the GetConfig method.
*/
OMX_ERRORTYPE (*GetConfig)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_INOUT OMX_PTR pComponentConfigStructure);
/** refer to OMX_SetConfig in OMX_core.h or the OMX IL
specification for details on the SetConfig method.
*/
OMX_ERRORTYPE (*SetConfig)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentConfigStructure);
/** refer to OMX_GetExtensionIndex in OMX_core.h or the OMX IL
specification for details on the GetExtensionIndex method.
*/
OMX_ERRORTYPE (*GetExtensionIndex)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_STRING cParameterName,
OMX_OUT OMX_INDEXTYPE* pIndexType);
/** refer to OMX_GetState in OMX_core.h or the OMX IL
specification for details on the GetState method.
*/
OMX_ERRORTYPE (*GetState)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_STATETYPE* pState);
/** The ComponentTunnelRequest method will interact with another OMX
component to determine if tunneling is possible and to setup the
tunneling. The return codes for this method can be used to
determine if tunneling is not possible, or if tunneling is not
supported.
Base profile components (i.e. non-interop) do not support this
method and should return OMX_ErrorNotImplemented
The interop profile component MUST support tunneling to another
interop profile component with a compatible port parameters.
A component may also support proprietary communication.
If proprietary communication is supported the negotiation of
proprietary communication is done outside of OMX in a vendor
specific way. It is only required that the proper result be
returned and the details of how the setup is done is left
to the component implementation.
When this method is invoked when nPort in an output port, the
component will:
1. Populate the pTunnelSetup structure with the output port's
requirements and constraints for the tunnel.
When this method is invoked when nPort in an input port, the
component will:
1. Query the necessary parameters from the output port to
determine if the ports are compatible for tunneling
2. If the ports are compatible, the component should store
the tunnel step provided by the output port
3. Determine which port (either input or output) is the buffer
supplier, and call OMX_SetParameter on the output port to
indicate this selection.
The component will return from this call within 5 msec.
@param [in] hComp
Handle of the component to be accessed. This is the component
handle returned by the call to the OMX_GetHandle method.
@param [in] nPort
nPort is used to select the port on the component to be used
for tunneling.
@param [in] hTunneledComp
Handle of the component to tunnel with. This is the component
handle returned by the call to the OMX_GetHandle method. When
this parameter is 0x0 the component should setup the port for
communication with the application / IL Client.
@param [in] nPortOutput
nPortOutput is used indicate the port the component should
tunnel with.
@param [in] pTunnelSetup
Pointer to the tunnel setup structure. When nPort is an output port
the component should populate the fields of this structure. When
When nPort is an input port the component should review the setup
provided by the component with the output port.
@return OMX_ERRORTYPE
If the command successfully executes, the return code will be
OMX_ErrorNone. Otherwise the appropriate OMX error will be returned.
@ingroup tun
*/
OMX_ERRORTYPE (*ComponentTunnelRequest)(
OMX_IN OMX_HANDLETYPE hComp,
OMX_IN OMX_U32 nPort,
OMX_IN OMX_HANDLETYPE hTunneledComp,
OMX_IN OMX_U32 nTunneledPort,
OMX_INOUT OMX_TUNNELSETUPTYPE* pTunnelSetup);
/** refer to OMX_UseBuffer in OMX_core.h or the OMX IL
specification for details on the UseBuffer method.
@ingroup buf
*/
OMX_ERRORTYPE (*UseBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes,
OMX_IN OMX_U8* pBuffer);
/** refer to OMX_AllocateBuffer in OMX_core.h or the OMX IL
specification for details on the AllocateBuffer method.
@ingroup buf
*/
OMX_ERRORTYPE (*AllocateBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBuffer,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes);
/** refer to OMX_FreeBuffer in OMX_core.h or the OMX IL
specification for details on the FreeBuffer method.
@ingroup buf
*/
OMX_ERRORTYPE (*FreeBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
/** refer to OMX_EmptyThisBuffer in OMX_core.h or the OMX IL
specification for details on the EmptyThisBuffer method.
@ingroup buf
*/
OMX_ERRORTYPE (*EmptyThisBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
/** refer to OMX_FillThisBuffer in OMX_core.h or the OMX IL
specification for details on the FillThisBuffer method.
@ingroup buf
*/
OMX_ERRORTYPE (*FillThisBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
/** The SetCallbacks method is used by the core to specify the callback
structure from the application to the component. This is a blocking
call. The component will return from this call within 5 msec.
@param [in] hComponent
Handle of the component to be accessed. This is the component
handle returned by the call to the GetHandle function.
@param [in] pCallbacks
pointer to an OMX_CALLBACKTYPE structure used to provide the
callback information to the component
@param [in] pAppData
pointer to an application defined value. It is anticipated that
the application will pass a pointer to a data structure or a "this
pointer" in this area to allow the callback (in the application)
to determine the context of the call
@return OMX_ERRORTYPE
If the command successfully executes, the return code will be
OMX_ErrorNone. Otherwise the appropriate OMX error will be returned.
*/
OMX_ERRORTYPE (*SetCallbacks)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_CALLBACKTYPE* pCallbacks,
OMX_IN OMX_PTR pAppData);
/** ComponentDeInit method is used to deinitialize the component
providing a means to free any resources allocated at component
initialization. NOTE: After this call the component handle is
not valid for further use.
@param [in] hComponent
Handle of the component to be accessed. This is the component
handle returned by the call to the GetHandle function.
@return OMX_ERRORTYPE
If the command successfully executes, the return code will be
OMX_ErrorNone. Otherwise the appropriate OMX error will be returned.
*/
OMX_ERRORTYPE (*ComponentDeInit)(
OMX_IN OMX_HANDLETYPE hComponent);
/** @ingroup buf */
OMX_ERRORTYPE (*UseEGLImage)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN void* eglImage);
OMX_ERRORTYPE (*ComponentRoleEnum)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_U8 *cRole,
OMX_IN OMX_U32 nIndex);
} OMX_COMPONENTTYPE;
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
/* File EOF */

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010 The Khronos Group Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject
* to the following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
/** OMX_ComponentExt.h - OpenMax IL version 1.1.2
* The OMX_ComponentExt header file contains extensions to the definitions used
* by both the application and the component to access common items.
*/
#ifndef OMX_ComponentExt_h
#define OMX_ComponentExt_h
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Each OMX header must include all required header files to allow the
* header to compile without errors. The includes below are required
* for this header file to compile successfully
*/
#include <OMX_Types.h>
/** Set/query the commit mode */
typedef struct OMX_CONFIG_COMMITMODETYPE {
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_BOOL bDeferred;
} OMX_CONFIG_COMMITMODETYPE;
/** Explicit commit */
typedef struct OMX_CONFIG_COMMITTYPE {
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
} OMX_CONFIG_COMMITTYPE;
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* OMX_ComponentExt_h */

View File

@ -0,0 +1,195 @@
/*
* Copyright (c) 2008 The Khronos Group Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject
* to the following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
/** OMX_ContentPipe.h - OpenMax IL version 1.1.2
* The OMX_ContentPipe header file contains the definitions used to define
* the public interface for content piples. This header file is intended to
* be used by the component.
*/
#ifndef OMX_CONTENTPIPE_H
#define OMX_CONTENTPIPE_H
#ifndef KD_EACCES
/* OpenKODE error codes. CPResult values may be zero (indicating success
or one of the following values) */
#define KD_EACCES (1)
#define KD_EADDRINUSE (2)
#define KD_EAGAIN (5)
#define KD_EBADF (7)
#define KD_EBUSY (8)
#define KD_ECONNREFUSED (9)
#define KD_ECONNRESET (10)
#define KD_EDEADLK (11)
#define KD_EDESTADDRREQ (12)
#define KD_ERANGE (35)
#define KD_EEXIST (13)
#define KD_EFBIG (14)
#define KD_EHOSTUNREACH (15)
#define KD_EINVAL (17)
#define KD_EIO (18)
#define KD_EISCONN (20)
#define KD_EISDIR (21)
#define KD_EMFILE (22)
#define KD_ENAMETOOLONG (23)
#define KD_ENOENT (24)
#define KD_ENOMEM (25)
#define KD_ENOSPC (26)
#define KD_ENOSYS (27)
#define KD_ENOTCONN (28)
#define KD_EPERM (33)
#define KD_ETIMEDOUT (36)
#define KD_EILSEQ (19)
#endif
/** Map types from OMX standard types only here so interface is as generic as possible. */
typedef OMX_U32 CPresult;
typedef char * CPstring;
typedef void * CPhandle;
typedef OMX_U32 CPuint;
typedef OMX_S32 CPint;
typedef char CPbyte;
typedef OMX_BOOL CPbool;
/** enumeration of origin types used in the CP_PIPETYPE's Seek function
* @ingroup cp
*/
typedef enum CP_ORIGINTYPE {
CP_OriginBegin,
CP_OriginCur,
CP_OriginEnd,
CP_OriginKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
CP_OriginVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
CP_OriginMax = 0X7FFFFFFF
} CP_ORIGINTYPE;
/** enumeration of contact access types used in the CP_PIPETYPE's Open function
* @ingroup cp
*/
typedef enum CP_ACCESSTYPE {
CP_AccessRead,
CP_AccessWrite,
CP_AccessReadWrite ,
CP_AccessKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
CP_AccessVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
CP_AccessMax = 0X7FFFFFFF
} CP_ACCESSTYPE;
/** enumeration of results returned by the CP_PIPETYPE's CheckAvailableBytes function
* @ingroup cp
*/
typedef enum CP_CHECKBYTESRESULTTYPE
{
CP_CheckBytesOk, /**< There are at least the request number
of bytes available */
CP_CheckBytesNotReady, /**< The pipe is still retrieving bytes
and presently lacks sufficient bytes.
Client will be called when they are
sufficient bytes are available. */
CP_CheckBytesInsufficientBytes , /**< The pipe has retrieved all bytes
but those available are less than those
requested */
CP_CheckBytesAtEndOfStream, /**< The pipe has reached the end of stream
and no more bytes are available. */
CP_CheckBytesOutOfBuffers, /**< All read/write buffers are currently in use. */
CP_CheckBytesKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
CP_CheckBytesVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
CP_CheckBytesMax = 0X7FFFFFFF
} CP_CHECKBYTESRESULTTYPE;
/** enumeration of content pipe events sent to the client callback.
* @ingroup cp
*/
typedef enum CP_EVENTTYPE{
CP_BytesAvailable, /** bytes requested in a CheckAvailableBytes call are now available*/
CP_Overflow, /** enumeration of content pipe events sent to the client callback*/
CP_PipeDisconnected , /** enumeration of content pipe events sent to the client callback*/
CP_EventKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
CP_EventVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
CP_EventMax = 0X7FFFFFFF
} CP_EVENTTYPE;
/** content pipe definition
* @ingroup cp
*/
typedef struct CP_PIPETYPE
{
/** Open a content stream for reading or writing. */
CPresult (*Open)( CPhandle* hContent, CPstring szURI, CP_ACCESSTYPE eAccess );
/** Close a content stream. */
CPresult (*Close)( CPhandle hContent );
/** Create a content source and open it for writing. */
CPresult (*Create)( CPhandle *hContent, CPstring szURI );
/** Check the that specified number of bytes are available for reading or writing (depending on access type).*/
CPresult (*CheckAvailableBytes)( CPhandle hContent, CPuint nBytesRequested, CP_CHECKBYTESRESULTTYPE *eResult );
/** Seek to certain position in the content relative to the specified origin. */
CPresult (*SetPosition)( CPhandle hContent, CPint nOffset, CP_ORIGINTYPE eOrigin);
/** Retrieve the current position relative to the start of the content. */
CPresult (*GetPosition)( CPhandle hContent, CPuint *pPosition);
/** Retrieve data of the specified size from the content stream (advance content pointer by size of data).
Note: pipe client provides pointer. This function is appropriate for small high frequency reads. */
CPresult (*Read)( CPhandle hContent, CPbyte *pData, CPuint nSize);
/** Retrieve a buffer allocated by the pipe that contains the requested number of bytes.
Buffer contains the next block of bytes, as specified by nSize, of the content. nSize also
returns the size of the block actually read. Content pointer advances the by the returned size.
Note: pipe provides pointer. This function is appropriate for large reads. The client must call
ReleaseReadBuffer when done with buffer.
In some cases the requested block may not reside in contiguous memory within the
pipe implementation. For instance if the pipe leverages a circular buffer then the requested
block may straddle the boundary of the circular buffer. By default a pipe implementation
performs a copy in this case to provide the block to the pipe client in one contiguous buffer.
If, however, the client sets bForbidCopy, then the pipe returns only those bytes preceding the memory
boundary. Here the client may retrieve the data in segments over successive calls. */
CPresult (*ReadBuffer)( CPhandle hContent, CPbyte **ppBuffer, CPuint *nSize, CPbool bForbidCopy);
/** Release a buffer obtained by ReadBuffer back to the pipe. */
CPresult (*ReleaseReadBuffer)(CPhandle hContent, CPbyte *pBuffer);
/** Write data of the specified size to the content (advance content pointer by size of data).
Note: pipe client provides pointer. This function is appropriate for small high frequency writes. */
CPresult (*Write)( CPhandle hContent, CPbyte *data, CPuint nSize);
/** Retrieve a buffer allocated by the pipe used to write data to the content.
Client will fill buffer with output data. Note: pipe provides pointer. This function is appropriate
for large writes. The client must call WriteBuffer when done it has filled the buffer with data.*/
CPresult (*GetWriteBuffer)( CPhandle hContent, CPbyte **ppBuffer, CPuint nSize);
/** Deliver a buffer obtained via GetWriteBuffer to the pipe. Pipe will write the
the contents of the buffer to content and advance content pointer by the size of the buffer */
CPresult (*WriteBuffer)( CPhandle hContent, CPbyte *pBuffer, CPuint nFilledSize);
/** Register a per-handle client callback with the content pipe. */
CPresult (*RegisterCallback)( CPhandle hContent, CPresult (*ClientCallback)(CP_EVENTTYPE eEvent, CPuint iParam));
} CP_PIPETYPE;
#endif

Some files were not shown because too many files have changed in this diff Show More