summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp/GMPServiceChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gmp/GMPServiceChild.cpp')
-rw-r--r--dom/media/gmp/GMPServiceChild.cpp569
1 files changed, 569 insertions, 0 deletions
diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp
new file mode 100644
index 0000000000..e3b1f00727
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPServiceChild.h"
+
+#include "GMPContentParent.h"
+#include "GMPLog.h"
+#include "GMPParent.h"
+#include "base/task.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsReadableUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "runnable_utils.h"
+
+namespace mozilla::gmp {
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPServiceChild"
+
+already_AddRefed<GeckoMediaPluginServiceChild>
+GeckoMediaPluginServiceChild::GetSingleton() {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ RefPtr<GeckoMediaPluginService> service(
+ GeckoMediaPluginService::GetGeckoMediaPluginService());
+#ifdef DEBUG
+ if (service) {
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
+ CallQueryInterface(service.get(), getter_AddRefs(chromeService));
+ MOZ_ASSERT(!chromeService);
+ }
+#endif
+ return service.forget().downcast<GeckoMediaPluginServiceChild>();
+}
+
+nsresult GeckoMediaPluginServiceChild::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+
+ nsresult rv = AddShutdownBlocker();
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We expect xpcom to be live when calling this, so we should be able to "
+ "add a blocker");
+ GMP_LOG_DEBUG("%s::%s failed to add shutdown blocker!", __CLASS__,
+ __FUNCTION__);
+ return rv;
+ }
+
+ return GeckoMediaPluginService::Init();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceChild,
+ GeckoMediaPluginService, nsIAsyncShutdownBlocker)
+
+// Used to identify blockers that we put in place.
+static const nsLiteralString kShutdownBlockerName =
+ u"GeckoMediaPluginServiceChild: shutdown"_ns;
+
+// nsIAsyncShutdownBlocker members
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetName(nsAString& aName) {
+ aName = kShutdownBlockerName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetState(nsIPropertyBag**) { return NS_OK; }
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::BlockShutdown(nsIAsyncShutdownClient*) {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+
+ mXPCOMWillShutdown = true;
+
+ MutexAutoLock lock(mMutex);
+ Unused << NS_WARN_IF(NS_FAILED(mGMPThread->Dispatch(
+ NewRunnableMethod("GeckoMediaPluginServiceChild::BeginShutdown", this,
+ &GeckoMediaPluginServiceChild::BeginShutdown))));
+ return NS_OK;
+}
+// End nsIAsyncShutdownBlocker members
+
+GeckoMediaPluginServiceChild::~GeckoMediaPluginServiceChild() {
+ MOZ_ASSERT(!mServiceChild);
+}
+
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginServiceChild::GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) {
+ AssertOnGMPThread();
+ MOZ_ASSERT(!mShuttingDownOnGMPThread,
+ "Should not be called if GMPThread is shutting down!");
+
+ MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
+ new MozPromiseHolder<GetGMPContentParentPromise>();
+ RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+
+ nsCString api(aAPI);
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ RefPtr<GeckoMediaPluginServiceChild> self(this);
+
+ mPendingGetContentParents += 1;
+
+ GetServiceChild()->Then(
+ thread, __func__,
+ [nodeIdVariant = aNodeIdVariant, self, api, tags = aTags.Clone(), helper,
+ rawHolder](GMPServiceChild* child) {
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(
+ rawHolder);
+ nsresult rv;
+
+ nsTArray<base::ProcessId> alreadyBridgedTo;
+ child->GetAlreadyBridgedTo(alreadyBridgedTo);
+
+ base::ProcessId otherProcess;
+ nsCString displayName;
+ uint32_t pluginId = 0;
+ ipc::Endpoint<PGMPContentParent> endpoint;
+ nsCString errorDescription;
+
+ bool ok = child->SendLaunchGMP(
+ nodeIdVariant, api, tags, alreadyBridgedTo, &pluginId,
+ &otherProcess, &displayName, &endpoint, &rv, &errorDescription);
+
+ if (helper && pluginId) {
+ // Note: Even if the launch failed, we need to connect the crash
+ // helper so that if the launch failed due to the plugin crashing, we
+ // can report the crash via the crash reporter. The crash handling
+ // notification will arrive shortly if the launch failed due to the
+ // plugin crashing.
+ self->ConnectCrashHelper(pluginId, helper);
+ }
+
+ if (!ok || NS_FAILED(rv)) {
+ MediaResult error(
+ rv, nsPrintfCString(
+ "GeckoMediaPluginServiceChild::GetContentParent "
+ "SendLaunchGMPForNodeId failed with description (%s)",
+ errorDescription.get()));
+
+ GMP_LOG_DEBUG("%s failed to launch GMP with error: %s", __CLASS__,
+ error.Description().get());
+ self->mPendingGetContentParents -= 1;
+ self->RemoveShutdownBlockerIfNeeded();
+
+ holder->Reject(error, __func__);
+ return;
+ }
+
+ RefPtr<GMPContentParent> parent = child->GetBridgedGMPContentParent(
+ otherProcess, std::move(endpoint));
+ if (!alreadyBridgedTo.Contains(otherProcess)) {
+ parent->SetDisplayName(displayName);
+ parent->SetPluginId(pluginId);
+ }
+
+ // The content parent is no longer pending.
+ self->mPendingGetContentParents -= 1;
+ MOZ_ASSERT(child->HaveContentParents(),
+ "We should have at least one content parent!");
+ // We don't check if we need to remove the shutdown blocker here as
+ // we should always have at least one live content parent.
+
+ RefPtr<GMPContentParent::CloseBlocker> blocker(
+ new GMPContentParent::CloseBlocker(parent));
+ holder->Resolve(blocker, __func__);
+ },
+ [self, rawHolder](MediaResult result) {
+ self->mPendingGetContentParents -= 1;
+ self->RemoveShutdownBlockerIfNeeded();
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(
+ rawHolder);
+ holder->Reject(result, __func__);
+ });
+
+ return promise;
+}
+
+typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+typedef mozilla::dom::GMPAPITags GMPAPITags;
+
+struct GMPCapabilityAndVersion {
+ explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities)
+ : mName(aCapabilities.name()), mVersion(aCapabilities.version()) {
+ for (const GMPAPITags& tags : aCapabilities.capabilities()) {
+ GMPCapability cap;
+ cap.mAPIName = tags.api();
+ for (const nsACString& tag : tags.tags()) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ mCapabilities.AppendElement(std::move(cap));
+ }
+ }
+
+ nsCString ToString() const {
+ nsCString s;
+ s.Append(mName);
+ s.AppendLiteral(" version=");
+ s.Append(mVersion);
+ s.AppendLiteral(" tags=[");
+ StringJoinAppend(s, " "_ns, mCapabilities,
+ [](auto& tags, const GMPCapability& cap) {
+ tags.Append(cap.mAPIName);
+ for (const nsACString& tag : cap.mAPITags) {
+ tags.AppendLiteral(":");
+ tags.Append(tag);
+ }
+ });
+ s.AppendLiteral("]");
+ return s;
+ }
+
+ nsCString mName;
+ nsCString mVersion;
+ nsTArray<GMPCapability> mCapabilities;
+};
+
+StaticMutex sGMPCapabilitiesMutex;
+StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
+
+static auto GMPCapabilitiesToString() {
+ return StringJoin(", "_ns, *sGMPCapabilities,
+ [](nsACString& dest, const GMPCapabilityAndVersion& gmp) {
+ dest.Append(gmp.ToString());
+ });
+}
+
+/* static */
+void GeckoMediaPluginServiceChild::UpdateGMPCapabilities(
+ nsTArray<GMPCapabilityData>&& aCapabilities) {
+ {
+ // The mutex should unlock before sending the "gmp-changed" observer
+ // service notification.
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>();
+ ClearOnShutdown(&sGMPCapabilities);
+ }
+ sGMPCapabilities->Clear();
+ for (const GMPCapabilityData& plugin : aCapabilities) {
+ sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin));
+ }
+
+ GMP_LOG_DEBUG("%s::%s {%s}", __CLASS__, __FUNCTION__,
+ GMPCapabilitiesToString().get());
+ }
+
+ // Fire a notification so that any MediaKeySystemAccess
+ // requests waiting on a CDM to download will retry.
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
+ }
+}
+
+void GeckoMediaPluginServiceChild::BeginShutdown() {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%s::%s: mServiceChild=%p,", __CLASS__, __FUNCTION__,
+ mServiceChild.get());
+ // It's possible this gets called twice if the parent sends us a message to
+ // shutdown and we block shutdown in content in close proximity.
+ mShuttingDownOnGMPThread = true;
+ RemoveShutdownBlockerIfNeeded();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool* aHasPlugin) {
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ *aHasPlugin = false;
+ return NS_OK;
+ }
+
+ nsCString api(aAPI);
+ for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) {
+ if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) {
+ *aHasPlugin = true;
+ return NS_OK;
+ }
+ }
+
+ *aHasPlugin = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetNodeId(
+ const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName, UniquePtr<GetNodeIdCallback>&& aCallback) {
+ AssertOnGMPThread();
+
+ GetNodeIdCallback* rawCallback = aCallback.release();
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ nsString origin(aOrigin);
+ nsString topLevelOrigin(aTopLevelOrigin);
+ nsString gmpName(aGMPName);
+ GetServiceChild()->Then(
+ thread, __func__,
+ [rawCallback, origin, topLevelOrigin, gmpName](GMPServiceChild* child) {
+ UniquePtr<GetNodeIdCallback> callback(rawCallback);
+ nsCString outId;
+ if (!child->SendGetGMPNodeId(origin, topLevelOrigin, gmpName, &outId)) {
+ callback->Done(NS_ERROR_FAILURE, ""_ns);
+ return;
+ }
+
+ callback->Done(NS_OK, outId);
+ },
+ [rawCallback](nsresult rv) {
+ UniquePtr<GetNodeIdCallback> callback(rawCallback);
+ callback->Done(NS_ERROR_FAILURE, ""_ns);
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aSomeData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s: aTopic=%s", __CLASS__, __FUNCTION__, aTopic);
+ if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ if (mServiceChild) {
+ MutexAutoLock lock(mMutex);
+ mozilla::SyncRunnable::DispatchToThread(
+ mGMPThread,
+ WrapRunnable(mServiceChild.get(), &PGMPServiceChild::Close));
+ mServiceChild = nullptr;
+ }
+ ShutdownGMPThread();
+ }
+
+ return NS_OK;
+}
+
+RefPtr<GeckoMediaPluginServiceChild::GetServiceChildPromise>
+GeckoMediaPluginServiceChild::GetServiceChild() {
+ AssertOnGMPThread();
+
+ if (!mServiceChild) {
+ if (mShuttingDownOnGMPThread) {
+ // We have begun shutdown. Don't allow a new connection to the main
+ // process to be instantiated. This also prevents new plugins being
+ // instantiated.
+ return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (!contentChild) {
+ return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ MozPromiseHolder<GetServiceChildPromise>* holder =
+ mGetServiceChildPromises.AppendElement();
+ RefPtr<GetServiceChildPromise> promise = holder->Ensure(__func__);
+ if (mGetServiceChildPromises.Length() == 1) {
+ nsCOMPtr<nsIRunnable> r =
+ WrapRunnable(contentChild, &dom::ContentChild::SendCreateGMPService);
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+ return promise;
+ }
+ return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(),
+ __func__);
+}
+
+void GeckoMediaPluginServiceChild::SetServiceChild(
+ RefPtr<GMPServiceChild>&& aServiceChild) {
+ AssertOnGMPThread();
+ MOZ_ASSERT(!mServiceChild, "Should not already have service child!");
+ GMP_LOG_DEBUG("%s::%s: aServiceChild=%p", __CLASS__, __FUNCTION__,
+ aServiceChild.get());
+
+ mServiceChild = std::move(aServiceChild);
+
+ nsTArray<MozPromiseHolder<GetServiceChildPromise>> holders =
+ std::move(mGetServiceChildPromises);
+ for (MozPromiseHolder<GetServiceChildPromise>& holder : holders) {
+ holder.Resolve(mServiceChild.get(), __func__);
+ }
+}
+
+void GeckoMediaPluginServiceChild::RemoveGMPContentParent(
+ GMPContentParent* aGMPContentParent) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG(
+ "%s::%s: aGMPContentParent=%p, mServiceChild=%p, "
+ "mShuttingDownOnGMPThread=%s",
+ __CLASS__, __FUNCTION__, aGMPContentParent, mServiceChild.get(),
+ mShuttingDownOnGMPThread ? "true" : "false");
+
+ if (mServiceChild) {
+ mServiceChild->RemoveGMPContentParent(aGMPContentParent);
+ GMP_LOG_DEBUG(
+ "%s::%s: aGMPContentParent removed, "
+ "mServiceChild->HaveContentParents()=%s",
+ __CLASS__, __FUNCTION__,
+ mServiceChild->HaveContentParents() ? "true" : "false");
+ RemoveShutdownBlockerIfNeeded();
+ }
+}
+
+nsresult GeckoMediaPluginServiceChild::AddShutdownBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mShuttingDownOnGMPThread,
+ "No call paths should add blockers once we're shutting down!");
+#ifdef DEBUG
+ MOZ_ASSERT(!mShutdownBlockerAdded, "Should only add blocker once!");
+ mShutdownBlockerAdded = true;
+#endif
+ GMP_LOG_DEBUG("%s::%s ", __CLASS__, __FUNCTION__);
+
+ nsresult rv = GetShutdownBarrier()->AddBlocker(
+ this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
+ kShutdownBlockerName);
+ return rv;
+}
+
+void GeckoMediaPluginServiceChild::RemoveShutdownBlocker() {
+ AssertOnGMPThread();
+ MOZ_ASSERT(mShuttingDownOnGMPThread,
+ "We should only remove blockers once we're "
+ "shutting down!");
+ GMP_LOG_DEBUG("%s::%s ", __CLASS__, __FUNCTION__);
+ nsresult rv = mMainThread->Dispatch(NS_NewRunnableFunction(
+ "GeckoMediaPluginServiceChild::"
+ "RemoveShutdownBlocker",
+ [this, self = RefPtr<GeckoMediaPluginServiceChild>(this)]() {
+ nsresult rv = GetShutdownBarrier()->RemoveBlocker(this);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Main thread should always be alive when we "
+ "call this!");
+ }
+}
+
+void GeckoMediaPluginServiceChild::RemoveShutdownBlockerIfNeeded() {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG(
+ "%s::%s mPendingGetContentParents=%" PRIu32
+ " mServiceChild->HaveContentParents()=%s "
+ "mShuttingDownOnGMPThread=%s",
+ __CLASS__, __FUNCTION__, mPendingGetContentParents,
+ mServiceChild && mServiceChild->HaveContentParents() ? "true" : "false",
+ mShuttingDownOnGMPThread ? "true" : "false");
+
+ bool haveOneOrMoreContentParents =
+ mPendingGetContentParents > 0 ||
+ (mServiceChild && mServiceChild->HaveContentParents());
+
+ if (!mShuttingDownOnGMPThread || haveOneOrMoreContentParents) {
+ return;
+ }
+ RemoveShutdownBlocker();
+}
+
+already_AddRefed<GMPContentParent> GMPServiceChild::GetBridgedGMPContentParent(
+ ProcessId aOtherPid, ipc::Endpoint<PGMPContentParent>&& endpoint) {
+ return do_AddRef(mContentParents.LookupOrInsertWith(aOtherPid, [&] {
+ MOZ_ASSERT(aOtherPid == endpoint.OtherPid());
+
+ auto parent = MakeRefPtr<GMPContentParent>();
+
+ DebugOnly<bool> ok = endpoint.Bind(parent);
+ MOZ_ASSERT(ok);
+
+ return parent;
+ }));
+}
+
+void GMPServiceChild::RemoveGMPContentParent(
+ GMPContentParent* aGMPContentParent) {
+ for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<GMPContentParent>& parent = iter.Data();
+ if (parent == aGMPContentParent) {
+ iter.Remove();
+ break;
+ }
+ }
+}
+
+void GMPServiceChild::GetAlreadyBridgedTo(
+ nsTArray<base::ProcessId>& aAlreadyBridgedTo) {
+ AppendToArray(aAlreadyBridgedTo, mContentParents.Keys());
+}
+
+class OpenPGMPServiceChild : public mozilla::Runnable {
+ public:
+ OpenPGMPServiceChild(RefPtr<GMPServiceChild>&& aGMPServiceChild,
+ ipc::Endpoint<PGMPServiceChild>&& aEndpoint)
+ : Runnable("gmp::OpenPGMPServiceChild"),
+ mGMPServiceChild(std::move(aGMPServiceChild)),
+ mEndpoint(std::move(aEndpoint)) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+ if (mEndpoint.Bind(mGMPServiceChild.get())) {
+ gmp->SetServiceChild(std::move(mGMPServiceChild));
+ } else {
+ gmp->SetServiceChild(nullptr);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<GMPServiceChild> mGMPServiceChild;
+ ipc::Endpoint<PGMPServiceChild> mEndpoint;
+};
+
+/* static */
+bool GMPServiceChild::Create(Endpoint<PGMPServiceChild>&& aGMPService) {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+
+ RefPtr<GMPServiceChild> serviceChild(new GMPServiceChild());
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = gmpThread->Dispatch(
+ new OpenPGMPServiceChild(std::move(serviceChild), std::move(aGMPService)),
+ NS_DISPATCH_NORMAL);
+ return NS_SUCCEEDED(rv);
+}
+
+ipc::IPCResult GMPServiceChild::RecvBeginShutdown() {
+ RefPtr<GeckoMediaPluginServiceChild> service =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(service && service->mServiceChild.get() == this);
+ if (service) {
+ service->BeginShutdown();
+ }
+ return IPC_OK();
+}
+
+bool GMPServiceChild::HaveContentParents() const {
+ return mContentParents.Count() > 0;
+}
+
+} // namespace mozilla::gmp
+
+#undef __CLASS__