summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2022-04-20 10:56:49 -0500
committerMatt A. Tobin <email@mattatobin.com>2022-04-20 10:56:49 -0500
commit9bf43033f3210dbd154cec9595847aae6ece0020 (patch)
tree63c63925d7a6cf2ccbab217183c68cf2d89e5593
parent960b7eb6fea2a84a2c3f4a38c5b2c152002f2fb3 (diff)
downloadaura-central-9bf43033f3210dbd154cec9595847aae6ece0020.tar.gz
Revert "Issue %3015 - Part 3: Remove GMP code from addons, crash handler and tests."
This reverts commit 1699dbe78bf43eaa5ef4c593f075dfdc59c02b15.
-rw-r--r--components/addons/content/extensions.js19
-rw-r--r--components/addons/content/extensions.xml6
-rw-r--r--components/addons/locale/extensions.properties4
-rw-r--r--components/crashes/CrashManager.jsm3
-rw-r--r--components/crashes/CrashService.js3
-rw-r--r--components/crashes/nsICrashService.idl1
-rw-r--r--dom/locales/en-US/chrome/plugins.properties10
-rw-r--r--dom/media/DecoderDoctorDiagnostics.h2
-rw-r--r--dom/media/MediaPrefs.h1
-rw-r--r--dom/media/gtest/GMPTestMonitor.h46
-rw-r--r--dom/media/gtest/TestGMPCrossOrigin.cpp1546
-rw-r--r--dom/media/gtest/TestGMPRemoveAndDelete.cpp490
-rw-r--r--dom/media/gtest/TestGMPUtils.cpp60
-rw-r--r--dom/media/gtest/moz.build4
-rw-r--r--dom/media/test/mochitest.ini33
-rw-r--r--dom/media/test/test_eme_request_notifications.html88
-rw-r--r--dom/media/test/test_gmp_playback.html40
-rw-r--r--dom/webidl/PluginCrashedEvent.webidl2
-rw-r--r--system/graphics/thebes/gfxPrefs.cpp2
-rw-r--r--testing/mochitest/runtests.py8
-rw-r--r--testing/mochitest/runtestsremote.py4
21 files changed, 2369 insertions, 3 deletions
diff --git a/components/addons/content/extensions.js b/components/addons/content/extensions.js
index 11c853524..fe84bc460 100644
--- a/components/addons/content/extensions.js
+++ b/components/addons/content/extensions.js
@@ -1014,7 +1014,7 @@ var gViewController = {
cmd_showItemPreferences: {
isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
if (!aAddon ||
- !aAddon.isActive ||
+ (!aAddon.isActive && !aAddon.isGMPlugin) ||
!aAddon.optionsURL) {
return false;
}
@@ -2753,7 +2753,15 @@ var gDetailView = {
var fullDesc = document.getElementById("detail-fulldesc");
if (aAddon.fullDescription) {
- fullDesc.textContent = aAddon.fullDescription;
+ // The following is part of an awful hack to include the licenses for GMP
+ // plugins without having bug 624602 fixed yet, and intentionally ignores
+ // localisation.
+ if (aAddon.isGMPlugin) {
+ fullDesc.innerHTML = aAddon.fullDescription;
+ } else {
+ fullDesc.textContent = aAddon.fullDescription;
+ }
+
fullDesc.hidden = false;
} else {
fullDesc.hidden = true;
@@ -3028,6 +3036,13 @@ var gDetailView = {
errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
errorLink.href = this._addon.blocklistURL;
errorLink.hidden = false;
+ } else if (this._addon.isGMPlugin && !this._addon.isInstalled &&
+ this._addon.isActive) {
+ this.node.setAttribute("notification", "warning");
+ let warning = document.getElementById("detail-warning");
+ warning.textContent =
+ gStrings.ext.formatStringFromName("details.notification.gmpPending",
+ [this._addon.name], 1);
} else {
this.node.removeAttribute("notification");
}
diff --git a/components/addons/content/extensions.xml b/components/addons/content/extensions.xml
index 9ea6a50df..f862b0f7e 100644
--- a/components/addons/content/extensions.xml
+++ b/components/addons/content/extensions.xml
@@ -1328,6 +1328,12 @@
this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
this._errorLink.href = this.mAddon.blocklistURL;
this._errorLink.hidden = false;
+ } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
+ this.mAddon.isActive) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent =
+ gStrings.ext.formatStringFromName("notification.gmpPending",
+ [this.mAddon.name], 1);
} else {
this.removeAttribute("notification");
}
diff --git a/components/addons/locale/extensions.properties b/components/addons/locale/extensions.properties
index 43ab02a47..c5de4f0c0 100644
--- a/components/addons/locale/extensions.properties
+++ b/components/addons/locale/extensions.properties
@@ -57,6 +57,8 @@ notification.downloadError.retry.tooltip=Try downloading this add-on again
notification.installError=There was an error installing %1$S.
notification.installError.retry=Try again
notification.installError.retry.tooltip=Try downloading and installing this add-on again
+#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name.
+notification.gmpPending=%1$S will be installed shortly.
#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
contributionAmount2=Suggested Contribution: %S
@@ -98,6 +100,8 @@ details.notification.install=%1$S will be installed after you restart %2$S.
details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
details.notification.upgrade=%1$S will be updated after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name
+details.notification.gmpPending=%1$S will be installed shortly.
installFromFile.dialogTitle=Select add-on to install
installFromFile.filterName=Add-ons
diff --git a/components/crashes/CrashManager.jsm b/components/crashes/CrashManager.jsm
index 199b9185a..537ab328d 100644
--- a/components/crashes/CrashManager.jsm
+++ b/components/crashes/CrashManager.jsm
@@ -128,6 +128,9 @@ this.CrashManager.prototype = Object.freeze({
// A crash in a plugin process.
PROCESS_TYPE_PLUGIN: "plugin",
+ // A crash in a Gecko media plugin process.
+ PROCESS_TYPE_GMPLUGIN: "gmplugin",
+
// A crash in the GPU process.
PROCESS_TYPE_GPU: "gpu",
diff --git a/components/crashes/CrashService.js b/components/crashes/CrashService.js
index ef955676c..56f8b69e7 100644
--- a/components/crashes/CrashService.js
+++ b/components/crashes/CrashService.js
@@ -34,6 +34,9 @@ CrashService.prototype = Object.freeze({
case Ci.nsICrashService.PROCESS_TYPE_PLUGIN:
processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
break;
+ case Ci.nsICrashService.PROCESS_TYPE_GMPLUGIN:
+ processType = Services.crashmanager.PROCESS_TYPE_GMPLUGIN;
+ break;
case Ci.nsICrashService.PROCESS_TYPE_GPU:
processType = Services.crashmanager.PROCESS_TYPE_GPU;
break;
diff --git a/components/crashes/nsICrashService.idl b/components/crashes/nsICrashService.idl
index d3ce8c19d..57a412804 100644
--- a/components/crashes/nsICrashService.idl
+++ b/components/crashes/nsICrashService.idl
@@ -22,6 +22,7 @@ interface nsICrashService : nsISupports
const long PROCESS_TYPE_MAIN = 0;
const long PROCESS_TYPE_CONTENT = 1;
const long PROCESS_TYPE_PLUGIN = 2;
+ const long PROCESS_TYPE_GMPLUGIN = 3;
const long PROCESS_TYPE_GPU = 4;
const long CRASH_TYPE_CRASH = 0;
diff --git a/dom/locales/en-US/chrome/plugins.properties b/dom/locales/en-US/chrome/plugins.properties
index d3c8464f6..d5e65fea7 100644
--- a/dom/locales/en-US/chrome/plugins.properties
+++ b/dom/locales/en-US/chrome/plugins.properties
@@ -20,3 +20,13 @@ mimetype_label=MIME Type
description_label=Description
suffixes_label=Suffixes
learn_more_label=Learn More
+
+# GMP Plugins
+gmp_license_info=License information
+gmp_privacy_info=Privacy Information
+
+openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
+openH264_description2=This plugin is automatically installed by Mozilla to comply with the WebRTC specification and to enable WebRTC calls with devices that require the H.264 video codec. Visit http://www.openh264.org/ to view the codec source code and learn more about the implementation.
+
+widevine_name=Widevine Content Decryption Module provided by Google Inc.
+widevine_description2=Play back protected web video.
diff --git a/dom/media/DecoderDoctorDiagnostics.h b/dom/media/DecoderDoctorDiagnostics.h
index 45216b0ef..51f7664b1 100644
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -115,6 +116,7 @@ private:
bool mFFmpegFailedToLoad = false;
bool mVideoNotSupported = false;
bool mAudioNotSupported = false;
+ nsCString mGMP;
nsString mKeySystem;
bool mIsKeySystemSupported = false;
diff --git a/dom/media/MediaPrefs.h b/dom/media/MediaPrefs.h
index d05556b38..179711b5f 100644
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -35,6 +35,7 @@ static StripAtomic<Type> Get##Name##PrefDefault() { return Default; } \
PrefTemplate<Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
// Custom Definitions.
+#define GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT 3000
#define SUSPEND_BACKGROUND_VIDEO_DELAY_MS 10000
#define TEST_PREFERENCE_FAKE_RECOGNITION_SERVICE "media.webspeech.test.fake_recognition_service"
diff --git a/dom/media/gtest/GMPTestMonitor.h b/dom/media/gtest/GMPTestMonitor.h
new file mode 100644
index 000000000..13d662b17
--- /dev/null
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThreadUtils.h"
+
+#ifndef __GMPTestMonitor_h__
+#define __GMPTestMonitor_h__
+
+class GMPTestMonitor
+{
+public:
+ GMPTestMonitor()
+ : mFinished(false)
+ {
+ }
+
+ void AwaitFinished()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ while (!mFinished) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+ mFinished = false;
+ }
+
+private:
+ void MarkFinished()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mFinished = true;
+ }
+
+public:
+ void SetFinished()
+ {
+ NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this,
+ &GMPTestMonitor::MarkFinished));
+ }
+
+private:
+ bool mFinished;
+};
+
+#endif // __GMPTestMonitor_h__
diff --git a/dom/media/gtest/TestGMPCrossOrigin.cpp b/dom/media/gtest/TestGMPCrossOrigin.cpp
new file mode 100644
index 000000000..eb8b48d67
--- /dev/null
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -0,0 +1,1546 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "GMPTestMonitor.h"
+#include "GMPVideoDecoderProxy.h"
+#include "GMPVideoEncoderProxy.h"
+#include "GMPDecryptorProxy.h"
+#include "GMPServiceParent.h"
+#include "MediaPrefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/Atomics.h"
+#include "nsNSSComponent.h"
+#include "mozilla/DebugOnly.h"
+#include "GMPDeviceBinding.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#endif
+
+using namespace std;
+
+using namespace mozilla;
+using namespace mozilla::gmp;
+
+struct GMPTestRunner
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner)
+
+ GMPTestRunner() { MediaPrefs::GetSingleton(); }
+ void DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&));
+ void RunTestGMPTestCodec1(GMPTestMonitor& aMonitor);
+ void RunTestGMPTestCodec2(GMPTestMonitor& aMonitor);
+ void RunTestGMPTestCodec3(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor);
+
+private:
+ ~GMPTestRunner() { }
+};
+
+template<class T, class Base,
+ nsresult (NS_STDCALL GeckoMediaPluginService::*Getter)(GMPCrashHelper*,
+ nsTArray<nsCString>*,
+ const nsACString&,
+ UniquePtr<Base>&&)>
+class RunTestGMPVideoCodec : public Base
+{
+public:
+ void Done(T* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+ EXPECT_TRUE(aHost);
+ if (aGMP) {
+ aGMP->Close();
+ }
+ mMonitor.SetFinished();
+ }
+
+ static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin)
+ {
+ UniquePtr<GMPCallbackType> callback(new RunTestGMPVideoCodec(aMonitor));
+ Get(aOrigin, Move(callback));
+ }
+
+protected:
+ typedef T GMPCodecType;
+ typedef Base GMPCallbackType;
+
+ explicit RunTestGMPVideoCodec(GMPTestMonitor& aMonitor)
+ : mMonitor(aMonitor)
+ {
+ }
+
+ static nsresult Get(const nsACString& aNodeId, UniquePtr<Base>&& aCallback)
+ {
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ return ((*service).*Getter)(nullptr, &tags, aNodeId, Move(aCallback));
+ }
+
+protected:
+ GMPTestMonitor& mMonitor;
+};
+
+typedef RunTestGMPVideoCodec<GMPVideoDecoderProxy,
+ GetGMPVideoDecoderCallback,
+ &GeckoMediaPluginService::GetGMPVideoDecoder>
+ RunTestGMPVideoDecoder;
+typedef RunTestGMPVideoCodec<GMPVideoEncoderProxy,
+ GetGMPVideoEncoderCallback,
+ &GeckoMediaPluginService::GetGMPVideoEncoder>
+ RunTestGMPVideoEncoder;
+
+void
+GMPTestRunner::RunTestGMPTestCodec1(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING("o"));
+}
+
+void
+GMPTestRunner::RunTestGMPTestCodec2(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
+}
+
+void
+GMPTestRunner::RunTestGMPTestCodec3(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
+}
+
+template<class Base>
+class RunTestGMPCrossOrigin : public Base
+{
+public:
+ void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+
+ UniquePtr<typename Base::GMPCallbackType> callback(
+ new Step2(Base::mMonitor, aGMP, mShouldBeEqual));
+ nsresult rv = Base::Get(mOrigin2, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ Base::mMonitor.SetFinished();
+ }
+ }
+
+ static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+ const nsCString& aOrigin2)
+ {
+ UniquePtr<typename Base::GMPCallbackType> callback(
+ new RunTestGMPCrossOrigin<Base>(aMonitor, aOrigin1, aOrigin2));
+ nsresult rv = Base::Get(aOrigin1, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ aMonitor.SetFinished();
+ }
+ }
+
+private:
+ RunTestGMPCrossOrigin(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+ const nsCString& aOrigin2)
+ : Base(aMonitor),
+ mGMP(nullptr),
+ mOrigin2(aOrigin2),
+ mShouldBeEqual(aOrigin1.Equals(aOrigin2))
+ {
+ }
+
+ class Step2 : public Base
+ {
+ public:
+ Step2(GMPTestMonitor& aMonitor,
+ typename Base::GMPCodecType* aGMP,
+ bool aShouldBeEqual)
+ : Base(aMonitor),
+ mGMP(aGMP),
+ mShouldBeEqual(aShouldBeEqual)
+ {
+ }
+ void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+ if (aGMP) {
+ EXPECT_TRUE(mGMP &&
+ (mGMP->GetPluginId() == aGMP->GetPluginId()) == mShouldBeEqual);
+ }
+ if (mGMP) {
+ mGMP->Close();
+ }
+ Base::Done(aGMP, aHost);
+ }
+
+ private:
+ typename Base::GMPCodecType* mGMP;
+ bool mShouldBeEqual;
+ };
+
+ typename Base::GMPCodecType* mGMP;
+ nsCString mOrigin2;
+ bool mShouldBeEqual;
+};
+
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoDecoder>
+ RunTestGMPVideoDecoderCrossOrigin;
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoEncoder>
+ RunTestGMPVideoEncoderCrossOrigin;
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
+}
+
+static already_AddRefed<nsIThread>
+GetGMPThread()
+{
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ nsCOMPtr<nsIThread> thread;
+ EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread))));
+ return thread.forget();
+}
+
+/**
+ * Enumerate files under |aPath| (non-recursive).
+ */
+template<typename T>
+static nsresult
+EnumerateDir(nsIFile* aPath, T&& aDirIter)
+{
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ rv = iter->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> entry(do_QueryInterface(supports, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ aDirIter(entry);
+ }
+ return NS_OK;
+}
+
+/**
+ * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ (non-recursive).
+ */
+template<typename T>
+static nsresult
+EnumerateGMPStorageDir(const nsACString& aDir, T&& aDirIter)
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ MOZ_ASSERT(service);
+
+ // $profileDir/gmp/$platform/
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = service->GetStorageDir(getter_AddRefs(path));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+
+ // $profileDir/gmp/$platform/gmp-fake/
+ rv = path->Append(NS_LITERAL_STRING("gmp-fake"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/gmp-fake/$aDir/
+ rv = path->AppendNative(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return EnumerateDir(path, aDirIter);
+}
+
+class GMPShutdownObserver : public nsIRunnable
+ , public nsIObserver {
+public:
+ GMPShutdownObserver(already_AddRefed<nsIRunnable> aShutdownTask,
+ already_AddRefed<nsIRunnable> Continuation,
+ const nsACString& aNodeId)
+ : mShutdownTask(aShutdownTask)
+ , mContinuation(Continuation)
+ , mNodeId(NS_ConvertUTF8toUTF16(aNodeId))
+ {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<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) 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() {}
+ nsCOMPtr<nsIRunnable> mShutdownTask;
+ nsCOMPtr<nsIRunnable> mContinuation;
+ const nsString mNodeId;
+};
+
+NS_IMPL_ISUPPORTS(GMPShutdownObserver, nsIRunnable, nsIObserver)
+
+class NotifyObserversTask : public Runnable {
+public:
+ explicit NotifyObserversTask(const char* aTopic)
+ : mTopic(aTopic)
+ {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, mTopic, nullptr);
+ }
+ return NS_OK;
+ }
+ const char* mTopic;
+};
+
+class ClearGMPStorageTask : public nsIRunnable
+ , public nsIObserver {
+public:
+ ClearGMPStorageTask(already_AddRefed<nsIRunnable> Continuation,
+ nsIThread* aTarget, PRTime aSince)
+ : mContinuation(Continuation)
+ , mTarget(aTarget)
+ , mSince(aSince)
+ {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->AddObserver(this, "gmp-clear-storage-complete", false);
+ if (observerService) {
+ nsAutoString str;
+ if (mSince >= 0) {
+ str.AppendInt(static_cast<int64_t>(mSince));
+ }
+ observerService->NotifyObservers(
+ nullptr, "browser:purge-session-history", str.Data());
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) override
+ {
+ if (!strcmp(aTopic, "gmp-clear-storage-complete")) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->RemoveObserver(this, "gmp-clear-storage-complete");
+ mTarget->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+ }
+
+private:
+ virtual ~ClearGMPStorageTask() {}
+ nsCOMPtr<nsIRunnable> mContinuation;
+ nsCOMPtr<nsIThread> mTarget;
+ const PRTime mSince;
+};
+
+NS_IMPL_ISUPPORTS(ClearGMPStorageTask, nsIRunnable, nsIObserver)
+
+static void
+ClearGMPStorage(already_AddRefed<nsIRunnable> aContinuation,
+ nsIThread* aTarget, PRTime aSince = -1)
+{
+ RefPtr<ClearGMPStorageTask> task(
+ new ClearGMPStorageTask(Move(aContinuation), aTarget, aSince));
+ NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+}
+
+static void
+SimulatePBModeExit()
+{
+ NS_DispatchToMainThread(new NotifyObserversTask("last-pb-context-exited"), NS_DISPATCH_SYNC);
+}
+
+class TestGetNodeIdCallback : public GetNodeIdCallback
+{
+public:
+ TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult)
+ : mNodeId(aNodeId),
+ mResult(aResult)
+ {
+ }
+
+ void Done(nsresult aResult, const nsACString& aNodeId)
+ {
+ mResult = aResult;
+ mNodeId = aNodeId;
+ }
+
+private:
+ nsCString& mNodeId;
+ nsresult& mResult;
+};
+
+static nsCString
+GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode)
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ EXPECT_TRUE(service);
+ nsCString nodeId;
+ nsresult result;
+ UniquePtr<GetNodeIdCallback> callback(new TestGetNodeIdCallback(nodeId,
+ result));
+ // We rely on the fact that the GetNodeId implementation for
+ // GeckoMediaPluginServiceParent is synchronous.
+ nsresult rv = service->GetNodeId(aOrigin,
+ aTopLevelOrigin,
+ NS_LITERAL_STRING("gmp-fake"),
+ aInPBMode,
+ Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result));
+ return nodeId;
+}
+
+static bool
+IsGMPStorageIsEmpty()
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ MOZ_ASSERT(service);
+ nsCOMPtr<nsIFile> storage;
+ nsresult rv = service->GetStorageDir(getter_AddRefs(storage));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ bool exists = false;
+ if (storage) {
+ storage->Exists(&exists);
+ }
+ return !exists;
+}
+
+static void
+AssertIsOnGMPThread()
+{
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ MOZ_ASSERT(service);
+ nsCOMPtr<nsIThread> thread;
+ 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);
+}
+
+class GMPStorageTest : public GMPDecryptorProxyCallback
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageTest)
+
+ void DoTest(void (GMPStorageTest::*aTestMethod)()) {
+ EnsureNSSInitializedChromeOrContent();
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ ClearGMPStorage(NewRunnableMethod(this, aTestMethod), thread);
+ AwaitFinished();
+ }
+
+ GMPStorageTest()
+ : mDecryptor(nullptr)
+ , mMonitor("GMPStorageTest")
+ , mFinished(false)
+ {
+ }
+
+ void
+ Update(const nsCString& aMessage)
+ {
+ nsTArray<uint8_t> msg;
+ msg.AppendElements(aMessage.get(), aMessage.Length());
+ mDecryptor->UpdateSession(1, NS_LITERAL_CSTRING("fake-session-id"), msg);
+ }
+
+ void TestGetNodeId()
+ {
+ AssertIsOnGMPThread();
+
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ const nsString origin1 = NS_LITERAL_STRING("http://example1.com");
+ const nsString origin2 = NS_LITERAL_STRING("http://example2.org");
+
+ nsCString PBnodeId1 = GetNodeId(origin1, origin2, true);
+ nsCString PBnodeId2 = GetNodeId(origin1, origin2, true);
+
+ // Node ids for the same origins should be the same in PB mode.
+ EXPECT_TRUE(PBnodeId1.Equals(PBnodeId2));
+
+ nsCString PBnodeId3 = GetNodeId(origin2, origin1, true);
+
+ // Node ids with origin and top level origin swapped should be different.
+ EXPECT_TRUE(!PBnodeId3.Equals(PBnodeId1));
+
+ // Getting node ids in PB mode should not result in the node id being stored.
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ nsCString nodeId1 = GetNodeId(origin1, origin2, false);
+ nsCString nodeId2 = GetNodeId(origin1, origin2, false);
+
+ // NodeIds for the same origin pair in non-pb mode should be the same.
+ EXPECT_TRUE(nodeId1.Equals(nodeId2));
+
+ // Node ids for a given origin pair should be different for the PB origins should be the same in PB mode.
+ EXPECT_TRUE(!PBnodeId1.Equals(nodeId1));
+ EXPECT_TRUE(!PBnodeId2.Equals(nodeId2));
+
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ ClearGMPStorage(NewRunnableMethod<nsCString>(
+ this, &GMPStorageTest::TestGetNodeId_Continuation, nodeId1), thread);
+ }
+
+ void TestGetNodeId_Continuation(nsCString aNodeId1) {
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Once we clear storage, the node ids generated for the same origin-pair
+ // should be different.
+ const nsString origin1 = NS_LITERAL_STRING("http://example1.com");
+ const nsString origin2 = NS_LITERAL_STRING("http://example2.org");
+ nsCString nodeId3 = GetNodeId(origin1, origin2, false);
+ EXPECT_TRUE(!aNodeId1.Equals(nodeId3));
+
+ SetFinished();
+ }
+
+ class CreateDecryptorDone : public GetGMPDecryptorCallback
+ {
+ public:
+ explicit CreateDecryptorDone(GMPStorageTest* aRunner)
+ : mRunner(aRunner)
+ {
+ }
+
+ void Done(GMPDecryptorProxy* aDecryptor) override
+ {
+ mRunner->mDecryptor = aDecryptor;
+ EXPECT_TRUE(!!mRunner->mDecryptor);
+
+ if (mRunner->mDecryptor) {
+ mRunner->mDecryptor->Init(mRunner, false, true);
+ }
+ }
+
+ private:
+ RefPtr<GMPStorageTest> mRunner;
+ };
+
+ void CreateDecryptor(const nsCString& aNodeId,
+ const nsCString& aUpdate)
+ {
+ nsTArray<nsCString> updates;
+ updates.AppendElement(aUpdate);
+ nsCOMPtr<nsIRunnable> continuation(new Updates(this, Move(updates)));
+ CreateDecryptor(aNodeId, continuation);
+ }
+
+ void CreateDecryptor(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode,
+ const nsCString& aUpdate)
+ {
+ nsTArray<nsCString> updates;
+ updates.AppendElement(aUpdate);
+ CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, Move(updates));
+ }
+ class Updates : public Runnable
+ {
+ public:
+ Updates(GMPStorageTest* aRunner, nsTArray<nsCString>&& aUpdates)
+ : mRunner(aRunner),
+ mUpdates(Move(aUpdates))
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ for (auto& update : mUpdates) {
+ mRunner->Update(update);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<GMPStorageTest> mRunner;
+ nsTArray<nsCString> mUpdates;
+ };
+ void CreateDecryptor(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode,
+ nsTArray<nsCString>&& aUpdates) {
+ nsCOMPtr<nsIRunnable> updates(new Updates(this, Move(aUpdates)));
+ CreateDecryptor(GetNodeId(aOrigin, aTopLevelOrigin, aInPBMode), updates);
+ }
+
+ void CreateDecryptor(const nsCString& aNodeId,
+ nsIRunnable* aContinuation) {
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ EXPECT_TRUE(service);
+
+ mNodeId = aNodeId;
+ EXPECT_TRUE(!mNodeId.IsEmpty());
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ UniquePtr<GetGMPDecryptorCallback> callback(
+ new CreateDecryptorDone(this));
+
+ // Continue after the OnSetDecryptorId message, so that we don't
+ // get warnings in the async shutdown tests due to receiving the
+ // SetDecryptorId message after we've started shutdown.
+ mSetDecryptorIdContinuation = aContinuation;
+
+ nsresult rv =
+ service->GetGMPDecryptor(nullptr, &tags, mNodeId, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ void TestBasicStorage() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+
+ // Send a message to the fake GMP for it to run its own tests internally.
+ // It sends us a "test-storage complete" message when its passed, or
+ // some other message if its tests fail.
+ Expect(NS_LITERAL_CSTRING("test-storage complete"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ /**
+ * 1. Generate storage data for some sites.
+ * 2. Forget about one of the sites.
+ * 3. Check if the storage data for the forgotten site are erased correctly.
+ * 4. Check if the storage data for other sites remain unchanged.
+ */
+ void TestForgetThisSite() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestForgetThisSite_AnotherSite);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ void TestForgetThisSite_AnotherSite() {
+ Shutdown();
+
+ // Generate storage data for another site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestForgetThisSite_CollectSiteInfo);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example3.com"),
+ NS_LITERAL_STRING("http://example4.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ struct NodeInfo {
+ explicit NodeInfo(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+ : siteToForget(aSite)
+ , mPattern(aPattern)
+ { }
+ nsCString siteToForget;
+ mozilla::OriginAttributesPattern mPattern;
+ nsTArray<nsCString> expectedRemainingNodeIds;
+ };
+
+ class NodeIdCollector {
+ public:
+ explicit NodeIdCollector(NodeInfo* aInfo) : mNodeInfo(aInfo) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = ReadSalt(aFile, salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ if (!MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) {
+ mNodeInfo->expectedRemainingNodeIds.AppendElement(salt);
+ }
+ }
+ private:
+ NodeInfo* mNodeInfo;
+ };
+
+ void TestForgetThisSite_CollectSiteInfo() {
+ mozilla::OriginAttributesPattern pattern;
+
+ nsAutoPtr<NodeInfo> siteInfo(
+ new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"),
+ pattern));
+ // Collect nodeIds that are expected to remain for later comparison.
+ EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), NodeIdCollector(siteInfo));
+ // Invoke "Forget this site" on the main thread.
+ NS_DispatchToMainThread(NewRunnableMethod<nsAutoPtr<NodeInfo>>(
+ this, &GMPStorageTest::TestForgetThisSite_Forget, siteInfo));
+ }
+
+ void TestForgetThisSite_Forget(nsAutoPtr<NodeInfo> aSiteInfo) {
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget),
+ aSiteInfo->mPattern);
+
+ nsCOMPtr<nsIThread> thread;
+ service->GetThread(getter_AddRefs(thread));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsAutoPtr<NodeInfo>>(
+ this, &GMPStorageTest::TestForgetThisSite_Verify, aSiteInfo);
+ thread->Dispatch(r, NS_DISPATCH_NORMAL);
+
+ nsCOMPtr<nsIRunnable> f = NewRunnableMethod(
+ this, &GMPStorageTest::SetFinished);
+ thread->Dispatch(f, NS_DISPATCH_NORMAL);
+ }
+
+ class NodeIdVerifier {
+ public:
+ explicit NodeIdVerifier(const NodeInfo* aInfo)
+ : mNodeInfo(aInfo)
+ , mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = ReadSalt(aFile, salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ // Shouldn't match the origin if we clear correctly.
+ EXPECT_FALSE(MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern));
+ // Check if remaining nodeIDs are as expected.
+ EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt));
+ }
+ ~NodeIdVerifier() {
+ EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty());
+ }
+ private:
+ const NodeInfo* mNodeInfo;
+ nsTArray<nsCString> mExpectedRemainingNodeIds;
+ };
+
+ class StorageVerifier {
+ public:
+ explicit StorageVerifier(const NodeInfo* aInfo)
+ : mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = aFile->GetNativeLeafName(salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt));
+ }
+ ~StorageVerifier() {
+ EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty());
+ }
+ private:
+ nsTArray<nsCString> mExpectedRemainingNodeIds;
+ };
+
+ void TestForgetThisSite_Verify(nsAutoPtr<NodeInfo> aSiteInfo) {
+ nsresult rv = EnumerateGMPStorageDir(
+ NS_LITERAL_CSTRING("id"), NodeIdVerifier(aSiteInfo));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = EnumerateGMPStorageDir(
+ NS_LITERAL_CSTRING("storage"), StorageVerifier(aSiteInfo));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/id/.
+ * 3. Pass |t| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage are removed.
+ */
+ void TestClearRecentHistory1() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory1_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+}
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
+ * 3. Pass |t| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage are removed.
+ */
+ void TestClearRecentHistory2() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory2_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
+ * 3. Pass |t+1| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage remain unchanged.
+ */
+ void TestClearRecentHistory3() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory3_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ class MaxMTimeFinder {
+ public:
+ MaxMTimeFinder() : mMaxTime(0) {}
+ void operator()(nsIFile* aFile) {
+ PRTime lastModified;
+ nsresult rv = aFile->GetLastModifiedTime(&lastModified);
+ if (NS_SUCCEEDED(rv) && lastModified > mMaxTime) {
+ mMaxTime = lastModified;
+ }
+ EnumerateDir(aFile, *this);
+ }
+ PRTime GetResult() const { return mMaxTime; }
+ private:
+ PRTime mMaxTime;
+ };
+
+ void TestClearRecentHistory1_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult());
+ }
+
+ void TestClearRecentHistory2_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult());
+ }
+
+ void TestClearRecentHistory3_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckNonEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult() + 1);
+ }
+
+ class FileCounter {
+ public:
+ FileCounter() : mCount(0) {}
+ void operator()(nsIFile* aFile) {
+ ++mCount;
+ }
+ int GetCount() const { return mCount; }
+ private:
+ int mCount;
+ };
+
+ void TestClearRecentHistory_CheckEmpty() {
+ FileCounter c1;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/
+ EXPECT_EQ(c1.GetCount(), 0);
+
+ FileCounter c2;
+ rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be no files under $profileDir/gmp/$platform/gmp-fake/storage/
+ EXPECT_EQ(c2.GetCount(), 0);
+
+ SetFinished();
+ }
+
+ void TestClearRecentHistory_CheckNonEmpty() {
+ FileCounter c1;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be one directory under $profileDir/gmp/$platform/gmp-fake/id/
+ EXPECT_EQ(c1.GetCount(), 1);
+
+ FileCounter c2;
+ rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be one directory under $profileDir/gmp/$platform/gmp-fake/storage/
+ EXPECT_EQ(c2.GetCount(), 1);
+
+ SetFinished();
+ }
+
+ void TestCrossOriginStorage() {
+ EXPECT_TRUE(!mDecryptor);
+
+ // Send the decryptor the message "store recordid $time"
+ // Wait for the decrytor to send us "stored recordid $time"
+ auto t = time(0);
+ nsCString response("stored crossOriginTestRecordId ");
+ response.AppendInt((int64_t)t);
+ Expect(response, NewRunnableMethod(this,
+ &GMPStorageTest::TestCrossOriginStorage_RecordStoredContinuation));
+
+ nsCString update("store crossOriginTestRecordId ");
+ update.AppendInt((int64_t)t);
+
+ // Open decryptor on one, origin, write a record, and test that that
+ // record can't be read on another origin.
+ CreateDecryptor(NS_LITERAL_STRING("http://example3.com"),
+ NS_LITERAL_STRING("http://example4.com"),
+ false,
+ update);
+ }
+
+ void TestCrossOriginStorage_RecordStoredContinuation() {
+ // Close the old decryptor, and create a new one on a different origin,
+ // and try to read the record.
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example5.com"),
+ NS_LITERAL_STRING("http://example6.com"),
+ false,
+ NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId"));
+ }
+
+ void TestPBStorage() {
+ // Send the decryptor the message "store recordid $time"
+ // Wait for the decrytor to send us "stored recordid $time"
+ nsCString response("stored pbdata test-pb-data");
+ Expect(response, NewRunnableMethod(this,
+ &GMPStorageTest::TestPBStorage_RecordStoredContinuation));
+
+ // Open decryptor on one, origin, write a record, close decryptor,
+ // open another, and test that record can be read, close decryptor,
+ // then send pb-last-context-closed notification, then open decryptor
+ // and check that it can't read that data; it should have been purged.
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("store pbdata test-pb-data"));
+ }
+
+ void TestPBStorage_RecordStoredContinuation() {
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 12 bytes)"),
+ NewRunnableMethod(this,
+ &GMPStorageTest::TestPBStorage_RecordRetrievedContinuation));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("retrieve pbdata"));
+ }
+
+ void TestPBStorage_RecordRetrievedContinuation() {
+ Shutdown();
+ SimulatePBModeExit();
+
+ Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 0 bytes)"),
+ NewRunnableMethod(this,
+ &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("retrieve pbdata"));
+ }
+
+ void NextAsyncShutdownTimeoutTest(nsIRunnable* aContinuation)
+ {
+ if (mDecryptor) {
+ Update(NS_LITERAL_CSTRING("shutdown-mode timeout"));
+ Shutdown();
+ }
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(aContinuation, NS_DISPATCH_NORMAL);
+ }
+
+ void CreateAsyncShutdownTimeoutGMP(const nsAString& aOrigin1,
+ const nsAString& aOrigin2,
+ void (GMPStorageTest::*aCallback)()) {
+ nsCOMPtr<nsIRunnable> continuation(
+ NewRunnableMethod<nsCOMPtr<nsIRunnable>>(
+ this,
+ &GMPStorageTest::NextAsyncShutdownTimeoutTest,
+ NewRunnableMethod(this, aCallback)));
+
+ CreateDecryptor(GetNodeId(aOrigin1, aOrigin2, false), continuation);
+ }
+
+ void TestAsyncShutdownTimeout() {
+ // Create decryptors that timeout in their async shutdown.
+ // If the gtest hangs on shutdown, test fails!
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example7.com"),
+ NS_LITERAL_STRING("http://example8.com"),
+ &GMPStorageTest::TestAsyncShutdownTimeout2);
+ };
+
+ void TestAsyncShutdownTimeout2() {
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example9.com"),
+ NS_LITERAL_STRING("http://example10.com"),
+ &GMPStorageTest::TestAsyncShutdownTimeout3);
+ };
+
+ void TestAsyncShutdownTimeout3() {
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example11.com"),
+ NS_LITERAL_STRING("http://example12.com"),
+ &GMPStorageTest::SetFinished);
+ };
+
+ void TestAsyncShutdownStorage() {
+ // 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, NewRunnableMethod<nsCString>(this,
+ &GMPStorageTest::TestAsyncShutdownStorage_ReceivedShutdownToken, token));
+
+ // Test that a GMP can write to storage during shutdown, and retrieve
+ // that written data in a subsequent session.
+ CreateDecryptor(NS_LITERAL_STRING("http://example13.com"),
+ NS_LITERAL_STRING("http://example14.com"),
+ false,
+ update);
+ }
+
+ void TestAsyncShutdownStorage_ReceivedShutdownToken(const nsCString& aToken) {
+ ShutdownThen(NewRunnableMethod<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.
+ nsCString response("retrieved shutdown-token ");
+ response.Append(aToken);
+ Expect(response,
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example13.com"),
+ NS_LITERAL_STRING("http://example14.com"),
+ false,
+ NS_LITERAL_CSTRING("retrieve-shutdown-token"));
+ }
+
+#if defined(XP_WIN)
+ void TestOutputProtection() {
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("OP tests completed"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example15.com"),
+ NS_LITERAL_STRING("http://example16.com"),
+ false,
+ NS_LITERAL_CSTRING("test-op-apis"));
+ }
+#endif
+
+ void TestPluginVoucher() {
+ Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example17.com"),
+ NS_LITERAL_STRING("http://example18.com"),
+ false,
+ 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) {
+ // Create a number of records of different names.
+ const uint32_t num = 100;
+ nsTArray<nsCString> updates(num);
+ 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);
+
+ nsCString& update = *updates.AppendElement();
+ update.AppendLiteral("store data");
+ AppendIntPadded(update, i);
+ update.AppendLiteral(" test-data");
+ AppendIntPadded(update, i);
+
+ nsCOMPtr<nsIRunnable> continuation;
+ if (i + 1 == num) {
+ continuation =
+ NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames);
+ }
+ Expect(response, continuation.forget());
+ }
+
+ CreateDecryptor(NS_LITERAL_STRING("http://foo.com"),
+ NS_LITERAL_STRING("http://bar.com"),
+ aPrivateBrowsing,
+ Move(updates));
+ }
+
+ void TestGetRecordNames_QueryNames() {
+ nsCString response("record-names ");
+ response.Append(mRecordNames);
+ Expect(response,
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+ Update(NS_LITERAL_CSTRING("retrieve-record-names"));
+ }
+
+ void GetRecordNamesPersistentStorage() {
+ TestGetRecordNames(false);
+ }
+
+ void TestLongRecordNames() {
+ NS_NAMED_LITERAL_CSTRING(longRecordName,
+ "A_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "long_record_name");
+
+ NS_NAMED_LITERAL_CSTRING(data, "Just_some_arbitrary_data.");
+
+ MOZ_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE);
+ MOZ_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH
+
+ nsCString response("stored ");
+ response.Append(longRecordName);
+ response.AppendLiteral(" ");
+ response.Append(data);
+ Expect(response, NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ nsCString update("store ");
+ update.Append(longRecordName);
+ update.AppendLiteral(" ");
+ update.Append(data);
+ CreateDecryptor(NS_LITERAL_STRING("http://fuz.com"),
+ NS_LITERAL_STRING("http://baz.com"),
+ false,
+ update);
+ }
+
+ void TestNodeId() {
+ // Calculate the nodeId, and the device bound nodeId. Start a GMP, and
+ // have it return the device bound nodeId that it's been passed. Assert
+ // they have the same value.
+ const nsString origin = NS_LITERAL_STRING("http://example-fuz-baz.com");
+ nsCString originSalt1 = GetNodeId(origin, origin, false);
+
+ nsCString salt = originSalt1;
+ std::string nodeId;
+ EXPECT_TRUE(CalculateGMPDeviceId(salt.BeginWriting(), salt.Length(), nodeId));
+
+ std::string expected = "node-id " + nodeId;
+ Expect(nsDependentCString(expected.c_str()), NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(originSalt1,
+ NS_LITERAL_CSTRING("retrieve-node-id"));
+ }
+
+ void Expect(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation) {
+ mExpected.AppendElement(ExpectedMessage(aMessage, Move(aContinuation)));
+ }
+
+ void AwaitFinished() {
+ while (!mFinished) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+ mFinished = false;
+ }
+
+ void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) {
+ EXPECT_TRUE(!!mDecryptor);
+ if (!mDecryptor) {
+ return;
+ }
+ EXPECT_FALSE(mNodeId.IsEmpty());
+ RefPtr<GMPShutdownObserver> task(
+ new GMPShutdownObserver(NewRunnableMethod(this, &GMPStorageTest::Shutdown),
+ Move(aContinuation), mNodeId));
+ NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+ }
+
+ void Shutdown() {
+ if (mDecryptor) {
+ mDecryptor->Close();
+ mDecryptor = nullptr;
+ mNodeId = EmptyCString();
+ }
+ }
+
+ void Dummy() {
+ }
+
+ void SetFinished() {
+ mFinished = true;
+ Shutdown();
+ NS_DispatchToMainThread(NewRunnableMethod(this, &GMPStorageTest::Dummy));
+ }
+
+ void SessionMessage(const nsCString& aSessionId,
+ mozilla::dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override
+ {
+ MonitorAutoLock mon(mMonitor);
+
+ nsCString msg((const char*)aMessage.Elements(), aMessage.Length());
+ EXPECT_TRUE(mExpected.Length() > 0);
+ bool matches = mExpected[0].mMessage.Equals(msg);
+ EXPECT_STREQ(mExpected[0].mMessage.get(), msg.get());
+ if (mExpected.Length() > 0 && matches) {
+ nsCOMPtr<nsIRunnable> continuation = mExpected[0].mContinuation;
+ mExpected.RemoveElementAt(0);
+ if (continuation) {
+ NS_DispatchToCurrentThread(continuation);
+ }
+ }
+ }
+
+ void SetDecryptorId(uint32_t aId) override
+ {
+ if (!mSetDecryptorIdContinuation) {
+ return;
+ }
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(mSetDecryptorIdContinuation, NS_DISPATCH_NORMAL);
+ mSetDecryptorIdContinuation = nullptr;
+ }
+
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const nsCString& aSessionId) override { }
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override {}
+ void ResolvePromise(uint32_t aPromiseId) override {}
+ void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) override { }
+ void ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime) override {}
+ void SessionClosed(const nsCString& aSessionId) override {}
+ void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) override {}
+ void Decrypted(uint32_t aId,
+ mozilla::DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override { }
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override { }
+
+ void Terminated() override {
+ if (mDecryptor) {
+ mDecryptor->Close();
+ mDecryptor = nullptr;
+ }
+ }
+
+private:
+ ~GMPStorageTest() { }
+
+ struct ExpectedMessage {
+ ExpectedMessage(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation)
+ : mMessage(aMessage)
+ , mContinuation(aContinuation)
+ {}
+ nsCString mMessage;
+ nsCOMPtr<nsIRunnable> mContinuation;
+ };
+
+ nsTArray<ExpectedMessage> mExpected;
+
+ RefPtr<nsIRunnable> mSetDecryptorIdContinuation;
+
+ GMPDecryptorProxy* mDecryptor;
+ Monitor mMonitor;
+ Atomic<bool> mFinished;
+ nsCString mNodeId;
+};
+
+void
+GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&))
+{
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+
+ GMPTestMonitor monitor;
+ thread->Dispatch(NewRunnableMethod<GMPTestMonitor&>(this,
+ aTestMethod,
+ monitor),
+ NS_DISPATCH_NORMAL);
+ monitor.AwaitFinished();
+}
+
+TEST(GeckoMediaPlugins, GMPTestCodec) {
+ RefPtr<GMPTestRunner> runner = new GMPTestRunner();
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec1);
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec2);
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec3);
+}
+
+TEST(GeckoMediaPlugins, GMPCrossOrigin) {
+ RefPtr<GMPTestRunner> runner = new GMPTestRunner();
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin1);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin2);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin3);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin4);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageGetNodeId) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestGetNodeId);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageBasic) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestBasicStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageForgetThisSite) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestForgetThisSite);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory1) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory1);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory2) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory2);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory3) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory3);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageCrossOrigin) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestCrossOriginStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStoragePrivateBrowsing) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestPBStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownTimeout) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestAsyncShutdownTimeout);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestAsyncShutdownStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPPluginVoucher) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestPluginVoucher);
+}
+
+#if defined(XP_WIN)
+TEST(GeckoMediaPlugins, GMPOutputProtection) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestOutputProtection);
+}
+#endif
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesPersistentStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::GetRecordNamesPersistentStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageLongRecordNames) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestLongRecordNames);
+}
+
+TEST(GeckoMediaPlugins, GMPNodeId) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestNodeId);
+}
diff --git a/dom/media/gtest/TestGMPRemoveAndDelete.cpp b/dom/media/gtest/TestGMPRemoveAndDelete.cpp
new file mode 100644
index 000000000..31049bb1d
--- /dev/null
+++ b/dom/media/gtest/TestGMPRemoveAndDelete.cpp
@@ -0,0 +1,490 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPService.h"
+#include "GMPTestMonitor.h"
+#include "gmp-api/gmp-video-host.h"
+#include "gtest/gtest.h"
+#include "mozilla/Services.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIObserverService.h"
+#include "GMPVideoDecoderProxy.h"
+#include "GMPServiceParent.h"
+#include "GMPService.h"
+#include "GMPUtils.h"
+#include "mozilla/StaticPtr.h"
+#include "MediaPrefs.h"
+
+#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
+#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
+#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
+
+#define GMP_DELETED_TOPIC "gmp-directory-deleted"
+
+#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
+
+using namespace mozilla;
+using namespace mozilla::gmp;
+
+class GMPRemoveTest : public nsIObserver
+ , public GMPVideoDecoderCallbackProxy
+{
+public:
+ GMPRemoveTest();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // Called when a GMP plugin directory has been successfully deleted.
+ // |aData| will contain the directory path.
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+
+ // Create a new GMP plugin directory that we can trash and add it to the GMP
+ // service. Remove the original plugin directory. Original plugin directory
+ // gets re-added at destruction.
+ void Setup();
+
+ bool CreateVideoDecoder(nsCString aNodeId = EmptyCString());
+ void CloseVideoDecoder();
+
+ void DeletePluginDirectory(bool aCanDefer);
+
+ // Decode a dummy frame.
+ GMPErr Decode();
+
+ // Wait until TestMonitor has been signaled.
+ void Wait();
+
+ // Did we get a Terminated() callback from the plugin?
+ bool IsTerminated();
+
+ // From GMPVideoDecoderCallbackProxy
+ // Set mDecodeResult; unblock TestMonitor.
+ virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ virtual void Error(GMPErr aError) override;
+
+ // From GMPVideoDecoderCallbackProxy
+ // We expect this to be called when a plugin has been forcibly closed.
+ virtual void Terminated() override;
+
+ // Ignored GMPVideoDecoderCallbackProxy members
+ virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
+ virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
+ virtual void InputDataExhausted() override {}
+ virtual void DrainComplete() override {}
+ virtual void ResetComplete() override {}
+
+private:
+ virtual ~GMPRemoveTest();
+
+ void gmp_Decode();
+ void gmp_GetVideoDecoder(nsCString aNodeId,
+ GMPVideoDecoderProxy** aOutDecoder,
+ GMPVideoHost** aOutHost);
+ void GeneratePlugin();
+
+ GMPTestMonitor mTestMonitor;
+ nsCOMPtr<nsIThread> mGMPThread;
+
+ bool mIsTerminated;
+
+ // Path to the cloned GMP we have created.
+ nsString mTmpPath;
+ nsCOMPtr<nsIFile> mTmpDir;
+
+ // Path to the original GMP. Store so that we can re-add it after we're done
+ // testing.
+ nsString mOriginalPath;
+
+ GMPVideoDecoderProxy* mDecoder;
+ GMPVideoHost* mHost;
+ GMPErr mDecodeResult;
+};
+
+/*
+ * Simple test that the plugin is deleted when forcibly removed and deleted.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedSimple)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ test->DeletePluginDirectory(false /* force immediate */);
+ test->Wait();
+}
+
+/*
+ * Simple test that the plugin is deleted when deferred deletion is allowed.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredSimple)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ test->DeletePluginDirectory(true /* can defer */);
+ test->Wait();
+}
+
+/*
+ * Test that the plugin is unavailable immediately after a forced
+ * RemoveAndDelete, and that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Test that we can decode a frame.
+ GMPErr err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ test->DeletePluginDirectory(false /* force immediate */);
+ test->Wait();
+
+ // Test that the VideoDecoder is no longer available.
+ EXPECT_FALSE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Test that we were notified of the plugin's destruction.
+ EXPECT_TRUE(test->IsTerminated());
+}
+
+/*
+ * Test that the plugin is still usable after a deferred RemoveAndDelete, and
+ * that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredInUse)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Make sure decoding works before we do anything.
+ GMPErr err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ test->DeletePluginDirectory(true /* can defer */);
+
+ // Test that decoding still works.
+ err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ // Test that this origin is still able to fetch the video decoder.
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ test->CloseVideoDecoder();
+ test->Wait();
+}
+
+static StaticRefPtr<GeckoMediaPluginService> gService;
+static StaticRefPtr<GeckoMediaPluginServiceParent> gServiceParent;
+
+static GeckoMediaPluginService*
+GetService()
+{
+ if (!gService) {
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ gService = service;
+ }
+
+ return gService.get();
+}
+
+static GeckoMediaPluginServiceParent*
+GetServiceParent()
+{
+ if (!gServiceParent) {
+ RefPtr<GeckoMediaPluginServiceParent> parent =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ gServiceParent = parent;
+ }
+
+ return gServiceParent.get();
+}
+
+NS_IMPL_ISUPPORTS(GMPRemoveTest, nsIObserver)
+
+GMPRemoveTest::GMPRemoveTest()
+ : mIsTerminated(false)
+ , mDecoder(nullptr)
+ , mHost(nullptr)
+{
+}
+
+GMPRemoveTest::~GMPRemoveTest()
+{
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(mTmpDir->Exists(&exists)) && !exists);
+
+ EXPECT_OK(GetServiceParent()->AddPluginDirectory(mOriginalPath));
+}
+
+void
+GMPRemoveTest::Setup()
+{
+ // Initialize media preferences.
+ MediaPrefs::GetSingleton();
+ GeneratePlugin();
+ GetService()->GetThread(getter_AddRefs(mGMPThread));
+
+ // Spin the event loop until the GMP service has had a chance to complete
+ // adding GMPs from MOZ_GMP_PATH. Otherwise, the RemovePluginDirectory()
+ // below may complete before we're finished adding GMPs from MOZ_GMP_PATH,
+ // and we'll end up not removing the GMP, and the test will fail.
+ RefPtr<AbstractThread> thread(GetServiceParent()->GetAbstractGMPThread());
+ EXPECT_TRUE(thread);
+ GMPTestMonitor* mon = &mTestMonitor;
+ GetServiceParent()->EnsureInitialized()->Then(thread, __func__,
+ [mon]() { mon->SetFinished(); },
+ [mon]() { mon->SetFinished(); }
+ );
+ mTestMonitor.AwaitFinished();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
+ EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
+
+ GetServiceParent()->AsyncAddPluginDirectory(mTmpPath)->Then(thread, __func__,
+ [mon]() { mon->SetFinished(); },
+ [mon]() { mon->SetFinished(); }
+ );
+ mTestMonitor.AwaitFinished();
+}
+
+bool
+GMPRemoveTest::CreateVideoDecoder(nsCString aNodeId)
+{
+ GMPVideoHost* host;
+ GMPVideoDecoderProxy* decoder = nullptr;
+
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod<nsCString, GMPVideoDecoderProxy**, GMPVideoHost**>(
+ this, &GMPRemoveTest::gmp_GetVideoDecoder, aNodeId, &decoder, &host),
+ NS_DISPATCH_NORMAL);
+
+ mTestMonitor.AwaitFinished();
+
+ if (!decoder) {
+ return false;
+ }
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+ codec.mGMPApiVersion = 33;
+
+ nsTArray<uint8_t> empty;
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod<const GMPVideoCodec&, const nsTArray<uint8_t>&, GMPVideoDecoderCallbackProxy*, int32_t>(
+ decoder, &GMPVideoDecoderProxy::InitDecode,
+ codec, empty, this, 1 /* core count */),
+ NS_DISPATCH_SYNC);
+
+ if (mDecoder) {
+ CloseVideoDecoder();
+ }
+
+ mDecoder = decoder;
+ mHost = host;
+
+ return true;
+}
+
+void
+GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId,
+ GMPVideoDecoderProxy** aOutDecoder,
+ GMPVideoHost** aOutHost)
+{
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ class Callback : public GetGMPVideoDecoderCallback
+ {
+ public:
+ Callback(GMPTestMonitor* aMonitor, GMPVideoDecoderProxy** aDecoder, GMPVideoHost** aHost)
+ : mMonitor(aMonitor), mDecoder(aDecoder), mHost(aHost) { }
+ virtual void Done(GMPVideoDecoderProxy* aDecoder, GMPVideoHost* aHost) override {
+ *mDecoder = aDecoder;
+ *mHost = aHost;
+ mMonitor->SetFinished();
+ }
+ private:
+ GMPTestMonitor* mMonitor;
+ GMPVideoDecoderProxy** mDecoder;
+ GMPVideoHost** mHost;
+ };
+
+ UniquePtr<GetGMPVideoDecoderCallback>
+ cb(new Callback(&mTestMonitor, aOutDecoder, aOutHost));
+
+ if (NS_FAILED(GetService()->GetGMPVideoDecoder(nullptr, &tags, aNodeId, Move(cb)))) {
+ mTestMonitor.SetFinished();
+ }
+}
+
+void
+GMPRemoveTest::CloseVideoDecoder()
+{
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod(mDecoder, &GMPVideoDecoderProxy::Close),
+ NS_DISPATCH_SYNC);
+
+ mDecoder = nullptr;
+ mHost = nullptr;
+}
+
+void
+GMPRemoveTest::DeletePluginDirectory(bool aCanDefer)
+{
+ GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer);
+}
+
+GMPErr
+GMPRemoveTest::Decode()
+{
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod(this, &GMPRemoveTest::gmp_Decode),
+ NS_DISPATCH_NORMAL);
+
+ mTestMonitor.AwaitFinished();
+ return mDecodeResult;
+}
+
+void
+GMPRemoveTest::gmp_Decode()
+{
+ // from gmp-fake.cpp
+ struct EncodedFrame {
+ uint32_t length_;
+ uint8_t h264_compat_;
+ uint32_t magic_;
+ uint32_t width_;
+ uint32_t height_;
+ uint8_t y_;
+ uint8_t u_;
+ uint8_t v_;
+ uint32_t timestamp_;
+ };
+
+ GMPVideoFrame* absFrame;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &absFrame);
+ EXPECT_EQ(err, GMPNoErr);
+
+ GMPUniquePtr<GMPVideoEncodedFrame>
+ frame(static_cast<GMPVideoEncodedFrame*>(absFrame));
+ err = frame->CreateEmptyFrame(sizeof(EncodedFrame) /* size */);
+ EXPECT_EQ(err, GMPNoErr);
+
+ EncodedFrame* frameData = reinterpret_cast<EncodedFrame*>(frame->Buffer());
+ frameData->magic_ = 0x4652414d;
+ frameData->width_ = frameData->height_ = 16;
+
+ nsTArray<uint8_t> empty;
+ nsresult rv = mDecoder->Decode(Move(frame), false /* aMissingFrames */, empty);
+ EXPECT_OK(rv);
+}
+
+void
+GMPRemoveTest::Wait()
+{
+ mTestMonitor.AwaitFinished();
+}
+
+bool
+GMPRemoveTest::IsTerminated()
+{
+ return mIsTerminated;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+GMPRemoveTest::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ EXPECT_TRUE(!strcmp(GMP_DELETED_TOPIC, aTopic));
+
+ nsString data(aData);
+ if (mTmpPath.Equals(data)) {
+ mTestMonitor.SetFinished();
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, GMP_DELETED_TOPIC);
+ }
+
+ return NS_OK;
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ aDecodedFrame->Destroy();
+ mDecodeResult = GMPNoErr;
+ mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Error(GMPErr aError)
+{
+ mDecodeResult = aError;
+ mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Terminated()
+{
+ mIsTerminated = true;
+ if (mDecoder) {
+ mDecoder->Close();
+ mDecoder = nullptr;
+ }
+}
+
+void
+GMPRemoveTest::GeneratePlugin()
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> gmpDir;
+ nsCOMPtr<nsIFile> origDir;
+ nsCOMPtr<nsIFile> tmpDir;
+
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR,
+ getter_AddRefs(gmpDir));
+ EXPECT_OK(rv);
+ rv = gmpDir->Append(GMP_DIR_NAME);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(origDir));
+ EXPECT_OK(rv);
+ rv = origDir->Append(GMP_OLD_VERSION);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(tmpDir));
+ EXPECT_OK(rv);
+ rv = tmpDir->Append(GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+ bool exists = false;
+ rv = tmpDir->Exists(&exists);
+ EXPECT_OK(rv);
+ if (exists) {
+ rv = tmpDir->Remove(true);
+ EXPECT_OK(rv);
+ }
+ rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(tmpDir));
+ EXPECT_OK(rv);
+ rv = tmpDir->Append(GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+
+ EXPECT_OK(origDir->GetPath(mOriginalPath));
+ EXPECT_OK(tmpDir->GetPath(mTmpPath));
+ mTmpDir = tmpDir;
+}
diff --git a/dom/media/gtest/TestGMPUtils.cpp b/dom/media/gtest/TestGMPUtils.cpp
new file mode 100644
index 000000000..00f09f812
--- /dev/null
+++ b/dom/media/gtest/TestGMPUtils.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "GMPUtils.h"
+#include "nsString.h"
+#include "MediaPrefs.h"
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace mozilla;
+
+void TestSplitAt(const char* aInput,
+ const char* aDelims,
+ size_t aNumExpectedTokens,
+ const char* aExpectedTokens[])
+{
+ // Initialize media preferences.
+ MediaPrefs::GetSingleton();
+ nsCString input(aInput);
+ nsTArray<nsCString> tokens;
+ SplitAt(aDelims, input, tokens);
+ EXPECT_EQ(tokens.Length(), aNumExpectedTokens) << "Should get expected number of tokens";
+ for (size_t i = 0; i < tokens.Length(); i++) {
+ EXPECT_TRUE(tokens[i].EqualsASCII(aExpectedTokens[i]))
+ << "Tokenize fail; expected=" << aExpectedTokens[i] << " got=" <<
+ tokens[i].BeginReading();
+ }
+}
+
+TEST(GeckoMediaPlugins, GMPUtils) {
+ {
+ const char* input = "1,2,3,4";
+ const char* delims = ",";
+ const char* tokens[] = { "1", "2", "3", "4" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+
+ {
+ const char* input = "a simple, comma, seperated, list";
+ const char* delims = ",";
+ const char* tokens[] = { "a simple", " comma", " seperated", " list" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+
+ {
+ const char* input = // Various platform line endings...
+ "line1\r\n" // Windows
+ "line2\r" // Old MacOSX
+ "line3\n" // Unix
+ "line4";
+ const char* delims = "\r\n";
+ const char* tokens[] = { "line1", "line2", "line3", "line4" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+}
diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build
index b7bd4b068..512d0a087 100644
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -10,6 +10,9 @@ UNIFIED_SOURCES += [
'TestAudioMixer.cpp',
'TestAudioPacketizer.cpp',
'TestAudioSegment.cpp',
+ 'TestGMPCrossOrigin.cpp',
+ 'TestGMPRemoveAndDelete.cpp',
+ 'TestGMPUtils.cpp',
'TestIntervalSet.cpp',
'TestMediaDataDecoder.cpp',
'TestMediaEventSource.cpp',
@@ -56,6 +59,7 @@ LOCAL_INCLUDES += [
'/dom/media',
'/dom/media/encoder',
'/dom/media/fmp4',
+ '/dom/media/gmp',
'/security/certverifier',
'/security/pkix/include',
]
diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini
index 24f3735d4..742ac1b1c 100644
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -425,6 +425,7 @@ support-files =
dirac.ogg^headers^
dynamic_redirect.sjs
dynamic_resource.sjs
+ eme.js
file_access_controls.html
flac-s24.flac
flac-s24.flac^headers^
@@ -681,11 +682,43 @@ tags=capturestream
[test_decoder_disable.html]
[test_defaultMuted.html]
[test_delay_load.html]
+[test_eme_session_callable_value.html]
+[test_eme_canvas_blocked.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_detach_media_keys.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_initDataTypes.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_missing_pssh.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_non_mse_fails.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_request_notifications.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_requestKeySystemAccess.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_setMediaKeys_before_attach_MediaSource.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case1.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case2.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case3.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_waitingforkey.html]
+skip-if = toolkit == 'android' # bug 1149374
[test_empty_resource.html]
[test_error_in_video_document.html]
[test_error_on_404.html]
[test_fastSeek.html]
[test_fastSeek-forwards.html]
+[test_gmp_playback.html]
+skip-if = (os != 'win' || os_version == '5.1') # Only gmp-clearkey on Windows Vista and later decodes
[test_imagecapture.html]
[test_info_leak.html]
[test_invalid_reject.html]
diff --git a/dom/media/test/test_eme_request_notifications.html b/dom/media/test/test_eme_request_notifications.html
new file mode 100644
index 000000000..c2fab5b15
--- /dev/null
+++ b/dom/media/test/test_eme_request_notifications.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function SetPrefs(prefs) {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({"set": prefs}, function() { resolve(); });
+ });
+}
+
+function observe() {
+ return new Promise(function(resolve, reject) {
+ var observer = function(subject, topic, data) {
+ SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request");
+ resolve(JSON.parse(data).status);
+ };
+ SpecialPowers.Services.obs.addObserver(observer, "mediakeys-request", false);
+ });
+}
+
+function Test(test) {
+ var p = test.prefs ? SetPrefs(test.prefs) : Promise.resolve();
+ observedStatus = "nothing";
+ var name = "'" + test.keySystem + "'";
+
+ var res = observe().then((status) => {
+ is(status, test.expectedStatus, name + " expected status");
+ });
+
+ p.then(() => navigator.requestMediaKeySystemAccess(test.keySystem, gCencMediaKeySystemConfig))
+ .then((keySystemAccess) => keySystemAccess.createMediaKeys());
+
+ return res;
+}
+
+const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
+
+var tests = [
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: 'api-disabled',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: (isWinXP ? 'cdm-not-supported' : 'cdm-disabled'),
+ prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: (isWinXP ? 'cdm-not-supported' : 'cdm-not-installed'),
+ prefs: [["media.eme.enabled", true], , ["media.gmp-widevinecdm.enabled", true]]
+ },
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", true]]
+ }
+];
+
+SetupEMEPref(function() {
+ tests.reduce(function(p,c,i,array) {
+ return p.then(function() { return Test(c); });
+ }, Promise.resolve()).then(SimpleTest.finish);
+});
+
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_gmp_playback.html b/dom/media/test/test_gmp_playback.html
new file mode 100644
index 000000000..b65a4203c
--- /dev/null
+++ b/dom/media/test/test_gmp_playback.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should play OK</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function startTest() {
+ var v = document.createElement("video");
+ ok(v.canPlayType('video/mp4; codecs="avc1.64000d,mp4a.40.2"') != "",
+ "Should be able to play MP4/H.264/AAC via <video> using GMP");
+ v.src = "short.mp4";
+ v.addEventListener("ended", function(event) {
+ ok(true, "Reached end");
+ SimpleTest.finish();
+ });
+ document.body.appendChild(v);
+ v.play();
+}
+
+var testPrefs = [
+ ['media.gmp.decoder.aac', 1], // gmp-clearkey
+ ['media.gmp.decoder.h264', 1], // gmp-clearkey
+ ['media.gmp.decoder.enabled', true]
+];
+
+SpecialPowers.pushPrefEnv({'set': testPrefs}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/webidl/PluginCrashedEvent.webidl b/dom/webidl/PluginCrashedEvent.webidl
index c94315385..8eed7244e 100644
--- a/dom/webidl/PluginCrashedEvent.webidl
+++ b/dom/webidl/PluginCrashedEvent.webidl
@@ -13,6 +13,7 @@ interface PluginCrashedEvent : Event
readonly attribute DOMString? browserDumpID;
readonly attribute DOMString? pluginFilename;
readonly attribute boolean submittedCrashReport;
+ readonly attribute boolean gmpPlugin;
};
dictionary PluginCrashedEventInit : EventInit
@@ -23,4 +24,5 @@ dictionary PluginCrashedEventInit : EventInit
DOMString? browserDumpID = null;
DOMString? pluginFilename = null;
boolean submittedCrashReport = false;
+ boolean gmpPlugin = false;
};
diff --git a/system/graphics/thebes/gfxPrefs.cpp b/system/graphics/thebes/gfxPrefs.cpp
index 9ec9b638a..72bc58d78 100644
--- a/system/graphics/thebes/gfxPrefs.cpp
+++ b/system/graphics/thebes/gfxPrefs.cpp
@@ -111,7 +111,7 @@ gfxPrefs::Pref::SetChangeCallback(ChangeCallback aCallback)
FireChangeCallback();
}
-// On lightweight processes such as for GPU, XPCOM is not initialized,
+// On lightweight processes such as for GMP and GPU, XPCOM is not initialized,
// and therefore we don't have access to Preferences. When XPCOM is not
// available we rely on manual synchronization of gfxPrefs values over IPC.
/* static */ bool
diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py
index f52a83672..6763952af 100644
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1499,6 +1499,14 @@ toolbar#nav-bar {
if not self.disable_leak_checking:
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
+ try:
+ gmp_path = self.getGMPPluginPath(options)
+ if gmp_path is not None:
+ browserEnv["MOZ_GMP_PATH"] = gmp_path
+ except EnvironmentError:
+ self.log.error('Could not find path to gmp-fake plugin!')
+ return None
+
if options.fatalAssertions:
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
diff --git a/testing/mochitest/runtestsremote.py b/testing/mochitest/runtestsremote.py
index a6f264a36..8010703e9 100644
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -256,6 +256,10 @@ class MochiRemote(MochitestDesktop):
except mozdevice.DMError:
self.log.warning("Error getting device information")
+ def getGMPPluginPath(self, options):
+ # TODO: bug 1149374
+ return None
+
def buildBrowserEnv(self, options, debugger=False):
browserEnv = MochitestDesktop.buildBrowserEnv(
self,