/* -*- 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 "mozilla/RefPtr.h" #include "ChromiumCDMCallback.h" #include "GMPTestMonitor.h" #include "GMPServiceParent.h" #include "MediaResult.h" #include "nsIFile.h" #include "nsISimpleEnumerator.h" #include "nsNSSComponent.h" //For EnsureNSSInitializedChromeOrContent #include "nsThreadUtils.h" using namespace mozilla; using namespace mozilla::gmp; static already_AddRefed GetGMPThread() { RefPtr service = GeckoMediaPluginService::GetGeckoMediaPluginService(); nsCOMPtr thread; EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread)))); return thread.forget(); } static RefPtr GetAbstractGMPThread() { RefPtr service = GeckoMediaPluginService::GetGeckoMediaPluginService(); return service->GetAbstractGMPThread(); } /** * Enumerate files under |aPath| (non-recursive). */ template static nsresult EnumerateDir(nsIFile* aPath, T&& aDirIter) { nsCOMPtr iter; nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter)); if (NS_FAILED(rv)) { return rv; } nsCOMPtr 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 static nsresult EnumerateCDMStorageDir(const nsACString& aDir, T&& aDirIter) { RefPtr service = GeckoMediaPluginServiceParent::GetSingleton(); MOZ_ASSERT(service); // $profileDir/gmp/$platform/ nsCOMPtr 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 aShutdownTask, already_AddRefed 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 observerService = mozilla::services::GetObserverService(); EXPECT_TRUE(observerService); observerService->AddObserver(this, "gmp-shutdown", false); nsCOMPtr 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 observerService = mozilla::services::GetObserverService(); EXPECT_TRUE(observerService); observerService->RemoveObserver(this, "gmp-shutdown"); nsCOMPtr thread(GetGMPThread()); thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL); } return NS_OK; } private: virtual ~GMPShutdownObserver() {} nsCOMPtr mShutdownTask; nsCOMPtr 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 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 Continuation, nsIThread* aTarget, PRTime aSince) : mContinuation(Continuation) , mTarget(aTarget) , mSince(aSince) {} NS_DECL_THREADSAFE_ISUPPORTS NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr 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(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 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() {} nsCOMPtr mContinuation; nsCOMPtr mTarget; const PRTime mSince; }; NS_IMPL_ISUPPORTS(ClearCDMStorageTask, nsIRunnable, nsIObserver) static void ClearCDMStorage(already_AddRefed aContinuation, nsIThread* aTarget, PRTime aSince = -1) { RefPtr task( new ClearCDMStorageTask(std::move(aContinuation), aTarget, aSince)); SystemGroup::Dispatch(TaskCategory::Other, task.forget()); } static void SimulatePBModeExit() { // SystemGroup::EventTargetFor() doesn't support NS_DISPATCH_SYNC. 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 NodeId GetNodeId(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 NodeId(origin, topLevelOrigin, aGmpName); } static nsCString GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, bool aInPBMode) { RefPtr service = GeckoMediaPluginServiceParent::GetSingleton(); EXPECT_TRUE(service); nsCString nodeId; nsresult result; UniquePtr 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, NS_LITERAL_STRING("gmp-fake"), std::move(callback)); EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result)); return nodeId; } static bool IsCDMStorageIsEmpty() { RefPtr service = GeckoMediaPluginServiceParent::GetSingleton(); MOZ_ASSERT(service); nsCOMPtr 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 service = GeckoMediaPluginService::GetGeckoMediaPluginService(); MOZ_ASSERT(service); nsCOMPtr thread; service->GetThread(getter_AddRefs(thread)); MOZ_ASSERT(thread); nsCOMPtr currentThread; DebugOnly 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 thread(GetGMPThread()); ClearCDMStorage( NewRunnableMethod("CDMStorageTest::DoTest", this, aTestMethod), thread); AwaitFinished(); } CDMStorageTest() : mMonitor("CDMStorageTest") , mFinished(false) { } void Update(const nsCString& aMessage) { nsTArray msg; msg.AppendElements(aMessage.get(), aMessage.Length()); mCDM->UpdateSession(NS_LITERAL_CSTRING("fake-session-id"), 1, msg); } void TestGetNodeId() { AssertIsOnGMPThread(); EXPECT_TRUE(IsCDMStorageIsEmpty()); 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(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 thread(GetGMPThread()); ClearCDMStorage( NewRunnableMethod("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 = 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(); } void CreateDecryptor(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, bool aInPBMode, const nsCString& aUpdate) { nsTArray updates; updates.AppendElement(aUpdate); CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, std::move(updates)); } void CreateDecryptor(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, bool aInPBMode, nsTArray&& aUpdates) { CreateDecryptor(GetNodeId(aOrigin, aTopLevelOrigin, NS_LITERAL_STRING("gmp-fake"), aInPBMode), std::move(aUpdates)); } void CreateDecryptor(const NodeId& aNodeId, nsTArray&& aUpdates) { RefPtr service = GeckoMediaPluginService::GetGeckoMediaPluginService(); EXPECT_TRUE(service); nsTArray tags; tags.AppendElement(NS_LITERAL_CSTRING("fake")); RefPtr self = this; RefPtr promise = service->GetCDM(aNodeId, std::move(tags), nullptr); auto thread = GetAbstractGMPThread(); promise->Then(thread, __func__, [self, aUpdates](RefPtr cdm) { self->mCDM = cdm; EXPECT_TRUE(!!self->mCDM); self->mCallback.reset(new CallbackProxy(self)); nsCString failureReason; self->mCDM->Init(self->mCallback.get(), false, true, GetMainThreadEventTarget(), failureReason); for (auto& update : aUpdates) { self->Update(update); } }, [](MediaResult rv) { EXPECT_TRUE(false); }); } void TestBasicStorage() { AssertIsOnGMPThread(); EXPECT_TRUE(IsCDMStorageIsEmpty()); RefPtr 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("CDMStorageTest::SetFinished", this, &CDMStorageTest::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(IsCDMStorageIsEmpty()); // Generate storage data for some site. nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestForgetThisSite_AnotherSite", this, &CDMStorageTest::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 r = NewRunnableMethod("CDMStorageTest::TestForgetThisSite_CollectSiteInfo", this, &CDMStorageTest::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 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; UniquePtr siteInfo( new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"), pattern)); // Collect nodeIds that are expected to remain for later comparison. EnumerateCDMStorageDir(NS_LITERAL_CSTRING("id"), NodeIdCollector(siteInfo.get())); // Invoke "Forget this site" on the main thread. SystemGroup::Dispatch(TaskCategory::Other, NewRunnableMethod&&>( "CDMStorageTest::TestForgetThisSite_Forget", this, &CDMStorageTest::TestForgetThisSite_Forget, std::move(siteInfo))); } void TestForgetThisSite_Forget(UniquePtr&& aSiteInfo) { RefPtr service = GeckoMediaPluginServiceParent::GetSingleton(); service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget), aSiteInfo->mPattern); nsCOMPtr thread; service->GetThread(getter_AddRefs(thread)); nsCOMPtr r = NewRunnableMethod&&>( "CDMStorageTest::TestForgetThisSite_Verify", this, &CDMStorageTest::TestForgetThisSite_Verify, std::move(aSiteInfo)); thread->Dispatch(r, NS_DISPATCH_NORMAL); nsCOMPtr 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->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 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 mExpectedRemainingNodeIds; }; void TestForgetThisSite_Verify(UniquePtr&& aSiteInfo) { nsresult rv = EnumerateCDMStorageDir( NS_LITERAL_CSTRING("id"), NodeIdVerifier(aSiteInfo.get())); EXPECT_TRUE(NS_SUCCEEDED(rv)); rv = EnumerateCDMStorageDir( NS_LITERAL_CSTRING("storage"), StorageVerifier(aSiteInfo.get())); 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(IsCDMStorageIsEmpty()); // Generate storage data for some site. nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory1_Clear", this, &CDMStorageTest::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(IsCDMStorageIsEmpty()); // Generate storage data for some site. nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory2_Clear", this, &CDMStorageTest::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(IsCDMStorageIsEmpty()); // Generate storage data for some site. nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory3_Clear", this, &CDMStorageTest::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 = EnumerateCDMStorageDir(NS_LITERAL_CSTRING("id"), f); EXPECT_TRUE(NS_SUCCEEDED(rv)); nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, &CDMStorageTest::TestClearRecentHistory_CheckEmpty); nsCOMPtr t(GetGMPThread()); ClearCDMStorage(r.forget(), t, f.GetResult()); } void TestClearRecentHistory2_Clear() { MaxMTimeFinder f; nsresult rv = EnumerateCDMStorageDir(NS_LITERAL_CSTRING("storage"), f); EXPECT_TRUE(NS_SUCCEEDED(rv)); nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, &CDMStorageTest::TestClearRecentHistory_CheckEmpty); nsCOMPtr t(GetGMPThread()); ClearCDMStorage(r.forget(), t, f.GetResult()); } void TestClearRecentHistory3_Clear() { MaxMTimeFinder f; nsresult rv = EnumerateCDMStorageDir(NS_LITERAL_CSTRING("storage"), f); EXPECT_TRUE(NS_SUCCEEDED(rv)); nsCOMPtr r = NewRunnableMethod("CDMStorageTest::TestClearRecentHistory_CheckNonEmpty", this, &CDMStorageTest::TestClearRecentHistory_CheckNonEmpty); nsCOMPtr 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(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 = EnumerateCDMStorageDir(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 = EnumerateCDMStorageDir(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 = EnumerateCDMStorageDir(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(!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(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("CDMStorageTest::SetFinished", this, &CDMStorageTest::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( "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(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( "CDMStorageTest::TestPBStorage_RecordRetrievedContinuation", this, &CDMStorageTest::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("CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished)); CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"), NS_LITERAL_STRING("http://pb2.com"), true, NS_LITERAL_CSTRING("retrieve pbdata")); } #if defined(XP_WIN) void TestOutputProtection() { Shutdown(); Expect(NS_LITERAL_CSTRING("OP tests completed"), NewRunnableMethod("CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished)); CreateDecryptor(NS_LITERAL_STRING("http://example15.com"), NS_LITERAL_STRING("http://example16.com"), false, NS_LITERAL_CSTRING("test-op-apis")); } #endif 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("CDMStorageTest::SetFinished", this, &CDMStorageTest::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 Expect(const nsCString& aMessage, already_AddRefed aContinuation) { mExpected.AppendElement(ExpectedMessage(aMessage, std::move(aContinuation))); } void AwaitFinished() { mozilla::SpinEventLoopUntil([&]() -> bool { return mFinished; }); mFinished = false; } void ShutdownThen(already_AddRefed aContinuation) { EXPECT_TRUE(!!mCDM); if (!mCDM) { return; } EXPECT_FALSE(mNodeId.IsEmpty()); RefPtr task(new GMPShutdownObserver( NewRunnableMethod( "CDMStorageTest::Shutdown", this, &CDMStorageTest::Shutdown), std::move(aContinuation), mNodeId)); SystemGroup::Dispatch(TaskCategory::Other, task.forget()); } void Shutdown() { if (mCDM) { mCDM->Shutdown(); mCDM = nullptr; mNodeId = EmptyCString(); } } void Dummy() { } void SetFinished() { mFinished = true; Shutdown(); nsCOMPtr task = NewRunnableMethod("CDMStorageTest::Dummy", this, &CDMStorageTest::Dummy); SystemGroup::Dispatch(TaskCategory::Other, task.forget()); } void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, const nsTArray& 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 continuation = mExpected[0].mContinuation; mExpected.RemoveElementAt(0); if (continuation) { NS_DispatchToCurrentThread(continuation); } } } void Terminated() { if (mCDM) { mCDM->Shutdown(); mCDM = nullptr; } } private: ~CDMStorageTest() { } struct ExpectedMessage { ExpectedMessage(const nsCString& aMessage, already_AddRefed aContinuation) : mMessage(aMessage) , mContinuation(aContinuation) {} nsCString mMessage; nsCOMPtr mContinuation; }; nsTArray mExpected; RefPtr mSetDecryptorIdContinuation; RefPtr mCDM; Monitor mMonitor; Atomic 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, nsresult aError, const nsCString& aErrorMessage) override { } void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, nsTArray&& aMessage) override { mRunner->SessionMessage(aSessionId, aMessageType, std::move(aMessage)); } void SessionKeysChange(const nsCString& aSessionId, nsTArray&& aKeysInfo) override { } void ExpirationChange(const nsCString& aSessionId, double aSecondsSinceEpoch) override { } void SessionClosed(const nsCString& aSessionId) override { } void LegacySessionError(const nsCString& aSessionId, nsresult aError, uint32_t aSystemCode, const nsCString& aMessage) override { } void Terminated() override { mRunner->Terminated(); } void Shutdown() override { mRunner->Shutdown(); } private: // Warning: Weak ref. CDMStorageTest* mRunner; }; UniquePtr mCallback; }; // class CDMStorageTest TEST(GeckoMediaPlugins, CDMStorageGetNodeId) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestGetNodeId); } TEST(GeckoMediaPlugins, CDMStorageBasic) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestBasicStorage); } TEST(GeckoMediaPlugins, CDMStorageForgetThisSite) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestForgetThisSite); } TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory1) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestClearRecentHistory1); } TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory2) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestClearRecentHistory2); } TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory3) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestClearRecentHistory3); } TEST(GeckoMediaPlugins, CDMStorageCrossOrigin) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestCrossOriginStorage); } TEST(GeckoMediaPlugins, CDMStoragePrivateBrowsing) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestPBStorage); } #if defined(XP_WIN) TEST(GeckoMediaPlugins, GMPOutputProtection) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestOutputProtection); } #endif TEST(GeckoMediaPlugins, CDMStorageLongRecordNames) { RefPtr runner = new CDMStorageTest(); runner->DoTest(&CDMStorageTest::TestLongRecordNames); }