Mypal/dom/media/gtest/TestGMPRemoveAndDelete.cpp

491 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 8; 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 "GMPService.h"
#include "GMPTestMonitor.h"
#include "gmp-api/gmp-video-host.h"
#include "gtest/gtest.h"
#include "mozilla/Services.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIObserverService.h"
#include "GMPVideoDecoderProxy.h"
#include "GMPServiceParent.h"
#include "GMPService.h"
#include "GMPUtils.h"
#include "mozilla/StaticPtr.h"
#include "MediaPrefs.h"
#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
#define GMP_DELETED_TOPIC "gmp-directory-deleted"
#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
using namespace mozilla;
using namespace mozilla::gmp;
class GMPRemoveTest : public nsIObserver
, public GMPVideoDecoderCallbackProxy
{
public:
GMPRemoveTest();
NS_DECL_THREADSAFE_ISUPPORTS
// Called when a GMP plugin directory has been successfully deleted.
// |aData| will contain the directory path.
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) override;
// Create a new GMP plugin directory that we can trash and add it to the GMP
// service. Remove the original plugin directory. Original plugin directory
// gets re-added at destruction.
void Setup();
bool CreateVideoDecoder(nsCString aNodeId = EmptyCString());
void CloseVideoDecoder();
void DeletePluginDirectory(bool aCanDefer);
// Decode a dummy frame.
GMPErr Decode();
// Wait until TestMonitor has been signaled.
void Wait();
// Did we get a Terminated() callback from the plugin?
bool IsTerminated();
// From GMPVideoDecoderCallbackProxy
// Set mDecodeResult; unblock TestMonitor.
virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
virtual void Error(GMPErr aError) override;
// From GMPVideoDecoderCallbackProxy
// We expect this to be called when a plugin has been forcibly closed.
virtual void Terminated() override;
// Ignored GMPVideoDecoderCallbackProxy members
virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
virtual void InputDataExhausted() override {}
virtual void DrainComplete() override {}
virtual void ResetComplete() override {}
private:
virtual ~GMPRemoveTest();
void gmp_Decode();
void gmp_GetVideoDecoder(nsCString aNodeId,
GMPVideoDecoderProxy** aOutDecoder,
GMPVideoHost** aOutHost);
void GeneratePlugin();
GMPTestMonitor mTestMonitor;
nsCOMPtr<nsIThread> mGMPThread;
bool mIsTerminated;
// Path to the cloned GMP we have created.
nsString mTmpPath;
nsCOMPtr<nsIFile> mTmpDir;
// Path to the original GMP. Store so that we can re-add it after we're done
// testing.
nsString mOriginalPath;
GMPVideoDecoderProxy* mDecoder;
GMPVideoHost* mHost;
GMPErr mDecodeResult;
};
/*
* Simple test that the plugin is deleted when forcibly removed and deleted.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteForcedSimple)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
test->DeletePluginDirectory(false /* force immediate */);
test->Wait();
}
/*
* Simple test that the plugin is deleted when deferred deletion is allowed.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredSimple)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
test->DeletePluginDirectory(true /* can defer */);
test->Wait();
}
/*
* Test that the plugin is unavailable immediately after a forced
* RemoveAndDelete, and that the plugin is deleted afterwards.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Test that we can decode a frame.
GMPErr err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
test->DeletePluginDirectory(false /* force immediate */);
test->Wait();
// Test that the VideoDecoder is no longer available.
EXPECT_FALSE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Test that we were notified of the plugin's destruction.
EXPECT_TRUE(test->IsTerminated());
}
/*
* Test that the plugin is still usable after a deferred RemoveAndDelete, and
* that the plugin is deleted afterwards.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredInUse)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Make sure decoding works before we do anything.
GMPErr err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
test->DeletePluginDirectory(true /* can defer */);
// Test that decoding still works.
err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
// Test that this origin is still able to fetch the video decoder.
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
test->CloseVideoDecoder();
test->Wait();
}
static StaticRefPtr<GeckoMediaPluginService> gService;
static StaticRefPtr<GeckoMediaPluginServiceParent> gServiceParent;
static GeckoMediaPluginService*
GetService()
{
if (!gService) {
RefPtr<GeckoMediaPluginService> service =
GeckoMediaPluginService::GetGeckoMediaPluginService();
gService = service;
}
return gService.get();
}
static GeckoMediaPluginServiceParent*
GetServiceParent()
{
if (!gServiceParent) {
RefPtr<GeckoMediaPluginServiceParent> parent =
GeckoMediaPluginServiceParent::GetSingleton();
gServiceParent = parent;
}
return gServiceParent.get();
}
NS_IMPL_ISUPPORTS(GMPRemoveTest, nsIObserver)
GMPRemoveTest::GMPRemoveTest()
: mIsTerminated(false)
, mDecoder(nullptr)
, mHost(nullptr)
{
}
GMPRemoveTest::~GMPRemoveTest()
{
bool exists;
EXPECT_TRUE(NS_SUCCEEDED(mTmpDir->Exists(&exists)) && !exists);
EXPECT_OK(GetServiceParent()->AddPluginDirectory(mOriginalPath));
}
void
GMPRemoveTest::Setup()
{
// Initialize media preferences.
MediaPrefs::GetSingleton();
GeneratePlugin();
GetService()->GetThread(getter_AddRefs(mGMPThread));
// Spin the event loop until the GMP service has had a chance to complete
// adding GMPs from MOZ_GMP_PATH. Otherwise, the RemovePluginDirectory()
// below may complete before we're finished adding GMPs from MOZ_GMP_PATH,
// and we'll end up not removing the GMP, and the test will fail.
RefPtr<AbstractThread> thread(GetServiceParent()->GetAbstractGMPThread());
EXPECT_TRUE(thread);
GMPTestMonitor* mon = &mTestMonitor;
GetServiceParent()->EnsureInitialized()->Then(thread, __func__,
[mon]() { mon->SetFinished(); },
[mon]() { mon->SetFinished(); }
);
mTestMonitor.AwaitFinished();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
GetServiceParent()->AsyncAddPluginDirectory(mTmpPath)->Then(thread, __func__,
[mon]() { mon->SetFinished(); },
[mon]() { mon->SetFinished(); }
);
mTestMonitor.AwaitFinished();
}
bool
GMPRemoveTest::CreateVideoDecoder(nsCString aNodeId)
{
GMPVideoHost* host;
GMPVideoDecoderProxy* decoder = nullptr;
mGMPThread->Dispatch(
NewNonOwningRunnableMethod<nsCString, GMPVideoDecoderProxy**, GMPVideoHost**>(
this, &GMPRemoveTest::gmp_GetVideoDecoder, aNodeId, &decoder, &host),
NS_DISPATCH_NORMAL);
mTestMonitor.AwaitFinished();
if (!decoder) {
return false;
}
GMPVideoCodec codec;
memset(&codec, 0, sizeof(codec));
codec.mGMPApiVersion = 33;
nsTArray<uint8_t> empty;
mGMPThread->Dispatch(
NewNonOwningRunnableMethod<const GMPVideoCodec&, const nsTArray<uint8_t>&, GMPVideoDecoderCallbackProxy*, int32_t>(
decoder, &GMPVideoDecoderProxy::InitDecode,
codec, empty, this, 1 /* core count */),
NS_DISPATCH_SYNC);
if (mDecoder) {
CloseVideoDecoder();
}
mDecoder = decoder;
mHost = host;
return true;
}
void
GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId,
GMPVideoDecoderProxy** aOutDecoder,
GMPVideoHost** aOutHost)
{
nsTArray<nsCString> tags;
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
tags.AppendElement(NS_LITERAL_CSTRING("fake"));
class Callback : public GetGMPVideoDecoderCallback
{
public:
Callback(GMPTestMonitor* aMonitor, GMPVideoDecoderProxy** aDecoder, GMPVideoHost** aHost)
: mMonitor(aMonitor), mDecoder(aDecoder), mHost(aHost) { }
virtual void Done(GMPVideoDecoderProxy* aDecoder, GMPVideoHost* aHost) override {
*mDecoder = aDecoder;
*mHost = aHost;
mMonitor->SetFinished();
}
private:
GMPTestMonitor* mMonitor;
GMPVideoDecoderProxy** mDecoder;
GMPVideoHost** mHost;
};
UniquePtr<GetGMPVideoDecoderCallback>
cb(new Callback(&mTestMonitor, aOutDecoder, aOutHost));
if (NS_FAILED(GetService()->GetGMPVideoDecoder(nullptr, &tags, aNodeId, Move(cb)))) {
mTestMonitor.SetFinished();
}
}
void
GMPRemoveTest::CloseVideoDecoder()
{
mGMPThread->Dispatch(
NewNonOwningRunnableMethod(mDecoder, &GMPVideoDecoderProxy::Close),
NS_DISPATCH_SYNC);
mDecoder = nullptr;
mHost = nullptr;
}
void
GMPRemoveTest::DeletePluginDirectory(bool aCanDefer)
{
GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer);
}
GMPErr
GMPRemoveTest::Decode()
{
mGMPThread->Dispatch(
NewNonOwningRunnableMethod(this, &GMPRemoveTest::gmp_Decode),
NS_DISPATCH_NORMAL);
mTestMonitor.AwaitFinished();
return mDecodeResult;
}
void
GMPRemoveTest::gmp_Decode()
{
// from gmp-fake.cpp
struct EncodedFrame {
uint32_t length_;
uint8_t h264_compat_;
uint32_t magic_;
uint32_t width_;
uint32_t height_;
uint8_t y_;
uint8_t u_;
uint8_t v_;
uint32_t timestamp_;
};
GMPVideoFrame* absFrame;
GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &absFrame);
EXPECT_EQ(err, GMPNoErr);
GMPUniquePtr<GMPVideoEncodedFrame>
frame(static_cast<GMPVideoEncodedFrame*>(absFrame));
err = frame->CreateEmptyFrame(sizeof(EncodedFrame) /* size */);
EXPECT_EQ(err, GMPNoErr);
EncodedFrame* frameData = reinterpret_cast<EncodedFrame*>(frame->Buffer());
frameData->magic_ = 0x4652414d;
frameData->width_ = frameData->height_ = 16;
nsTArray<uint8_t> empty;
nsresult rv = mDecoder->Decode(Move(frame), false /* aMissingFrames */, empty);
EXPECT_OK(rv);
}
void
GMPRemoveTest::Wait()
{
mTestMonitor.AwaitFinished();
}
bool
GMPRemoveTest::IsTerminated()
{
return mIsTerminated;
}
// nsIObserver
NS_IMETHODIMP
GMPRemoveTest::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
EXPECT_TRUE(!strcmp(GMP_DELETED_TOPIC, aTopic));
nsString data(aData);
if (mTmpPath.Equals(data)) {
mTestMonitor.SetFinished();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->RemoveObserver(this, GMP_DELETED_TOPIC);
}
return NS_OK;
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Decoded(GMPVideoi420Frame* aDecodedFrame)
{
aDecodedFrame->Destroy();
mDecodeResult = GMPNoErr;
mTestMonitor.SetFinished();
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Error(GMPErr aError)
{
mDecodeResult = aError;
mTestMonitor.SetFinished();
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Terminated()
{
mIsTerminated = true;
if (mDecoder) {
mDecoder->Close();
mDecoder = nullptr;
}
}
void
GMPRemoveTest::GeneratePlugin()
{
nsresult rv;
nsCOMPtr<nsIFile> gmpDir;
nsCOMPtr<nsIFile> origDir;
nsCOMPtr<nsIFile> tmpDir;
rv = NS_GetSpecialDirectory(NS_GRE_DIR,
getter_AddRefs(gmpDir));
EXPECT_OK(rv);
rv = gmpDir->Append(GMP_DIR_NAME);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(origDir));
EXPECT_OK(rv);
rv = origDir->Append(GMP_OLD_VERSION);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(tmpDir));
EXPECT_OK(rv);
rv = tmpDir->Append(GMP_NEW_VERSION);
EXPECT_OK(rv);
bool exists = false;
rv = tmpDir->Exists(&exists);
EXPECT_OK(rv);
if (exists) {
rv = tmpDir->Remove(true);
EXPECT_OK(rv);
}
rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(tmpDir));
EXPECT_OK(rv);
rv = tmpDir->Append(GMP_NEW_VERSION);
EXPECT_OK(rv);
EXPECT_OK(origDir->GetPath(mOriginalPath));
EXPECT_OK(tmpDir->GetPath(mTmpPath));
mTmpDir = tmpDir;
}