1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/MIDIAccess.h"
8 #include "mozilla/dom/MIDIAccessManager.h"
9 #include "mozilla/dom/MIDIPort.h"
10 #include "mozilla/dom/MIDIAccessBinding.h"
11 #include "mozilla/dom/MIDIConnectionEvent.h"
12 #include "mozilla/dom/MIDIOptionsBinding.h"
13 #include "mozilla/dom/MIDIOutputMapBinding.h"
14 #include "mozilla/dom/MIDIInputMapBinding.h"
15 #include "mozilla/dom/MIDIOutputMap.h"
16 #include "mozilla/dom/MIDIInputMap.h"
17 #include "mozilla/dom/MIDIOutput.h"
18 #include "mozilla/dom/MIDIInput.h"
19 #include "mozilla/dom/MIDITypes.h"
20 #include "mozilla/dom/Promise.h"
21 #include "mozilla/dom/PContent.h"
22 #include "mozilla/dom/Document.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsContentPermissionHelper.h"
25 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
26 #include "ipc/IPCMessageUtils.h"
29 namespace mozilla::dom
{
31 NS_IMPL_CYCLE_COLLECTION_CLASS(MIDIAccess
)
32 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIAccess
, DOMEventTargetHelper
)
33 NS_IMPL_CYCLE_COLLECTION_TRACE_END
35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MIDIAccess
,
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputMap
)
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputMap
)
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessPromise
)
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
42 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MIDIAccess
,
44 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputMap
)
45 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputMap
)
46 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessPromise
)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
50 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIAccess
)
51 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
52 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
54 NS_IMPL_ADDREF_INHERITED(MIDIAccess
, DOMEventTargetHelper
)
55 NS_IMPL_RELEASE_INHERITED(MIDIAccess
, DOMEventTargetHelper
)
57 MIDIAccess::MIDIAccess(nsPIDOMWindowInner
* aWindow
, bool aSysexEnabled
,
58 Promise
* aAccessPromise
)
59 : DOMEventTargetHelper(aWindow
),
60 mInputMap(new MIDIInputMap(aWindow
)),
61 mOutputMap(new MIDIOutputMap(aWindow
)),
62 mSysexEnabled(aSysexEnabled
),
63 mAccessPromise(aAccessPromise
),
66 MOZ_ASSERT(aAccessPromise
);
67 KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange
);
70 MIDIAccess::~MIDIAccess() { Shutdown(); }
72 void MIDIAccess::Shutdown() {
73 LOG("MIDIAccess::Shutdown");
77 mDestructionObservers
.Broadcast(void_t());
78 if (MIDIAccessManager::IsRunning()) {
79 MIDIAccessManager::Get()->RemoveObserver(this);
84 void MIDIAccess::FireConnectionEvent(MIDIPort
* aPort
) {
86 MIDIConnectionEventInit init
;
91 if (aPort
->State() == MIDIPortDeviceState::Disconnected
) {
92 if (aPort
->Type() == MIDIPortType::Input
&&
93 MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap
, id
, rv
)) {
94 MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap
, id
, rv
);
95 } else if (aPort
->Type() == MIDIPortType::Output
&&
96 MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap
, id
, rv
)) {
97 MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap
, id
, rv
);
99 // Check to make sure Has()/Delete() calls haven't failed.
100 if (NS_WARN_IF(rv
.Failed())) {
101 LOG("Inconsistency during FireConnectionEvent");
105 // If we receive an event from a port that is not in one of our port maps,
106 // this means a port that was disconnected has been reconnected, with the
107 // port owner holding the object during that time, and we should add that
108 // port object to our maps again.
109 if (aPort
->Type() == MIDIPortType::Input
&&
110 !MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap
, id
, rv
)) {
111 if (NS_WARN_IF(rv
.Failed())) {
112 LOG("Input port not found");
115 MIDIInputMap_Binding::MaplikeHelpers::Set(
116 mInputMap
, id
, *(static_cast<MIDIInput
*>(aPort
)), rv
);
117 if (NS_WARN_IF(rv
.Failed())) {
118 LOG("Map Set failed for input port");
121 } else if (aPort
->Type() == MIDIPortType::Output
&&
122 !MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap
, id
,
124 if (NS_WARN_IF(rv
.Failed())) {
125 LOG("Output port not found");
128 MIDIOutputMap_Binding::MaplikeHelpers::Set(
129 mOutputMap
, id
, *(static_cast<MIDIOutput
*>(aPort
)), rv
);
130 if (NS_WARN_IF(rv
.Failed())) {
131 LOG("Map set failed for output port");
136 RefPtr
<MIDIConnectionEvent
> event
=
137 MIDIConnectionEvent::Constructor(this, u
"statechange"_ns
, init
);
138 DispatchTrustedEvent(event
);
141 void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo
& aInfo
,
143 nsAutoString
id(aInfo
.id());
144 MIDIPortType type
= static_cast<MIDIPortType
>(aInfo
.type());
145 RefPtr
<MIDIPort
> port
;
146 if (type
== MIDIPortType::Input
) {
148 MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap
, id
, aRv
);
149 if (hasPort
|| NS_WARN_IF(aRv
.Failed())) {
150 // We already have the port in our map.
153 port
= MIDIInput::Create(GetOwner(), this, aInfo
, mSysexEnabled
);
154 if (NS_WARN_IF(!port
)) {
155 LOG("Couldn't create input port");
156 aRv
.Throw(NS_ERROR_FAILURE
);
159 MIDIInputMap_Binding::MaplikeHelpers::Set(
160 mInputMap
, id
, *(static_cast<MIDIInput
*>(port
.get())), aRv
);
161 if (NS_WARN_IF(aRv
.Failed())) {
162 LOG("Coudld't set input port in map");
165 } else if (type
== MIDIPortType::Output
) {
167 MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap
, id
, aRv
);
168 if (hasPort
|| NS_WARN_IF(aRv
.Failed())) {
169 // We already have the port in our map.
172 port
= MIDIOutput::Create(GetOwner(), this, aInfo
, mSysexEnabled
);
173 if (NS_WARN_IF(!port
)) {
174 LOG("Couldn't create output port");
175 aRv
.Throw(NS_ERROR_FAILURE
);
178 MIDIOutputMap_Binding::MaplikeHelpers::Set(
179 mOutputMap
, id
, *(static_cast<MIDIOutput
*>(port
.get())), aRv
);
180 if (NS_WARN_IF(aRv
.Failed())) {
181 LOG("Coudld't set output port in map");
185 // If we hit this, then we have some port that is neither input nor output.
187 MOZ_CRASH("We shouldn't be here!");
189 // Set up port to listen for destruction of this access object.
190 mDestructionObservers
.AddObserver(port
);
192 // If we haven't resolved the promise for handing the MIDIAccess object to
193 // content, this means we're still populating the list of already connected
194 // devices. Don't fire events yet.
195 if (!mAccessPromise
) {
196 FireConnectionEvent(port
);
200 // For the MIDIAccess object, only worry about new connections, where we create
201 // MIDIPort objects. When a port is removed and the MIDIPortRemove event is
202 // received, that will be handled by the MIDIPort object itself, and it will
203 // request removal from MIDIAccess's maps.
204 void MIDIAccess::Notify(const MIDIPortList
& aEvent
) {
205 LOG("MIDIAcess::Notify");
206 for (const auto& port
: aEvent
.ports()) {
207 // Something went very wrong. Warn and return.
209 MaybeCreateMIDIPort(port
, rv
);
211 if (!mAccessPromise
) {
214 mAccessPromise
->MaybeReject(std::move(rv
));
215 mAccessPromise
= nullptr;
218 if (!mAccessPromise
) {
221 mAccessPromise
->MaybeResolve(this);
222 mAccessPromise
= nullptr;
225 JSObject
* MIDIAccess::WrapObject(JSContext
* aCx
,
226 JS::Handle
<JSObject
*> aGivenProto
) {
227 return MIDIAccess_Binding::Wrap(aCx
, this, aGivenProto
);
230 void MIDIAccess::RemovePortListener(MIDIAccessDestructionObserver
* aObs
) {
231 mDestructionObservers
.RemoveObserver(aObs
);
234 void MIDIAccess::DisconnectFromOwner() {
235 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange
);
237 DOMEventTargetHelper::DisconnectFromOwner();
240 } // namespace mozilla::dom