Bug 1055395 - gTests for GMPAsyncShutdown. r=edwin
authorChris Pearce <cpearce@mozilla.com>
Thu, 23 Oct 2014 20:37:55 +1300
changeset 236216 fcf48b5ce92d896fd45acbc491b0acade33f15db
parent 236215 312a39b17090cc348a3d936f18d2423e82c4cc28
child 236217 87123c85507ba683271fb36b33c5d4fc10b21c4c
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)
reviewersedwin
bugs1055395
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 1055395 - gTests for GMPAsyncShutdown. r=edwin
content/media/gmp/GMPParent.cpp
content/media/gmp/GMPService.cpp
content/media/gtest/TestGMPCrossOrigin.cpp
dom/media/gmp-plugin/gmp-fake.cpp
dom/media/gmp-plugin/gmp-test-decryptor.cpp
dom/media/gmp-plugin/gmp-test-decryptor.h
dom/media/gmp-plugin/gmp-test-storage.h
--- a/content/media/gmp/GMPParent.cpp
+++ b/content/media/gmp/GMPParent.cpp
@@ -354,31 +354,54 @@ GMPParent::Shutdown()
   if (!mDeleteProcessOnlyOnUnload) {
     // Destroy ourselves and rise from the fire to save memory
     nsRefPtr<GMPParent> self(this);
     mService->ReAddOnGMPThread(self);
   } // else we've been asked to die and stay dead
   MOZ_ASSERT(mState == GMPStateNotLoaded);
 }
 
+class NotifyGMPShutdownTask : public nsRunnable {
+public:
+  NotifyGMPShutdownTask(const nsAString& aNodeId)
+    : mNodeId(aNodeId)
+  {
+  }
+  NS_IMETHOD Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+    MOZ_ASSERT(obsService);
+    if (obsService) {
+      obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
+    }
+    return NS_OK;
+  }
+  nsString mNodeId;
+};
+
 void
 GMPParent::DeleteProcess()
 {
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
 
   if (mState != GMPStateClosing) {
     // Don't Close() twice!
     // Probably remove when bug 1043671 is resolved
     mState = GMPStateClosing;
     Close();
   }
   mProcess->Delete();
   LOGD(("%s::%s: Shut down process %p", __CLASS__, __FUNCTION__, (void *) mProcess));
   mProcess = nullptr;
   mState = GMPStateNotLoaded;
+
+  NS_DispatchToMainThread(
+    new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
+    NS_DISPATCH_NORMAL);
+
 }
 
 void
 GMPParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
 {
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
 
   // If the constructor fails, we'll get called before it's added
@@ -979,16 +1002,20 @@ GMPParent::SetNodeId(const nsACString& a
   MOZ_ASSERT(CanBeUsedFrom(aNodeId));
   mNodeId = aNodeId;
 }
 
 bool
 GMPParent::RecvAsyncShutdownRequired()
 {
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+  if (mAsyncShutdownRequired) {
+    NS_WARNING("Received AsyncShutdownRequired message more than once!");
+    return true;
+  }
   mAsyncShutdownRequired = true;
   mService->AsyncShutdownNeeded(this);
   return true;
 }
 
 bool
 GMPParent::RecvAsyncShutdownComplete()
 {
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -513,16 +513,17 @@ GeckoMediaPluginService::GetGMPDecryptor
 }
 
 void
 GeckoMediaPluginService::AsyncShutdownNeeded(GMPParent* aParent)
 {
   LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
+  MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent));
   mAsyncShutdownPlugins.AppendElement(aParent);
 }
 
 void
 GeckoMediaPluginService::AsyncShutdownComplete(GMPParent* aParent)
 {
   LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
--- a/content/media/gtest/TestGMPCrossOrigin.cpp
+++ b/content/media/gtest/TestGMPCrossOrigin.cpp
@@ -101,16 +101,76 @@ GMPTestRunner::RunTestGMPCrossOrigin()
               encoder1->ParentID() == encoder2->ParentID());
 
   if (decoder1) decoder1->Close();
   if (decoder2) decoder2->Close();
   if (encoder1) encoder1->Close();
   if (encoder2) encoder2->Close();
 }
 
+static already_AddRefed<nsIThread>
+GetGMPThread()
+{
+  nsRefPtr<GeckoMediaPluginService> service =
+    GeckoMediaPluginService::GetGeckoMediaPluginService();
+  nsCOMPtr<nsIThread> thread;
+  EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread))));
+  return thread.forget();
+}
+
+class GMPShutdownObserver : public nsIRunnable
+                          , public nsIObserver {
+public:
+  GMPShutdownObserver(nsIRunnable* aShutdownTask,
+                      nsIRunnable* Continuation,
+                      const nsACString& aNodeId)
+    : mShutdownTask(aShutdownTask)
+    , mContinuation(Continuation)
+    , mNodeId(NS_ConvertUTF8toUTF16(aNodeId))
+  {}
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  NS_IMETHOD Run() MOZ_OVERRIDE {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIObserverService> observerService =
+        mozilla::services::GetObserverService();
+    EXPECT_TRUE(observerService);
+    observerService->AddObserver(this, "gmp-shutdown", false);
+
+    nsCOMPtr<nsIThread> thread(GetGMPThread());
+    thread->Dispatch(mShutdownTask, NS_DISPATCH_NORMAL);
+    return NS_OK;
+  }
+
+  NS_IMETHOD Observe(nsISupports* aSubject,
+                     const char* aTopic,
+                     const char16_t* aSomeData) MOZ_OVERRIDE
+  {
+    if (!strcmp(aTopic, "gmp-shutdown") &&
+        mNodeId.Equals(nsDependentString(aSomeData))) {
+      nsCOMPtr<nsIObserverService> observerService =
+          mozilla::services::GetObserverService();
+      EXPECT_TRUE(observerService);
+      observerService->RemoveObserver(this, "gmp-shutdown");
+      nsCOMPtr<nsIThread> thread(GetGMPThread());
+      thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
+    }
+    return NS_OK;
+  }
+
+private:
+  virtual ~GMPShutdownObserver() {}
+  nsRefPtr<nsIRunnable> mShutdownTask;
+  nsRefPtr<nsIRunnable> mContinuation;
+  const nsString mNodeId;
+};
+
+NS_IMPL_ISUPPORTS(GMPShutdownObserver, nsIRunnable, nsIObserver)
+
 class NotifyObserversTask : public nsRunnable {
 public:
   NotifyObserversTask(const char* aTopic)
     : mTopic(aTopic)
   {}
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIObserverService> observerService =
@@ -225,25 +285,16 @@ AssertIsOnGMPThread()
   service->GetThread(getter_AddRefs(thread));
   MOZ_ASSERT(thread);
   nsCOMPtr<nsIThread> currentThread;
   DebugOnly<nsresult> rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   MOZ_ASSERT(currentThread == thread);
 }
 
-static already_AddRefed<nsIThread>
-GetGMPThread()
-{
-  nsRefPtr<GeckoMediaPluginService> service =
-    GeckoMediaPluginService::GetGeckoMediaPluginService();
-  nsCOMPtr<nsIThread> thread;
-  EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread))));
-  return thread.forget();
-}
 class GMPStorageTest : public GMPDecryptorProxyCallback
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageTest)
 
   void DoTest(void (GMPStorageTest::*aTestMethod)()) {
     EnsureNSSInitializedChromeOrContent();
     nsCOMPtr<nsIThread> thread(GetGMPThread());
     ClearGMPStorage(NS_NewRunnableMethod(this, aTestMethod), thread);
@@ -318,25 +369,23 @@ class GMPStorageTest : public GMPDecrypt
 
   void CreateDecryptor(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
                        bool aInPBMode) {
     nsRefPtr<GeckoMediaPluginService> service =
       GeckoMediaPluginService::GetGeckoMediaPluginService();
     EXPECT_TRUE(service);
 
-    const nsCString nodeId = GetNodeId(aOrigin,
-                                       aTopLevelOrigin,
-                                       aInPBMode);
-    EXPECT_TRUE(!nodeId.IsEmpty());
+    mNodeId = GetNodeId(aOrigin, aTopLevelOrigin, aInPBMode);
+    EXPECT_TRUE(!mNodeId.IsEmpty());
 
     nsTArray<nsCString> tags;
     tags.AppendElement(NS_LITERAL_CSTRING("fake"));
 
-    nsresult rv = service->GetGMPDecryptor(&tags, nodeId, &mDecryptor);
+    nsresult rv = service->GetGMPDecryptor(&tags, mNodeId, &mDecryptor);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
     EXPECT_TRUE(!!mDecryptor);
 
     if (mDecryptor) {
       mDecryptor->Init(this);
     }
   }
 
@@ -391,17 +440,16 @@ class GMPStorageTest : public GMPDecrypt
                     false);
 
     Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"),
            NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
     Update(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId"));
   }
 
   void TestPBStorage() {
-
     // Open decryptor on one, origin, write a record, close decryptor,
     // open another, and test that record can be read, close decryptor,
     // then send pb-last-context-closed notification, then open decryptor
     // and check that it can't read that data; it should have been purged.
     CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
                     NS_LITERAL_STRING("pb2.com"),
                     true);
 
@@ -435,34 +483,109 @@ class GMPStorageTest : public GMPDecrypt
     CreateDecryptor(NS_LITERAL_STRING("pb1.com"),
                     NS_LITERAL_STRING("pb2.com"),
                     true);
 
     Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 0 bytes)"),
            NS_NewRunnableMethod(this,
               &GMPStorageTest::SetFinished));
     Update(NS_LITERAL_CSTRING("retrieve pbdata"));
+  }
 
+  void CreateAsyncShutdownTimeoutGMP(const nsAString& aOrigin1,
+                                     const nsAString& aOrigin2) {
+    CreateDecryptor(aOrigin1, aOrigin2, false);
+    Update(NS_LITERAL_CSTRING("shutdown-mode timeout"));
+    Shutdown();
+  }
+
+  void TestAsyncShutdownTimeout() {
+    // Create decryptors that timeout in their async shutdown.
+    // If the gtest hangs on shutdown, test fails!
+    CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example7.com"),
+                                  NS_LITERAL_STRING("example8.com"));
+    CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example9.com"),
+                                  NS_LITERAL_STRING("example10.com"));
+    CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("example11.com"),
+                                  NS_LITERAL_STRING("example12.com"));
+    SetFinished();
+  };
+
+  void TestAsyncShutdownStorage() {
+    // Test that a GMP can write to storage during shutdown, and retrieve
+    // that written data in a subsequent session.
+    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
+                    NS_LITERAL_STRING("example14.com"),
+                    false);
+
+    // Instruct the GMP to write a token (the current timestamp, so it's
+    // unique) during async shutdown, then shutdown the plugin, re-create
+    // it, and check that the token was successfully stored.
+    auto t = time(0);
+    nsCString update("shutdown-mode token ");
+    nsCString token;
+    token.AppendInt((int64_t)t);
+    update.Append(token);
+
+    // Wait for a response from the GMP, so we know it's had time to receive
+    // the token.
+    nsCString response("shutdown-token received ");
+    response.Append(token);
+    Expect(response, NS_NewRunnableMethodWithArg<nsCString>(this,
+      &GMPStorageTest::TestAsyncShutdownStorage_ReceivedShutdownToken, token));
+
+    Update(update);
+  }
+
+  void TestAsyncShutdownStorage_ReceivedShutdownToken(const nsCString& aToken) {
+    ShutdownThen(NS_NewRunnableMethodWithArg<nsCString>(this,
+      &GMPStorageTest::TestAsyncShutdownStorage_AsyncShutdownComplete, aToken));
+  }
+
+  void TestAsyncShutdownStorage_AsyncShutdownComplete(const nsCString& aToken) {
+    // Create a new instance of the plugin, retrieve the token written
+    // during shutdown and verify it is correct.
+    CreateDecryptor(NS_LITERAL_STRING("example13.com"),
+                    NS_LITERAL_STRING("example14.com"),
+                    false);
+    nsCString response("retrieved shutdown-token ");
+    response.Append(aToken);
+    Expect(response,
+           NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+    Update(NS_LITERAL_CSTRING("retrieve-shutdown-token"));
   }
 
   void Expect(const nsCString& aMessage, nsIRunnable* aContinuation) {
     mExpected.AppendElement(ExpectedMessage(aMessage, aContinuation));
   }
 
   void AwaitFinished() {
     while (!mFinished) {
       NS_ProcessNextEvent(nullptr, true);
     }
     mFinished = false;
   }
 
+  void ShutdownThen(nsIRunnable* aContinuation) {
+    EXPECT_TRUE(!!mDecryptor);
+    if (!mDecryptor) {
+      return;
+    }
+    EXPECT_FALSE(mNodeId.IsEmpty());
+    nsRefPtr<GMPShutdownObserver> task(
+      new GMPShutdownObserver(NS_NewRunnableMethod(this, &GMPStorageTest::Shutdown),
+                              aContinuation, mNodeId));
+    NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+  }
+
   void Shutdown() {
     if (mDecryptor) {
       mDecryptor->Close();
       mDecryptor = nullptr;
+      mNodeId = EmptyCString();
     }
   }
 
   void Dummy() {
   }
 
   void SetFinished() {
     mFinished = true;
@@ -526,16 +649,17 @@ private:
     nsRefPtr<nsIRunnable> mContinuation;
   };
 
   nsTArray<ExpectedMessage> mExpected;
 
   GMPDecryptorProxy* mDecryptor;
   Monitor mMonitor;
   Atomic<bool> mFinished;
+  nsCString mNodeId;
 };
 
 void
 GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)())
 {
   nsRefPtr<GeckoMediaPluginService> service =
     GeckoMediaPluginService::GetGeckoMediaPluginService();
   nsCOMPtr<nsIThread> thread;
@@ -568,8 +692,18 @@ TEST(GeckoMediaPlugins, GMPStorageCrossO
   nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestCrossOriginStorage);
 }
 
 TEST(GeckoMediaPlugins, GMPStoragePrivateBrowsing) {
   nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestPBStorage);
 }
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownTimeout) {
+  nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
+  runner->DoTest(&GMPStorageTest::TestAsyncShutdownTimeout);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownStorage) {
+  nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
+  runner->DoTest(&GMPStorageTest::TestAsyncShutdownStorage);
+}
--- a/dom/media/gmp-plugin/gmp-fake.cpp
+++ b/dom/media/gmp-plugin/gmp-fake.cpp
@@ -397,17 +397,20 @@ extern "C" {
   GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
     if (!strcmp (aApiName, "decode-video")) {
       *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
     } else if (!strcmp (aApiName, "encode-video")) {
       *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
     } else if (!strcmp (aApiName, "eme-decrypt")) {
-      *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
+      *aPluginApi = new FakeDecryptor();
+      return GMPNoErr;
+    } else if (!strcmp (aApiName, "async-shutdown")) {
+      *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
       return GMPNoErr;
     }
     return GMPGenericErr;
   }
 
   PUBLIC_FUNC void
   GMPShutdown (void) {
     g_platform_api = NULL;
--- a/dom/media/gmp-plugin/gmp-test-decryptor.cpp
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -28,19 +28,18 @@ static bool sMultiClientTest = false;
 void
 MaybeFinish()
 {
   if (sFinishedTruncateTest && sFinishedReplaceTest && sMultiClientTest) {
     FakeDecryptor::Message("test-storage complete");
   }
 }
 
-FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost)
-  : mHost(aHost)
-  , mCallback(nullptr)
+FakeDecryptor::FakeDecryptor()
+  : mCallback(nullptr)
 {
   assert(!sInstance);
   sInstance = this;
 }
 
 void FakeDecryptor::DecryptingComplete()
 {
   sInstance = nullptr;
@@ -75,18 +74,18 @@ public:
     , mThen(aThen)
   {}
   void Run() MOZ_OVERRIDE {
     ReadRecord(mId, mThen);
   }
   void Destroy() MOZ_OVERRIDE {
     delete this;
   }
+  string mId;
   ReadContinuation* mThen;
-  string mId;
 };
 
 class TestEmptyContinuation : public ReadContinuation {
 public:
   void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
     if (aData != "") {
       FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated");
     }
@@ -173,17 +172,17 @@ public:
   virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) MOZ_OVERRIDE {
     if (GMP_FAILED(aStatus)) {
       FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially.");
       sMultiClientTest = true;
       MaybeFinish();
       return;
     }
 
-    auto err = GMPOpenRecord(OpenAgainRecordId, new OpenedSecondTimeContinuation(aRecord));
+    GMPOpenRecord(OpenAgainRecordId, new OpenedSecondTimeContinuation(aRecord));
 
     delete this;
   }
 };
 
 void
 FakeDecryptor::TestStorage()
 {
@@ -263,16 +262,41 @@ public:
       FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " +
                              len + " bytes)");
     }
     delete this;
   }
   string mRecordId;
 };
 
+class ReportReadRecordContinuation : public ReadContinuation {
+public:
+  ReportReadRecordContinuation(const string& aRecordId)
+    : mRecordId(aRecordId)
+  {}
+  void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
+    if (GMP_FAILED(aErr)) {
+      FakeDecryptor::Message("retrieved " + mRecordId + " failed");
+    } else {
+      FakeDecryptor::Message("retrieved " + mRecordId + " " + aData);
+    }
+    delete this;
+  }
+  string mRecordId;
+};
+
+enum ShutdownMode {
+  ShutdownNormal,
+  ShutdownTimeout,
+  ShutdownStoreToken
+};
+
+static ShutdownMode sShutdownMode = ShutdownNormal;
+static string sShutdownToken = "";
+
 void
 FakeDecryptor::UpdateSession(uint32_t aPromiseId,
                              const char* aSessionId,
                              uint32_t aSessionIdLength,
                              const uint8_t* aResponse,
                              uint32_t aResponseSize)
 {
   std::string response((const char*)aResponse, (const char*)(aResponse)+aResponseSize);
@@ -285,10 +309,53 @@ FakeDecryptor::UpdateSession(uint32_t aP
     const string& id = tokens[1];
     const string& value = tokens[2];
     WriteRecord(id,
                 value,
                 new ReportWritten(id, value));
   } else if (task == "retrieve") {
     const string& id = tokens[1];
     ReadRecord(id, new ReportReadStatusContinuation(id));
+  } else if (task == "shutdown-mode") {
+    const string& mode = tokens[1];
+    if (mode == "timeout") {
+      sShutdownMode = ShutdownTimeout;
+    } else if (mode == "token") {
+      sShutdownMode = ShutdownStoreToken;
+      sShutdownToken = tokens[2];
+      Message("shutdown-token received " + sShutdownToken);
+    }
+  } else if (task == "retrieve-shutdown-token") {
+    ReadRecord("shutdown-token", new ReportReadRecordContinuation("shutdown-token"));
   }
 }
+
+class CompleteShutdownTask : public GMPTask {
+public:
+  CompleteShutdownTask(GMPAsyncShutdownHost* aHost)
+    : mHost(aHost)
+  {
+  }
+  virtual void Run() {
+    mHost->ShutdownComplete();
+  }
+  virtual void Destroy() { delete this; }
+  GMPAsyncShutdownHost* mHost;
+};
+
+void
+TestAsyncShutdown::BeginShutdown() {
+  switch (sShutdownMode) {
+    case ShutdownNormal:
+      mHost->ShutdownComplete();
+      break;
+    case ShutdownTimeout:
+      // Don't do anything; wait for timeout, Gecko should kill
+      // the plugin and recover.
+      break;
+    case ShutdownStoreToken:
+      // Store message, then shutdown.
+      WriteRecord("shutdown-token",
+                  sShutdownToken,
+                  new CompleteShutdownTask(mHost));
+      break;
+  }
+}
--- a/dom/media/gmp-plugin/gmp-test-decryptor.h
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -9,17 +9,17 @@
 #include "gmp-decryption.h"
 #include "gmp-async-shutdown.h"
 #include <string>
 #include "mozilla/Attributes.h"
 
 class FakeDecryptor : public GMPDecryptor {
 public:
 
-  FakeDecryptor(GMPDecryptorHost* aHost);
+  FakeDecryptor();
 
   virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE {
     mCallback = aCallback;
   }
 
   virtual void CreateSession(uint32_t aPromiseId,
                              const char* aInitDataType,
                              uint32_t aInitDataTypeSize,
@@ -65,17 +65,28 @@ public:
   }
 
   virtual void DecryptingComplete() MOZ_OVERRIDE;
 
   static void Message(const std::string& aMessage);
 
 private:
 
+  virtual ~FakeDecryptor() {}
   static FakeDecryptor* sInstance;
 
   void TestStorage();
 
   GMPDecryptorCallback* mCallback;
-  GMPDecryptorHost* mHost;
+};
+
+class TestAsyncShutdown : public GMPAsyncShutdown {
+public:
+  TestAsyncShutdown(GMPAsyncShutdownHost* aHost)
+    : mHost(aHost)
+  {
+  }
+  virtual void BeginShutdown() MOZ_OVERRIDE;
+private:
+  GMPAsyncShutdownHost* mHost;
 };
 
 #endif
--- a/dom/media/gmp-plugin/gmp-test-storage.h
+++ b/dom/media/gmp-plugin/gmp-test-storage.h
@@ -7,16 +7,17 @@
 #define TEST_GMP_STORAGE_H__
 
 #include "gmp-errors.h"
 #include "gmp-platform.h"
 #include <string>
 
 class ReadContinuation {
 public:
+  virtual ~ReadContinuation() {}
   virtual void ReadComplete(GMPErr aErr, const std::string& aData) = 0;
 };
 
 // Reads a record to storage using GMPRecord.
 // Calls ReadContinuation with read data.
 GMPErr
 ReadRecord(const std::string& aRecordName,
            ReadContinuation* aContinuation);
@@ -40,16 +41,17 @@ GMPOpenRecord(const char* aName,
               GMPRecord** aOutRecord,
               GMPRecordClient* aClient);
 
 GMPErr
 GMPRunOnMainThread(GMPTask* aTask);
 
 class OpenContinuation {
 public:
+  virtual ~OpenContinuation() {}
   virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0;
 };
 
 GMPErr
 GMPOpenRecord(const std::string& aRecordName,
            OpenContinuation* aContinuation);
 
 #endif // TEST_GMP_STORAGE_H__