diff options
Diffstat (limited to '')
-rw-r--r-- | dom/midi/MIDIAccess.cpp | 250 |
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 |