TestCDMStorage.cpp (46505B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ChromiumCDMCallback.h" 8 #include "ChromiumCDMParent.h" 9 #include "GMPServiceParent.h" 10 #include "GMPTestMonitor.h" 11 #include "MediaResult.h" 12 #include "gtest/gtest.h" 13 #include "mozilla/RefPtr.h" 14 #include "mozilla/SchedulerGroup.h" 15 #include "mozilla/SpinEventLoopUntil.h" 16 #include "mozilla/gtest/MozAssertions.h" 17 #include "nsCRTGlue.h" 18 #include "nsDirectoryServiceDefs.h" 19 #include "nsDirectoryServiceUtils.h" 20 #include "nsIFile.h" 21 #include "nsNSSComponent.h" //For EnsureNSSInitializedChromeOrContent 22 #include "nsThreadUtils.h" 23 24 using namespace mozilla; 25 using namespace mozilla::gmp; 26 27 static already_AddRefed<nsIThread> GetGMPThread() { 28 RefPtr<GeckoMediaPluginService> service = 29 GeckoMediaPluginService::GetGeckoMediaPluginService(); 30 nsCOMPtr<nsIThread> thread; 31 EXPECT_NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread))); 32 return thread.forget(); 33 } 34 35 /** 36 * Enumerate files under |aPath| (non-recursive). 37 */ 38 template <typename T> 39 static nsresult EnumerateDir(nsIFile* aPath, T&& aDirIter) { 40 nsCOMPtr<nsIDirectoryEnumerator> iter; 41 nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter)); 42 if (NS_FAILED(rv)) { 43 return rv; 44 } 45 46 nsCOMPtr<nsIFile> entry; 47 while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(entry))) && entry) { 48 aDirIter(entry); 49 } 50 return NS_OK; 51 } 52 53 /** 54 * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ 55 * (non-recursive). 56 */ 57 template <typename T> 58 static nsresult EnumerateCDMStorageDir(const nsACString& aDir, T&& aDirIter) { 59 RefPtr<GeckoMediaPluginServiceParent> service = 60 GeckoMediaPluginServiceParent::GetSingleton(); 61 MOZ_RELEASE_ASSERT(service); 62 63 // $profileDir/gmp/$platform/ 64 nsCOMPtr<nsIFile> path; 65 nsresult rv = service->GetStorageDir(getter_AddRefs(path)); 66 if (NS_FAILED(rv)) { 67 return rv; 68 } 69 70 // $profileDir/gmp/$platform/gmp-fake/ 71 rv = path->Append(u"gmp-fake"_ns); 72 if (NS_FAILED(rv)) { 73 return rv; 74 } 75 76 // $profileDir/gmp/$platform/gmp-fake/$aDir/ 77 rv = path->AppendNative(aDir); 78 if (NS_FAILED(rv)) { 79 return rv; 80 } 81 82 return EnumerateDir(path, aDirIter); 83 } 84 85 class GMPShutdownObserver : public nsIRunnable, public nsIObserver { 86 public: 87 GMPShutdownObserver(already_AddRefed<nsIRunnable> aShutdownTask, 88 already_AddRefed<nsIRunnable> Continuation, 89 const nsACString& aNodeId) 90 : mShutdownTask(aShutdownTask), 91 mContinuation(Continuation), 92 mNodeId(NS_ConvertUTF8toUTF16(aNodeId)) {} 93 94 NS_DECL_THREADSAFE_ISUPPORTS 95 96 NS_IMETHOD Run() override { 97 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 98 nsCOMPtr<nsIObserverService> observerService = 99 mozilla::services::GetObserverService(); 100 EXPECT_TRUE(observerService); 101 observerService->AddObserver(this, "gmp-shutdown", false); 102 103 nsCOMPtr<nsIThread> thread(GetGMPThread()); 104 thread->Dispatch(mShutdownTask, NS_DISPATCH_NORMAL); 105 return NS_OK; 106 } 107 108 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, 109 const char16_t* aSomeData) override { 110 if (!strcmp(aTopic, "gmp-shutdown") && 111 mNodeId.Equals(nsDependentString(aSomeData))) { 112 nsCOMPtr<nsIObserverService> observerService = 113 mozilla::services::GetObserverService(); 114 EXPECT_TRUE(observerService); 115 observerService->RemoveObserver(this, "gmp-shutdown"); 116 nsCOMPtr<nsIThread> thread(GetGMPThread()); 117 thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL); 118 } 119 return NS_OK; 120 } 121 122 private: 123 virtual ~GMPShutdownObserver() = default; 124 nsCOMPtr<nsIRunnable> mShutdownTask; 125 nsCOMPtr<nsIRunnable> mContinuation; 126 const nsString mNodeId; 127 }; 128 129 NS_IMPL_ISUPPORTS(GMPShutdownObserver, nsIRunnable, nsIObserver) 130 131 class NotifyObserversTask : public Runnable { 132 public: 133 explicit NotifyObserversTask(const char* aTopic) 134 : mozilla::Runnable("NotifyObserversTask"), mTopic(aTopic) {} 135 NS_IMETHOD Run() override { 136 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 137 nsCOMPtr<nsIObserverService> observerService = 138 mozilla::services::GetObserverService(); 139 if (observerService) { 140 observerService->NotifyObservers(nullptr, mTopic, nullptr); 141 } 142 return NS_OK; 143 } 144 const char* mTopic; 145 }; 146 147 class ClearCDMStorageTask : public nsIRunnable, public nsIObserver { 148 public: 149 ClearCDMStorageTask(already_AddRefed<nsIRunnable> Continuation, 150 nsIThread* aTarget, PRTime aSince) 151 : mContinuation(Continuation), mTarget(aTarget), mSince(aSince) {} 152 153 NS_DECL_THREADSAFE_ISUPPORTS 154 155 NS_IMETHOD Run() override { 156 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 157 nsCOMPtr<nsIObserverService> observerService = 158 mozilla::services::GetObserverService(); 159 EXPECT_TRUE(observerService); 160 observerService->AddObserver(this, "gmp-clear-storage-complete", false); 161 if (observerService) { 162 nsAutoString str; 163 if (mSince >= 0) { 164 str.AppendInt(static_cast<int64_t>(mSince)); 165 } 166 observerService->NotifyObservers(nullptr, "browser:purge-session-history", 167 str.Data()); 168 } 169 return NS_OK; 170 } 171 172 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, 173 const char16_t* aSomeData) override { 174 if (!strcmp(aTopic, "gmp-clear-storage-complete")) { 175 nsCOMPtr<nsIObserverService> observerService = 176 mozilla::services::GetObserverService(); 177 EXPECT_TRUE(observerService); 178 observerService->RemoveObserver(this, "gmp-clear-storage-complete"); 179 mTarget->Dispatch(mContinuation, NS_DISPATCH_NORMAL); 180 } 181 return NS_OK; 182 } 183 184 private: 185 virtual ~ClearCDMStorageTask() = default; 186 nsCOMPtr<nsIRunnable> mContinuation; 187 nsCOMPtr<nsIThread> mTarget; 188 const PRTime mSince; 189 }; 190 191 NS_IMPL_ISUPPORTS(ClearCDMStorageTask, nsIRunnable, nsIObserver) 192 193 static void ClearCDMStorage(already_AddRefed<nsIRunnable> aContinuation, 194 nsIThread* aTarget, PRTime aSince = -1) { 195 RefPtr<ClearCDMStorageTask> task( 196 new ClearCDMStorageTask(std::move(aContinuation), aTarget, aSince)); 197 SchedulerGroup::Dispatch(task.forget()); 198 } 199 200 static void SimulatePBModeExit() { 201 NS_DispatchAndSpinEventLoopUntilComplete( 202 "SimulatePBModeExit"_ns, GetMainThreadSerialEventTarget(), 203 MakeAndAddRef<NotifyObserversTask>("last-pb-context-exited")); 204 } 205 206 class TestGetNodeIdCallback : public GetNodeIdCallback { 207 public: 208 TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult) 209 : mNodeId(aNodeId), mResult(aResult) {} 210 211 void Done(nsresult aResult, const nsACString& aNodeId) { 212 mResult = aResult; 213 mNodeId = aNodeId; 214 } 215 216 private: 217 nsCString& mNodeId; 218 nsresult& mResult; 219 }; 220 221 static NodeIdParts GetNodeIdParts(const nsAString& aOrigin, 222 const nsAString& aTopLevelOrigin, 223 const nsAString& aGmpName, bool aInPBMode) { 224 OriginAttributes attrs; 225 attrs.mPrivateBrowsingId = aInPBMode ? 1 : 0; 226 227 nsAutoCString suffix; 228 attrs.CreateSuffix(suffix); 229 230 nsAutoString origin; 231 origin.Assign(aOrigin); 232 origin.Append(NS_ConvertUTF8toUTF16(suffix)); 233 234 nsAutoString topLevelOrigin; 235 topLevelOrigin.Assign(aTopLevelOrigin); 236 topLevelOrigin.Append(NS_ConvertUTF8toUTF16(suffix)); 237 return NodeIdParts{origin, topLevelOrigin, nsString(aGmpName)}; 238 } 239 240 static nsCString GetNodeId(const nsAString& aOrigin, 241 const nsAString& aTopLevelOrigin, bool aInPBMode) { 242 RefPtr<GeckoMediaPluginServiceParent> service = 243 GeckoMediaPluginServiceParent::GetSingleton(); 244 EXPECT_TRUE(service); 245 nsCString nodeId; 246 nsresult result; 247 UniquePtr<GetNodeIdCallback> callback( 248 new TestGetNodeIdCallback(nodeId, result)); 249 250 OriginAttributes attrs; 251 attrs.mPrivateBrowsingId = aInPBMode ? 1 : 0; 252 253 nsAutoCString suffix; 254 attrs.CreateSuffix(suffix); 255 256 nsAutoString origin; 257 origin.Assign(aOrigin); 258 origin.Append(NS_ConvertUTF8toUTF16(suffix)); 259 260 nsAutoString topLevelOrigin; 261 topLevelOrigin.Assign(aTopLevelOrigin); 262 topLevelOrigin.Append(NS_ConvertUTF8toUTF16(suffix)); 263 264 // We rely on the fact that the GetNodeId implementation for 265 // GeckoMediaPluginServiceParent is synchronous. 266 nsresult rv = service->GetNodeId(origin, topLevelOrigin, u"gmp-fake"_ns, 267 std::move(callback)); 268 EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result)); 269 return nodeId; 270 } 271 272 static bool IsCDMStorageIsEmpty() { 273 RefPtr<GeckoMediaPluginServiceParent> service = 274 GeckoMediaPluginServiceParent::GetSingleton(); 275 MOZ_RELEASE_ASSERT(service); 276 nsCOMPtr<nsIFile> storage; 277 nsresult rv = service->GetStorageDir(getter_AddRefs(storage)); 278 EXPECT_NS_SUCCEEDED(rv); 279 bool exists = false; 280 if (storage) { 281 storage->Exists(&exists); 282 } 283 return !exists; 284 } 285 286 static void AssertIsOnGMPThread() { 287 RefPtr<GeckoMediaPluginService> service = 288 GeckoMediaPluginService::GetGeckoMediaPluginService(); 289 MOZ_RELEASE_ASSERT(service); 290 nsCOMPtr<nsIThread> thread; 291 service->GetThread(getter_AddRefs(thread)); 292 MOZ_RELEASE_ASSERT(thread); 293 nsCOMPtr<nsIThread> currentThread; 294 nsresult rv = NS_GetCurrentThread(getter_AddRefs(currentThread)); 295 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 296 MOZ_RELEASE_ASSERT(currentThread == thread); 297 } 298 299 class CDMStorageTest { 300 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMStorageTest) 301 302 void DoTest(void (CDMStorageTest::*aTestMethod)()) { 303 EnsureNSSInitializedChromeOrContent(); 304 nsCOMPtr<nsIThread> thread(GetGMPThread()); 305 ClearCDMStorage( 306 NewRunnableMethod("CDMStorageTest::DoTest", this, aTestMethod), thread); 307 AwaitFinished(); 308 } 309 310 CDMStorageTest() : mMonitor("CDMStorageTest"), mFinished(false) {} 311 312 void Update(const nsCString& aMessage) { 313 nsTArray<uint8_t> msg; 314 msg.AppendElements(aMessage.get(), aMessage.Length()); 315 mCDM->UpdateSession("fake-session-id"_ns, 1, msg); 316 } 317 318 void TestGetNodeId() { 319 AssertIsOnGMPThread(); 320 321 EXPECT_TRUE(IsCDMStorageIsEmpty()); 322 323 const nsString origin1 = u"http://example1.com"_ns; 324 const nsString origin2 = u"http://example2.org"_ns; 325 326 nsCString PBnodeId1 = GetNodeId(origin1, origin2, true); 327 nsCString PBnodeId2 = GetNodeId(origin1, origin2, true); 328 329 // Node ids for the same origins should be the same in PB mode. 330 EXPECT_TRUE(PBnodeId1.Equals(PBnodeId2)); 331 332 nsCString PBnodeId3 = GetNodeId(origin2, origin1, true); 333 334 // Node ids with origin and top level origin swapped should be different. 335 EXPECT_TRUE(!PBnodeId3.Equals(PBnodeId1)); 336 337 // Getting node ids in PB mode should not result in the node id being 338 // stored. 339 EXPECT_TRUE(IsCDMStorageIsEmpty()); 340 341 nsCString nodeId1 = GetNodeId(origin1, origin2, false); 342 nsCString nodeId2 = GetNodeId(origin1, origin2, false); 343 344 // NodeIds for the same origin pair in non-pb mode should be the same. 345 EXPECT_TRUE(nodeId1.Equals(nodeId2)); 346 347 // Node ids for a given origin pair should be different for the PB origins 348 // should be the same in PB mode. 349 EXPECT_TRUE(!PBnodeId1.Equals(nodeId1)); 350 EXPECT_TRUE(!PBnodeId2.Equals(nodeId2)); 351 352 nsCOMPtr<nsIThread> thread(GetGMPThread()); 353 ClearCDMStorage(NewRunnableMethod<nsCString>( 354 "CDMStorageTest::TestGetNodeId_Continuation", this, 355 &CDMStorageTest::TestGetNodeId_Continuation, nodeId1), 356 thread); 357 } 358 359 void TestGetNodeId_Continuation(nsCString aNodeId1) { 360 EXPECT_TRUE(IsCDMStorageIsEmpty()); 361 362 // Once we clear storage, the node ids generated for the same origin-pair 363 // should be different. 364 const nsString origin1 = u"http://example1.com"_ns; 365 const nsString origin2 = u"http://example2.org"_ns; 366 nsCString nodeId3 = GetNodeId(origin1, origin2, false); 367 EXPECT_TRUE(!aNodeId1.Equals(nodeId3)); 368 369 SetFinished(); 370 } 371 372 void CreateDecryptor(const nsAString& aOrigin, 373 const nsAString& aTopLevelOrigin, bool aInPBMode, 374 const nsCString& aUpdate) { 375 nsTArray<nsCString> updates; 376 updates.AppendElement(aUpdate); 377 CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, std::move(updates)); 378 } 379 380 void CreateDecryptor(const nsAString& aOrigin, 381 const nsAString& aTopLevelOrigin, bool aInPBMode, 382 nsTArray<nsCString>&& aUpdates) { 383 CreateDecryptor( 384 GetNodeIdParts(aOrigin, aTopLevelOrigin, u"gmp-fake"_ns, aInPBMode), 385 std::move(aUpdates)); 386 } 387 388 void CreateDecryptor(const NodeIdParts& aNodeId, 389 nsTArray<nsCString>&& aUpdates) { 390 RefPtr<GeckoMediaPluginService> service = 391 GeckoMediaPluginService::GetGeckoMediaPluginService(); 392 EXPECT_TRUE(service); 393 394 nsCString keySystem{"fake"_ns}; 395 396 RefPtr<CDMStorageTest> self = this; 397 RefPtr<gmp::GetCDMParentPromise> promise = 398 service->GetCDM(aNodeId, keySystem, nullptr); 399 nsCOMPtr<nsISerialEventTarget> thread = GetGMPThread(); 400 promise->Then( 401 thread, __func__, 402 [self, updates = std::move(aUpdates), 403 thread](RefPtr<gmp::ChromiumCDMParent> cdm) mutable { 404 self->mCDM = cdm; 405 EXPECT_TRUE(!!self->mCDM); 406 self->mCallback.reset(new CallbackProxy(self)); 407 nsCString failureReason; 408 self->mCDM 409 ->Init(self->mCallback.get(), false, true, 410 GetMainThreadSerialEventTarget()) 411 ->Then( 412 thread, __func__, 413 [self, updates = std::move(updates)] { 414 for (const auto& update : updates) { 415 self->Update(update); 416 } 417 }, 418 [](MediaResult rv) { EXPECT_TRUE(false); }); 419 }, 420 [](MediaResult rv) { EXPECT_TRUE(false); }); 421 } 422 423 void TestBasicStorage() { 424 AssertIsOnGMPThread(); 425 EXPECT_TRUE(IsCDMStorageIsEmpty()); 426 427 RefPtr<GeckoMediaPluginService> service = 428 GeckoMediaPluginService::GetGeckoMediaPluginService(); 429 430 // Send a message to the fake GMP for it to run its own tests internally. 431 // It sends us a "test-storage complete" message when its passed, or 432 // some other message if its tests fail. 433 Expect("test-storage complete"_ns, 434 NewRunnableMethod("CDMStorageTest::SetFinished", this, 435 &CDMStorageTest::SetFinished)); 436 437 CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, 438 "test-storage"_ns); 439 } 440 441 /** 442 * 1. Generate storage data for some sites. 443 * 2. Forget about one of the sites. 444 * 3. Check if the storage data for the forgotten site are erased correctly. 445 * 4. Check if the storage data for other sites remain unchanged. 446 */ 447 void TestForgetThisSite() { 448 AssertIsOnGMPThread(); 449 EXPECT_TRUE(IsCDMStorageIsEmpty()); 450 451 // Generate storage data for some site. 452 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 453 "CDMStorageTest::TestForgetThisSite_AnotherSite", this, 454 &CDMStorageTest::TestForgetThisSite_AnotherSite); 455 Expect("test-storage complete"_ns, r.forget()); 456 457 CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, 458 "test-storage"_ns); 459 } 460 461 void TestForgetThisSite_AnotherSite() { 462 Shutdown(); 463 464 // Generate storage data for another site. 465 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 466 "CDMStorageTest::TestForgetThisSite_CollectSiteInfo", this, 467 &CDMStorageTest::TestForgetThisSite_CollectSiteInfo); 468 Expect("test-storage complete"_ns, r.forget()); 469 470 CreateDecryptor(u"http://example3.com"_ns, u"http://example4.com"_ns, false, 471 "test-storage"_ns); 472 } 473 474 struct NodeInfo { 475 explicit NodeInfo(const nsACString& aSite, 476 const mozilla::OriginAttributesPattern& aPattern) 477 : siteToForget(aSite), mPattern(aPattern) {} 478 nsCString siteToForget; 479 mozilla::OriginAttributesPattern mPattern; 480 nsTArray<nsCString> mExpectedRemainingNodeIds; 481 }; 482 483 class NodeIdCollector { 484 public: 485 explicit NodeIdCollector(NodeInfo* aInfo) : mNodeInfo(aInfo) {} 486 void operator()(nsIFile* aFile) { 487 nsCString salt; 488 nsresult rv = ReadSalt(aFile, salt); 489 ASSERT_NS_SUCCEEDED(rv); 490 if (!MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) { 491 mNodeInfo->mExpectedRemainingNodeIds.AppendElement(salt); 492 } 493 } 494 495 private: 496 NodeInfo* mNodeInfo; 497 }; 498 499 void TestForgetThisSite_CollectSiteInfo() { 500 mozilla::OriginAttributesPattern pattern; 501 502 UniquePtr<NodeInfo> siteInfo( 503 new NodeInfo("http://example1.com"_ns, pattern)); 504 // Collect nodeIds that are expected to remain for later comparison. 505 EnumerateCDMStorageDir("id"_ns, NodeIdCollector(siteInfo.get())); 506 // Invoke "Forget this site" on the main thread. 507 SchedulerGroup::Dispatch(NewRunnableMethod<UniquePtr<NodeInfo>&&>( 508 "CDMStorageTest::TestForgetThisSite_Forget", this, 509 &CDMStorageTest::TestForgetThisSite_Forget, std::move(siteInfo))); 510 } 511 512 void TestForgetThisSite_Forget(UniquePtr<NodeInfo>&& aSiteInfo) { 513 RefPtr<GeckoMediaPluginServiceParent> service = 514 GeckoMediaPluginServiceParent::GetSingleton(); 515 service->ForgetThisSiteNative( 516 NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget), aSiteInfo->mPattern); 517 518 nsCOMPtr<nsIThread> thread; 519 service->GetThread(getter_AddRefs(thread)); 520 521 nsCOMPtr<nsIRunnable> r = NewRunnableMethod<UniquePtr<NodeInfo>&&>( 522 "CDMStorageTest::TestForgetThisSite_Verify", this, 523 &CDMStorageTest::TestForgetThisSite_Verify, std::move(aSiteInfo)); 524 thread->Dispatch(r, NS_DISPATCH_NORMAL); 525 526 nsCOMPtr<nsIRunnable> f = NewRunnableMethod( 527 "CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished); 528 thread->Dispatch(f, NS_DISPATCH_NORMAL); 529 } 530 531 class NodeIdVerifier { 532 public: 533 explicit NodeIdVerifier(const NodeInfo* aInfo) 534 : mNodeInfo(aInfo), 535 mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} 536 void operator()(nsIFile* aFile) { 537 nsCString salt; 538 nsresult rv = ReadSalt(aFile, salt); 539 ASSERT_NS_SUCCEEDED(rv); 540 // Shouldn't match the origin if we clear correctly. 541 EXPECT_FALSE( 542 MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) 543 << "Found files persisted that match against a site that should " 544 "have been removed!"; 545 // Check if remaining nodeIDs are as expected. 546 EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) 547 << "Failed to remove salt from expected remaining node ids. This " 548 "indicates storage that should be forgotten is still persisted!"; 549 } 550 ~NodeIdVerifier() { 551 EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) 552 << "Some expected remaining node ids were not checked against. This " 553 "indicates that data we expected to find in storage was missing!"; 554 } 555 556 private: 557 const NodeInfo* mNodeInfo; 558 nsTArray<nsCString> mExpectedRemainingNodeIds; 559 }; 560 561 class StorageVerifier { 562 public: 563 explicit StorageVerifier(const NodeInfo* aInfo) 564 : mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} 565 void operator()(nsIFile* aFile) { 566 nsCString salt; 567 nsresult rv = aFile->GetNativeLeafName(salt); 568 ASSERT_NS_SUCCEEDED(rv); 569 EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) 570 << "Failed to remove salt from expected remaining node ids. This " 571 "indicates storage that should be forgotten is still persisted!"; 572 } 573 ~StorageVerifier() { 574 EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) 575 << "Some expected remaining node ids were not checked against. This " 576 "indicates that data we expected to find in storage was missing!"; 577 } 578 579 private: 580 nsTArray<nsCString> mExpectedRemainingNodeIds; 581 }; 582 583 void TestForgetThisSite_Verify(UniquePtr<NodeInfo>&& aSiteInfo) { 584 nsresult rv = 585 EnumerateCDMStorageDir("id"_ns, NodeIdVerifier(aSiteInfo.get())); 586 EXPECT_NS_SUCCEEDED(rv); 587 588 rv = EnumerateCDMStorageDir("storage"_ns, StorageVerifier(aSiteInfo.get())); 589 EXPECT_NS_SUCCEEDED(rv); 590 } 591 592 /** 593 * 1. Generate storage data for some sites. 594 * 2. Forget about base domain example1.com 595 * 3. Check if the storage data for the forgotten site are erased correctly. 596 * 4. Check if the storage data for other sites remain unchanged. 597 */ 598 void TestForgetThisBaseDomain() { 599 AssertIsOnGMPThread(); 600 EXPECT_TRUE(IsCDMStorageIsEmpty()); 601 602 // Generate storage data for some site. 603 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 604 "CDMStorageTest::TestForgetThisBaseDomain_SecondSite", this, 605 &CDMStorageTest::TestForgetThisBaseDomain_SecondSite); 606 Expect("test-storage complete"_ns, r.forget()); 607 608 CreateDecryptor(u"http://media.example1.com"_ns, 609 u"http://tld.example2.com"_ns, false, "test-storage"_ns); 610 } 611 612 void TestForgetThisBaseDomain_SecondSite() { 613 Shutdown(); 614 615 // Generate storage data for another site. 616 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 617 "CDMStorageTest::TestForgetThisBaseDomain_ThirdSite", this, 618 &CDMStorageTest::TestForgetThisBaseDomain_ThirdSite); 619 Expect("test-storage complete"_ns, r.forget()); 620 621 CreateDecryptor(u"http://media.somewhereelse.com"_ns, 622 u"http://home.example1.com"_ns, false, "test-storage"_ns); 623 } 624 625 void TestForgetThisBaseDomain_ThirdSite() { 626 Shutdown(); 627 628 // Generate storage data for another site. 629 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 630 "CDMStorageTest::TestForgetThisBaseDomain_CollectSiteInfo", this, 631 &CDMStorageTest::TestForgetThisBaseDomain_CollectSiteInfo); 632 Expect("test-storage complete"_ns, r.forget()); 633 634 CreateDecryptor(u"http://media.example3.com"_ns, 635 u"http://tld.long-example1.com"_ns, false, 636 "test-storage"_ns); 637 } 638 639 struct BaseDomainNodeInfo { 640 explicit BaseDomainNodeInfo(const nsACString& aBaseDomain) 641 : baseDomainToForget(aBaseDomain) {} 642 nsCString baseDomainToForget; 643 644 nsTArray<nsCString> mExpectedRemainingNodeIds; 645 }; 646 647 class BaseDomainNodeIdCollector { 648 public: 649 explicit BaseDomainNodeIdCollector(BaseDomainNodeInfo* aInfo) 650 : mNodeInfo(aInfo) {} 651 void operator()(nsIFile* aFile) { 652 nsCString salt; 653 nsresult rv = ReadSalt(aFile, salt); 654 ASSERT_NS_SUCCEEDED(rv); 655 if (!MatchBaseDomain(aFile, mNodeInfo->baseDomainToForget)) { 656 mNodeInfo->mExpectedRemainingNodeIds.AppendElement(salt); 657 } 658 } 659 660 private: 661 BaseDomainNodeInfo* mNodeInfo; 662 }; 663 664 void TestForgetThisBaseDomain_CollectSiteInfo() { 665 UniquePtr<BaseDomainNodeInfo> siteInfo( 666 new BaseDomainNodeInfo("example1.com"_ns)); 667 // Collect nodeIds that are expected to remain for later comparison. 668 EnumerateCDMStorageDir("id"_ns, BaseDomainNodeIdCollector(siteInfo.get())); 669 // Invoke "ForgetThisBaseDomain" on the main thread. 670 SchedulerGroup::Dispatch(NewRunnableMethod<UniquePtr<BaseDomainNodeInfo>&&>( 671 "CDMStorageTest::TestForgetThisBaseDomain_Forget", this, 672 &CDMStorageTest::TestForgetThisBaseDomain_Forget, std::move(siteInfo))); 673 } 674 675 void TestForgetThisBaseDomain_Forget( 676 UniquePtr<BaseDomainNodeInfo>&& aSiteInfo) { 677 RefPtr<GeckoMediaPluginServiceParent> service = 678 GeckoMediaPluginServiceParent::GetSingleton(); 679 service->ForgetThisBaseDomain( 680 NS_ConvertUTF8toUTF16(aSiteInfo->baseDomainToForget)); 681 682 nsCOMPtr<nsIThread> thread; 683 service->GetThread(getter_AddRefs(thread)); 684 685 nsCOMPtr<nsIRunnable> r = 686 NewRunnableMethod<UniquePtr<BaseDomainNodeInfo>&&>( 687 "CDMStorageTest::TestForgetThisBaseDomain_Verify", this, 688 &CDMStorageTest::TestForgetThisBaseDomain_Verify, 689 std::move(aSiteInfo)); 690 thread->Dispatch(r, NS_DISPATCH_NORMAL); 691 692 nsCOMPtr<nsIRunnable> f = NewRunnableMethod( 693 "CDMStorageTest::SetFinished", this, &CDMStorageTest::SetFinished); 694 thread->Dispatch(f, NS_DISPATCH_NORMAL); 695 } 696 697 class BaseDomainNodeIdVerifier { 698 public: 699 explicit BaseDomainNodeIdVerifier(const BaseDomainNodeInfo* aInfo) 700 : mNodeInfo(aInfo), 701 mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} 702 void operator()(nsIFile* aFile) { 703 nsCString salt; 704 nsresult rv = ReadSalt(aFile, salt); 705 ASSERT_NS_SUCCEEDED(rv); 706 // Shouldn't match the origin if we clear correctly. 707 EXPECT_FALSE(MatchBaseDomain(aFile, mNodeInfo->baseDomainToForget)) 708 << "Found files persisted that match against a domain that should " 709 "have been removed!"; 710 // Check if remaining nodeIDs are as expected. 711 EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) 712 << "Failed to remove salt from expected remaining node ids. This " 713 "indicates storage that should be forgotten is still persisted!"; 714 } 715 ~BaseDomainNodeIdVerifier() { 716 EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) 717 << "Some expected remaining node ids were not checked against. This " 718 "indicates that data we expected to find in storage was missing!"; 719 } 720 721 private: 722 const BaseDomainNodeInfo* mNodeInfo; 723 nsTArray<nsCString> mExpectedRemainingNodeIds; 724 }; 725 726 class BaseDomainStorageVerifier { 727 public: 728 explicit BaseDomainStorageVerifier(const BaseDomainNodeInfo* aInfo) 729 : mExpectedRemainingNodeIds(aInfo->mExpectedRemainingNodeIds.Clone()) {} 730 void operator()(nsIFile* aFile) { 731 nsCString salt; 732 nsresult rv = aFile->GetNativeLeafName(salt); 733 ASSERT_NS_SUCCEEDED(rv); 734 EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt)) 735 << "Failed to remove salt from expected remaining node ids. This " 736 "indicates storage that should be forgotten is still persisted!"; 737 ; 738 } 739 ~BaseDomainStorageVerifier() { 740 EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty()) 741 << "Some expected remaining node ids were not checked against. This " 742 "indicates that data we expected to find in storage was missing!"; 743 ; 744 } 745 746 private: 747 nsTArray<nsCString> mExpectedRemainingNodeIds; 748 }; 749 750 void TestForgetThisBaseDomain_Verify( 751 UniquePtr<BaseDomainNodeInfo>&& aSiteInfo) { 752 nsresult rv = EnumerateCDMStorageDir( 753 "id"_ns, BaseDomainNodeIdVerifier(aSiteInfo.get())); 754 EXPECT_NS_SUCCEEDED(rv); 755 756 rv = EnumerateCDMStorageDir("storage"_ns, 757 BaseDomainStorageVerifier(aSiteInfo.get())); 758 EXPECT_NS_SUCCEEDED(rv); 759 } 760 761 /** 762 * 1. Generate some storage data. 763 * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/id/. 764 * 3. Pass |t| to clear recent history. 765 * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and 766 * $profileDir/gmp/$platform/gmp-fake/storage are removed. 767 */ 768 void TestClearRecentHistory1() { 769 AssertIsOnGMPThread(); 770 EXPECT_TRUE(IsCDMStorageIsEmpty()); 771 772 // Generate storage data for some site. 773 nsCOMPtr<nsIRunnable> r = 774 NewRunnableMethod("CDMStorageTest::TestClearRecentHistory1_Clear", this, 775 &CDMStorageTest::TestClearRecentHistory1_Clear); 776 Expect("test-storage complete"_ns, r.forget()); 777 778 CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, 779 "test-storage"_ns); 780 } 781 782 /** 783 * 1. Generate some storage data. 784 * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/. 785 * 3. Pass |t| to clear recent history. 786 * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and 787 * $profileDir/gmp/$platform/gmp-fake/storage are removed. 788 */ 789 void TestClearRecentHistory2() { 790 AssertIsOnGMPThread(); 791 EXPECT_TRUE(IsCDMStorageIsEmpty()); 792 793 // Generate storage data for some site. 794 nsCOMPtr<nsIRunnable> r = 795 NewRunnableMethod("CDMStorageTest::TestClearRecentHistory2_Clear", this, 796 &CDMStorageTest::TestClearRecentHistory2_Clear); 797 Expect("test-storage complete"_ns, r.forget()); 798 799 CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, 800 "test-storage"_ns); 801 } 802 803 /** 804 * 1. Generate some storage data. 805 * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/. 806 * 3. Pass |t+1| to clear recent history. 807 * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and 808 * $profileDir/gmp/$platform/gmp-fake/storage remain unchanged. 809 */ 810 void TestClearRecentHistory3() { 811 AssertIsOnGMPThread(); 812 EXPECT_TRUE(IsCDMStorageIsEmpty()); 813 814 // Generate storage data for some site. 815 nsCOMPtr<nsIRunnable> r = 816 NewRunnableMethod("CDMStorageTest::TestClearRecentHistory3_Clear", this, 817 &CDMStorageTest::TestClearRecentHistory3_Clear); 818 Expect("test-storage complete"_ns, r.forget()); 819 820 CreateDecryptor(u"http://example1.com"_ns, u"http://example2.com"_ns, false, 821 "test-storage"_ns); 822 } 823 824 class MaxMTimeFinder { 825 public: 826 MaxMTimeFinder() : mMaxTime(0) {} 827 void operator()(nsIFile* aFile) { 828 PRTime lastModified; 829 nsresult rv = aFile->GetLastModifiedTime(&lastModified); 830 if (NS_SUCCEEDED(rv) && lastModified > mMaxTime) { 831 mMaxTime = lastModified; 832 } 833 EnumerateDir(aFile, *this); 834 } 835 PRTime GetResult() const { return mMaxTime; } 836 837 private: 838 PRTime mMaxTime; 839 }; 840 841 void TestClearRecentHistory1_Clear() { 842 MaxMTimeFinder f; 843 nsresult rv = EnumerateCDMStorageDir("id"_ns, f); 844 EXPECT_NS_SUCCEEDED(rv); 845 846 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 847 "CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, 848 &CDMStorageTest::TestClearRecentHistory_CheckEmpty); 849 nsCOMPtr<nsIThread> t(GetGMPThread()); 850 ClearCDMStorage(r.forget(), t, f.GetResult()); 851 } 852 853 void TestClearRecentHistory2_Clear() { 854 MaxMTimeFinder f; 855 nsresult rv = EnumerateCDMStorageDir("storage"_ns, f); 856 EXPECT_NS_SUCCEEDED(rv); 857 858 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 859 "CDMStorageTest::TestClearRecentHistory_CheckEmpty", this, 860 &CDMStorageTest::TestClearRecentHistory_CheckEmpty); 861 nsCOMPtr<nsIThread> t(GetGMPThread()); 862 ClearCDMStorage(r.forget(), t, f.GetResult()); 863 } 864 865 void TestClearRecentHistory3_Clear() { 866 MaxMTimeFinder f; 867 nsresult rv = EnumerateCDMStorageDir("storage"_ns, f); 868 EXPECT_NS_SUCCEEDED(rv); 869 870 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 871 "CDMStorageTest::TestClearRecentHistory_CheckNonEmpty", this, 872 &CDMStorageTest::TestClearRecentHistory_CheckNonEmpty); 873 nsCOMPtr<nsIThread> t(GetGMPThread()); 874 ClearCDMStorage(r.forget(), t, f.GetResult() + 1); 875 } 876 877 class FileCounter { 878 public: 879 FileCounter() : mCount(0) {} 880 void operator()(nsIFile* aFile) { ++mCount; } 881 int GetCount() const { return mCount; } 882 883 private: 884 int mCount; 885 }; 886 887 void TestClearRecentHistory_CheckEmpty() { 888 FileCounter c1; 889 nsresult rv = EnumerateCDMStorageDir("id"_ns, c1); 890 EXPECT_NS_SUCCEEDED(rv); 891 // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/ 892 EXPECT_EQ(c1.GetCount(), 0); 893 894 FileCounter c2; 895 rv = EnumerateCDMStorageDir("storage"_ns, c2); 896 EXPECT_NS_SUCCEEDED(rv); 897 // There should be no files under 898 // $profileDir/gmp/$platform/gmp-fake/storage/ 899 EXPECT_EQ(c2.GetCount(), 0); 900 901 SetFinished(); 902 } 903 904 void TestClearRecentHistory_CheckNonEmpty() { 905 FileCounter c1; 906 nsresult rv = EnumerateCDMStorageDir("id"_ns, c1); 907 EXPECT_NS_SUCCEEDED(rv); 908 // There should be one directory under 909 // $profileDir/gmp/$platform/gmp-fake/id/ 910 EXPECT_EQ(c1.GetCount(), 1); 911 912 FileCounter c2; 913 rv = EnumerateCDMStorageDir("storage"_ns, c2); 914 EXPECT_NS_SUCCEEDED(rv); 915 // There should be one directory under 916 // $profileDir/gmp/$platform/gmp-fake/storage/ 917 EXPECT_EQ(c2.GetCount(), 1); 918 919 SetFinished(); 920 } 921 922 void TestCrossOriginStorage() { 923 EXPECT_TRUE(!mCDM); 924 925 // Send the decryptor the message "store recordid $time" 926 // Wait for the decrytor to send us "stored recordid $time" 927 auto t = time(0); 928 nsCString response("stored crossOriginTestRecordId "); 929 response.AppendInt((int64_t)t); 930 Expect( 931 response, 932 NewRunnableMethod( 933 "CDMStorageTest::TestCrossOriginStorage_RecordStoredContinuation", 934 this, 935 &CDMStorageTest::TestCrossOriginStorage_RecordStoredContinuation)); 936 937 nsCString update("store crossOriginTestRecordId "); 938 update.AppendInt((int64_t)t); 939 940 // Open decryptor on one, origin, write a record, and test that that 941 // record can't be read on another origin. 942 CreateDecryptor(u"http://example3.com"_ns, u"http://example4.com"_ns, false, 943 update); 944 } 945 946 void TestCrossOriginStorage_RecordStoredContinuation() { 947 // Close the old decryptor, and create a new one on a different origin, 948 // and try to read the record. 949 Shutdown(); 950 951 Expect(nsLiteralCString( 952 "retrieve crossOriginTestRecordId succeeded (length 0 bytes)"), 953 NewRunnableMethod("CDMStorageTest::SetFinished", this, 954 &CDMStorageTest::SetFinished)); 955 956 CreateDecryptor(u"http://example5.com"_ns, u"http://example6.com"_ns, false, 957 "retrieve crossOriginTestRecordId"_ns); 958 } 959 960 void TestPBStorage() { 961 // Send the decryptor the message "store recordid $time" 962 // Wait for the decrytor to send us "stored recordid $time" 963 nsCString response("stored pbdata test-pb-data"); 964 Expect(response, 965 NewRunnableMethod( 966 "CDMStorageTest::TestPBStorage_RecordStoredContinuation", this, 967 &CDMStorageTest::TestPBStorage_RecordStoredContinuation)); 968 969 // Open decryptor on one, origin, write a record, close decryptor, 970 // open another, and test that record can be read, close decryptor, 971 // then send pb-last-context-closed notification, then open decryptor 972 // and check that it can't read that data; it should have been purged. 973 CreateDecryptor(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, 974 "store pbdata test-pb-data"_ns); 975 } 976 977 void TestPBStorage_RecordStoredContinuation() { 978 Shutdown(); 979 980 Expect( 981 "retrieve pbdata succeeded (length 12 bytes)"_ns, 982 NewRunnableMethod( 983 "CDMStorageTest::TestPBStorage_RecordRetrievedContinuation", this, 984 &CDMStorageTest::TestPBStorage_RecordRetrievedContinuation)); 985 986 CreateDecryptor(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, 987 "retrieve pbdata"_ns); 988 } 989 990 void TestPBStorage_RecordRetrievedContinuation() { 991 Shutdown(); 992 SimulatePBModeExit(); 993 994 Expect("retrieve pbdata succeeded (length 0 bytes)"_ns, 995 NewRunnableMethod("CDMStorageTest::SetFinished", this, 996 &CDMStorageTest::SetFinished)); 997 998 CreateDecryptor(u"http://pb1.com"_ns, u"http://pb2.com"_ns, true, 999 "retrieve pbdata"_ns); 1000 } 1001 1002 #if defined(XP_WIN) 1003 void TestOutputProtection() { 1004 Shutdown(); 1005 1006 Expect("OP tests completed"_ns, 1007 NewRunnableMethod("CDMStorageTest::SetFinished", this, 1008 &CDMStorageTest::SetFinished)); 1009 1010 CreateDecryptor(u"http://example15.com"_ns, u"http://example16.com"_ns, 1011 false, "test-op-apis"_ns); 1012 } 1013 #endif 1014 1015 void TestLongRecordNames() { 1016 constexpr auto longRecordName = 1017 "A_" 1018 "very_very_very_very_very_very_very_very_very_" 1019 "very_very_very_very_very_very_" 1020 "very_very_very_very_very_very_very_very_very_" 1021 "very_very_very_very_very_very_" 1022 "very_very_very_very_very_very_very_very_very_" 1023 "very_very_very_very_very_very_" 1024 "very_very_very_very_very_very_very_very_very_" 1025 "very_very_very_very_very_very_" 1026 "very_very_very_very_very_very_very_very_very_" 1027 "very_very_very_very_very_very_" 1028 "very_very_very_very_very_very_very_very_very_" 1029 "very_very_very_very_very_very_" 1030 "very_very_very_very_very_very_very_very_very_" 1031 "very_very_very_very_very_very_" 1032 "very_very_very_very_very_very_very_very_very_" 1033 "very_very_very_very_very_very_" 1034 "very_very_very_very_very_very_very_very_very_" 1035 "very_very_very_very_very_very_" 1036 "very_very_very_very_very_very_very_very_very_" 1037 "very_very_very_very_very_very_" 1038 "very_very_very_very_very_very_very_very_very_" 1039 "very_very_very_very_very_very_" 1040 "very_very_very_very_very_very_very_very_very_" 1041 "very_very_very_very_very_very_" 1042 "very_very_very_very_very_very_very_very_very_" 1043 "very_very_very_very_very_very_" 1044 "very_very_very_very_very_very_very_very_very_" 1045 "very_very_very_very_very_very_" 1046 "very_very_very_very_very_very_very_very_very_" 1047 "very_very_very_very_very_very_" 1048 "long_record_name"_ns; 1049 1050 constexpr auto data = "Just_some_arbitrary_data."_ns; 1051 1052 MOZ_RELEASE_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE); 1053 MOZ_RELEASE_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH 1054 1055 nsCString response("stored "); 1056 response.Append(longRecordName); 1057 response.AppendLiteral(" "); 1058 response.Append(data); 1059 Expect(response, NewRunnableMethod("CDMStorageTest::SetFinished", this, 1060 &CDMStorageTest::SetFinished)); 1061 1062 nsCString update("store "); 1063 update.Append(longRecordName); 1064 update.AppendLiteral(" "); 1065 update.Append(data); 1066 CreateDecryptor(u"http://fuz.com"_ns, u"http://baz.com"_ns, false, update); 1067 } 1068 1069 void Expect(const nsCString& aMessage, 1070 already_AddRefed<nsIRunnable> aContinuation) { 1071 mExpected.AppendElement( 1072 ExpectedMessage(aMessage, std::move(aContinuation))); 1073 } 1074 1075 void AwaitFinished() { 1076 mozilla::SpinEventLoopUntil("CDMStorageTest::AwaitFinished"_ns, 1077 [&]() -> bool { return mFinished; }); 1078 mFinished = false; 1079 } 1080 1081 void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) { 1082 EXPECT_TRUE(!!mCDM); 1083 if (!mCDM) { 1084 return; 1085 } 1086 EXPECT_FALSE(mNodeId.IsEmpty()); 1087 RefPtr<GMPShutdownObserver> task(new GMPShutdownObserver( 1088 NewRunnableMethod("CDMStorageTest::Shutdown", this, 1089 &CDMStorageTest::Shutdown), 1090 std::move(aContinuation), mNodeId)); 1091 SchedulerGroup::Dispatch(task.forget()); 1092 } 1093 1094 void Shutdown() { 1095 if (mCDM) { 1096 mCDM->Shutdown(); 1097 mCDM = nullptr; 1098 mNodeId.Truncate(); 1099 } 1100 } 1101 1102 void Dummy() {} 1103 1104 void SetFinished() { 1105 mFinished = true; 1106 Shutdown(); 1107 nsCOMPtr<nsIRunnable> task = NewRunnableMethod( 1108 "CDMStorageTest::Dummy", this, &CDMStorageTest::Dummy); 1109 SchedulerGroup::Dispatch(task.forget()); 1110 } 1111 1112 void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, 1113 const nsTArray<uint8_t>& aMessage) { 1114 MonitorAutoLock mon(mMonitor); 1115 1116 nsCString msg((const char*)aMessage.Elements(), aMessage.Length()); 1117 EXPECT_TRUE(mExpected.Length() > 0); 1118 bool matches = mExpected[0].mMessage.Equals(msg); 1119 EXPECT_STREQ(mExpected[0].mMessage.get(), msg.get()); 1120 if (mExpected.Length() > 0 && matches) { 1121 nsCOMPtr<nsIRunnable> continuation = mExpected[0].mContinuation; 1122 mExpected.RemoveElementAt(0); 1123 if (continuation) { 1124 NS_DispatchToCurrentThread(continuation); 1125 } 1126 } 1127 } 1128 1129 void Terminated() { 1130 if (mCDM) { 1131 mCDM->Shutdown(); 1132 mCDM = nullptr; 1133 } 1134 } 1135 1136 private: 1137 ~CDMStorageTest() = default; 1138 1139 struct ExpectedMessage { 1140 ExpectedMessage(const nsCString& aMessage, 1141 already_AddRefed<nsIRunnable> aContinuation) 1142 : mMessage(aMessage), mContinuation(aContinuation) {} 1143 nsCString mMessage; 1144 nsCOMPtr<nsIRunnable> mContinuation; 1145 }; 1146 1147 nsTArray<ExpectedMessage> mExpected; 1148 1149 RefPtr<gmp::ChromiumCDMParent> mCDM; 1150 Monitor mMonitor MOZ_UNANNOTATED; 1151 Atomic<bool> mFinished; 1152 nsCString mNodeId; 1153 1154 class CallbackProxy : public ChromiumCDMCallback { 1155 public: 1156 explicit CallbackProxy(CDMStorageTest* aRunner) : mRunner(aRunner) {} 1157 1158 void SetSessionId(uint32_t aPromiseId, 1159 const nsCString& aSessionId) override {} 1160 1161 void ResolveLoadSessionPromise(uint32_t aPromiseId, 1162 bool aSuccessful) override {} 1163 1164 void ResolvePromiseWithKeyStatus(uint32_t aPromiseId, 1165 uint32_t aKeyStatus) override {} 1166 1167 void ResolvePromise(uint32_t aPromiseId) override {} 1168 1169 void RejectPromise(uint32_t aPromiseId, ErrorResult&& aError, 1170 const nsCString& aErrorMessage) override {} 1171 1172 void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType, 1173 nsTArray<uint8_t>&& aMessage) override { 1174 mRunner->SessionMessage(aSessionId, aMessageType, std::move(aMessage)); 1175 } 1176 1177 void SessionKeysChange( 1178 const nsCString& aSessionId, 1179 nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) override {} 1180 1181 void ExpirationChange(const nsCString& aSessionId, 1182 double aSecondsSinceEpoch) override {} 1183 1184 void SessionClosed(const nsCString& aSessionId) override {} 1185 1186 void QueryOutputProtectionStatus() override {} 1187 1188 void Terminated() override { mRunner->Terminated(); } 1189 1190 void Shutdown() override { mRunner->Shutdown(); } 1191 1192 private: 1193 // Warning: Weak ref. 1194 CDMStorageTest* mRunner; 1195 }; 1196 1197 UniquePtr<CallbackProxy> mCallback; 1198 }; // class CDMStorageTest 1199 1200 static nsresult CreateTestDirectory(nsCOMPtr<nsIFile>& aOut) { 1201 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(aOut)); 1202 if (NS_FAILED(rv)) { 1203 return rv; 1204 } 1205 nsCString dirName; 1206 dirName.SetLength(32); 1207 NS_MakeRandomString(dirName.BeginWriting(), 32); 1208 aOut->Append(NS_ConvertUTF8toUTF16(dirName)); 1209 rv = aOut->Create(nsIFile::DIRECTORY_TYPE, 0755); 1210 if (NS_FAILED(rv)) { 1211 return rv; 1212 } 1213 return NS_OK; 1214 } 1215 1216 void TestMatchBaseDomain_MatchOrigin() { 1217 nsCOMPtr<nsIFile> testDir; 1218 nsresult rv = CreateTestDirectory(testDir); 1219 EXPECT_NS_SUCCEEDED(rv); 1220 1221 rv = WriteToFile(testDir, "origin"_ns, 1222 "https://video.subdomain.removeme.github.io"_ns); 1223 EXPECT_NS_SUCCEEDED(rv); 1224 rv = WriteToFile(testDir, "topLevelOrigin"_ns, 1225 "https://embedder.example.com"_ns); 1226 EXPECT_NS_SUCCEEDED(rv); 1227 bool result = MatchBaseDomain(testDir, "removeme.github.io"_ns); 1228 EXPECT_TRUE(result); 1229 testDir->Remove(true); 1230 } 1231 1232 void TestMatchBaseDomain_MatchTLD() { 1233 nsCOMPtr<nsIFile> testDir; 1234 nsresult rv = CreateTestDirectory(testDir); 1235 EXPECT_NS_SUCCEEDED(rv); 1236 1237 rv = WriteToFile(testDir, "origin"_ns, 1238 "https://video.example.com^userContextId=4"_ns); 1239 EXPECT_NS_SUCCEEDED(rv); 1240 rv = WriteToFile(testDir, "topLevelOrigin"_ns, 1241 "https://evil.web.megacorp.co.uk^privateBrowsingId=1"_ns); 1242 EXPECT_NS_SUCCEEDED(rv); 1243 bool result = MatchBaseDomain(testDir, "megacorp.co.uk"_ns); 1244 EXPECT_TRUE(result); 1245 testDir->Remove(true); 1246 } 1247 1248 void TestMatchBaseDomain_NoMatch() { 1249 nsCOMPtr<nsIFile> testDir; 1250 nsresult rv = CreateTestDirectory(testDir); 1251 EXPECT_NS_SUCCEEDED(rv); 1252 1253 rv = WriteToFile(testDir, "origin"_ns, 1254 "https://video.example.com^userContextId=4"_ns); 1255 EXPECT_NS_SUCCEEDED(rv); 1256 rv = WriteToFile(testDir, "topLevelOrigin"_ns, 1257 "https://evil.web.megacorp.co.uk^privateBrowsingId=1"_ns); 1258 EXPECT_NS_SUCCEEDED(rv); 1259 bool result = MatchBaseDomain(testDir, "longer-example.com"_ns); 1260 EXPECT_FALSE(result); 1261 testDir->Remove(true); 1262 } 1263 1264 TEST(GeckoMediaPlugins, MatchBaseDomain_MatchOrigin) 1265 { 1266 TestMatchBaseDomain_MatchOrigin(); 1267 } 1268 1269 TEST(GeckoMediaPlugins, MatchBaseDomain_MatchTLD) 1270 { 1271 TestMatchBaseDomain_MatchTLD(); 1272 } 1273 1274 TEST(GeckoMediaPlugins, MatchBaseDomain_NoMatch) 1275 { 1276 TestMatchBaseDomain_NoMatch(); 1277 } 1278 1279 // Bug 1776767 - Skip all GMP tests on Windows ASAN / CCOV 1280 #if !(defined(XP_WIN) && (defined(MOZ_ASAN) || defined(MOZ_CODE_COVERAGE))) 1281 TEST(GeckoMediaPlugins, CDMStorageGetNodeId) 1282 { 1283 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1284 runner->DoTest(&CDMStorageTest::TestGetNodeId); 1285 } 1286 1287 TEST(GeckoMediaPlugins, CDMStorageBasic) 1288 { 1289 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1290 runner->DoTest(&CDMStorageTest::TestBasicStorage); 1291 } 1292 1293 TEST(GeckoMediaPlugins, CDMStorageForgetThisSite) 1294 { 1295 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1296 runner->DoTest(&CDMStorageTest::TestForgetThisSite); 1297 } 1298 1299 TEST(GeckoMediaPlugins, CDMStorageForgetThisBaseDomain) 1300 { 1301 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1302 runner->DoTest(&CDMStorageTest::TestForgetThisBaseDomain); 1303 } 1304 1305 TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory1) 1306 { 1307 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1308 runner->DoTest(&CDMStorageTest::TestClearRecentHistory1); 1309 } 1310 1311 TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory2) 1312 { 1313 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1314 runner->DoTest(&CDMStorageTest::TestClearRecentHistory2); 1315 } 1316 1317 TEST(GeckoMediaPlugins, CDMStorageClearRecentHistory3) 1318 { 1319 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1320 runner->DoTest(&CDMStorageTest::TestClearRecentHistory3); 1321 } 1322 1323 TEST(GeckoMediaPlugins, CDMStorageCrossOrigin) 1324 { 1325 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1326 runner->DoTest(&CDMStorageTest::TestCrossOriginStorage); 1327 } 1328 1329 TEST(GeckoMediaPlugins, CDMStoragePrivateBrowsing) 1330 { 1331 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1332 runner->DoTest(&CDMStorageTest::TestPBStorage); 1333 } 1334 1335 TEST(GeckoMediaPlugins, CDMStorageLongRecordNames) 1336 { 1337 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1338 runner->DoTest(&CDMStorageTest::TestLongRecordNames); 1339 } 1340 1341 # if defined(XP_WIN) 1342 TEST(GeckoMediaPlugins, GMPOutputProtection) 1343 { 1344 RefPtr<CDMStorageTest> runner = new CDMStorageTest(); 1345 runner->DoTest(&CDMStorageTest::TestOutputProtection); 1346 } 1347 # endif // defined(XP_WIN) 1348 #endif // !(defined(XP_WIN) && (defined(MOZ_ASAN) || 1349 // defined(MOZ_CODE_COVERAGE)))