diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/media/gtest/TestCDMStorage.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/gtest/TestCDMStorage.cpp')
-rw-r--r-- | dom/media/gtest/TestCDMStorage.cpp | 1342 |
1 files changed, 1342 insertions, 0 deletions
diff --git a/dom/media/gtest/TestCDMStorage.cpp b/dom/media/gtest/TestCDMStorage.cpp new file mode 100644 index 0000000000..d6249cc95f --- /dev/null +++ b/dom/media/gtest/TestCDMStorage.cpp @@ -0,0 +1,1342 @@ +/* -*- 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 "ChromiumCDMCallback.h" +#include "ChromiumCDMParent.h" +#include "GMPServiceParent.h" +#include "GMPTestMonitor.h" +#include "MediaResult.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIFile.h" +#include "nsCRTGlue.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsNSSComponent.h" //For EnsureNSSInitializedChromeOrContent +#include "nsThreadUtils.h" + +using namespace mozilla; +using namespace mozilla::gmp; + +static already_AddRefed<nsIThread> GetGMPThread() { + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + nsCOMPtr<nsIThread> thread; + EXPECT_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<nsIDirectoryEnumerator> iter; + nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIFile> entry; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(entry))) && entry) { + aDirIter(entry); + } + return NS_OK; +} + +/** + * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ + * (non-recursive). + */ +template <typename T> +static nsresult EnumerateCDMStorageDir(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(u"gmp-fake"_ns); + 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() = default; + 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) + : mozilla::Runnable("NotifyObserversTask"), 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 ClearCDMStorageTask : public nsIRunnable, public nsIObserver { + public: + ClearCDMStorageTask(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 ~ClearCDMStorageTask() = default; + nsCOMPtr<nsIRunnable> mContinuation; + nsCOMPtr<nsIThread> mTarget; + const PRTime mSince; +}; + +NS_IMPL_ISUPPORTS(ClearCDMStorageTask, nsIRunnable, nsIObserver) + +static void ClearCDMStorage(already_AddRefed<nsIRunnable> aContinuation, + nsIThread* aTarget, PRTime aSince = -1) { + RefPtr<ClearCDMStorageTask> task( + new ClearCDMStorageTask(std::move(aContinuation), aTarget, aSince)); + SchedulerGroup::Dispatch(task.forget()); +} + +static void SimulatePBModeExit() { + NS_DispatchAndSpinEventLoopUntilComplete( + "SimulatePBModeExit"_ns, GetMainThreadSerialEventTarget(), + MakeAndAddRef<NotifyObserversTask>("last-pb-context-exited")); +} + +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 NodeIdParts GetNodeIdParts(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGmpName, bool aInPBMode) { + OriginAttributes attrs; + attrs.mPrivateBrowsingId = aInPBMode ? 1 : 0; + + nsAutoCString suffix; + attrs.CreateSuffix(suffix); + + nsAutoString origin; + origin.Assign(aOrigin); + origin.Append(NS_ConvertUTF8toUTF16(suffix)); + + nsAutoString topLevelOrigin; + topLevelOrigin.Assign(aTopLevelOrigin); + topLevelOrigin.Append(NS_ConvertUTF8toUTF16(suffix)); + return NodeIdParts{origin, topLevelOrigin, nsString(aGmpName)}; +} + +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)); + + OriginAttributes attrs; + attrs.mPrivateBrowsingId = aInPBMode ? 1 : 0; + + nsAutoCString suffix; + attrs.CreateSuffix(suffix); + + nsAutoString origin; + origin.Assign(aOrigin); + origin.Append(NS_ConvertUTF8toUTF16(suffix)); + + nsAutoString topLevelOrigin; + topLevelOrigin.Assign(aTopLevelOrigin); + topLevelOrigin.Append(NS_ConvertUTF8toUTF16(suffix)); + + // We rely on the fact that the GetNodeId implementation for + // GeckoMediaPluginServiceParent is synchronous. + nsresult rv = service->GetNodeId(origin, topLevelOrigin, u"gmp-fake"_ns, + std::move(callback)); + EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result)); + return nodeId; +} + +static bool IsCDMStorageIsEmpty() { + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + MOZ_ASSERT(service); + nsCOMPtr<nsIFile> storage; + nsresult rv = service->GetStorageDir(getter_AddRefs(storage)); + EXPECT_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 CDMStorageTest { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMStorageTest) + + void DoTest(void (CDMStorageTest::*aTestMethod)()) { + EnsureNSSInitializedChromeOrContent(); + nsCOMPtr<nsIThread> thread(GetGMPThread()); + ClearCDMStorage( + NewRunnableMethod("CDMStorageTest::DoTest", this, aTestMethod), thread); + AwaitFinished(); + } + + CDMStorageTest() : mMonitor("CDMStorageTest"), mFinished(false) {} + + void Update(const nsCString& aMessage) { + nsTArray<uint8_t> msg; + msg.AppendElements(aMessage.get(), aMessage.Length()); + mCDM->UpdateSession("fake-session-id"_ns, 1, msg); + } + + void TestGetNodeId() { + AssertIsOnGMPThread(); + + EXPECT_TRUE(IsCDMStorageIsEmpty()); + + const nsString origin1 = u"http://example1.com"_ns; + const nsString origin2 = u"http://example2.org"_ns; + + 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(IsCDMStorageIsEmpty()); + + 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()); + ClearCDMStorage(NewRunnableMethod<nsCString>( + "CDMStorageTest::TestGetNodeId_Continuation", this, + &CDMStorageTest::TestGetNodeId_Continuation, nodeId1), + thread); + } + + void TestGetNodeId_Continuation(nsCString aNodeId1) { + EXPECT_TRUE(IsCDMStorageIsEmpty()); + + // Once we clear storage, the node ids generated for the same origin-pair + // should be different. + const nsString origin1 = u"http://example1.com"_ns; + const nsString origin2 = u"http://example2.org"_ns; + nsCString nodeId3 = GetNodeId(origin1, origin2, false); + EXPECT_TRUE(!aNodeId1.Equals(nodeId3)); + + SetFinished(); + } + + void CreateDecryptor(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, bool aInPBMode, + const nsCString& aUpdate) { + nsTArray<nsCString> updates; + updates.AppendElement(aUpdate); + CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, std::move(updates)); + } + + void CreateDecryptor(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, bool aInPBMode, + nsTArray<nsCString>&& aUpdates) { + CreateDecryptor( + GetNodeIdParts(aOrigin, aTopLevelOrigin, u"gmp-fake"_ns, aInPBMode), + std::move(aUpdates)); + } + + void CreateDecryptor(const NodeIdParts& aNodeId, + nsTArray<nsCString>&& aUpdates) { + RefPtr<GeckoMediaPluginService> service = + GeckoMediaPluginService::GetGeckoMediaPluginService(); + EXPECT_TRUE(service); + + nsCString keySystem{"fake"_ns}; + + RefPtr<CDMStorageTest> self = this; + RefPtr<gmp::GetCDMParentPromise> promise = + service->GetCDM(aNodeId, keySystem, nullptr); + nsCOMPtr<nsISerialEventTarget> thread = GetGMPThread(); + promise->Then( + thread, __func__, + [self, updates = std::move(aUpdates), + thread](RefPtr<gmp::ChromiumCDMParent> cdm) mutable { + self->mCDM = cdm; + EXPECT_TRUE(!!self->mCDM); + self->mCallback.reset(new CallbackProxy(self)); + nsCString failureReason; + self->mCDM + ->Init(self->mCallback.get(), false, true, + GetMainThreadSerialEventTarget()) + ->Then( + thread, __func__, + [self, updates = std::move(updates)] { + for (const auto& update : updates) { + self->Update(update); + } + }, + [](MediaResult rv) { EXPECT_TRUE(false); }); + }, + [](MediaResult rv) { EXPECT_TRUE(false); }); + } + + void TestBasicStorage() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsCDMStorageIsEmpty()); + + 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("test-storage complete"_ns, + NewRunnableMethod("CDMStorageTest::SetFinished", this, + &CDMStorageTest::SetFinished)); + + CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, + "test-storage"_ns); + } + + /** + * 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(IsCDMStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestForgetThisSite_AnotherSite", this, + &CDMStorageTest::TestForgetThisSite_AnotherSite); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, + "test-storage"_ns); + } + + void TestForgetThisSite_AnotherSite() { + Shutdown(); + + // Generate storage data for another site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestForgetThisSite_CollectSiteInfo", this, + &CDMStorageTest::TestForgetThisSite_CollectSiteInfo); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://example3.com"_ns, u"http://example4.com"_ns, false, + "test-storage"_ns); + } + + struct NodeInfo { + explicit NodeInfo(const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern) + : siteToForget(aSite), mPattern(aPattern) {} + nsCString siteToForget; + mozilla::OriginAttributesPattern mPattern; + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + class NodeIdCollector { + public: + explicit NodeIdCollector(NodeInfo* aInfo) : mNodeInfo(aInfo) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_NS_SUCCEEDED(rv); + if (!MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) { + mNodeInfo->mExpectedRemainingNodeIds.AppendElement(salt); + } + } + + private: + NodeInfo* mNodeInfo; + }; + + void TestForgetThisSite_CollectSiteInfo() { + mozilla::OriginAttributesPattern pattern; + + UniquePtr<NodeInfo> siteInfo( + new NodeInfo("http://example1.com"_ns, pattern)); + // Collect nodeIds that are expected to remain for later comparison. + EnumerateCDMStorageDir("id"_ns, NodeIdCollector(siteInfo.get())); + // Invoke "Forget this site" on the main thread. + SchedulerGroup::Dispatch(NewRunnableMethod<UniquePtr<NodeInfo>&&>( + "CDMStorageTest::TestForgetThisSite_Forget", this, + &CDMStorageTest::TestForgetThisSite_Forget, std::move(siteInfo))); + } + + void TestForgetThisSite_Forget(UniquePtr<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<UniquePtr<NodeInfo>&&>( + "CDMStorageTest::TestForgetThisSite_Verify", this, + &CDMStorageTest::TestForgetThisSite_Verify, std::move(aSiteInfo)); + thread->Dispatch(r, NS_DISPATCH_NORMAL); + + nsCOMPtr<nsIRunnable> f = NewRunnableMethod( + "CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished); + thread->Dispatch(f, NS_DISPATCH_NORMAL); + } + + class NodeIdVerifier { + public: + explicit NodeIdVerifier(const NodeInfo* aInfo) + : mNodeInfo(aInfo), + mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_NS_SUCCEEDED(rv); + // Shouldn't match the origin if we clear correctly. + EXPECT_FALSE( + MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) + << "Found files persisted that match against a site that should " + "have been removed!"; + // Check if remaining nodeIDs are as expected. + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) + << "Failed to remove salt from expected remaining node ids. This " + "indicates storage that should be forgotten is still persisted!"; + } + ~NodeIdVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) + << "Some expected remaining node ids were not checked against. This " + "indicates that data we expected to find in storage was missing!"; + } + + private: + const NodeInfo* mNodeInfo; + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + class StorageVerifier { + public: + explicit StorageVerifier(const NodeInfo* aInfo) + : mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = aFile->GetNativeLeafName(salt); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) + << "Failed to remove salt from expected remaining node ids. This " + "indicates storage that should be forgotten is still persisted!"; + } + ~StorageVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) + << "Some expected remaining node ids were not checked against. This " + "indicates that data we expected to find in storage was missing!"; + } + + private: + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + void TestForgetThisSite_Verify(UniquePtr<NodeInfo>&& aSiteInfo) { + nsresult rv = + EnumerateCDMStorageDir("id"_ns, NodeIdVerifier(aSiteInfo.get())); + EXPECT_NS_SUCCEEDED(rv); + + rv = EnumerateCDMStorageDir("storage"_ns, StorageVerifier(aSiteInfo.get())); + EXPECT_NS_SUCCEEDED(rv); + } + + /** + * 1. Generate storage data for some sites. + * 2. Forget about base domain example1.com + * 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 TestForgetThisBaseDomain() { + AssertIsOnGMPThread(); + EXPECT_TRUE(IsCDMStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestForgetThisBaseDomain_SecondSite", this, + &CDMStorageTest::TestForgetThisBaseDomain_SecondSite); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://media.example1.com"_ns, + u"http://tld.example2.com"_ns, false, "test-storage"_ns); + } + + void TestForgetThisBaseDomain_SecondSite() { + Shutdown(); + + // Generate storage data for another site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestForgetThisBaseDomain_ThirdSite", this, + &CDMStorageTest::TestForgetThisBaseDomain_ThirdSite); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://media.somewhereelse.com"_ns, + u"http://home.example1.com"_ns, false, "test-storage"_ns); + } + + void TestForgetThisBaseDomain_ThirdSite() { + Shutdown(); + + // Generate storage data for another site. + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestForgetThisBaseDomain_CollectSiteInfo", this, + &CDMStorageTest::TestForgetThisBaseDomain_CollectSiteInfo); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://media.example3.com"_ns, + u"http://tld.long-example1.com"_ns, false, + "test-storage"_ns); + } + + struct BaseDomainNodeInfo { + explicit BaseDomainNodeInfo(const nsACString& aBaseDomain) + : baseDomainToForget(aBaseDomain) {} + nsCString baseDomainToForget; + + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + class BaseDomainNodeIdCollector { + public: + explicit BaseDomainNodeIdCollector(BaseDomainNodeInfo* aInfo) + : mNodeInfo(aInfo) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_NS_SUCCEEDED(rv); + if (!MatchBaseDomain(aFile, mNodeInfo->baseDomainToForget)) { + mNodeInfo->mExpectedRemainingNodeIds.AppendElement(salt); + } + } + + private: + BaseDomainNodeInfo* mNodeInfo; + }; + + void TestForgetThisBaseDomain_CollectSiteInfo() { + UniquePtr<BaseDomainNodeInfo> siteInfo( + new BaseDomainNodeInfo("example1.com"_ns)); + // Collect nodeIds that are expected to remain for later comparison. + EnumerateCDMStorageDir("id"_ns, BaseDomainNodeIdCollector(siteInfo.get())); + // Invoke "ForgetThisBaseDomain" on the main thread. + SchedulerGroup::Dispatch(NewRunnableMethod<UniquePtr<BaseDomainNodeInfo>&&>( + "CDMStorageTest::TestForgetThisBaseDomain_Forget", this, + &CDMStorageTest::TestForgetThisBaseDomain_Forget, std::move(siteInfo))); + } + + void TestForgetThisBaseDomain_Forget( + UniquePtr<BaseDomainNodeInfo>&& aSiteInfo) { + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + service->ForgetThisBaseDomain( + NS_ConvertUTF8toUTF16(aSiteInfo->baseDomainToForget)); + + nsCOMPtr<nsIThread> thread; + service->GetThread(getter_AddRefs(thread)); + + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<UniquePtr<BaseDomainNodeInfo>&&>( + "CDMStorageTest::TestForgetThisBaseDomain_Verify", this, + &CDMStorageTest::TestForgetThisBaseDomain_Verify, + std::move(aSiteInfo)); + thread->Dispatch(r, NS_DISPATCH_NORMAL); + + nsCOMPtr<nsIRunnable> f = NewRunnableMethod( + "CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished); + thread->Dispatch(f, NS_DISPATCH_NORMAL); + } + + class BaseDomainNodeIdVerifier { + public: + explicit BaseDomainNodeIdVerifier(const BaseDomainNodeInfo* aInfo) + : mNodeInfo(aInfo), + mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = ReadSalt(aFile, salt); + ASSERT_NS_SUCCEEDED(rv); + // Shouldn't match the origin if we clear correctly. + EXPECT_FALSE(MatchBaseDomain(aFile, mNodeInfo->baseDomainToForget)) + << "Found files persisted that match against a domain that should " + "have been removed!"; + // Check if remaining nodeIDs are as expected. + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) + << "Failed to remove salt from expected remaining node ids. This " + "indicates storage that should be forgotten is still persisted!"; + } + ~BaseDomainNodeIdVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) + << "Some expected remaining node ids were not checked against. This " + "indicates that data we expected to find in storage was missing!"; + } + + private: + const BaseDomainNodeInfo* mNodeInfo; + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + class BaseDomainStorageVerifier { + public: + explicit BaseDomainStorageVerifier(const BaseDomainNodeInfo* aInfo) + : mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} + void operator()(nsIFile* aFile) { + nsCString salt; + nsresult rv = aFile->GetNativeLeafName(salt); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) + << "Failed to remove salt from expected remaining node ids. This " + "indicates storage that should be forgotten is still persisted!"; + ; + } + ~BaseDomainStorageVerifier() { + EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) + << "Some expected remaining node ids were not checked against. This " + "indicates that data we expected to find in storage was missing!"; + ; + } + + private: + nsTArray<nsCString> mExpectedRemainingNodeIds; + }; + + void TestForgetThisBaseDomain_Verify( + UniquePtr<BaseDomainNodeInfo>&& aSiteInfo) { + nsresult rv = EnumerateCDMStorageDir( + "id"_ns, BaseDomainNodeIdVerifier(aSiteInfo.get())); + EXPECT_NS_SUCCEEDED(rv); + + rv = EnumerateCDMStorageDir("storage"_ns, + BaseDomainStorageVerifier(aSiteInfo.get())); + EXPECT_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(IsCDMStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("CDMStorageTest::TestClearRecentHistory1_Clear", this, + &CDMStorageTest::TestClearRecentHistory1_Clear); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, + "test-storage"_ns); + } + + /** + * 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(IsCDMStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("CDMStorageTest::TestClearRecentHistory2_Clear", this, + &CDMStorageTest::TestClearRecentHistory2_Clear); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, + "test-storage"_ns); + } + + /** + * 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(IsCDMStorageIsEmpty()); + + // Generate storage data for some site. + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("CDMStorageTest::TestClearRecentHistory3_Clear", this, + &CDMStorageTest::TestClearRecentHistory3_Clear); + Expect("test-storage complete"_ns, r.forget()); + + CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, + "test-storage"_ns); + } + + 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 = EnumerateCDMStorageDir("id"_ns, f); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, + &CDMStorageTest::TestClearRecentHistory_CheckEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearCDMStorage(r.forget(), t, f.GetResult()); + } + + void TestClearRecentHistory2_Clear() { + MaxMTimeFinder f; + nsresult rv = EnumerateCDMStorageDir("storage"_ns, f); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, + &CDMStorageTest::TestClearRecentHistory_CheckEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearCDMStorage(r.forget(), t, f.GetResult()); + } + + void TestClearRecentHistory3_Clear() { + MaxMTimeFinder f; + nsresult rv = EnumerateCDMStorageDir("storage"_ns, f); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "CDMStorageTest::TestClearRecentHistory_CheckNonEmpty", this, + &CDMStorageTest::TestClearRecentHistory_CheckNonEmpty); + nsCOMPtr<nsIThread> t(GetGMPThread()); + ClearCDMStorage(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 = EnumerateCDMStorageDir("id"_ns, c1); + EXPECT_NS_SUCCEEDED(rv); + // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/ + EXPECT_EQ(c1.GetCount(), 0); + + FileCounter c2; + rv = EnumerateCDMStorageDir("storage"_ns, c2); + EXPECT_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 = EnumerateCDMStorageDir("id"_ns, c1); + EXPECT_NS_SUCCEEDED(rv); + // There should be one directory under + // $profileDir/gmp/$platform/gmp-fake/id/ + EXPECT_EQ(c1.GetCount(), 1); + + FileCounter c2; + rv = EnumerateCDMStorageDir("storage"_ns, c2); + EXPECT_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(!mCDM); + + // 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( + "CDMStorageTest::TestCrossOriginStorage_RecordStoredContinuation", + this, + &CDMStorageTest::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(u"http://example3.com"_ns, u"http://example4.com"_ns, 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(nsLiteralCString( + "retrieve crossOriginTestRecordId succeeded (length 0 bytes)"), + NewRunnableMethod("CDMStorageTest::SetFinished", this, + &CDMStorageTest::SetFinished)); + + CreateDecryptor(u"http://example5.com"_ns, u"http://example6.com"_ns, false, + "retrieve crossOriginTestRecordId"_ns); + } + + 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( + "CDMStorageTest::TestPBStorage_RecordStoredContinuation", this, + &CDMStorageTest::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(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, + "store pbdata test-pb-data"_ns); + } + + void TestPBStorage_RecordStoredContinuation() { + Shutdown(); + + Expect( + "retrieve pbdata succeeded (length 12 bytes)"_ns, + NewRunnableMethod( + "CDMStorageTest::TestPBStorage_RecordRetrievedContinuation", this, + &CDMStorageTest::TestPBStorage_RecordRetrievedContinuation)); + + CreateDecryptor(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, + "retrieve pbdata"_ns); + } + + void TestPBStorage_RecordRetrievedContinuation() { + Shutdown(); + SimulatePBModeExit(); + + Expect("retrieve pbdata succeeded (length 0 bytes)"_ns, + NewRunnableMethod("CDMStorageTest::SetFinished", this, + &CDMStorageTest::SetFinished)); + + CreateDecryptor(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, + "retrieve pbdata"_ns); + } + +#if defined(XP_WIN) + void TestOutputProtection() { + Shutdown(); + + Expect("OP tests completed"_ns, + NewRunnableMethod("CDMStorageTest::SetFinished", this, + &CDMStorageTest::SetFinished)); + + CreateDecryptor(u"http://example15.com"_ns, u"http://example16.com"_ns, + false, "test-op-apis"_ns); + } +#endif + + void TestLongRecordNames() { + constexpr auto 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; + + constexpr auto data = "Just_some_arbitrary_data."_ns; + + 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("CDMStorageTest::SetFinished", this, + &CDMStorageTest::SetFinished)); + + nsCString update("store "); + update.Append(longRecordName); + update.AppendLiteral(" "); + update.Append(data); + CreateDecryptor(u"http://fuz.com"_ns, u"http://baz.com"_ns, false, update); + } + + void Expect(const nsCString& aMessage, + already_AddRefed<nsIRunnable> aContinuation) { + mExpected.AppendElement( + ExpectedMessage(aMessage, std::move(aContinuation))); + } + + void AwaitFinished() { + mozilla::SpinEventLoopUntil("CDMStorageTest::AwaitFinished"_ns, + [&]() -> bool { return mFinished; }); + mFinished = false; + } + + void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) { + EXPECT_TRUE(!!mCDM); + if (!mCDM) { + return; + } + EXPECT_FALSE(mNodeId.IsEmpty()); + RefPtr<GMPShutdownObserver> task(new GMPShutdownObserver( + NewRunnableMethod("CDMStorageTest::Shutdown", this, + &CDMStorageTest::Shutdown), + std::move(aContinuation), mNodeId)); + SchedulerGroup::Dispatch(task.forget()); + } + + void Shutdown() { + if (mCDM) { + mCDM->Shutdown(); + mCDM = nullptr; + mNodeId.Truncate(); + } + } + + void Dummy() {} + + void SetFinished() { + mFinished = true; + Shutdown(); + nsCOMPtr<nsIRunnable> task = NewRunnableMethod( + "CDMStorageTest::Dummy", this, &CDMStorageTest::Dummy); + SchedulerGroup::Dispatch(task.forget()); + } + + void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, + const nsTArray<uint8_t>& aMessage) { + 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 Terminated() { + if (mCDM) { + mCDM->Shutdown(); + mCDM = nullptr; + } + } + + private: + ~CDMStorageTest() = default; + + struct ExpectedMessage { + ExpectedMessage(const nsCString& aMessage, + already_AddRefed<nsIRunnable> aContinuation) + : mMessage(aMessage), mContinuation(aContinuation) {} + nsCString mMessage; + nsCOMPtr<nsIRunnable> mContinuation; + }; + + nsTArray<ExpectedMessage> mExpected; + + RefPtr<gmp::ChromiumCDMParent> mCDM; + Monitor mMonitor MOZ_UNANNOTATED; + Atomic<bool> mFinished; + nsCString mNodeId; + + class CallbackProxy : public ChromiumCDMCallback { + public: + explicit CallbackProxy(CDMStorageTest* aRunner) : mRunner(aRunner) {} + + void SetSessionId(uint32_t aPromiseId, + const nsCString& aSessionId) override {} + + void ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccessful) override {} + + void ResolvePromiseWithKeyStatus(uint32_t aPromiseId, + uint32_t aKeyStatus) override {} + + void ResolvePromise(uint32_t aPromiseId) override {} + + void RejectPromise(uint32_t aPromiseId, ErrorResult&& aError, + const nsCString& aErrorMessage) override {} + + void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, + nsTArray<uint8_t>&& aMessage) override { + mRunner->SessionMessage(aSessionId, aMessageType, std::move(aMessage)); + } + + void SessionKeysChange( + const nsCString& aSessionId, + nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) override {} + + void ExpirationChange(const nsCString& aSessionId, + double aSecondsSinceEpoch) override {} + + void SessionClosed(const nsCString& aSessionId) override {} + + void QueryOutputProtectionStatus() override {} + + void Terminated() override { mRunner->Terminated(); } + + void Shutdown() override { mRunner->Shutdown(); } + + private: + // Warning: Weak ref. + CDMStorageTest* mRunner; + }; + + UniquePtr<CallbackProxy> mCallback; +}; // class CDMStorageTest + +static nsresult CreateTestDirectory(nsCOMPtr<nsIFile>& aOut) { + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(aOut)); + if (NS_FAILED(rv)) { + return rv; + } + nsCString dirName; + dirName.SetLength(32); + NS_MakeRandomString(dirName.BeginWriting(), 32); + aOut->Append(NS_ConvertUTF8toUTF16(dirName)); + rv = aOut->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +void TestMatchBaseDomain_MatchOrigin() { + nsCOMPtr<nsIFile> testDir; + nsresult rv = CreateTestDirectory(testDir); + EXPECT_NS_SUCCEEDED(rv); + + rv = WriteToFile(testDir, "origin"_ns, + "https://video.subdomain.removeme.github.io"_ns); + EXPECT_NS_SUCCEEDED(rv); + rv = WriteToFile(testDir, "topLevelOrigin"_ns, + "https://embedder.example.com"_ns); + EXPECT_NS_SUCCEEDED(rv); + bool result = MatchBaseDomain(testDir, "removeme.github.io"_ns); + EXPECT_TRUE(result); + testDir->Remove(true); +} + +void TestMatchBaseDomain_MatchTLD() { + nsCOMPtr<nsIFile> testDir; + nsresult rv = CreateTestDirectory(testDir); + EXPECT_NS_SUCCEEDED(rv); + + rv = WriteToFile(testDir, "origin"_ns, + "https://video.example.com^userContextId=4"_ns); + EXPECT_NS_SUCCEEDED(rv); + rv = WriteToFile(testDir, "topLevelOrigin"_ns, + "https://evil.web.megacorp.co.uk^privateBrowsingId=1"_ns); + EXPECT_NS_SUCCEEDED(rv); + bool result = MatchBaseDomain(testDir, "megacorp.co.uk"_ns); + EXPECT_TRUE(result); + testDir->Remove(true); +} + +void TestMatchBaseDomain_NoMatch() { + nsCOMPtr<nsIFile> testDir; + nsresult rv = CreateTestDirectory(testDir); + EXPECT_NS_SUCCEEDED(rv); + + rv = WriteToFile(testDir, "origin"_ns, + "https://video.example.com^userContextId=4"_ns); + EXPECT_NS_SUCCEEDED(rv); + rv = WriteToFile(testDir, "topLevelOrigin"_ns, + "https://evil.web.megacorp.co.uk^privateBrowsingId=1"_ns); + EXPECT_NS_SUCCEEDED(rv); + bool result = MatchBaseDomain(testDir, "longer-example.com"_ns); + EXPECT_FALSE(result); + testDir->Remove(true); +} + +TEST(GeckoMediaPlugins, MatchBaseDomain_MatchOrigin) +{ TestMatchBaseDomain_MatchOrigin(); } + +TEST(GeckoMediaPlugins, MatchBaseDomain_MatchTLD) +{ TestMatchBaseDomain_MatchTLD(); } + +TEST(GeckoMediaPlugins, MatchBaseDomain_NoMatch) +{ TestMatchBaseDomain_NoMatch(); } + +// Bug 1776767 - Skip all GMP tests on Windows ASAN +#if !(defined(XP_WIN) && defined(MOZ_ASAN)) +TEST(GeckoMediaPlugins, CDMStorageGetNodeId) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestGetNodeId); +} + +TEST(GeckoMediaPlugins, CDMStorageBasic) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestBasicStorage); +} + +TEST(GeckoMediaPlugins, CDMStorageForgetThisSite) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestForgetThisSite); +} + +TEST(GeckoMediaPlugins, CDMStorageForgetThisBaseDomain) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestForgetThisBaseDomain); +} + +TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory1) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestClearRecentHistory1); +} + +TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory2) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestClearRecentHistory2); +} + +TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory3) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestClearRecentHistory3); +} + +TEST(GeckoMediaPlugins, CDMStorageCrossOrigin) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestCrossOriginStorage); +} + +TEST(GeckoMediaPlugins, CDMStoragePrivateBrowsing) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestPBStorage); +} + +TEST(GeckoMediaPlugins, CDMStorageLongRecordNames) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestLongRecordNames); +} + +# if defined(XP_WIN) +TEST(GeckoMediaPlugins, GMPOutputProtection) +{ + RefPtr<CDMStorageTest> runner = new CDMStorageTest(); + runner->DoTest(&CDMStorageTest::TestOutputProtection); +} +# endif // defined(XP_WIN) +#endif // !(defined(XP_WIN) && defined(MOZ_ASAN)) |