diff options
Diffstat (limited to 'dom/media/gtest/TestGMPCrossOrigin.cpp')
-rw-r--r-- | dom/media/gtest/TestGMPCrossOrigin.cpp | 1552 |
1 files changed, 1552 insertions, 0 deletions
diff --git a/dom/media/gtest/TestGMPCrossOrigin.cpp b/dom/media/gtest/TestGMPCrossOrigin.cpp new file mode 100644 index 000000000..036282153 --- /dev/null +++ b/dom/media/gtest/TestGMPCrossOrigin.cpp @@ -0,0 +1,1552 @@ +/* -*- 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 "gtest/gtest.h" +#include "nsAutoPtr.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "GMPTestMonitor.h" +#include "GMPVideoDecoderProxy.h" +#include "GMPVideoEncoderProxy.h" +#include "GMPDecryptorProxy.h" +#include "GMPServiceParent.h" +#include "MediaPrefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Atomics.h" +#include "nsNSSComponent.h" +#include "mozilla/DebugOnly.h" +#include "GMPDeviceBinding.h" +#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus +#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType + +#if defined(XP_WIN) +#include "mozilla/WindowsVersion.h" +#endif + +using namespace std; + +using namespace mozilla; +using namespace mozilla::gmp; + +struct GMPTestRunner +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner) + + GMPTestRunner() { MediaPrefs::GetSingleton(); } + void DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&)); + void RunTestGMPTestCodec1(GMPTestMonitor& aMonitor); + void RunTestGMPTestCodec2(GMPTestMonitor& aMonitor); + void RunTestGMPTestCodec3(GMPTestMonitor& aMonitor); + void RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor); + void RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor); + void RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor); + void RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor); + +private: + ~GMPTestRunner() { } +}; + +template<class T, class Base, + nsresult (NS_STDCALL GeckoMediaPluginService::*Getter)(GMPCrashHelper*, + nsTArray<nsCString>*, + const nsACString&, + UniquePtr<Base>&&)> +class RunTestGMPVideoCodec : public Base +{ +public: + void Done(T* aGMP, GMPVideoHost* aHost) override + { + EXPECT_TRUE(aGMP); + EXPECT_TRUE(aHost); + if (aGMP) { + aGMP->Close(); + } + mMonitor.SetFinished(); + } + + static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin) + { + UniquePtr<GMPCallbackType> callback(new RunTestGMPVideoCodec(aMonitor)); + Get(aOrigin, Move(callback)); + } + +protected: + typedef T GMPCodecType; + typedef Base GMPCallbackType; + + explicit RunTestGMPVideoCodec(GMPTestMonitor& aMonitor) + : mMonitor(aMonitor) + { + } + + static nsresult Get(const nsACString& aNodeId, UniquePtr<Base>&& aCallback) + { + nsTArray<nsCString> tags; + tags.AppendElement(NS_LITERAL_CSTRING("h264")); + tags.AppendElement(NS_LITERAL_CSTRING("fake")); + + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + return ((*service).*Getter)(nullptr, &tags, aNodeId, Move(aCallback)); + } + +protected: + GMPTestMonitor& mMonitor; +}; + +typedef RunTestGMPVideoCodec<GMPVideoDecoderProxy, + GetGMPVideoDecoderCallback, + &GeckoMediaPluginService::GetGMPVideoDecoder> + RunTestGMPVideoDecoder; +typedef RunTestGMPVideoCodec<GMPVideoEncoderProxy, + GetGMPVideoEncoderCallback, + &GeckoMediaPluginService::GetGMPVideoEncoder> + RunTestGMPVideoEncoder; + +void +GMPTestRunner::RunTestGMPTestCodec1(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING("o")); +} + +void +GMPTestRunner::RunTestGMPTestCodec2(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING("")); +} + +void +GMPTestRunner::RunTestGMPTestCodec3(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoEncoder::Run(aMonitor, NS_LITERAL_CSTRING("")); +} + +template<class Base> +class RunTestGMPCrossOrigin : public Base +{ +public: + void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override + { + EXPECT_TRUE(aGMP); + + UniquePtr<typename Base::GMPCallbackType> callback( + new Step2(Base::mMonitor, aGMP, mShouldBeEqual)); + nsresult rv = Base::Get(mOrigin2, Move(callback)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + Base::mMonitor.SetFinished(); + } + } + + static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin1, + const nsCString& aOrigin2) + { + UniquePtr<typename Base::GMPCallbackType> callback( + new RunTestGMPCrossOrigin<Base>(aMonitor, aOrigin1, aOrigin2)); + nsresult rv = Base::Get(aOrigin1, Move(callback)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + aMonitor.SetFinished(); + } + } + +private: + RunTestGMPCrossOrigin(GMPTestMonitor& aMonitor, const nsCString& aOrigin1, + const nsCString& aOrigin2) + : Base(aMonitor), + mGMP(nullptr), + mOrigin2(aOrigin2), + mShouldBeEqual(aOrigin1.Equals(aOrigin2)) + { + } + + class Step2 : public Base + { + public: + Step2(GMPTestMonitor& aMonitor, + typename Base::GMPCodecType* aGMP, + bool aShouldBeEqual) + : Base(aMonitor), + mGMP(aGMP), + mShouldBeEqual(aShouldBeEqual) + { + } + void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override + { + EXPECT_TRUE(aGMP); + if (aGMP) { + EXPECT_TRUE(mGMP && + (mGMP->GetPluginId() == aGMP->GetPluginId()) == mShouldBeEqual); + } + if (mGMP) { + mGMP->Close(); + } + Base::Done(aGMP, aHost); + } + + private: + typename Base::GMPCodecType* mGMP; + bool mShouldBeEqual; + }; + + typename Base::GMPCodecType* mGMP; + nsCString mOrigin2; + bool mShouldBeEqual; +}; + +typedef RunTestGMPCrossOrigin<RunTestGMPVideoDecoder> + RunTestGMPVideoDecoderCrossOrigin; +typedef RunTestGMPCrossOrigin<RunTestGMPVideoEncoder> + RunTestGMPVideoEncoderCrossOrigin; + +void +GMPTestRunner::RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoDecoderCrossOrigin::Run( + aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2")); +} + +void +GMPTestRunner::RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoEncoderCrossOrigin::Run( + aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2")); +} + +void +GMPTestRunner::RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoDecoderCrossOrigin::Run( + aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1")); +} + +void +GMPTestRunner::RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor) +{ + RunTestGMPVideoEncoderCrossOrigin::Run( + aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1")); +} + +static already_AddRefed<nsIThread> +GetGMPThread() +{ + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + nsCOMPtr<nsIThread> thread; + EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread)))); + return thread.forget(); +} + +/** + * Enumerate files under |aPath| (non-recursive). + */ +template<typename T> +static nsresult +EnumerateDir(nsIFile* aPath, T&& aDirIter) +{ + nsCOMPtr<nsISimpleEnumerator> iter; + nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) { + return rv; + } + + bool hasMore = false; + while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + rv = iter->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr<nsIFile> entry(do_QueryInterface(supports, &rv)); + if (NS_FAILED(rv)) { + continue; + } + + aDirIter(entry); + } + return NS_OK; +} + +/** + * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ (non-recursive). + */ +template<typename T> +static nsresult +EnumerateGMPStorageDir(const nsACString& aDir, T&& aDirIter) +{ + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + MOZ_ASSERT(service); + + // $profileDir/gmp/$platform/ + nsCOMPtr<nsIFile> path; + nsresult rv = service->GetStorageDir(getter_AddRefs(path)); + if (NS_FAILED(rv)) { + return rv; + } + + + // $profileDir/gmp/$platform/gmp-fake/ + rv = path->Append(NS_LITERAL_STRING("gmp-fake")); + if (NS_FAILED(rv)) { + return rv; + } + + // $profileDir/gmp/$platform/gmp-fake/$aDir/ + rv = path->AppendNative(aDir); + if (NS_FAILED(rv)) { + return rv; + } + + return EnumerateDir(path, aDirIter); +} + +class GMPShutdownObserver : public nsIRunnable + , public nsIObserver { +public: + GMPShutdownObserver(already_AddRefed<nsIRunnable> aShutdownTask, + already_AddRefed<nsIRunnable> Continuation, + const nsACString& aNodeId) + : mShutdownTask(aShutdownTask) + , mContinuation(Continuation) + , mNodeId(NS_ConvertUTF8toUTF16(aNodeId)) + {} + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + EXPECT_TRUE(observerService); + observerService->AddObserver(this, "gmp-shutdown", false); + + nsCOMPtr<nsIThread> thread(GetGMPThread()); + thread->Dispatch(mShutdownTask, NS_DISPATCH_NORMAL); + return NS_OK; + } + + NS_IMETHOD Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) override + { + if (!strcmp(aTopic, "gmp-shutdown") && + mNodeId.Equals(nsDependentString(aSomeData))) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + EXPECT_TRUE(observerService); + observerService->RemoveObserver(this, "gmp-shutdown"); + nsCOMPtr<nsIThread> thread(GetGMPThread()); + thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL); + } + return NS_OK; + } + +private: + virtual ~GMPShutdownObserver() {} + nsCOMPtr<nsIRunnable> mShutdownTask; + nsCOMPtr<nsIRunnable> mContinuation; + const nsString mNodeId; +}; + +NS_IMPL_ISUPPORTS(GMPShutdownObserver, nsIRunnable, nsIObserver) + +class NotifyObserversTask : public Runnable { +public: + explicit NotifyObserversTask(const char* aTopic) + : mTopic(aTopic) + {} + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(nullptr, mTopic, nullptr); + } + return NS_OK; + } + const char* mTopic; +}; + +class ClearGMPStorageTask : public nsIRunnable + , public nsIObserver { +public: + ClearGMPStorageTask(already_AddRefed<nsIRunnable> Continuation, + nsIThread* aTarget, PRTime aSince) + : mContinuation(Continuation) + , mTarget(aTarget) + , mSince(aSince) + {} + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + EXPECT_TRUE(observerService); + observerService->AddObserver(this, "gmp-clear-storage-complete", false); + if (observerService) { + nsAutoString str; + if (mSince >= 0) { + str.AppendInt(static_cast<int64_t>(mSince)); + } + observerService->NotifyObservers( + nullptr, "browser:purge-session-history", str.Data()); + } + return NS_OK; + } + + NS_IMETHOD Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) override + { + if (!strcmp(aTopic, "gmp-clear-storage-complete")) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + EXPECT_TRUE(observerService); + observerService->RemoveObserver(this, "gmp-clear-storage-complete"); + mTarget->Dispatch(mContinuation, NS_DISPATCH_NORMAL); + } + return NS_OK; + } + +private: + virtual ~ClearGMPStorageTask() {} + nsCOMPtr<nsIRunnable> mContinuation; + nsCOMPtr<nsIThread> mTarget; + const PRTime mSince; +}; + +NS_IMPL_ISUPPORTS(ClearGMPStorageTask, nsIRunnable, nsIObserver) + +static void +ClearGMPStorage(already_AddRefed<nsIRunnable> aContinuation, + nsIThread* aTarget, PRTime aSince = -1) +{ + RefPtr<ClearGMPStorageTask> task( + new ClearGMPStorageTask(Move(aContinuation), aTarget, aSince)); + NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL); +} + +static void +SimulatePBModeExit() +{ + NS_DispatchToMainThread(new NotifyObserversTask("last-pb-context-exited"), NS_DISPATCH_SYNC); +} + +class TestGetNodeIdCallback : public GetNodeIdCallback +{ +public: + TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult) + : mNodeId(aNodeId), + mResult(aResult) + { + } + + void Done(nsresult aResult, const nsACString& aNodeId) + { + mResult = aResult; + mNodeId = aNodeId; + } + +private: + nsCString& mNodeId; + nsresult& mResult; +}; + +static nsCString +GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + bool aInPBMode) +{ + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + EXPECT_TRUE(service); + nsCString nodeId; + nsresult result; + UniquePtr<GetNodeIdCallback> callback(new TestGetNodeIdCallback(nodeId, + result)); + // We rely on the fact that the GetNodeId implementation for + // GeckoMediaPluginServiceParent is synchronous. + nsresult rv = service->GetNodeId(aOrigin, + aTopLevelOrigin, + NS_LITERAL_STRING("gmp-fake"), + aInPBMode, + Move(callback)); + EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result)); + return nodeId; +} + +static bool +IsGMPStorageIsEmpty() +{ + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + MOZ_ASSERT(service); + nsCOMPtr<nsIFile> storage; + nsresult rv = service->GetStorageDir(getter_AddRefs(storage)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + bool exists = false; + if (storage) { + storage->Exists(&exists); + } + return !exists; +} + +static void +AssertIsOnGMPThread() +{ + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + MOZ_ASSERT(service); + nsCOMPtr<nsIThread> thread; + service->GetThread(getter_AddRefs(thread)); + MOZ_ASSERT(thread); + nsCOMPtr<nsIThread> currentThread; + DebugOnly<nsresult> rv = NS_GetCurrentThread(getter_AddRefs(currentThread)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(currentThread == thread); +} + +class GMPStorageTest : public GMPDecryptorProxyCallback +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageTest) + + void DoTest(void (GMPStorageTest::*aTestMethod)()) { + EnsureNSSInitializedChromeOrContent(); + nsCOMPtr<nsIThread> thread(GetGMPThread()); + ClearGMPStorage(NewRunnableMethod(this, aTestMethod), thread); + AwaitFinished(); + } + + GMPStorageTest() + : mDecryptor(nullptr) + , mMonitor("GMPStorageTest") + , mFinished(false) + { + } + + void + Update(const nsCString& aMessage) + { + nsTArray<uint8_t> msg; + msg.AppendElements(aMessage.get(), aMessage.Length()); + mDecryptor->UpdateSession(1, NS_LITERAL_CSTRING("fake-session-id"), msg); + } + + void TestGetNodeId() + { + AssertIsOnGMPThread(); + + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + const nsString origin1 = NS_LITERAL_STRING("http://example1.com"); + const nsString origin2 = NS_LITERAL_STRING("http://example2.org"); + + nsCString PBnodeId1 = GetNodeId(origin1, origin2, true); + nsCString PBnodeId2 = GetNodeId(origin1, origin2, true); + + // Node ids for the same origins should be the same in PB mode. + EXPECT_TRUE(PBnodeId1.Equals(PBnodeId2)); + + nsCString PBnodeId3 = GetNodeId(origin2, origin1, true); + + // Node ids with origin and top level origin swapped should be different. + EXPECT_TRUE(!PBnodeId3.Equals(PBnodeId1)); + + // Getting node ids in PB mode should not result in the node id being stored. + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + nsCString nodeId1 = GetNodeId(origin1, origin2, false); + nsCString nodeId2 = GetNodeId(origin1, origin2, false); + + // NodeIds for the same origin pair in non-pb mode should be the same. + EXPECT_TRUE(nodeId1.Equals(nodeId2)); + + // Node ids for a given origin pair should be different for the PB origins should be the same in PB mode. + EXPECT_TRUE(!PBnodeId1.Equals(nodeId1)); + EXPECT_TRUE(!PBnodeId2.Equals(nodeId2)); + + nsCOMPtr<nsIThread> thread(GetGMPThread()); + ClearGMPStorage(NewRunnableMethod<nsCString>( + this, &GMPStorageTest::TestGetNodeId_Continuation, nodeId1), thread); + } + + void TestGetNodeId_Continuation(nsCString aNodeId1) { + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + // Once we clear storage, the node ids generated for the same origin-pair + // should be different. + const nsString origin1 = NS_LITERAL_STRING("http://example1.com"); + const nsString origin2 = NS_LITERAL_STRING("http://example2.org"); + nsCString nodeId3 = GetNodeId(origin1, origin2, false); + EXPECT_TRUE(!aNodeId1.Equals(nodeId3)); + + SetFinished(); + } + + class CreateDecryptorDone : public GetGMPDecryptorCallback + { + public: + explicit CreateDecryptorDone(GMPStorageTest* aRunner) + : mRunner(aRunner) + { + } + + void Done(GMPDecryptorProxy* aDecryptor) override + { + mRunner->mDecryptor = aDecryptor; + EXPECT_TRUE(!!mRunner->mDecryptor); + + if (mRunner->mDecryptor) { + mRunner->mDecryptor->Init(mRunner, false, true); + } + } + + private: + RefPtr<GMPStorageTest> mRunner; + }; + + void CreateDecryptor(const nsCString& aNodeId, + const nsCString& aUpdate) + { + nsTArray<nsCString> updates; + updates.AppendElement(aUpdate); + nsCOMPtr<nsIRunnable> continuation(new Updates(this, Move(updates))); + CreateDecryptor(aNodeId, continuation); + } + + void CreateDecryptor(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + bool aInPBMode, + const nsCString& aUpdate) + { + nsTArray<nsCString> updates; + updates.AppendElement(aUpdate); + CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, Move(updates)); + } + class Updates : public Runnable + { + public: + Updates(GMPStorageTest* aRunner, nsTArray<nsCString>&& aUpdates) + : mRunner(aRunner), + mUpdates(Move(aUpdates)) + { + } + + NS_IMETHOD Run() override + { + for (auto& update : mUpdates) { + mRunner->Update(update); + } + return NS_OK; + } + + private: + RefPtr<GMPStorageTest> mRunner; + nsTArray<nsCString> mUpdates; + }; + void CreateDecryptor(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + bool aInPBMode, + nsTArray<nsCString>&& aUpdates) { + nsCOMPtr<nsIRunnable> updates(new Updates(this, Move(aUpdates))); + CreateDecryptor(GetNodeId(aOrigin, aTopLevelOrigin, aInPBMode), updates); + } + + void CreateDecryptor(const nsCString& aNodeId, + nsIRunnable* aContinuation) { + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + EXPECT_TRUE(service); + + mNodeId = aNodeId; + EXPECT_TRUE(!mNodeId.IsEmpty()); + + nsTArray<nsCString> tags; + tags.AppendElement(NS_LITERAL_CSTRING("fake")); + + UniquePtr<GetGMPDecryptorCallback> callback( + new CreateDecryptorDone(this)); + + // Continue after the OnSetDecryptorId message, so that we don't + // get warnings in the async shutdown tests due to receiving the + // SetDecryptorId message after we've started shutdown. + mSetDecryptorIdContinuation = aContinuation; + + nsresult rv = + service->GetGMPDecryptor(nullptr, &tags, mNodeId, Move(callback)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + } + + void TestBasicStorage() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + + // Send a message to the fake GMP for it to run its own tests internally. + // It sends us a "test-storage complete" message when its passed, or + // some other message if its tests fail. + Expect(NS_LITERAL_CSTRING("test-storage complete"), + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://example1.com"), + NS_LITERAL_STRING("http://example2.com"), + false, + NS_LITERAL_CSTRING("test-storage")); + } + + /** + * 1. Generate storage data for some sites. + * 2. Forget about one of the sites. + * 3. Check if the storage data for the forgotten site are erased correctly. + * 4. Check if the storage data for other sites remain unchanged. + */ + void TestForgetThisSite() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestForgetThisSite_AnotherSite); + Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget()); + + CreateDecryptor(NS_LITERAL_STRING("http://example1.com"), + NS_LITERAL_STRING("http://example2.com"), + false, + NS_LITERAL_CSTRING("test-storage")); + } + + void TestForgetThisSite_AnotherSite() { + Shutdown(); + + // Generate storage data for another site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestForgetThisSite_CollectSiteInfo); + Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget()); + + CreateDecryptor(NS_LITERAL_STRING("http://example3.com"), + NS_LITERAL_STRING("http://example4.com"), + false, + NS_LITERAL_CSTRING("test-storage")); + } + + struct NodeInfo { + explicit NodeInfo(const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern) + : siteToForget(aSite) + , mPattern(aPattern) + { } + nsCString siteToForget; + mozilla::OriginAttributesPattern mPattern; + nsTArray<nsCString> expectedRemainingNodeIds; + }; + + class NodeIdCollector { + public: + explicit NodeIdCollector(NodeInfo* aInfo) : mNodeInfo(aInfo) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + if (!MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) { + mNodeInfo->expectedRemainingNodeIds.AppendElement(salt); + } + } + private: + NodeInfo* mNodeInfo; + }; + + void TestForgetThisSite_CollectSiteInfo() { + mozilla::OriginAttributesPattern pattern; + + nsAutoPtr<NodeInfo> siteInfo( + new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"), + pattern)); + // Collect nodeIds that are expected to remain for later comparison. + EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), NodeIdCollector(siteInfo)); + // Invoke "Forget this site" on the main thread. + NS_DispatchToMainThread(NewRunnableMethod<nsAutoPtr<NodeInfo>>( + this, &GMPStorageTest::TestForgetThisSite_Forget, siteInfo)); + } + + void TestForgetThisSite_Forget(nsAutoPtr<NodeInfo> aSiteInfo) { + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget), + aSiteInfo->mPattern); + + nsCOMPtr<nsIThread> thread; + service->GetThread(getter_AddRefs(thread)); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsAutoPtr<NodeInfo>>( + this, &GMPStorageTest::TestForgetThisSite_Verify, aSiteInfo); + thread->Dispatch(r, NS_DISPATCH_NORMAL); + + nsCOMPtr<nsIRunnable> f = NewRunnableMethod( + this, &GMPStorageTest::SetFinished); + thread->Dispatch(f, NS_DISPATCH_NORMAL); + } + + class NodeIdVerifier { + public: + explicit NodeIdVerifier(const NodeInfo* aInfo) + : mNodeInfo(aInfo) + , mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + // Shouldn't match the origin if we clear correctly. + EXPECT_FALSE(MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)); + // Check if remaining nodeIDs are as expected. + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)); + } + ~NodeIdVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()); + } + private: + const NodeInfo* mNodeInfo; + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + class StorageVerifier { + public: + explicit StorageVerifier(const NodeInfo* aInfo) + : mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = aFile->GetNativeLeafName(salt); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)); + } + ~StorageVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()); + } + private: + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + void TestForgetThisSite_Verify(nsAutoPtr<NodeInfo> aSiteInfo) { + nsresult rv = EnumerateGMPStorageDir( + NS_LITERAL_CSTRING("id"), NodeIdVerifier(aSiteInfo)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + rv = EnumerateGMPStorageDir( + NS_LITERAL_CSTRING("storage"), StorageVerifier(aSiteInfo)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + } + + /** + * 1. Generate some storage data. + * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/id/. + * 3. Pass |t| to clear recent history. + * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and + * $profileDir/gmp/$platform/gmp-fake/storage are removed. + */ + void TestClearRecentHistory1() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory1_Clear); + Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget()); + + CreateDecryptor(NS_LITERAL_STRING("http://example1.com"), + NS_LITERAL_STRING("http://example2.com"), + false, + NS_LITERAL_CSTRING("test-storage")); +} + + /** + * 1. Generate some storage data. + * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/. + * 3. Pass |t| to clear recent history. + * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and + * $profileDir/gmp/$platform/gmp-fake/storage are removed. + */ + void TestClearRecentHistory2() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory2_Clear); + Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget()); + + CreateDecryptor(NS_LITERAL_STRING("http://example1.com"), + NS_LITERAL_STRING("http://example2.com"), + false, + NS_LITERAL_CSTRING("test-storage")); + } + + /** + * 1. Generate some storage data. + * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/. + * 3. Pass |t+1| to clear recent history. + * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and + * $profileDir/gmp/$platform/gmp-fake/storage remain unchanged. + */ + void TestClearRecentHistory3() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsGMPStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory3_Clear); + Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget()); + + CreateDecryptor(NS_LITERAL_STRING("http://example1.com"), + NS_LITERAL_STRING("http://example2.com"), + false, + NS_LITERAL_CSTRING("test-storage")); + } + + class MaxMTimeFinder { + public: + MaxMTimeFinder() : mMaxTime(0) {} + void operator()(nsIFile* aFile) { + PRTime lastModified; + nsresult rv = aFile->GetLastModifiedTime(&lastModified); + if (NS_SUCCEEDED(rv) && lastModified > mMaxTime) { + mMaxTime = lastModified; + } + EnumerateDir(aFile, *this); + } + PRTime GetResult() const { return mMaxTime; } + private: + PRTime mMaxTime; + }; + + void TestClearRecentHistory1_Clear() { + MaxMTimeFinder f; + nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), f); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearGMPStorage(r.forget(), t, f.GetResult()); + } + + void TestClearRecentHistory2_Clear() { + MaxMTimeFinder f; + nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearGMPStorage(r.forget(), t, f.GetResult()); + } + + void TestClearRecentHistory3_Clear() { + MaxMTimeFinder f; + nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + this, &GMPStorageTest::TestClearRecentHistory_CheckNonEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearGMPStorage(r.forget(), t, f.GetResult() + 1); + } + + class FileCounter { + public: + FileCounter() : mCount(0) {} + void operator()(nsIFile* aFile) { + ++mCount; + } + int GetCount() const { return mCount; } + private: + int mCount; + }; + + void TestClearRecentHistory_CheckEmpty() { + FileCounter c1; + nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/ + EXPECT_EQ(c1.GetCount(), 0); + + FileCounter c2; + rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + // There should be no files under $profileDir/gmp/$platform/gmp-fake/storage/ + EXPECT_EQ(c2.GetCount(), 0); + + SetFinished(); + } + + void TestClearRecentHistory_CheckNonEmpty() { + FileCounter c1; + nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + // There should be one directory under $profileDir/gmp/$platform/gmp-fake/id/ + EXPECT_EQ(c1.GetCount(), 1); + + FileCounter c2; + rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + // There should be one directory under $profileDir/gmp/$platform/gmp-fake/storage/ + EXPECT_EQ(c2.GetCount(), 1); + + SetFinished(); + } + + void TestCrossOriginStorage() { + EXPECT_TRUE(!mDecryptor); + + // Send the decryptor the message "store recordid $time" + // Wait for the decrytor to send us "stored recordid $time" + auto t = time(0); + nsCString response("stored crossOriginTestRecordId "); + response.AppendInt((int64_t)t); + Expect(response, NewRunnableMethod(this, + &GMPStorageTest::TestCrossOriginStorage_RecordStoredContinuation)); + + nsCString update("store crossOriginTestRecordId "); + update.AppendInt((int64_t)t); + + // Open decryptor on one, origin, write a record, and test that that + // record can't be read on another origin. + CreateDecryptor(NS_LITERAL_STRING("http://example3.com"), + NS_LITERAL_STRING("http://example4.com"), + false, + update); + } + + void TestCrossOriginStorage_RecordStoredContinuation() { + // Close the old decryptor, and create a new one on a different origin, + // and try to read the record. + Shutdown(); + + Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"), + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://example5.com"), + NS_LITERAL_STRING("http://example6.com"), + false, + NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId")); + } + + void TestPBStorage() { + // Send the decryptor the message "store recordid $time" + // Wait for the decrytor to send us "stored recordid $time" + nsCString response("stored pbdata test-pb-data"); + Expect(response, NewRunnableMethod(this, + &GMPStorageTest::TestPBStorage_RecordStoredContinuation)); + + // Open decryptor on one, origin, write a record, close decryptor, + // open another, and test that record can be read, close decryptor, + // then send pb-last-context-closed notification, then open decryptor + // and check that it can't read that data; it should have been purged. + CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"), + NS_LITERAL_STRING("http://pb2.com"), + true, + NS_LITERAL_CSTRING("store pbdata test-pb-data")); + } + + void TestPBStorage_RecordStoredContinuation() { + Shutdown(); + + Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 12 bytes)"), + NewRunnableMethod(this, + &GMPStorageTest::TestPBStorage_RecordRetrievedContinuation)); + + CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"), + NS_LITERAL_STRING("http://pb2.com"), + true, + NS_LITERAL_CSTRING("retrieve pbdata")); + } + + void TestPBStorage_RecordRetrievedContinuation() { + Shutdown(); + SimulatePBModeExit(); + + Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 0 bytes)"), + NewRunnableMethod(this, + &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"), + NS_LITERAL_STRING("http://pb2.com"), + true, + NS_LITERAL_CSTRING("retrieve pbdata")); + } + + void NextAsyncShutdownTimeoutTest(nsIRunnable* aContinuation) + { + if (mDecryptor) { + Update(NS_LITERAL_CSTRING("shutdown-mode timeout")); + Shutdown(); + } + nsCOMPtr<nsIThread> thread(GetGMPThread()); + thread->Dispatch(aContinuation, NS_DISPATCH_NORMAL); + } + + void CreateAsyncShutdownTimeoutGMP(const nsAString& aOrigin1, + const nsAString& aOrigin2, + void (GMPStorageTest::*aCallback)()) { + nsCOMPtr<nsIRunnable> continuation( + NewRunnableMethod<nsCOMPtr<nsIRunnable>>( + this, + &GMPStorageTest::NextAsyncShutdownTimeoutTest, + NewRunnableMethod(this, aCallback))); + + CreateDecryptor(GetNodeId(aOrigin1, aOrigin2, false), continuation); + } + + void TestAsyncShutdownTimeout() { + // Create decryptors that timeout in their async shutdown. + // If the gtest hangs on shutdown, test fails! + CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example7.com"), + NS_LITERAL_STRING("http://example8.com"), + &GMPStorageTest::TestAsyncShutdownTimeout2); + }; + + void TestAsyncShutdownTimeout2() { + CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example9.com"), + NS_LITERAL_STRING("http://example10.com"), + &GMPStorageTest::TestAsyncShutdownTimeout3); + }; + + void TestAsyncShutdownTimeout3() { + CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example11.com"), + NS_LITERAL_STRING("http://example12.com"), + &GMPStorageTest::SetFinished); + }; + + void TestAsyncShutdownStorage() { + // Instruct the GMP to write a token (the current timestamp, so it's + // unique) during async shutdown, then shutdown the plugin, re-create + // it, and check that the token was successfully stored. + auto t = time(0); + nsCString update("shutdown-mode token "); + nsCString token; + token.AppendInt((int64_t)t); + update.Append(token); + + // Wait for a response from the GMP, so we know it's had time to receive + // the token. + nsCString response("shutdown-token received "); + response.Append(token); + Expect(response, NewRunnableMethod<nsCString>(this, + &GMPStorageTest::TestAsyncShutdownStorage_ReceivedShutdownToken, token)); + + // Test that a GMP can write to storage during shutdown, and retrieve + // that written data in a subsequent session. + CreateDecryptor(NS_LITERAL_STRING("http://example13.com"), + NS_LITERAL_STRING("http://example14.com"), + false, + update); + } + + void TestAsyncShutdownStorage_ReceivedShutdownToken(const nsCString& aToken) { + ShutdownThen(NewRunnableMethod<nsCString>(this, + &GMPStorageTest::TestAsyncShutdownStorage_AsyncShutdownComplete, aToken)); + } + + void TestAsyncShutdownStorage_AsyncShutdownComplete(const nsCString& aToken) { + // Create a new instance of the plugin, retrieve the token written + // during shutdown and verify it is correct. + nsCString response("retrieved shutdown-token "); + response.Append(aToken); + Expect(response, + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://example13.com"), + NS_LITERAL_STRING("http://example14.com"), + false, + NS_LITERAL_CSTRING("retrieve-shutdown-token")); + } + +#if defined(XP_WIN) + void TestOutputProtection() { + Shutdown(); + + Expect(NS_LITERAL_CSTRING("OP tests completed"), + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://example15.com"), + NS_LITERAL_STRING("http://example16.com"), + false, + NS_LITERAL_CSTRING("test-op-apis")); + } +#endif + + void TestPluginVoucher() { + Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"), + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(NS_LITERAL_STRING("http://example17.com"), + NS_LITERAL_STRING("http://example18.com"), + false, + NS_LITERAL_CSTRING("retrieve-plugin-voucher")); + } + + void TestGetRecordNamesInMemoryStorage() { + TestGetRecordNames(true); + } + + nsCString mRecordNames; + + void AppendIntPadded(nsACString& aString, uint32_t aInt) { + if (aInt > 0 && aInt < 10) { + aString.AppendLiteral("0"); + } + aString.AppendInt(aInt); + } + + void TestGetRecordNames(bool aPrivateBrowsing) { + // Create a number of records of different names. + const uint32_t num = 100; + nsTArray<nsCString> updates(num); + for (uint32_t i = 0; i < num; i++) { + nsAutoCString response; + response.AppendLiteral("stored data"); + AppendIntPadded(response, i); + response.AppendLiteral(" test-data"); + AppendIntPadded(response, i); + + if (i != 0) { + mRecordNames.AppendLiteral(","); + } + mRecordNames.AppendLiteral("data"); + AppendIntPadded(mRecordNames, i); + + nsCString& update = *updates.AppendElement(); + update.AppendLiteral("store data"); + AppendIntPadded(update, i); + update.AppendLiteral(" test-data"); + AppendIntPadded(update, i); + + nsCOMPtr<nsIRunnable> continuation; + if (i + 1 == num) { + continuation = + NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames); + } + Expect(response, continuation.forget()); + } + + CreateDecryptor(NS_LITERAL_STRING("http://foo.com"), + NS_LITERAL_STRING("http://bar.com"), + aPrivateBrowsing, + Move(updates)); + } + + void TestGetRecordNames_QueryNames() { + nsCString response("record-names "); + response.Append(mRecordNames); + Expect(response, + NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + Update(NS_LITERAL_CSTRING("retrieve-record-names")); + } + + void GetRecordNamesPersistentStorage() { + TestGetRecordNames(false); + } + + void TestLongRecordNames() { + NS_NAMED_LITERAL_CSTRING(longRecordName, + "A_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_" + "long_record_name"); + + NS_NAMED_LITERAL_CSTRING(data, "Just_some_arbitrary_data."); + + MOZ_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE); + MOZ_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH + + nsCString response("stored "); + response.Append(longRecordName); + response.AppendLiteral(" "); + response.Append(data); + Expect(response, NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + nsCString update("store "); + update.Append(longRecordName); + update.AppendLiteral(" "); + update.Append(data); + CreateDecryptor(NS_LITERAL_STRING("http://fuz.com"), + NS_LITERAL_STRING("http://baz.com"), + false, + update); + } + + void TestNodeId() { + // Calculate the nodeId, and the device bound nodeId. Start a GMP, and + // have it return the device bound nodeId that it's been passed. Assert + // they have the same value. + const nsString origin = NS_LITERAL_STRING("http://example-fuz-baz.com"); + nsCString originSalt1 = GetNodeId(origin, origin, false); + + nsCString salt = originSalt1; + std::string nodeId; + EXPECT_TRUE(CalculateGMPDeviceId(salt.BeginWriting(), salt.Length(), nodeId)); + + std::string expected = "node-id " + nodeId; + Expect(nsDependentCString(expected.c_str()), NewRunnableMethod(this, &GMPStorageTest::SetFinished)); + + CreateDecryptor(originSalt1, + NS_LITERAL_CSTRING("retrieve-node-id")); + } + + void Expect(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation) { + mExpected.AppendElement(ExpectedMessage(aMessage, Move(aContinuation))); + } + + void AwaitFinished() { + while (!mFinished) { + NS_ProcessNextEvent(nullptr, true); + } + mFinished = false; + } + + void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) { + EXPECT_TRUE(!!mDecryptor); + if (!mDecryptor) { + return; + } + EXPECT_FALSE(mNodeId.IsEmpty()); + RefPtr<GMPShutdownObserver> task( + new GMPShutdownObserver(NewRunnableMethod(this, &GMPStorageTest::Shutdown), + Move(aContinuation), mNodeId)); + NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL); + } + + void Shutdown() { + if (mDecryptor) { + mDecryptor->Close(); + mDecryptor = nullptr; + mNodeId = EmptyCString(); + } + } + + void Dummy() { + } + + void SetFinished() { + mFinished = true; + Shutdown(); + NS_DispatchToMainThread(NewRunnableMethod(this, &GMPStorageTest::Dummy)); + } + + void SessionMessage(const nsCString& aSessionId, + mozilla::dom::MediaKeyMessageType aMessageType, + const nsTArray<uint8_t>& aMessage) override + { + MonitorAutoLock mon(mMonitor); + + nsCString msg((const char*)aMessage.Elements(), aMessage.Length()); + EXPECT_TRUE(mExpected.Length() > 0); + bool matches = mExpected[0].mMessage.Equals(msg); + EXPECT_STREQ(mExpected[0].mMessage.get(), msg.get()); + if (mExpected.Length() > 0 && matches) { + nsCOMPtr<nsIRunnable> continuation = mExpected[0].mContinuation; + mExpected.RemoveElementAt(0); + if (continuation) { + NS_DispatchToCurrentThread(continuation); + } + } + } + + void SetDecryptorId(uint32_t aId) override + { + if (!mSetDecryptorIdContinuation) { + return; + } + nsCOMPtr<nsIThread> thread(GetGMPThread()); + thread->Dispatch(mSetDecryptorIdContinuation, NS_DISPATCH_NORMAL); + mSetDecryptorIdContinuation = nullptr; + } + + void SetSessionId(uint32_t aCreateSessionToken, + const nsCString& aSessionId) override { } + void ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) override {} + void ResolvePromise(uint32_t aPromiseId) override {} + void RejectPromise(uint32_t aPromiseId, + nsresult aException, + const nsCString& aSessionId) override { } + void ExpirationChange(const nsCString& aSessionId, + UnixTime aExpiryTime) override {} + void SessionClosed(const nsCString& aSessionId) override {} + void SessionError(const nsCString& aSessionId, + nsresult aException, + uint32_t aSystemCode, + const nsCString& aMessage) override {} + void Decrypted(uint32_t aId, + mozilla::DecryptStatus aResult, + const nsTArray<uint8_t>& aDecryptedData) override { } + + void BatchedKeyStatusChanged(const nsCString& aSessionId, + const nsTArray<CDMKeyInfo>& aKeyInfos) override { } + + void Terminated() override { + if (mDecryptor) { + mDecryptor->Close(); + mDecryptor = nullptr; + } + } + +private: + ~GMPStorageTest() { } + + struct ExpectedMessage { + ExpectedMessage(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation) + : mMessage(aMessage) + , mContinuation(aContinuation) + {} + nsCString mMessage; + nsCOMPtr<nsIRunnable> mContinuation; + }; + + nsTArray<ExpectedMessage> mExpected; + + RefPtr<nsIRunnable> mSetDecryptorIdContinuation; + + GMPDecryptorProxy* mDecryptor; + Monitor mMonitor; + Atomic<bool> mFinished; + nsCString mNodeId; +}; + +void +GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&)) +{ + nsCOMPtr<nsIThread> thread(GetGMPThread()); + + GMPTestMonitor monitor; + thread->Dispatch(NewRunnableMethod<GMPTestMonitor&>(this, + aTestMethod, + monitor), + NS_DISPATCH_NORMAL); + monitor.AwaitFinished(); +} + +TEST(GeckoMediaPlugins, GMPTestCodec) { + RefPtr<GMPTestRunner> runner = new GMPTestRunner(); + runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec1); + runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec2); + runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec3); +} + +TEST(GeckoMediaPlugins, GMPCrossOrigin) { + RefPtr<GMPTestRunner> runner = new GMPTestRunner(); + runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin1); + runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin2); + runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin3); + runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin4); +} + +TEST(GeckoMediaPlugins, GMPStorageGetNodeId) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestGetNodeId); +} + +TEST(GeckoMediaPlugins, GMPStorageBasic) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestBasicStorage); +} + +TEST(GeckoMediaPlugins, GMPStorageForgetThisSite) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestForgetThisSite); +} + +TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory1) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestClearRecentHistory1); +} + +TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory2) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestClearRecentHistory2); +} + +TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory3) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestClearRecentHistory3); +} + +TEST(GeckoMediaPlugins, GMPStorageCrossOrigin) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestCrossOriginStorage); +} + +TEST(GeckoMediaPlugins, GMPStoragePrivateBrowsing) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestPBStorage); +} + +TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownTimeout) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestAsyncShutdownTimeout); +} + +TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownStorage) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestAsyncShutdownStorage); +} + +TEST(GeckoMediaPlugins, GMPPluginVoucher) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestPluginVoucher); +} + +#if defined(XP_WIN) +TEST(GeckoMediaPlugins, GMPOutputProtection) { + // Output Protection is not available pre-Vista. + if (!IsVistaOrLater()) { + return; + } + + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestOutputProtection); +} +#endif + +TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage); +} + +TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesPersistentStorage) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::GetRecordNamesPersistentStorage); +} + +TEST(GeckoMediaPlugins, GMPStorageLongRecordNames) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestLongRecordNames); +} + +TEST(GeckoMediaPlugins, GMPNodeId) { + RefPtr<GMPStorageTest> runner = new GMPStorageTest(); + runner->DoTest(&GMPStorageTest::TestNodeId); +} |