mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
0eda110956
@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -27,7 +27,10 @@ UNIFIED_SOURCES += [
|
||||
'wrappers/H264Converter.cpp'
|
||||
]
|
||||
|
||||
DIRS += ['agnostic/gmp']
|
||||
DIRS += [
|
||||
'agnostic/gmp',
|
||||
'omx'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WMF']:
|
||||
DIRS += [ 'wmf' ];
|
||||
|
353
dom/media/platforms/omx/GonkOmxPlatformLayer.cpp
Normal file
353
dom/media/platforms/omx/GonkOmxPlatformLayer.cpp
Normal 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
|
131
dom/media/platforms/omx/GonkOmxPlatformLayer.h
Normal file
131
dom/media/platforms/omx/GonkOmxPlatformLayer.h
Normal 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_
|
861
dom/media/platforms/omx/OmxDataDecoder.cpp
Normal file
861
dom/media/platforms/omx/OmxDataDecoder.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
204
dom/media/platforms/omx/OmxDataDecoder.h
Normal file
204
dom/media/platforms/omx/OmxDataDecoder.h
Normal 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_ */
|
49
dom/media/platforms/omx/OmxDecoderModule.cpp
Normal file
49
dom/media/platforms/omx/OmxDecoderModule.cpp
Normal 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");
|
||||
}
|
||||
|
||||
}
|
37
dom/media/platforms/omx/OmxDecoderModule.h
Normal file
37
dom/media/platforms/omx/OmxDecoderModule.h
Normal 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_
|
67
dom/media/platforms/omx/OmxPlatformLayer.h
Normal file
67
dom/media/platforms/omx/OmxPlatformLayer.h
Normal 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_
|
335
dom/media/platforms/omx/OmxPromiseLayer.cpp
Normal file
335
dom/media/platforms/omx/OmxPromiseLayer.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
221
dom/media/platforms/omx/OmxPromiseLayer.h
Normal file
221
dom/media/platforms/omx/OmxPromiseLayer.h
Normal 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_ */
|
46
dom/media/platforms/omx/moz.build
Normal file
46
dom/media/platforms/omx/moz.build
Normal 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'
|
@ -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) {
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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":
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
39
js/src/jit-test/tests/basic/iterable-error-messages.js
Normal file
39
js/src/jit-test/tests/basic/iterable-error-messages.js
Normal 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");
|
@ -3098,6 +3098,12 @@ BaselineCompiler::emit_JSOP_CALL()
|
||||
return emitCall();
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_CALLITER()
|
||||
{
|
||||
return emitCall();
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_NEW()
|
||||
{
|
||||
|
@ -156,6 +156,7 @@ namespace jit {
|
||||
_(JSOP_INITALIASEDLEXICAL) \
|
||||
_(JSOP_UNINITIALIZED) \
|
||||
_(JSOP_CALL) \
|
||||
_(JSOP_CALLITER) \
|
||||
_(JSOP_FUNCALL) \
|
||||
_(JSOP_FUNAPPLY) \
|
||||
_(JSOP_NEW) \
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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("(...)");
|
||||
|
@ -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 */
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
9
layout/reftests/box-sizing/intrinsic-1g.html
Normal file
9
layout/reftests/box-sizing/intrinsic-1g.html
Normal 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>
|
9
layout/reftests/box-sizing/intrinsic-1h.html
Normal file
9
layout/reftests/box-sizing/intrinsic-1h.html
Normal 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>
|
12
layout/reftests/box-sizing/intrinsic-1i.html
Normal file
12
layout/reftests/box-sizing/intrinsic-1i.html
Normal 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>
|
10
layout/reftests/box-sizing/intrinsic-1j.html
Normal file
10
layout/reftests/box-sizing/intrinsic-1j.html
Normal 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>
|
10
layout/reftests/box-sizing/intrinsic-1k.html
Normal file
10
layout/reftests/box-sizing/intrinsic-1k.html
Normal 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>
|
13
layout/reftests/box-sizing/intrinsic-1l.html
Normal file
13
layout/reftests/box-sizing/intrinsic-1l.html
Normal 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>
|
10
layout/reftests/box-sizing/intrinsic-1m.html
Normal file
10
layout/reftests/box-sizing/intrinsic-1m.html
Normal 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>
|
10
layout/reftests/box-sizing/intrinsic-1n.html
Normal file
10
layout/reftests/box-sizing/intrinsic-1n.html
Normal 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>
|
13
layout/reftests/box-sizing/intrinsic-1o.html
Normal file
13
layout/reftests/box-sizing/intrinsic-1o.html
Normal 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>
|
@ -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
|
||||
|
12
layout/reftests/bugs/1230466.html
Normal file
12
layout/reftests/bugs/1230466.html
Normal 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>
|
@ -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
|
||||
|
@ -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>
|
20
layout/reftests/text-decoration/emphasis-style-dynamic.html
Normal file
20
layout/reftests/text-decoration/emphasis-style-dynamic.html
Normal 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>
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>●</rt>験<rt>●</rt>テ<rt>●</rt>ス<rt>●</rt>ト<rt>●</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__))
|
@ -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>●</rt>験<rt>●</rt>テ<rt>●</rt>ス<rt>●</rt>ト<rt>●</rt></ruby></div>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>●</rt>験<rt>●</rt>テ<rt>●</rt>ス<rt>●</rt>ト<rt>●</rt></ruby></div>
|
@ -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>
|
@ -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>
|
@ -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>●</rt>験<rt>●</rt>テ<rt>●</rt>ス<rt>●</rt>ト<rt>●</rt></ruby></div>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>●</rt>験<rt>●</rt>テ<rt>●</rt>ス<rt>●</rt>ト<rt>●</rt></ruby></div>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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, ©))
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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; */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
1311
media/openmax_il/il112/OMX_Audio.h
Normal file
1311
media/openmax_il/il112/OMX_Audio.h
Normal file
File diff suppressed because it is too large
Load Diff
579
media/openmax_il/il112/OMX_Component.h
Normal file
579
media/openmax_il/il112/OMX_Component.h
Normal 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 */
|
61
media/openmax_il/il112/OMX_ComponentExt.h
Normal file
61
media/openmax_il/il112/OMX_ComponentExt.h
Normal 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 */
|
195
media/openmax_il/il112/OMX_ContentPipe.h
Normal file
195
media/openmax_il/il112/OMX_ContentPipe.h
Normal 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
Loading…
Reference in New Issue
Block a user