summaryrefslogtreecommitdiffstats
path: root/dom/midi/MIDIAccess.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/midi/MIDIAccess.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/dom/midi/MIDIAccess.cpp b/dom/midi/MIDIAccess.cpp
new file mode 100644
index 0000000000..fa8ae514c2
--- /dev/null
+++ b/dom/midi/MIDIAccess.cpp
@@ -0,0 +1,250 @@
+/* -*- 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/MIDIAccess.h"
+#include "mozilla/dom/MIDIAccessManager.h"
+#include "mozilla/dom/MIDIPort.h"
+#include "mozilla/dom/MIDIAccessBinding.h"
+#include "mozilla/dom/MIDIConnectionEvent.h"
+#include "mozilla/dom/MIDIOptionsBinding.h"
+#include "mozilla/dom/MIDIOutputMapBinding.h"
+#include "mozilla/dom/MIDIInputMapBinding.h"
+#include "mozilla/dom/MIDIOutputMap.h"
+#include "mozilla/dom/MIDIInputMap.h"
+#include "mozilla/dom/MIDIOutput.h"
+#include "mozilla/dom/MIDIInput.h"
+#include "mozilla/dom/MIDITypes.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentPermissionHelper.h"
+#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
+#include "ipc/IPCMessageUtils.h"
+#include "MIDILog.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MIDIAccess)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIAccess, DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MIDIAccess,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputMap)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputMap)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessPromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MIDIAccess,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputMap)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputMap)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessPromise)
+ tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIAccess)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MIDIAccess, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MIDIAccess, DOMEventTargetHelper)
+
+MIDIAccess::MIDIAccess(nsPIDOMWindowInner* aWindow, bool aSysexEnabled,
+ Promise* aAccessPromise)
+ : DOMEventTargetHelper(aWindow),
+ mInputMap(new MIDIInputMap(aWindow)),
+ mOutputMap(new MIDIOutputMap(aWindow)),
+ mSysexEnabled(aSysexEnabled),
+ mAccessPromise(aAccessPromise),
+ mHasShutdown(false) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aAccessPromise);
+ KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
+}
+
+MIDIAccess::~MIDIAccess() { Shutdown(); }
+
+void MIDIAccess::Shutdown() {
+ LOG("MIDIAccess::Shutdown");
+ if (mHasShutdown) {
+ return;
+ }
+ mDestructionObservers.Broadcast(void_t());
+ if (MIDIAccessManager::IsRunning()) {
+ MIDIAccessManager::Get()->RemoveObserver(this);
+ }
+ mHasShutdown = true;
+}
+
+void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
+ MOZ_ASSERT(aPort);
+ MIDIConnectionEventInit init;
+ init.mPort = aPort;
+ nsAutoString id;
+ aPort->GetId(id);
+ ErrorResult rv;
+ if (aPort->State() == MIDIPortDeviceState::Disconnected) {
+ if (aPort->Type() == MIDIPortType::Input && mInputMap->Has(id)) {
+ MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, aPort->StableId(),
+ rv);
+ mInputMap->Remove(id);
+ } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
+ MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap,
+ aPort->StableId(), rv);
+ mOutputMap->Remove(id);
+ }
+ // Check to make sure Has()/Delete() calls haven't failed.
+ if (NS_WARN_IF(rv.Failed())) {
+ LOG("Inconsistency during FireConnectionEvent");
+ return;
+ }
+ } else {
+ // If we receive an event from a port that is not in one of our port maps,
+ // this means a port that was disconnected has been reconnected, with the
+ // port owner holding the object during that time, and we should add that
+ // port object to our maps again.
+ if (aPort->Type() == MIDIPortType::Input && !mInputMap->Has(id)) {
+ if (NS_WARN_IF(rv.Failed())) {
+ LOG("Input port not found");
+ return;
+ }
+ MIDIInputMap_Binding::MaplikeHelpers::Set(
+ mInputMap, aPort->StableId(), *(static_cast<MIDIInput*>(aPort)), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ LOG("Map Set failed for input port");
+ return;
+ }
+ mInputMap->Insert(id, aPort);
+ } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
+ if (NS_WARN_IF(rv.Failed())) {
+ LOG("Output port not found");
+ return;
+ }
+ MIDIOutputMap_Binding::MaplikeHelpers::Set(
+ mOutputMap, aPort->StableId(), *(static_cast<MIDIOutput*>(aPort)),
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ LOG("Map set failed for output port");
+ return;
+ }
+ mOutputMap->Insert(id, aPort);
+ }
+ }
+ RefPtr<MIDIConnectionEvent> event =
+ MIDIConnectionEvent::Constructor(this, u"statechange"_ns, init);
+ DispatchTrustedEvent(event);
+}
+
+void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
+ ErrorResult& aRv) {
+ nsAutoString id(aInfo.id());
+ MIDIPortType type = static_cast<MIDIPortType>(aInfo.type());
+ RefPtr<MIDIPort> port;
+ if (type == MIDIPortType::Input) {
+ if (mInputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
+ // We already have the port in our map.
+ return;
+ }
+ port = MIDIInput::Create(GetOwner(), this, aInfo, mSysexEnabled);
+ if (NS_WARN_IF(!port)) {
+ LOG("Couldn't create input port");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ MIDIInputMap_Binding::MaplikeHelpers::Set(
+ mInputMap, port->StableId(), *(static_cast<MIDIInput*>(port.get())),
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ LOG("Coudld't set input port in map");
+ return;
+ }
+ mInputMap->Insert(id, port);
+ } else if (type == MIDIPortType::Output) {
+ if (mOutputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
+ // We already have the port in our map.
+ return;
+ }
+ port = MIDIOutput::Create(GetOwner(), this, aInfo, mSysexEnabled);
+ if (NS_WARN_IF(!port)) {
+ LOG("Couldn't create output port");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ MIDIOutputMap_Binding::MaplikeHelpers::Set(
+ mOutputMap, port->StableId(), *(static_cast<MIDIOutput*>(port.get())),
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ LOG("Coudld't set output port in map");
+ return;
+ }
+ mOutputMap->Insert(id, port);
+ } else {
+ // If we hit this, then we have some port that is neither input nor output.
+ // That is bad.
+ MOZ_CRASH("We shouldn't be here!");
+ }
+ // Set up port to listen for destruction of this access object.
+ mDestructionObservers.AddObserver(port);
+
+ // If we haven't resolved the promise for handing the MIDIAccess object to
+ // content, this means we're still populating the list of already connected
+ // devices. Don't fire events yet.
+ if (!mAccessPromise) {
+ FireConnectionEvent(port);
+ }
+}
+
+// For the MIDIAccess object, only worry about new connections, where we create
+// MIDIPort objects. When a port is removed and the MIDIPortRemove event is
+// received, that will be handled by the MIDIPort object itself, and it will
+// request removal from MIDIAccess's maps.
+void MIDIAccess::Notify(const MIDIPortList& aEvent) {
+ LOG("MIDIAcess::Notify");
+ if (!GetOwner()) {
+ // Do nothing if we've already been disconnected from the document.
+ return;
+ }
+
+ for (const auto& port : aEvent.ports()) {
+ // Something went very wrong. Warn and return.
+ ErrorResult rv;
+ MaybeCreateMIDIPort(port, rv);
+ if (rv.Failed()) {
+ if (!mAccessPromise) {
+ // We can't reject the promise so let's suppress the error instead
+ rv.SuppressException();
+ return;
+ }
+ mAccessPromise->MaybeReject(std::move(rv));
+ mAccessPromise = nullptr;
+ }
+ }
+ if (!mAccessPromise) {
+ return;
+ }
+ mAccessPromise->MaybeResolve(this);
+ mAccessPromise = nullptr;
+}
+
+JSObject* MIDIAccess::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MIDIAccess_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MIDIAccess::RemovePortListener(MIDIAccessDestructionObserver* aObs) {
+ mDestructionObservers.RemoveObserver(aObs);
+}
+
+void MIDIAccess::DisconnectFromOwner() {
+ IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
+
+ DOMEventTargetHelper::DisconnectFromOwner();
+ MIDIAccessManager::Get()->SendRefresh();
+}
+
+} // namespace mozilla::dom