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 "MIDIPlatformService.h"
8 #include "MIDIMessageQueue.h"
9 #include "TestMIDIPlatformService.h"
10 #ifdef MOZ_WEBMIDI_MIDIR_IMPL
11 # include "midirMIDIPlatformService.h"
12 #endif // MOZ_WEBMIDI_MIDIR_IMPL
13 #include "mozilla/ErrorResult.h"
14 #include "mozilla/StaticPrefs_midi.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/Unused.h"
17 #include "mozilla/dom/MIDIManagerParent.h"
18 #include "mozilla/dom/MIDIPlatformRunnables.h"
19 #include "mozilla/dom/MIDIUtils.h"
20 #include "mozilla/dom/PMIDIManagerParent.h"
21 #include "mozilla/ipc/BackgroundParent.h"
22 #include "mozilla/dom/MIDIPortParent.h"
24 using namespace mozilla
;
25 using namespace mozilla::dom
;
27 MIDIPlatformService::MIDIPlatformService()
28 : mHasSentPortList(false),
29 mMessageQueueMutex("MIDIPlatformServce::mMessageQueueMutex") {}
31 MIDIPlatformService::~MIDIPlatformService() = default;
33 void MIDIPlatformService::CheckAndReceive(const nsAString
& aPortId
,
34 const nsTArray
<MIDIMessage
>& aMsgs
) {
36 for (auto& port
: mPorts
) {
37 // TODO Clean this up when we split input/output port arrays
38 if (port
->MIDIPortInterface::Id() != aPortId
||
39 port
->Type() != MIDIPortType::Input
||
40 port
->ConnectionState() != MIDIPortConnectionState::Open
) {
43 if (!port
->SysexEnabled()) {
44 nsTArray
<MIDIMessage
> msgs
;
45 for (const auto& msg
: aMsgs
) {
46 if (!MIDIUtils::IsSysexMessage(msg
)) {
47 msgs
.AppendElement(msg
);
50 Unused
<< port
->SendReceive(msgs
);
52 Unused
<< port
->SendReceive(aMsgs
);
57 void MIDIPlatformService::AddPort(MIDIPortParent
* aPort
) {
60 mPorts
.AppendElement(aPort
);
63 void MIDIPlatformService::RemovePort(MIDIPortParent
* aPort
) {
64 // This should only be called from the background thread, when a MIDIPort
65 // actor has been destroyed.
68 mPorts
.RemoveElement(aPort
);
72 void MIDIPlatformService::BroadcastState(const MIDIPortInfo
& aPortInfo
,
73 const MIDIPortDeviceState
& aState
) {
75 for (auto& p
: mPorts
) {
76 if (p
->MIDIPortInterface::Id() == aPortInfo
.id() &&
77 p
->DeviceState() != aState
) {
78 p
->SendUpdateStatus(aState
, p
->ConnectionState());
83 void MIDIPlatformService::QueueMessages(const nsAString
& aId
,
84 nsTArray
<MIDIMessage
>& aMsgs
) {
87 MutexAutoLock
lock(mMessageQueueMutex
);
88 MIDIMessageQueue
* msgQueue
= mMessageQueues
.GetOrInsertNew(aId
);
95 void MIDIPlatformService::SendPortList() {
97 mHasSentPortList
= true;
99 for (auto& el
: mPortInfo
) {
100 l
.ports().AppendElement(el
);
102 for (auto& mgr
: mManagers
) {
103 Unused
<< mgr
->SendMIDIPortListUpdate(l
);
107 void MIDIPlatformService::Clear(MIDIPortParent
* aPort
) {
111 MutexAutoLock
lock(mMessageQueueMutex
);
112 MIDIMessageQueue
* msgQueue
=
113 mMessageQueues
.Get(aPort
->MIDIPortInterface::Id());
120 void MIDIPlatformService::AddPortInfo(MIDIPortInfo
& aPortInfo
) {
122 MOZ_ASSERT(XRE_IsParentProcess());
124 mPortInfo
.AppendElement(aPortInfo
);
126 // ORDER MATTERS HERE.
128 // When MIDI hardware is disconnected, all open MIDIPort objects revert to a
129 // "pending" state, and they are removed from the port maps of MIDIAccess
130 // objects. We need to send connection updates to all living ports first, THEN
131 // we can send port list updates to all of the live MIDIAccess objects. We
132 // have to go in this order because if a port object is still held live but is
133 // disconnected, it needs to readd itself to its originating MIDIAccess
134 // object. Running SendPortList first would cause MIDIAccess to create a new
135 // MIDIPort object, which would conflict (i.e. old disconnected object != new
136 // object in port map, which is against spec).
137 for (auto& port
: mPorts
) {
138 if (port
->MIDIPortInterface::Id() == aPortInfo
.id()) {
139 port
->SendUpdateStatus(MIDIPortDeviceState::Connected
,
140 port
->ConnectionState());
143 if (mHasSentPortList
) {
148 void MIDIPlatformService::RemovePortInfo(MIDIPortInfo
& aPortInfo
) {
150 mPortInfo
.RemoveElement(aPortInfo
);
151 BroadcastState(aPortInfo
, MIDIPortDeviceState::Disconnected
);
152 if (mHasSentPortList
) {
157 StaticRefPtr
<nsISerialEventTarget
> gMIDITaskQueue
;
160 void MIDIPlatformService::InitStatics() {
161 nsCOMPtr
<nsISerialEventTarget
> queue
;
163 NS_CreateBackgroundTaskQueue("MIDITaskQueue", getter_AddRefs(queue
)));
164 gMIDITaskQueue
= queue
.forget();
165 ClearOnShutdown(&gMIDITaskQueue
);
169 nsISerialEventTarget
* MIDIPlatformService::OwnerThread() {
170 return gMIDITaskQueue
;
173 StaticRefPtr
<MIDIPlatformService
> gMIDIPlatformService
;
176 bool MIDIPlatformService::IsRunning() {
177 return gMIDIPlatformService
!= nullptr;
180 void MIDIPlatformService::Close(mozilla::dom::MIDIPortParent
* aPort
) {
183 MutexAutoLock
lock(mMessageQueueMutex
);
184 MIDIMessageQueue
* msgQueue
=
185 mMessageQueues
.Get(aPort
->MIDIPortInterface::Id());
187 msgQueue
->ClearAfterNow();
190 // Send all messages before sending a close request
191 ScheduleSend(aPort
->MIDIPortInterface::Id());
192 // TODO We should probably have the send function schedule closing
193 ScheduleClose(aPort
);
197 MIDIPlatformService
* MIDIPlatformService::Get() {
198 // We should never touch the platform service in a child process.
199 MOZ_ASSERT(XRE_IsParentProcess());
202 if (StaticPrefs::midi_testing()) {
203 gMIDIPlatformService
= new TestMIDIPlatformService();
205 #ifdef MOZ_WEBMIDI_MIDIR_IMPL
207 gMIDIPlatformService
= new midirMIDIPlatformService();
209 #endif // MOZ_WEBMIDI_MIDIR_IMPL
210 gMIDIPlatformService
->Init();
212 return gMIDIPlatformService
;
215 void MIDIPlatformService::MaybeStop() {
218 // Service already stopped or never started. Exit.
221 // If we have any ports or managers left, we should still be alive.
222 if (!mPorts
.IsEmpty() || !mManagers
.IsEmpty()) {
226 gMIDIPlatformService
= nullptr;
229 void MIDIPlatformService::AddManager(MIDIManagerParent
* aManager
) {
231 mManagers
.AppendElement(aManager
);
232 // Managers add themselves during construction. We have to wait for the
233 // protocol construction to finish before we send them a port list. The
234 // runnable calls SendPortList, which iterates through the live manager list,
235 // so this saves us from having to worry about Manager pointer validity at
236 // time of runnable execution.
237 nsCOMPtr
<nsIRunnable
> r(new SendPortListRunnable());
238 OwnerThread()->Dispatch(r
.forget());
241 void MIDIPlatformService::RemoveManager(MIDIManagerParent
* aManager
) {
243 mManagers
.RemoveElement(aManager
);
247 void MIDIPlatformService::UpdateStatus(
248 MIDIPortParent
* aPort
, const MIDIPortDeviceState
& aDeviceState
,
249 const MIDIPortConnectionState
& aConnectionState
) {
251 aPort
->SendUpdateStatus(aDeviceState
, aConnectionState
);
254 void MIDIPlatformService::GetMessages(const nsAString
& aPortId
,
255 nsTArray
<MIDIMessage
>& aMsgs
) {
256 // Can run on either background thread or platform specific IO Thread.
258 MutexAutoLock
lock(mMessageQueueMutex
);
259 MIDIMessageQueue
* msgQueue
;
260 if (!mMessageQueues
.Get(aPortId
, &msgQueue
)) {
263 msgQueue
->GetMessages(aMsgs
);
267 void MIDIPlatformService::GetMessagesBefore(const nsAString
& aPortId
,
268 const TimeStamp
& aTimeStamp
,
269 nsTArray
<MIDIMessage
>& aMsgs
) {
270 // Can run on either background thread or platform specific IO Thread.
272 MutexAutoLock
lock(mMessageQueueMutex
);
273 MIDIMessageQueue
* msgQueue
;
274 if (!mMessageQueues
.Get(aPortId
, &msgQueue
)) {
277 msgQueue
->GetMessagesBefore(aTimeStamp
, aMsgs
);