diff options
Diffstat (limited to 'dom/media/gtest/TestGMPRemoveAndDelete.cpp')
-rw-r--r-- | dom/media/gtest/TestGMPRemoveAndDelete.cpp | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/dom/media/gtest/TestGMPRemoveAndDelete.cpp b/dom/media/gtest/TestGMPRemoveAndDelete.cpp new file mode 100644 index 0000000000..b969027c6e --- /dev/null +++ b/dom/media/gtest/TestGMPRemoveAndDelete.cpp @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "GMPServiceParent.h" +#include "GMPTestMonitor.h" +#include "GMPUtils.h" +#include "GMPVideoDecoderProxy.h" +#include "gmp-api/gmp-video-host.h" +#include "gtest/gtest.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIObserverService.h" + +#define GMP_DIR_NAME u"gmp-fakeopenh264"_ns +#define GMP_OLD_VERSION u"1.0"_ns +#define GMP_NEW_VERSION u"1.1"_ns + +#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 = ""_ns); + 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. + */ +// Bug 1115253 - disable test in win64 to reduce failure rate +#if !defined(_WIN64) +TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse) +{ + RefPtr<GMPRemoveTest> test(new GMPRemoveTest()); + + test->Setup(); + EXPECT_TRUE(test->CreateVideoDecoder("thisOrigin"_ns)); + + // 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("thisOrigin"_ns)); + + // 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("thisOrigin"_ns)); + + // 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("thisOrigin"_ns)); + + test->CloseVideoDecoder(); + test->Wait(); +} +#endif + +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() { + 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. + nsCOMPtr<nsISerialEventTarget> thread(GetServiceParent()->GetGMPThread()); + 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**>( + "GMPRemoveTest::gmp_GetVideoDecoder", 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; + NS_DispatchAndSpinEventLoopUntilComplete( + "GMPVideoDecoderProxy::InitDecode"_ns, mGMPThread, + NewNonOwningRunnableMethod<const GMPVideoCodec&, const nsTArray<uint8_t>&, + GMPVideoDecoderCallbackProxy*, int32_t>( + "GMPVideoDecoderProxy::InitDecode", decoder, + &GMPVideoDecoderProxy::InitDecode, codec, empty, this, + 1 /* core count */)); + + if (mDecoder) { + CloseVideoDecoder(); + } + + mDecoder = decoder; + mHost = host; + + return true; +} + +void GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId, + GMPVideoDecoderProxy** aOutDecoder, + GMPVideoHost** aOutHost) { + nsTArray<nsCString> tags; + tags.AppendElement("h264"_ns); + tags.AppendElement("fake"_ns); + + 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, + std::move(cb)))) { + mTestMonitor.SetFinished(); + } +} + +void GMPRemoveTest::CloseVideoDecoder() { + NS_DispatchAndSpinEventLoopUntilComplete( + "GMPVideoDecoderProxy::Close"_ns, mGMPThread, + NewNonOwningRunnableMethod("GMPVideoDecoderProxy::Close", mDecoder, + &GMPVideoDecoderProxy::Close)); + + mDecoder = nullptr; + mHost = nullptr; +} + +void GMPRemoveTest::DeletePluginDirectory(bool aCanDefer) { + GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer); +} + +GMPErr GMPRemoveTest::Decode() { + mGMPThread->Dispatch( + NewNonOwningRunnableMethod("GMPRemoveTest::gmp_Decode", this, + &GMPRemoveTest::gmp_Decode), + NS_DISPATCH_NORMAL); + + mTestMonitor.AwaitFinished(); + return mDecodeResult; +} + +void GMPRemoveTest::gmp_Decode() { +// from gmp-fake.cpp +#pragma pack(push, 1) + struct EncodedFrame { + struct SPSNalu { + uint32_t size_; + uint8_t payload[14]; + } sps_nalu; + struct PPSNalu { + uint32_t size_; + uint8_t payload[4]; + } pps_nalu; + struct IDRNalu { + uint32_t size_; + 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_; + } idr_nalu; + }; +#pragma pack(pop) + + 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->sps_nalu.size_ = sizeof(EncodedFrame::SPSNalu) - sizeof(uint32_t); + frameData->pps_nalu.size_ = sizeof(EncodedFrame::PPSNalu) - sizeof(uint32_t); + frameData->idr_nalu.size_ = sizeof(EncodedFrame::IDRNalu) - sizeof(uint32_t); + frameData->idr_nalu.h264_compat_ = 5; + frameData->idr_nalu.magic_ = 0x004000b8; + frameData->idr_nalu.width_ = frameData->idr_nalu.height_ = 16; + + nsTArray<uint8_t> empty; + nsresult rv = + mDecoder->Decode(std::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; +} |