tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)))