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
&& mInputMap
->Has(id
)) {
93 MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap
, aPort
->StableId(),
95 mInputMap
->Remove(id
);
96 } else if (aPort
->Type() == MIDIPortType::Output
&& mOutputMap
->Has(id
)) {
97 MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap
,
98 aPort
->StableId(), rv
);
99 mOutputMap
->Remove(id
);
101 // Check to make sure Has()/Delete() calls haven't failed.
102 if (NS_WARN_IF(rv
.Failed())) {
103 LOG("Inconsistency during FireConnectionEvent");
107 // If we receive an event from a port that is not in one of our port maps,
108 // this means a port that was disconnected has been reconnected, with the
109 // port owner holding the object during that time, and we should add that
110 // port object to our maps again.
111 if (aPort
->Type() == MIDIPortType::Input
&& !mInputMap
->Has(id
)) {
112 if (NS_WARN_IF(rv
.Failed())) {
113 LOG("Input port not found");
116 MIDIInputMap_Binding::MaplikeHelpers::Set(
117 mInputMap
, aPort
->StableId(), *(static_cast<MIDIInput
*>(aPort
)), rv
);
118 if (NS_WARN_IF(rv
.Failed())) {
119 LOG("Map Set failed for input port");
122 mInputMap
->Insert(id
, aPort
);
123 } else if (aPort
->Type() == MIDIPortType::Output
&& mOutputMap
->Has(id
)) {
124 if (NS_WARN_IF(rv
.Failed())) {
125 LOG("Output port not found");
128 MIDIOutputMap_Binding::MaplikeHelpers::Set(
129 mOutputMap
, aPort
->StableId(), *(static_cast<MIDIOutput
*>(aPort
)),
131 if (NS_WARN_IF(rv
.Failed())) {
132 LOG("Map set failed for output port");
135 mOutputMap
->Insert(id
, aPort
);
138 RefPtr
<MIDIConnectionEvent
> event
=
139 MIDIConnectionEvent::Constructor(this, u
"statechange"_ns
, init
);
140 DispatchTrustedEvent(event
);
143 void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo
& aInfo
,
145 nsAutoString
id(aInfo
.id());
146 MIDIPortType type
= static_cast<MIDIPortType
>(aInfo
.type());
147 RefPtr
<MIDIPort
> port
;
148 if (type
== MIDIPortType::Input
) {
149 if (mInputMap
->Has(id
) || 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
, port
->StableId(), *(static_cast<MIDIInput
*>(port
.get())),
162 if (NS_WARN_IF(aRv
.Failed())) {
163 LOG("Coudld't set input port in map");
166 mInputMap
->Insert(id
, port
);
167 } else if (type
== MIDIPortType::Output
) {
168 if (mOutputMap
->Has(id
) || 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
, port
->StableId(), *(static_cast<MIDIOutput
*>(port
.get())),
181 if (NS_WARN_IF(aRv
.Failed())) {
182 LOG("Coudld't set output port in map");
185 mOutputMap
->Insert(id
, port
);
187 // If we hit this, then we have some port that is neither input nor output.
189 MOZ_CRASH("We shouldn't be here!");
191 // Set up port to listen for destruction of this access object.
192 mDestructionObservers
.AddObserver(port
);
194 // If we haven't resolved the promise for handing the MIDIAccess object to
195 // content, this means we're still populating the list of already connected
196 // devices. Don't fire events yet.
197 if (!mAccessPromise
) {
198 FireConnectionEvent(port
);
202 // For the MIDIAccess object, only worry about new connections, where we create
203 // MIDIPort objects. When a port is removed and the MIDIPortRemove event is
204 // received, that will be handled by the MIDIPort object itself, and it will
205 // request removal from MIDIAccess's maps.
206 void MIDIAccess::Notify(const MIDIPortList
& aEvent
) {
207 LOG("MIDIAcess::Notify");
209 // Do nothing if we've already been disconnected from the document.
213 for (const auto& port
: aEvent
.ports()) {
214 // Something went very wrong. Warn and return.
216 MaybeCreateMIDIPort(port
, rv
);
218 if (!mAccessPromise
) {
219 // We can't reject the promise so let's suppress the error instead
220 rv
.SuppressException();
223 mAccessPromise
->MaybeReject(std::move(rv
));
224 mAccessPromise
= nullptr;
227 if (!mAccessPromise
) {
230 mAccessPromise
->MaybeResolve(this);
231 mAccessPromise
= nullptr;
234 JSObject
* MIDIAccess::WrapObject(JSContext
* aCx
,
235 JS::Handle
<JSObject
*> aGivenProto
) {
236 return MIDIAccess_Binding::Wrap(aCx
, this, aGivenProto
);
239 void MIDIAccess::RemovePortListener(MIDIAccessDestructionObserver
* aObs
) {
240 mDestructionObservers
.RemoveObserver(aObs
);
243 void MIDIAccess::DisconnectFromOwner() {
244 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange
);
246 DOMEventTargetHelper::DisconnectFromOwner();
247 MIDIAccessManager::Get()->SendRefresh();
250 } // namespace mozilla::dom