Bug 1100499 - Add GMP API to enumerate records stored on disk. r=jesup
authorChris Pearce <cpearce@mozilla.com>
Fri, 21 Nov 2014 12:25:12 +1300
changeset 241113 405b0c00071f7f583f722a48e6537950e1a29653
parent 241112 ffca01c1afea509f0cec9a504f9d72ed4e00d5ad
child 241114 f2d73c49836c525ae755a49307ba4abbb2608207
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs1100499
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1100499 - Add GMP API to enumerate records stored on disk. r=jesup
dom/media/gmp-plugin/gmp-test-decryptor.cpp
dom/media/gmp-plugin/gmp-test-decryptor.h
dom/media/gmp-plugin/gmp-test-storage.cpp
dom/media/gmp-plugin/gmp-test-storage.h
dom/media/gmp/GMPPlatform.cpp
dom/media/gmp/GMPStorageChild.cpp
dom/media/gmp/GMPStorageChild.h
dom/media/gmp/GMPStorageParent.cpp
dom/media/gmp/GMPStorageParent.h
dom/media/gmp/PGMPStorage.ipdl
dom/media/gmp/gmp-api/gmp-audio-codec.h
dom/media/gmp/gmp-api/gmp-errors.h
dom/media/gmp/gmp-api/gmp-platform.h
dom/media/gmp/gmp-api/gmp-storage.h
dom/media/gtest/TestGMPCrossOrigin.cpp
--- a/dom/media/gmp-plugin/gmp-test-decryptor.cpp
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -280,16 +280,55 @@ public:
     } else {
       FakeDecryptor::Message("retrieved " + mRecordId + " " + aData);
     }
     delete this;
   }
   string mRecordId;
 };
 
+static void
+RecvGMPRecordIterator(GMPRecordIterator* aRecordIterator,
+                      void* aUserArg,
+                      GMPErr aStatus)
+{
+  FakeDecryptor* decryptor = reinterpret_cast<FakeDecryptor*>(aUserArg);
+  decryptor->ProcessRecordNames(aRecordIterator, aStatus);
+}
+
+void
+FakeDecryptor::ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+                                  GMPErr aStatus)
+{
+  if (sInstance != this) {
+    FakeDecryptor::Message("Error aUserArg was not passed through GetRecordIterator");
+    return;
+  }
+  if (GMP_FAILED(aStatus)) {
+    FakeDecryptor::Message("Error GetRecordIterator failed");
+    return;
+  }
+  std::string response("record-names ");
+  bool first = true;
+  const char* name = nullptr;
+  uint32_t len = 0;
+  while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
+    std::string s(name, name+len);
+    if (!first) {
+      response += ",";
+    } else {
+      first = false;
+    }
+    response += s;
+    aRecordIterator->NextRecord();
+  }
+  aRecordIterator->Close();
+  FakeDecryptor::Message(response);
+}
+
 enum ShutdownMode {
   ShutdownNormal,
   ShutdownTimeout,
   ShutdownStoreToken
 };
 
 static ShutdownMode sShutdownMode = ShutdownNormal;
 static string sShutdownToken = "";
@@ -330,16 +369,18 @@ FakeDecryptor::UpdateSession(uint32_t aP
   } else if (task == "test-op-apis") {
     mozilla::gmptest::TestOuputProtectionAPIs();
   } else if (task == "retrieve-plugin-voucher") {
     const uint8_t* rawVoucher = nullptr;
     uint32_t length = 0;
     mHost->GetPluginVoucher(&rawVoucher, &length);
     std::string voucher((const char*)rawVoucher, (const char*)(rawVoucher + length));
     Message("retrieved plugin-voucher: " + voucher);
+  } else if (task == "retrieve-record-names") {
+    GMPEnumRecordNames(&RecvGMPRecordIterator, this);
   }
 }
 
 class CompleteShutdownTask : public GMPTask {
 public:
   explicit CompleteShutdownTask(GMPAsyncShutdownHost* aHost)
     : mHost(aHost)
   {
--- a/dom/media/gmp-plugin/gmp-test-decryptor.h
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -63,16 +63,19 @@ public:
                        GMPEncryptedBufferMetadata* aMetadata) MOZ_OVERRIDE
   {
   }
 
   virtual void DecryptingComplete() MOZ_OVERRIDE;
 
   static void Message(const std::string& aMessage);
 
+  void ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+                          GMPErr aStatus);
+
 private:
 
   virtual ~FakeDecryptor() {}
   static FakeDecryptor* sInstance;
 
   void TestStorage();
 
   GMPDecryptorCallback* mCallback;
--- a/dom/media/gmp-plugin/gmp-test-storage.cpp
+++ b/dom/media/gmp-plugin/gmp-test-storage.cpp
@@ -188,8 +188,15 @@ GMPOpenRecord(const std::string& aRecord
                            aRecordName.size(),
                            &record,
                            client);
   if (GMP_FAILED(err)) {
     return err;
   }
   return client->Init(record, aContinuation);
 }
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                   void* aUserArg)
+{
+  return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg);
+}
--- a/dom/media/gmp-plugin/gmp-test-storage.h
+++ b/dom/media/gmp-plugin/gmp-test-storage.h
@@ -47,11 +47,15 @@ GMPRunOnMainThread(GMPTask* aTask);
 class OpenContinuation {
 public:
   virtual ~OpenContinuation() {}
   virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0;
 };
 
 GMPErr
 GMPOpenRecord(const std::string& aRecordName,
-           OpenContinuation* aContinuation);
+              OpenContinuation* aContinuation);
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                   void* aUserArg);
 
 #endif // TEST_GMP_STORAGE_H__
--- a/dom/media/gmp/GMPPlatform.cpp
+++ b/dom/media/gmp/GMPPlatform.cpp
@@ -153,18 +153,20 @@ CreateMutex(GMPMutex** aMutex)
 }
 
 GMPErr
 CreateRecord(const char* aRecordName,
              uint32_t aRecordNameSize,
              GMPRecord** aOutRecord,
              GMPRecordClient* aClient)
 {
+  MOZ_ASSERT(IsOnChildMainThread());
+
   if (sMainLoop != MessageLoop::current()) {
-    NS_WARNING("GMP called CreateRecord() on non-main thread!");
+    MOZ_ASSERT(false, "GMP called CreateRecord() on non-main thread!");
     return GMPGenericErr;
   }
   if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE) {
     NS_WARNING("GMP tried to CreateRecord with too long record name");
     return GMPGenericErr;
   }
   GMPStorageChild* storage = sChild->GetGMPStorage();
   if (!storage) {
@@ -189,16 +191,35 @@ SetTimerOnMainThread(GMPTask* aTask, int
 
 GMPErr
 GetClock(GMPTimestamp* aOutTime)
 {
   *aOutTime = time(0) * 1000;
   return GMPNoErr;
 }
 
+GMPErr
+CreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                     void* aUserArg)
+{
+  if (sMainLoop != MessageLoop::current()) {
+    MOZ_ASSERT(false, "GMP called CreateRecord() on non-main thread!");
+    return GMPGenericErr;
+  }
+  if (!aRecvIteratorFunc) {
+    return GMPInvalidArgErr;
+  }
+  GMPStorageChild* storage = sChild->GetGMPStorage();
+  if (!storage) {
+    return GMPGenericErr;
+  }
+  MOZ_ASSERT(storage);
+  return storage->EnumerateRecords(aRecvIteratorFunc, aUserArg);
+}
+
 void
 InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
 {
   if (!sMainLoop) {
     sMainLoop = MessageLoop::current();
   }
   if (!sChild) {
     sChild = aChild;
@@ -207,16 +228,17 @@ InitPlatformAPI(GMPPlatformAPI& aPlatfor
   aPlatformAPI.version = 0;
   aPlatformAPI.createthread = &CreateThread;
   aPlatformAPI.runonmainthread = &RunOnMainThread;
   aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
   aPlatformAPI.createmutex = &CreateMutex;
   aPlatformAPI.createrecord = &CreateRecord;
   aPlatformAPI.settimer = &SetTimerOnMainThread;
   aPlatformAPI.getcurrenttime = &GetClock;
+  aPlatformAPI.getrecordenumerator = &CreateRecordIterator;
 }
 
 GMPThreadImpl::GMPThreadImpl()
 : mMutex("GMPThreadImpl"),
   mThread("GMPThread")
 {
   MOZ_COUNT_CTOR(GMPThread);
 }
--- a/dom/media/gmp/GMPStorageChild.cpp
+++ b/dom/media/gmp/GMPStorageChild.cpp
@@ -263,20 +263,99 @@ GMPStorageChild::RecvWriteComplete(const
   }
   record->WriteComplete(aStatus);
   if (GMP_FAILED(aStatus)) {
     Close(record);
   }
   return true;
 }
 
+GMPErr
+GMPStorageChild::EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                                  void* aUserArg)
+{
+  if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
+    MOZ_ASSERT(false, "GMP used GMPStorage on non-main thread.");
+    return GMPGenericErr;
+  }
+  if (mShutdown) {
+    NS_WARNING("GMPStorage used after it's been shutdown!");
+    return GMPClosedErr;
+  }
+  if (!SendGetRecordNames()) {
+    return GMPGenericErr;
+  }
+  MOZ_ASSERT(aRecvIteratorFunc);
+  mPendingRecordIterators.push(RecordIteratorContext(aRecvIteratorFunc, aUserArg));
+  return GMPNoErr;
+}
+
+class GMPRecordIteratorImpl : public GMPRecordIterator {
+public:
+  GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
+    : mRecordNames(aRecordNames)
+    , mIndex(0)
+  {
+    mRecordNames.Sort();
+  }
+
+  virtual GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) MOZ_OVERRIDE {
+    if (!aOutName || !aOutNameLength) {
+      return GMPInvalidArgErr;
+    }
+    if (mIndex == mRecordNames.Length()) {
+      return GMPEndOfEnumeration;
+    }
+    *aOutName = mRecordNames[mIndex].get();
+    *aOutNameLength = mRecordNames[mIndex].Length();
+    return GMPNoErr;
+  }
+
+  virtual GMPErr NextRecord() MOZ_OVERRIDE {
+    if (mIndex < mRecordNames.Length()) {
+      mIndex++;
+    }
+    return (mIndex < mRecordNames.Length()) ? GMPNoErr
+                                            : GMPEndOfEnumeration;
+  }
+
+  virtual void Close() MOZ_OVERRIDE {
+    delete this;
+  }
+
+private:
+  nsTArray<nsCString> mRecordNames;
+  size_t mIndex;
+};
+
+bool
+GMPStorageChild::RecvRecordNames(const InfallibleTArray<nsCString>& aRecordNames,
+                                 const GMPErr& aStatus)
+{
+  if (mShutdown || mPendingRecordIterators.empty()) {
+    return true;
+  }
+  RecordIteratorContext ctx = mPendingRecordIterators.front();
+  mPendingRecordIterators.pop();
+
+  if (GMP_FAILED(aStatus)) {
+    ctx.mFunc(nullptr, ctx.mUserArg, aStatus);
+  } else {
+    ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr);
+  }
+  return true;
+}
+
 bool
 GMPStorageChild::RecvShutdown()
 {
   // Block any new storage requests, and thus any messages back to the
   // parent. We don't delete any objects here, as that may invalidate
   // GMPRecord pointers held by the GMP.
   mShutdown = true;
+  while (!mPendingRecordIterators.empty()) {
+    mPendingRecordIterators.pop();
+  }
   return true;
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPStorageChild.h
+++ b/dom/media/gmp/GMPStorageChild.h
@@ -5,16 +5,19 @@
 
 #ifndef GMPStorageChild_h_
 #define GMPStorageChild_h_
 
 #include "mozilla/gmp/PGMPStorageChild.h"
 #include "gmp-storage.h"
 #include "nsTHashtable.h"
 #include "nsRefPtrHashtable.h"
+#include "gmp-platform.h"
+
+#include <queue>
 
 namespace mozilla {
 namespace gmp {
 
 class GMPChild;
 class GMPStorageChild;
 
 class GMPRecordImpl : public GMPRecord
@@ -65,31 +68,48 @@ public:
   GMPErr Read(GMPRecordImpl* aRecord);
 
   GMPErr Write(GMPRecordImpl* aRecord,
                const uint8_t* aData,
                uint32_t aDataSize);
 
   GMPErr Close(GMPRecordImpl* aRecord);
 
+  GMPErr EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                          void* aUserArg);
+
 protected:
   ~GMPStorageChild() {}
 
   // PGMPStorageChild
   virtual bool RecvOpenComplete(const nsCString& aRecordName,
                                 const GMPErr& aStatus) MOZ_OVERRIDE;
   virtual bool RecvReadComplete(const nsCString& aRecordName,
                                 const GMPErr& aStatus,
                                 const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
   virtual bool RecvWriteComplete(const nsCString& aRecordName,
                                  const GMPErr& aStatus) MOZ_OVERRIDE;
+  virtual bool RecvRecordNames(const InfallibleTArray<nsCString>& aRecordNames,
+                               const GMPErr& aStatus) MOZ_OVERRIDE;
   virtual bool RecvShutdown() MOZ_OVERRIDE;
 
 private:
   nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
   GMPChild* mPlugin;
+
+  struct RecordIteratorContext {
+    explicit RecordIteratorContext(RecvGMPRecordIteratorPtr aFunc,
+                                   void* aUserArg)
+      : mFunc(aFunc)
+      , mUserArg(aUserArg)
+    {}
+    RecvGMPRecordIteratorPtr mFunc;
+    void* mUserArg;
+  };
+
+  std::queue<RecordIteratorContext> mPendingRecordIterators;
   bool mShutdown;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPStorageChild_h_
--- a/dom/media/gmp/GMPStorageParent.cpp
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -13,16 +13,18 @@
 #include "gmp-storage.h"
 #include "mozilla/unused.h"
 #include "nsTHashtable.h"
 #include "nsDataHashtable.h"
 #include "prio.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/Base64.h"
+#include "nsISimpleEnumerator.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
@@ -99,19 +101,27 @@ OpenStorageFile(const nsCString& aRecord
   MOZ_ASSERT(aOutFD);
 
   nsCOMPtr<nsIFile> f;
   nsresult rv = GetGMPStorageDir(getter_AddRefs(f), aNodeId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsAutoString recordNameHash;
-  recordNameHash.AppendInt(HashString(aRecordName.get()));
-  f->Append(recordNameHash);
+  nsAutoCString recordNameBase64;
+  rv = Base64Encode(aRecordName, recordNameBase64);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Base64 can encode to a '/' character, which will mess with file paths,
+  // so we need to replace that here with something that won't mess with paths.
+  recordNameBase64.ReplaceChar('/', '-');
+
+  f->AppendNative(recordNameBase64);
 
   auto mode = PR_RDWR | PR_CREATE_FILE;
   if (aMode == Truncate) {
     mode |= PR_TRUNCATE;
   }
 
   return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
 }
@@ -188,16 +198,64 @@ public:
       return GMPGenericErr;
     }
     mFiles.Put(aRecordName, fd);
 
     int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
     return (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
   }
 
+  virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) MOZ_OVERRIDE
+  {
+    nsCOMPtr<nsIFile> storageDir;
+    nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return GMPGenericErr;
+    }
+
+    nsCOMPtr<nsISimpleEnumerator> iter;
+    rv = storageDir->GetDirectoryEntries(getter_AddRefs(iter));
+    if (NS_FAILED(rv)) {
+      return GMPGenericErr;
+    }
+
+    bool hasMore;
+    while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+      nsCOMPtr<nsISupports> supports;
+      rv = iter->GetNext(getter_AddRefs(supports));
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+      nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+
+      nsAutoCString leafName;
+      rv = dirEntry->GetNativeLeafName(leafName);
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+
+      // The record's file name is the Base64 encode of the record name,
+      // with '/' characters replaced with '-' characters. Base64 decode
+      // to extract the file name.
+      leafName.ReplaceChar('-', '/');
+      nsAutoCString recordName;
+      rv = Base64Decode(leafName, recordName);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        continue;
+      }
+
+      aOutRecordNames.AppendElement(recordName);
+    }
+
+    return GMPNoErr;
+  }
+
   virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
   {
     PRFileDesc* fd = mFiles.Get(aRecordName);
     if (fd) {
       if (PR_Close(fd) == PR_SUCCESS) {
         mFiles.Remove(aRecordName);
       } else {
         NS_WARNING("GMPDiskStorage Failed to clsose file.");
@@ -250,16 +308,22 @@ public:
     Record* record = nullptr;
     if (!mRecords.Get(aRecordName, &record)) {
       return GMPClosedErr;
     }
     record->mData = aBytes;
     return GMPNoErr;
   }
 
+  virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) MOZ_OVERRIDE
+  {
+    mRecords.EnumerateRead(EnumRecordNames, &aOutRecordNames);
+    return GMPNoErr;
+  }
+
   virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
   {
     Record* record = nullptr;
     if (!mRecords.Get(aRecordName, &record)) {
       return;
     }
     if (!record->mData.Length()) {
       // Record is empty, delete.
@@ -272,16 +336,26 @@ public:
 private:
 
   struct Record {
     Record() : mIsOpen(false) {}
     nsTArray<uint8_t> mData;
     bool mIsOpen;
   };
 
+  static PLDHashOperator
+  EnumRecordNames(const nsACString& aKey,
+                  Record* aRecord,
+                  void* aUserArg)
+  {
+    nsTArray<nsCString>* names = reinterpret_cast<nsTArray<nsCString>*>(aUserArg);
+    names->AppendElement(aKey);
+    return PL_DHASH_NEXT;
+  }
+
   nsClassHashtable<nsCStringHashKey, Record> mRecords;
 };
 
 GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
                                    GMPParent* aPlugin)
   : mNodeId(aNodeId)
   , mPlugin(aPlugin)
   , mShutdown(false)
@@ -385,16 +459,32 @@ GMPStorageParent::RecvWrite(const nsCStr
   }
 
   unused << SendWriteComplete(aRecordName, mStorage->Write(aRecordName, aBytes));
 
   return true;
 }
 
 bool
+GMPStorageParent::RecvGetRecordNames()
+{
+  LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+
+  if (mShutdown) {
+    return true;
+  }
+
+  nsTArray<nsCString> recordNames;
+  GMPErr status = mStorage->GetRecordNames(recordNames);
+  unused << SendRecordNames(recordNames, status);
+
+  return true;
+}
+
+bool
 GMPStorageParent::RecvClose(const nsCString& aRecordName)
 {
   LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
 
   if (mShutdown) {
     return true;
   }
 
--- a/dom/media/gmp/GMPStorageParent.h
+++ b/dom/media/gmp/GMPStorageParent.h
@@ -20,16 +20,17 @@ public:
   virtual ~GMPStorage() {}
 
   virtual GMPErr Open(const nsCString& aRecordName) = 0;
   virtual bool IsOpen(const nsCString& aRecordName) = 0;
   virtual GMPErr Read(const nsCString& aRecordName,
                       nsTArray<uint8_t>& aOutBytes) = 0;
   virtual GMPErr Write(const nsCString& aRecordName,
                        const nsTArray<uint8_t>& aBytes) = 0;
+  virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) = 0;
   virtual void Close(const nsCString& aRecordName) = 0;
 };
 
 class GMPStorageParent : public PGMPStorageParent {
 public:
   NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
   GMPStorageParent(const nsCString& aNodeId,
                    GMPParent* aPlugin);
@@ -37,16 +38,17 @@ public:
   nsresult Init();
   void Shutdown();
 
 protected:
   virtual bool RecvOpen(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual bool RecvWrite(const nsCString& aRecordName,
                          const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
+  virtual bool RecvGetRecordNames() MOZ_OVERRIDE;
   virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
 private:
   ~GMPStorageParent() {}
 
   UniquePtr<GMPStorage> mStorage;
 
--- a/dom/media/gmp/PGMPStorage.ipdl
+++ b/dom/media/gmp/PGMPStorage.ipdl
@@ -14,21 +14,23 @@ namespace gmp {
 async protocol PGMPStorage
 {
   manager PGMP;
 
 child:
   OpenComplete(nsCString aRecordName, GMPErr aStatus);
   ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
   WriteComplete(nsCString aRecordName, GMPErr aStatus);
+  RecordNames(nsCString[] aRecordNames, GMPErr aStatus);
   Shutdown();
 
 parent:
   Open(nsCString aRecordName);
   Read(nsCString aRecordName);
   Write(nsCString aRecordName, uint8_t[] aBytes);
   Close(nsCString aRecordName);
+  GetRecordNames();
   __delete__();
 
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/gmp-api/gmp-audio-codec.h
+++ b/dom/media/gmp/gmp-api/gmp-audio-codec.h
@@ -32,12 +32,12 @@ struct GMPAudioCodec
   uint32_t mChannelCount;
   uint32_t mBitsPerChannel;
   uint32_t mSamplesPerSecond;
 
   // Codec extra data, such as vorbis setup header, or
   // AAC AudioSpecificConfig.
   // These are null/0 if not externally negotiated
   const uint8_t* mExtraData;
-  size_t         mExtraDataLen;
+  uint32_t       mExtraDataLen;
 };
 
 #endif // GMP_AUDIO_CODEC_h_
--- a/dom/media/gmp/gmp-api/gmp-errors.h
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -40,15 +40,17 @@ typedef enum {
   GMPAllocErr = 3,
   GMPNotImplementedErr = 4,
   GMPRecordInUse = 5,
   GMPQuotaExceededErr = 6,
   GMPDecodeErr = 7,
   GMPEncodeErr = 8,
   GMPNoKeyErr = 9,
   GMPCryptoErr = 10,
+  GMPEndOfEnumeration = 11,
+  GMPInvalidArgErr = 12,
   GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
 } GMPErr;
 
 #define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
 #define GMP_FAILED(x) ((x) != GMPNoErr)
 
 #endif // GMP_ERRORS_h_
--- a/dom/media/gmp/gmp-api/gmp-platform.h
+++ b/dom/media/gmp/gmp-api/gmp-platform.h
@@ -77,25 +77,42 @@ typedef GMPErr (*GMPCreateRecordPtr)(con
                                      uint32_t aRecordNameSize,
                                      GMPRecord** aOutRecord,
                                      GMPRecordClient* aClient);
 
 // Call on main thread only.
 typedef GMPErr (*GMPSetTimerOnMainThreadPtr)(GMPTask* aTask, int64_t aTimeoutMS);
 typedef GMPErr (*GMPGetCurrentTimePtr)(GMPTimestamp* aOutTime);
 
+typedef void (*RecvGMPRecordIteratorPtr)(GMPRecordIterator* aRecordIterator,
+                                         void* aUserArg,
+                                         GMPErr aStatus);
+
+// Creates a GMPCreateRecordIterator to enumerate the records in storage.
+// When the iterator is ready, the function at aRecvIteratorFunc
+// is called with the GMPRecordIterator as an argument. If the operation
+// fails, RecvGMPRecordIteratorPtr is called with a failure aStatus code.
+// The list that the iterator is covering is fixed when
+// GMPCreateRecordIterator is called, it is *not* updated when changes are
+// made to storage.
+// Iterator begins pointing at first record.
+// aUserArg is passed to the aRecvIteratorFunc upon completion.
+typedef GMPErr (*GMPCreateRecordIteratorPtr)(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+                                             void* aUserArg);
+
 struct GMPPlatformAPI {
   // Increment the version when things change. Can only add to the struct,
   // do not change what already exists. Pointers to functions may be NULL
   // when passed to plugins, but beware backwards compat implications of
   // doing that.
   uint16_t version; // Currently version 0
 
   GMPCreateThreadPtr createthread;
   GMPRunOnMainThreadPtr runonmainthread;
   GMPSyncRunOnMainThreadPtr syncrunonmainthread;
   GMPCreateMutexPtr createmutex;
   GMPCreateRecordPtr createrecord;
   GMPSetTimerOnMainThreadPtr settimer;
   GMPGetCurrentTimePtr getcurrenttime;
+  GMPCreateRecordIteratorPtr getrecordenumerator;
 };
 
 #endif // GMP_PLATFORM_h_
--- a/dom/media/gmp/gmp-api/gmp-storage.h
+++ b/dom/media/gmp/gmp-api/gmp-storage.h
@@ -105,9 +105,36 @@ class GMPRecordClient {
   // - GMPGenericErr - Unspecified error.
   // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
   // call Close() on the GMPRecord to dispose of it.
   virtual void WriteComplete(GMPErr aStatus) = 0;
 
   virtual ~GMPRecordClient() {}
 };
 
+// Iterates over the records that are available. Note: this list maintains
+// a snapshot of the records that were present when the iterator was created.
+// Create by calling the GMPCreateRecordIteratorPtr function on the
+// GMPPlatformAPI struct.
+// Iteration is in alphabetical order.
+class GMPRecordIterator {
+public:
+  // Retrieve the name for the current record.
+  // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+  // reached the end.
+  virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;
+
+  // Advance iteration to the next record.
+  // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+  // reached the end.
+  virtual GMPErr NextRecord() = 0;
+
+  // Signals to the GMP host that the GMP is finished with the
+  // GMPRecordIterator. GMPs must call this to release memory held by
+  // the GMPRecordIterator. Do not access the GMPRecordIterator pointer
+  // after calling this!
+  // Memory retrieved by GetName is *not* valid after calling Close()!
+  virtual void Close() = 0;
+
+  virtual ~GMPRecordIterator() {}
+};
+
 #endif // GMP_STORAGE_h_
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -575,16 +575,77 @@ class GMPStorageTest : public GMPDecrypt
     CreateDecryptor(NS_LITERAL_STRING("example17.com"),
                     NS_LITERAL_STRING("example18.com"),
                     false);
     Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"),
            NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
     Update(NS_LITERAL_CSTRING("retrieve-plugin-voucher"));
   }
 
+  void TestGetRecordNamesInMemoryStorage() {
+    TestGetRecordNames(true);
+  }
+
+  nsCString mRecordNames;
+
+  void AppendIntPadded(nsACString& aString, uint32_t aInt) {
+    if (aInt > 0 && aInt < 10) {
+      aString.AppendLiteral("0");
+    }
+    aString.AppendInt(aInt);
+  }
+
+  void TestGetRecordNames(bool aPrivateBrowsing) {
+    CreateDecryptor(NS_LITERAL_STRING("foo.com"),
+                    NS_LITERAL_STRING("bar.com"),
+                    aPrivateBrowsing);
+
+    // Create a number of records of different names.
+    const uint32_t num = 100;
+    for (uint32_t i = 0; i < num; i++) {
+      nsAutoCString response;
+      response.AppendLiteral("stored data");
+      AppendIntPadded(response, i);
+      response.AppendLiteral(" test-data");
+      AppendIntPadded(response, i);
+
+      if (i != 0) {
+        mRecordNames.AppendLiteral(",");
+      }
+      mRecordNames.AppendLiteral("data");
+      AppendIntPadded(mRecordNames, i);
+
+      nsAutoCString update;
+      update.AppendLiteral("store data");
+      AppendIntPadded(update, i);
+      update.AppendLiteral(" test-data");
+      AppendIntPadded(update, i);
+
+      nsIRunnable* continuation = nullptr;
+      if (i + 1 == num) {
+        continuation =
+          NS_NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames);
+      }
+      Expect(response, continuation);
+      Update(update);
+    }
+  }
+
+  void TestGetRecordNames_QueryNames() {
+    nsCString response("record-names ");
+    response.Append(mRecordNames);
+    Expect(response,
+           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+    Update(NS_LITERAL_CSTRING("retrieve-record-names"));
+  }
+
+  void GetRecordNamesPersistentStorage() {
+    TestGetRecordNames(false);
+  }
+
   void Expect(const nsCString& aMessage, nsIRunnable* aContinuation) {
     mExpected.AppendElement(ExpectedMessage(aMessage, aContinuation));
   }
 
   void AwaitFinished() {
     while (!mFinished) {
       NS_ProcessNextEvent(nullptr, true);
     }
@@ -746,8 +807,18 @@ TEST(GeckoMediaPlugins, GMPOutputProtect
   if (!IsVistaOrLater()) {
     return;
   }
 
   nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestOutputProtection);
 }
 #endif
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) {
+  nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
+  runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesPersistentStorage) {
+  nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
+  runner->DoTest(&GMPStorageTest::GetRecordNamesPersistentStorage);
+}