зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 55ea2c2fb857 (bug 1034368
) for bustage
This commit is contained in:
Родитель
2f0df889d5
Коммит
493e418c84
|
@ -399,32 +399,6 @@ GMPChild::GetGMPTimers()
|
|||
return mTimerChild;
|
||||
}
|
||||
|
||||
PGMPStorageChild*
|
||||
GMPChild::AllocPGMPStorageChild()
|
||||
{
|
||||
return new GMPStorageChild(this);
|
||||
}
|
||||
|
||||
bool
|
||||
GMPChild::DeallocPGMPStorageChild(PGMPStorageChild* aActor)
|
||||
{
|
||||
mStorage = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
GMPStorageChild*
|
||||
GMPChild::GetGMPStorage()
|
||||
{
|
||||
if (!mStorage) {
|
||||
PGMPStorageChild* sc = SendPGMPStorageConstructor();
|
||||
if (!sc) {
|
||||
return nullptr;
|
||||
}
|
||||
mStorage = static_cast<GMPStorageChild*>(sc);
|
||||
}
|
||||
return mStorage;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPChild::RecvCrashPluginNow()
|
||||
{
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "mozilla/gmp/PGMPChild.h"
|
||||
#include "GMPSharedMemManager.h"
|
||||
#include "GMPTimerChild.h"
|
||||
#include "GMPStorageChild.h"
|
||||
#include "gmp-async-shutdown.h"
|
||||
#include "gmp-entrypoints.h"
|
||||
#include "prlink.h"
|
||||
|
@ -38,7 +37,6 @@ public:
|
|||
|
||||
// Main thread only.
|
||||
GMPTimerChild* GetGMPTimers();
|
||||
GMPStorageChild* GetGMPStorage();
|
||||
|
||||
// GMPSharedMem
|
||||
virtual void CheckThread() MOZ_OVERRIDE;
|
||||
|
@ -69,9 +67,6 @@ private:
|
|||
virtual PGMPTimerChild* AllocPGMPTimerChild() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual PGMPStorageChild* AllocPGMPStorageChild() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPStorageChild(PGMPStorageChild* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvCrashPluginNow() MOZ_OVERRIDE;
|
||||
virtual bool RecvBeginAsyncShutdown() MOZ_OVERRIDE;
|
||||
|
||||
|
@ -80,7 +75,6 @@ private:
|
|||
|
||||
GMPAsyncShutdown* mAsyncShutdown;
|
||||
nsRefPtr<GMPTimerChild> mTimerChild;
|
||||
nsRefPtr<GMPStorageChild> mStorage;
|
||||
|
||||
PRLibrary* mLib;
|
||||
GMPGetAPIFunc mGetAPIFunc;
|
||||
|
|
|
@ -171,10 +171,6 @@ GMPParent::CloseIfUnused()
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Any async shutdown must be complete. Shutdown GMPStorage.
|
||||
for (size_t i = mStorage.Length(); i > 0; i--) {
|
||||
mStorage[i - 1]->Shutdown();
|
||||
}
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
@ -222,14 +218,17 @@ GMPParent::CloseActive(bool aDieWhenUnloaded)
|
|||
mVideoDecoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mVideoEncoders.Length(); i > 0; i--) {
|
||||
mVideoEncoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mDecryptors.Length(); i > 0; i--) {
|
||||
mDecryptors[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mAudioDecoders.Length(); i > 0; i--) {
|
||||
mAudioDecoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
@ -238,9 +237,6 @@ GMPParent::CloseActive(bool aDieWhenUnloaded)
|
|||
mTimers[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Note: We don't shutdown storage API objects here, as they need to
|
||||
// work during async shutdown of GMPs.
|
||||
|
||||
// Note: the shutdown of the codecs is async! don't kill
|
||||
// the plugin-container until they're all safely shut down via
|
||||
// CloseIfUnused();
|
||||
|
@ -662,29 +658,6 @@ GMPParent::DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor)
|
|||
return true;
|
||||
}
|
||||
|
||||
PGMPStorageParent*
|
||||
GMPParent::AllocPGMPStorageParent()
|
||||
{
|
||||
GMPStorageParent* p = new GMPStorageParent(mOrigin, this);
|
||||
mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
|
||||
return p;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
|
||||
{
|
||||
GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
|
||||
p->Shutdown();
|
||||
mStorage.RemoveElement(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* actor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "GMPVideoDecoderParent.h"
|
||||
#include "GMPVideoEncoderParent.h"
|
||||
#include "GMPTimerParent.h"
|
||||
#include "GMPStorageParent.h"
|
||||
#include "mozilla/gmp/PGMPParent.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nscore.h"
|
||||
|
@ -155,10 +154,6 @@ private:
|
|||
virtual PGMPAudioDecoderParent* AllocPGMPAudioDecoderParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvPGMPStorageConstructor(PGMPStorageParent* actor) MOZ_OVERRIDE;
|
||||
virtual PGMPStorageParent* AllocPGMPStorageParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPStorageParent(PGMPStorageParent* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvPGMPTimerConstructor(PGMPTimerParent* actor) MOZ_OVERRIDE;
|
||||
virtual PGMPTimerParent* AllocPGMPTimerParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) MOZ_OVERRIDE;
|
||||
|
@ -182,7 +177,6 @@ private:
|
|||
nsTArray<nsRefPtr<GMPDecryptorParent>> mDecryptors;
|
||||
nsTArray<nsRefPtr<GMPAudioDecoderParent>> mAudioDecoders;
|
||||
nsTArray<nsRefPtr<GMPTimerParent>> mTimers;
|
||||
nsTArray<nsRefPtr<GMPStorageParent>> mStorage;
|
||||
nsCOMPtr<nsIThread> mGMPThread;
|
||||
// Origin the plugin is assigned to, or empty if the the plugin is not
|
||||
// assigned to an origin.
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPPlatform.h"
|
||||
#include "GMPStorageChild.h"
|
||||
#include "GMPTimerChild.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
@ -152,30 +151,6 @@ CreateMutex(GMPMutex** aMutex)
|
|||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
CreateRecord(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
if (sMainLoop != MessageLoop::current()) {
|
||||
NS_WARNING("GMP called CreateRecord() on non-main thread!");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE) {
|
||||
NS_WARNING("GMP tried to CreateRecord with too long record name");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
GMPStorageChild* storage = sChild->GetGMPStorage();
|
||||
if (!storage) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
MOZ_ASSERT(storage);
|
||||
return storage->CreateRecord(nsDependentCString(aRecordName, aRecordNameSize),
|
||||
aOutRecord,
|
||||
aClient);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS)
|
||||
{
|
||||
|
@ -209,7 +184,7 @@ InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
|
|||
aPlatformAPI.runonmainthread = &RunOnMainThread;
|
||||
aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
|
||||
aPlatformAPI.createmutex = &CreateMutex;
|
||||
aPlatformAPI.createrecord = &CreateRecord;
|
||||
aPlatformAPI.createrecord = nullptr;
|
||||
aPlatformAPI.settimer = &SetTimerOnMainThread;
|
||||
aPlatformAPI.getcurrenttime = &GetClock;
|
||||
}
|
||||
|
|
|
@ -1,282 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPStorageChild.h"
|
||||
#include "GMPChild.h"
|
||||
#include "gmp-storage.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
GMPRecordImpl::GMPRecordImpl(GMPStorageChild* aOwner,
|
||||
const nsCString& aName,
|
||||
GMPRecordClient* aClient)
|
||||
: mName(aName)
|
||||
, mClient(aClient)
|
||||
, mOwner(aOwner)
|
||||
, mIsClosed(true)
|
||||
{
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Open()
|
||||
{
|
||||
if (!mIsClosed) {
|
||||
return GMPRecordInUse;
|
||||
}
|
||||
return mOwner->Open(this);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::OpenComplete(GMPErr aStatus)
|
||||
{
|
||||
mIsClosed = false;
|
||||
mClient->OpenComplete(aStatus);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Read()
|
||||
{
|
||||
if (mIsClosed) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return mOwner->Read(this);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aBytes,
|
||||
uint32_t aLength)
|
||||
{
|
||||
mClient->ReadComplete(aStatus, aBytes, aLength);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Write(const uint8_t* aData, uint32_t aDataSize)
|
||||
{
|
||||
if (mIsClosed) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return mOwner->Write(this, aData, aDataSize);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::WriteComplete(GMPErr aStatus)
|
||||
{
|
||||
mClient->WriteComplete(aStatus);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Close()
|
||||
{
|
||||
nsRefPtr<GMPRecordImpl> kungfuDeathGrip(this);
|
||||
|
||||
if (!mIsClosed) {
|
||||
// Delete the storage child's reference to us.
|
||||
mOwner->Close(this);
|
||||
// Owner should callback MarkClosed().
|
||||
MOZ_ASSERT(mIsClosed);
|
||||
}
|
||||
|
||||
// Delete our self reference.
|
||||
Release();
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::MarkClosed()
|
||||
{
|
||||
mIsClosed = true;
|
||||
}
|
||||
|
||||
GMPStorageChild::GMPStorageChild(GMPChild* aPlugin)
|
||||
: mPlugin(aPlugin)
|
||||
, mShutdown(false)
|
||||
{
|
||||
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::CreateRecord(const nsCString& aRecordName,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
MOZ_ASSERT(aRecordName.Length() && aOutRecord);
|
||||
nsRefPtr<GMPRecordImpl> record(new GMPRecordImpl(this, aRecordName, aClient));
|
||||
mRecords.Put(aRecordName, record); // Addrefs
|
||||
|
||||
// The GMPRecord holds a self reference until the GMP calls Close() on
|
||||
// it. This means the object is always valid (even if neutered) while
|
||||
// the GMP expects it to be.
|
||||
record.forget(aOutRecord);
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Open(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (!SendOpen(aRecord->Name())) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Read(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (!SendRead(aRecord->Name())) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Write(GMPRecordImpl* aRecord,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (aDataSize > GMP_MAX_RECORD_SIZE) {
|
||||
return GMPQuotaExceededErr;
|
||||
}
|
||||
nsTArray<uint8_t> data;
|
||||
data.AppendElements(aData, aDataSize);
|
||||
if (!SendWrite(aRecord->Name(), data)) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Close(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (!mRecords.Contains(aRecord->Name())) {
|
||||
// Already closed.
|
||||
return GMPClosedErr;
|
||||
}
|
||||
|
||||
GMPErr rv = GMPNoErr;
|
||||
if (!mShutdown && !SendClose(aRecord->Name())) {
|
||||
rv = GMPGenericErr;
|
||||
}
|
||||
|
||||
aRecord->MarkClosed();
|
||||
mRecords.Remove(aRecord->Name());
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->OpenComplete(aStatus);
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvReadComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus,
|
||||
const InfallibleTArray<uint8_t>& aBytes)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->ReadComplete(aStatus,
|
||||
aBytes.Elements(),
|
||||
aBytes.Length());
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->WriteComplete(aStatus);
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvShutdown()
|
||||
{
|
||||
// Block any new storage requests, and thus any messages back to the
|
||||
// parent. We don't delete any objects here, as that may invalidate
|
||||
// GMPRecord pointers held by the GMP.
|
||||
mShutdown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -1,95 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GMPStorageChild_h_
|
||||
#define GMPStorageChild_h_
|
||||
|
||||
#include "mozilla/gmp/PGMPStorageChild.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
class GMPChild;
|
||||
class GMPStorageChild;
|
||||
|
||||
class GMPRecordImpl : public GMPRecord
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPRecordImpl)
|
||||
|
||||
GMPRecordImpl(GMPStorageChild* aOwner,
|
||||
const nsCString& aName,
|
||||
GMPRecordClient* aClient);
|
||||
|
||||
// GMPRecord.
|
||||
virtual GMPErr Open() MOZ_OVERRIDE;
|
||||
virtual GMPErr Read() MOZ_OVERRIDE;
|
||||
virtual GMPErr Write(const uint8_t* aData,
|
||||
uint32_t aDataSize) MOZ_OVERRIDE;
|
||||
virtual GMPErr Close() MOZ_OVERRIDE;
|
||||
|
||||
const nsCString& Name() const { return mName; }
|
||||
|
||||
void OpenComplete(GMPErr aStatus);
|
||||
void ReadComplete(GMPErr aStatus, const uint8_t* aBytes, uint32_t aLength);
|
||||
void WriteComplete(GMPErr aStatus);
|
||||
|
||||
void MarkClosed();
|
||||
|
||||
private:
|
||||
~GMPRecordImpl() {}
|
||||
const nsCString mName;
|
||||
GMPRecordClient* const mClient;
|
||||
GMPStorageChild* const mOwner;
|
||||
bool mIsClosed;
|
||||
};
|
||||
|
||||
class GMPStorageChild : public PGMPStorageChild
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPStorageChild)
|
||||
|
||||
GMPStorageChild(GMPChild* aPlugin);
|
||||
|
||||
GMPErr CreateRecord(const nsCString& aRecordName,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient);
|
||||
|
||||
GMPErr Open(GMPRecordImpl* aRecord);
|
||||
|
||||
GMPErr Read(GMPRecordImpl* aRecord);
|
||||
|
||||
GMPErr Write(GMPRecordImpl* aRecord,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize);
|
||||
|
||||
GMPErr Close(GMPRecordImpl* aRecord);
|
||||
|
||||
protected:
|
||||
~GMPStorageChild() {}
|
||||
|
||||
// PGMPStorageChild
|
||||
virtual bool RecvOpenComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus) MOZ_OVERRIDE;
|
||||
virtual bool RecvReadComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus,
|
||||
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
|
||||
virtual bool RecvWriteComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus) MOZ_OVERRIDE;
|
||||
virtual bool RecvShutdown() MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
|
||||
GMPChild* mPlugin;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GMPStorageChild_h_
|
|
@ -1,287 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPStorageParent.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "plhash.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "GMPParent.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "mozilla/unused.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* GetGMPLog();
|
||||
|
||||
#define LOGD(msg) PR_LOG(GetGMPLog(), PR_LOG_DEBUG, msg)
|
||||
#define LOG(level, msg) PR_LOG(GetGMPLog(), (level), msg)
|
||||
#else
|
||||
#define LOGD(msg)
|
||||
#define LOG(level, msg)
|
||||
#endif
|
||||
|
||||
#ifdef __CLASS__
|
||||
#undef __CLASS__
|
||||
#endif
|
||||
#define __CLASS__ "GMPParent"
|
||||
|
||||
namespace gmp {
|
||||
|
||||
class GetTempDirTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD Run() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
tmpFile->GetPath(mPath);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsString mPath;
|
||||
};
|
||||
|
||||
// We store the records in files in the system temp dir.
|
||||
static nsresult
|
||||
GetGMPStorageDir(nsIFile** aTempDir, const nsString& aOrigin)
|
||||
{
|
||||
if (NS_WARN_IF(!aTempDir)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Directory service is main thread only...
|
||||
nsRefPtr<GetTempDirTask> task = new GetTempDirTask();
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
mozilla::SyncRunnable::DispatchToThread(mainThread, task);
|
||||
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsresult rv = NS_NewLocalFile(task->mPath, false, getter_AddRefs(tmpFile));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = tmpFile->AppendNative(nsDependentCString("mozilla-gmp-storage"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// TODO: When aOrigin is the same node-id as the GMP sees in the child
|
||||
// process (a UUID or somesuch), we can just append it un-hashed here.
|
||||
// This should reduce the chance of hash collsions exposing data.
|
||||
nsAutoString nodeIdHash;
|
||||
nodeIdHash.AppendInt(HashString(aOrigin.get()));
|
||||
rv = tmpFile->Append(nodeIdHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
||||
if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
tmpFile.forget(aTempDir);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
GMPStorageParent::GMPStorageParent(const nsString& aOrigin,
|
||||
GMPParent* aPlugin)
|
||||
: mOrigin(aOrigin)
|
||||
, mPlugin(aPlugin)
|
||||
, mShutdown(false)
|
||||
{
|
||||
}
|
||||
|
||||
enum OpenFileMode { ReadWrite, Truncate };
|
||||
|
||||
nsresult
|
||||
OpenStorageFile(const nsCString& aRecordName,
|
||||
const nsString& aNodeId,
|
||||
const OpenFileMode aMode,
|
||||
PRFileDesc** aOutFD)
|
||||
{
|
||||
MOZ_ASSERT(aOutFD);
|
||||
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), aNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString recordNameHash;
|
||||
recordNameHash.AppendInt(HashString(aRecordName.get()));
|
||||
f->Append(recordNameHash);
|
||||
|
||||
auto mode = PR_RDWR | PR_CREATE_FILE;
|
||||
if (aMode == Truncate) {
|
||||
mode |= PR_TRUNCATE;
|
||||
}
|
||||
|
||||
return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvOpen(const nsCString& aRecordName)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mOrigin.EqualsASCII("null")) {
|
||||
// Refuse to open storage if the page is the "null" origin; if the page
|
||||
// is opened from disk.
|
||||
NS_WARNING("Refusing to open storage for null origin");
|
||||
unused << SendOpenComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aRecordName.IsEmpty() || mFiles.Contains(aRecordName)) {
|
||||
unused << SendOpenComplete(aRecordName, GMPRecordInUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = nullptr;
|
||||
nsresult rv = OpenStorageFile(aRecordName, mOrigin, ReadWrite, &fd);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to open storage file.");
|
||||
unused << SendOpenComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
mFiles.Put(aRecordName, fd);
|
||||
unused << SendOpenComplete(aRecordName, GMPNoErr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvRead(const nsCString& aRecordName)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
nsTArray<uint8_t> data;
|
||||
if (!fd) {
|
||||
unused << SendReadComplete(aRecordName, GMPClosedErr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
|
||||
PR_Seek(fd, 0, PR_SEEK_SET);
|
||||
|
||||
if (len > GMP_MAX_RECORD_SIZE) {
|
||||
// Refuse to read big records.
|
||||
unused << SendReadComplete(aRecordName, GMPQuotaExceededErr, data);
|
||||
return true;
|
||||
}
|
||||
data.SetLength(len);
|
||||
auto bytesRead = PR_Read(fd, data.Elements(), len);
|
||||
auto res = (bytesRead == len) ? GMPNoErr : GMPGenericErr;
|
||||
unused << SendReadComplete(aRecordName, res, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvWrite(const nsCString& aRecordName,
|
||||
const InfallibleTArray<uint8_t>& aBytes)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
|
||||
unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
unused << SendWriteComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write operations overwrite the entire record. So re-open the file
|
||||
// in truncate mode, to clear its contents.
|
||||
PR_Close(fd);
|
||||
mFiles.Remove(aRecordName);
|
||||
if (NS_FAILED(OpenStorageFile(aRecordName, mOrigin, Truncate, &fd))) {
|
||||
unused << SendWriteComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
mFiles.Put(aRecordName, fd);
|
||||
|
||||
int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
|
||||
auto res = (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
|
||||
unused << SendWriteComplete(aRecordName, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvClose(const nsCString& aRecordName)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
return true;
|
||||
}
|
||||
PR_Close(fd);
|
||||
mFiles.Remove(aRecordName);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
|
||||
{
|
||||
PR_Close(entry);
|
||||
return PL_DHASH_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
GMPStorageParent::Shutdown()
|
||||
{
|
||||
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
|
||||
|
||||
if (mShutdown) {
|
||||
return;
|
||||
}
|
||||
mShutdown = true;
|
||||
unused << SendShutdown();
|
||||
|
||||
mFiles.Enumerate(CloseFile, nullptr);
|
||||
MOZ_ASSERT(!mFiles.Count());
|
||||
}
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -1,45 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GMPStorageParent_h_
|
||||
#define GMPStorageParent_h_
|
||||
|
||||
#include "mozilla/gmp/PGMPStorageParent.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "prio.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
class GMPParent;
|
||||
|
||||
class GMPStorageParent : public PGMPStorageParent {
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
|
||||
GMPStorageParent(const nsString& aOrigin, GMPParent* aPlugin);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
protected:
|
||||
virtual bool RecvOpen(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual bool RecvWrite(const nsCString& aRecordName,
|
||||
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
|
||||
virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
|
||||
const nsString mOrigin;
|
||||
nsRefPtr<GMPParent> mPlugin;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GMPStorageParent_h_
|
|
@ -9,7 +9,6 @@ include protocol PCrashReporter;
|
|||
include protocol PGMPDecryptor;
|
||||
include protocol PGMPAudioDecoder;
|
||||
include protocol PGMPTimer;
|
||||
include protocol PGMPStorage;
|
||||
|
||||
using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
|
||||
|
||||
|
@ -24,12 +23,10 @@ intr protocol PGMP
|
|||
manages PGMPVideoEncoder;
|
||||
manages PCrashReporter;
|
||||
manages PGMPTimer;
|
||||
manages PGMPStorage;
|
||||
|
||||
parent:
|
||||
async PCrashReporter(NativeThreadId tid);
|
||||
async PGMPTimer();
|
||||
async PGMPStorage();
|
||||
|
||||
async AsyncShutdownComplete();
|
||||
async AsyncShutdownRequired();
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
include protocol PGMP;
|
||||
include GMPTypes;
|
||||
|
||||
using GMPErr from "gmp-errors.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
async protocol PGMPStorage
|
||||
{
|
||||
manager PGMP;
|
||||
|
||||
child:
|
||||
OpenComplete(nsCString aRecordName, GMPErr aStatus);
|
||||
ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
|
||||
WriteComplete(nsCString aRecordName, GMPErr aStatus);
|
||||
Shutdown();
|
||||
|
||||
parent:
|
||||
Open(nsCString aRecordName);
|
||||
Read(nsCString aRecordName);
|
||||
Write(nsCString aRecordName, uint8_t[] aBytes);
|
||||
Close(nsCString aRecordName);
|
||||
__delete__();
|
||||
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -69,8 +69,6 @@ typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread);
|
|||
typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask);
|
||||
typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask);
|
||||
typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex);
|
||||
|
||||
// Call on main thread only.
|
||||
typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
|
|
|
@ -20,31 +20,16 @@
|
|||
#include "gmp-errors.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Maximum size of a record, in bytes.
|
||||
#define GMP_MAX_RECORD_SIZE (1024 * 1024 * 1024)
|
||||
|
||||
// Maximum length of a record name.
|
||||
#define GMP_MAX_RECORD_NAME_SIZE 200
|
||||
|
||||
// Provides basic per-origin storage for CDMs. GMPRecord instances can be
|
||||
// retrieved by calling GMPPlatformAPI->openstorage. Multiple GMPRecords
|
||||
// with different names can be open at once, but a single record can only
|
||||
// be opened by one client at a time. This interface is asynchronous, with
|
||||
// results being returned via callbacks to the GMPRecordClient pointer
|
||||
// provided to the GMPPlatformAPI->openstorage call, on the main thread.
|
||||
//
|
||||
// Lifecycle: Once opened, the GMPRecord object remains allocated until
|
||||
// GMPRecord::Close() is called. If any GMPRecord function, either
|
||||
// synchronously or asynchronously through a GMPRecordClient callback,
|
||||
// returns an error, the GMP is responsible for calling Close() on the
|
||||
// GMPRecord to delete the GMPRecord object's memory. If your GMP does not
|
||||
// call Close(), the GMPRecord's memory will leak.
|
||||
class GMPRecord {
|
||||
public:
|
||||
|
||||
// Opens the record. Calls OpenComplete() once the record is open.
|
||||
// Note: Only work when GMP is loading content from a webserver.
|
||||
// Does not work for web pages on loaded from disk.
|
||||
// Note: OpenComplete() is only called if this returns GMPNoErr.
|
||||
virtual GMPErr Open() = 0;
|
||||
|
||||
|
@ -54,15 +39,13 @@ public:
|
|||
virtual GMPErr Read() = 0;
|
||||
|
||||
// Writes aDataSize bytes of aData into the record, overwriting the
|
||||
// contents of the record, truncating it to aDataSize length.
|
||||
// Overwriting with 0 bytes "deletes" the record.
|
||||
// contents of the record. Overwriting with 0 bytes "deletes" the file.
|
||||
// Note: WriteComplete is only called if this returns GMPNoErr.
|
||||
virtual GMPErr Write(const uint8_t* aData, uint32_t aDataSize) = 0;
|
||||
|
||||
// Closes a record, deletes the GMPRecord object. The GMPRecord object
|
||||
// must not be used after this is called, request a new one with
|
||||
// GMPPlatformAPI->openstorage to re-open this record. Cancels all
|
||||
// callbacks.
|
||||
// Closes a record. GMPRecord object must not be used after this is
|
||||
// called, request a new one with GMPPlatformAPI->openstorage to re-open
|
||||
// this record. Cancels all callbacks.
|
||||
virtual GMPErr Close() = 0;
|
||||
|
||||
virtual ~GMPRecord() {}
|
||||
|
@ -78,8 +61,7 @@ class GMPRecordClient {
|
|||
// - GMPNoErr - Record opened successfully. Record may be empty.
|
||||
// - GMPRecordInUse - This record is in use by another client.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
// Do not use the GMPRecord if aStatus is not GMPNoErr.
|
||||
virtual void OpenComplete(GMPErr aStatus) = 0;
|
||||
|
||||
// Response to a GMPRecord::Read() call, where aData is the record contents,
|
||||
|
@ -92,8 +74,7 @@ class GMPRecordClient {
|
|||
// - GMPRecordInUse - There are other operations or clients in use on
|
||||
// this record.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
// Do not continue to use the GMPRecord if aStatus is not GMPNoErr.
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) = 0;
|
||||
|
@ -103,8 +84,7 @@ class GMPRecordClient {
|
|||
// - GMPRecordInUse - There are other operations or clients in use on
|
||||
// this record.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
// Do not continue to use the GMPRecord if aStatus is not GMPNoErr.
|
||||
virtual void WriteComplete(GMPErr aStatus) = 0;
|
||||
|
||||
virtual ~GMPRecordClient() {}
|
||||
|
|
|
@ -46,8 +46,6 @@ EXPORTS += [
|
|||
'GMPProcessParent.h',
|
||||
'GMPService.h',
|
||||
'GMPSharedMemManager.h',
|
||||
'GMPStorageChild.h',
|
||||
'GMPStorageParent.h',
|
||||
'GMPTimerChild.h',
|
||||
'GMPTimerParent.h',
|
||||
'GMPVideoDecoderChild.h',
|
||||
|
@ -76,8 +74,6 @@ UNIFIED_SOURCES += [
|
|||
'GMPProcessParent.cpp',
|
||||
'GMPService.cpp',
|
||||
'GMPSharedMemManager.cpp',
|
||||
'GMPStorageChild.cpp',
|
||||
'GMPStorageParent.cpp',
|
||||
'GMPTimerChild.cpp',
|
||||
'GMPTimerParent.cpp',
|
||||
'GMPVideoDecoderChild.cpp',
|
||||
|
@ -95,7 +91,6 @@ IPDL_SOURCES += [
|
|||
'PGMP.ipdl',
|
||||
'PGMPAudioDecoder.ipdl',
|
||||
'PGMPDecryptor.ipdl',
|
||||
'PGMPStorage.ipdl',
|
||||
'PGMPTimer.ipdl',
|
||||
'PGMPVideoDecoder.ipdl',
|
||||
'PGMPVideoEncoder.ipdl',
|
||||
|
|
Загрузка…
Ссылка в новой задаче