summaryrefslogtreecommitdiffstats
path: root/dom/midi/MIDIAccessManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/midi/MIDIAccessManager.cpp')
-rw-r--r--dom/midi/MIDIAccessManager.cpp176
1 files changed, 176 insertions, 0 deletions
diff --git a/dom/midi/MIDIAccessManager.cpp b/dom/midi/MIDIAccessManager.cpp
new file mode 100644
index 0000000000..c0556f1ffd
--- /dev/null
+++ b/dom/midi/MIDIAccessManager.cpp
@@ -0,0 +1,176 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/MIDIAccessManager.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MIDIAccess.h"
+#include "mozilla/dom/MIDIManagerChild.h"
+#include "mozilla/dom/MIDIPermissionRequest.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/StaticPrefs_midi.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+namespace {
+// Singleton object for MIDIAccessManager
+StaticRefPtr<MIDIAccessManager> gMIDIAccessManager;
+} // namespace
+
+MIDIAccessManager::MIDIAccessManager() : mHasPortList(false), mChild(nullptr) {}
+
+MIDIAccessManager::~MIDIAccessManager() = default;
+
+// static
+MIDIAccessManager* MIDIAccessManager::Get() {
+ if (!gMIDIAccessManager) {
+ gMIDIAccessManager = new MIDIAccessManager();
+ ClearOnShutdown(&gMIDIAccessManager);
+ }
+ return gMIDIAccessManager;
+}
+
+// static
+bool MIDIAccessManager::IsRunning() { return !!gMIDIAccessManager; }
+
+already_AddRefed<Promise> MIDIAccessManager::RequestMIDIAccess(
+ nsPIDOMWindowInner* aWindow, const MIDIOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aWindow);
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ nsCOMPtr<Document> doc = aWindow->GetDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+#ifndef MOZ_WEBMIDI_MIDIR_IMPL
+ if (!StaticPrefs::midi_testing()) {
+ // If we don't have a MIDI implementation and testing is disabled we can't
+ // allow accessing WebMIDI. However we don't want to return something
+ // different from a normal rejection because we don't want websites to use
+ // the error as a way to fingerprint users, so we throw a security error
+ // as if the request had been rejected by the user.
+ aRv.ThrowSecurityError("Access not allowed");
+ return nullptr;
+ }
+#endif
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(doc, u"midi"_ns)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRunnable> permRunnable =
+ new MIDIPermissionRequest(aWindow, p, aOptions);
+ aRv = NS_DispatchToMainThread(permRunnable);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ return p.forget();
+}
+
+bool MIDIAccessManager::AddObserver(Observer<MIDIPortList>* aObserver) {
+ // Add observer before we start the service, otherwise we can end up with
+ // device lists being received before we have observers to send them to.
+ mChangeObservers.AddObserver(aObserver);
+
+ if (!mChild) {
+ StartActor();
+ } else {
+ mChild->SendRefresh();
+ }
+
+ return true;
+}
+
+// Sets up the actor to talk to the parent.
+//
+// We Bootstrap the actor manually rather than using a constructor so that
+// we can bind the parent endpoint to a dedicated task queue.
+void MIDIAccessManager::StartActor() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mChild);
+
+ // Grab PBackground.
+ ::mozilla::ipc::PBackgroundChild* pBackground =
+ BackgroundChild::GetOrCreateForCurrentThread();
+
+ // Create the endpoints and bind the one on the child side.
+ Endpoint<PMIDIManagerParent> parentEndpoint;
+ Endpoint<PMIDIManagerChild> childEndpoint;
+ MOZ_ALWAYS_SUCCEEDS(
+ PMIDIManager::CreateEndpoints(&parentEndpoint, &childEndpoint));
+ mChild = new MIDIManagerChild();
+ MOZ_ALWAYS_TRUE(childEndpoint.Bind(mChild));
+
+ // Kick over to the parent to connect things over there.
+ pBackground->SendCreateMIDIManager(std::move(parentEndpoint));
+}
+
+void MIDIAccessManager::RemoveObserver(Observer<MIDIPortList>* aObserver) {
+ mChangeObservers.RemoveObserver(aObserver);
+ if (mChangeObservers.Length() == 0) {
+ // If we're out of listeners, go ahead and shut down. Make sure to cleanup
+ // the IPDL protocol also.
+ if (mChild) {
+ mChild->Shutdown();
+ mChild = nullptr;
+ }
+ gMIDIAccessManager = nullptr;
+ }
+}
+
+void MIDIAccessManager::SendRefresh() {
+ if (mChild) {
+ mChild->SendRefresh();
+ }
+}
+
+void MIDIAccessManager::CreateMIDIAccess(nsPIDOMWindowInner* aWindow,
+ bool aNeedsSysex, Promise* aPromise) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aPromise);
+ RefPtr<MIDIAccess> a(new MIDIAccess(aWindow, aNeedsSysex, aPromise));
+ if (NS_WARN_IF(!AddObserver(a))) {
+ aPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+ if (!mHasPortList) {
+ // Hold the access object until we get a connected device list.
+ mAccessHolder.AppendElement(a);
+ } else {
+ // If we already have a port list, just send it to the MIDIAccess object now
+ // so it can prepopulate its device list and resolve the promise.
+ a->Notify(mPortList);
+ }
+}
+
+void MIDIAccessManager::Update(const MIDIPortList& aPortList) {
+ mPortList = aPortList;
+ mChangeObservers.Broadcast(aPortList);
+ if (!mHasPortList) {
+ mHasPortList = true;
+ // Now that we've broadcast the already-connected port list, content
+ // should manage the lifetime of the MIDIAccess object, so we can clear the
+ // keep-alive array.
+ mAccessHolder.Clear();
+ }
+}
+
+} // namespace mozilla::dom