/src/mozilla-central/dom/midi/MIDIPlatformService.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "MIDIPlatformService.h" |
8 | | #include "TestMIDIPlatformService.h" |
9 | | #include "mozilla/Unused.h" |
10 | | #include "mozilla/dom/PMIDIManagerParent.h" |
11 | | #include "mozilla/ipc/BackgroundParent.h" |
12 | | #include "mozilla/dom/MIDIPortParent.h" |
13 | | |
14 | | using namespace mozilla; |
15 | | using namespace mozilla::dom; |
16 | | |
17 | | |
18 | | MIDIPlatformService::MIDIPlatformService() : |
19 | | mHasSentPortList(false), |
20 | | mMessageQueueMutex("MIDIPlatformServce::mMessageQueueMutex") |
21 | 0 | { |
22 | 0 | } |
23 | | |
24 | | MIDIPlatformService::~MIDIPlatformService() |
25 | 0 | { |
26 | 0 | } |
27 | | |
28 | | void |
29 | | MIDIPlatformService::CheckAndReceive(const nsAString& aPortId, |
30 | | const nsTArray<MIDIMessage>& aMsgs) |
31 | 0 | { |
32 | 0 | AssertIsOnBackgroundThread(); |
33 | 0 | for (auto& port : mPorts) { |
34 | 0 | // TODO Clean this up when we split input/output port arrays |
35 | 0 | if (port->MIDIPortInterface::Id() != aPortId || |
36 | 0 | port->Type() != MIDIPortType::Input || |
37 | 0 | port->ConnectionState() != MIDIPortConnectionState::Open) { |
38 | 0 | continue; |
39 | 0 | } |
40 | 0 | if (!port->SysexEnabled()) { |
41 | 0 | nsTArray<MIDIMessage> msgs; |
42 | 0 | for (auto& msg : aMsgs) { |
43 | 0 | if (!MIDIUtils::IsSysexMessage(msg)) { |
44 | 0 | msgs.AppendElement(msg); |
45 | 0 | } |
46 | 0 | } |
47 | 0 | Unused << port->SendReceive(msgs); |
48 | 0 | } else { |
49 | 0 | Unused << port->SendReceive(aMsgs); |
50 | 0 | } |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | | void |
55 | | MIDIPlatformService::AddPort(MIDIPortParent* aPort) |
56 | 0 | { |
57 | 0 | MOZ_ASSERT(aPort); |
58 | 0 | AssertIsOnBackgroundThread(); |
59 | 0 | mPorts.AppendElement(aPort); |
60 | 0 | } |
61 | | |
62 | | void |
63 | | MIDIPlatformService::RemovePort(MIDIPortParent* aPort) |
64 | 0 | { |
65 | 0 | // This should only be called from the background thread, when a MIDIPort |
66 | 0 | // actor has been destroyed. |
67 | 0 | AssertIsOnBackgroundThread(); |
68 | 0 | MOZ_ASSERT(aPort); |
69 | 0 | mPorts.RemoveElement(aPort); |
70 | 0 | MaybeStop(); |
71 | 0 | } |
72 | | |
73 | | void |
74 | | MIDIPlatformService::BroadcastState(const MIDIPortInfo& aPortInfo, |
75 | | const MIDIPortDeviceState& aState) |
76 | 0 | { |
77 | 0 | AssertIsOnBackgroundThread(); |
78 | 0 | for (auto& p : mPorts) { |
79 | 0 | if (p->MIDIPortInterface::Id() == aPortInfo.id() && |
80 | 0 | p->DeviceState() != aState) { |
81 | 0 | p->SendUpdateStatus(aState, p->ConnectionState()); |
82 | 0 | } |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | | void |
87 | | MIDIPlatformService::QueueMessages(const nsAString& aId, nsTArray<MIDIMessage>& aMsgs) |
88 | 0 | { |
89 | 0 | AssertIsOnBackgroundThread(); |
90 | 0 | { |
91 | 0 | MutexAutoLock lock(mMessageQueueMutex); |
92 | 0 | MIDIMessageQueue* msgQueue = mMessageQueues.LookupOrAdd(aId); |
93 | 0 | msgQueue->Add(aMsgs); |
94 | 0 | ScheduleSend(aId); |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | void |
99 | | MIDIPlatformService::SendPortList() |
100 | 0 | { |
101 | 0 | AssertIsOnBackgroundThread(); |
102 | 0 | mHasSentPortList = true; |
103 | 0 | MIDIPortList l; |
104 | 0 | for (auto& el : mPortInfo) { |
105 | 0 | l.ports().AppendElement(el); |
106 | 0 | } |
107 | 0 | for (auto& mgr : mManagers) { |
108 | 0 | Unused << mgr->SendMIDIPortListUpdate(l); |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | | void |
113 | | MIDIPlatformService::Clear(MIDIPortParent* aPort) |
114 | 0 | { |
115 | 0 | AssertIsOnBackgroundThread(); |
116 | 0 | MOZ_ASSERT(aPort); |
117 | 0 | { |
118 | 0 | MutexAutoLock lock(mMessageQueueMutex); |
119 | 0 | MIDIMessageQueue* msgQueue = mMessageQueues.Get(aPort->MIDIPortInterface::Id()); |
120 | 0 | if (msgQueue) { |
121 | 0 | msgQueue->Clear(); |
122 | 0 | } |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | | void |
127 | | MIDIPlatformService::AddPortInfo(MIDIPortInfo& aPortInfo) |
128 | 0 | { |
129 | 0 | AssertIsOnBackgroundThread(); |
130 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
131 | 0 |
|
132 | 0 | mPortInfo.AppendElement(aPortInfo); |
133 | 0 |
|
134 | 0 | // ORDER MATTERS HERE. |
135 | 0 | // |
136 | 0 | // When MIDI hardware is disconnected, all open MIDIPort objects revert to a |
137 | 0 | // "pending" state, and they are removed from the port maps of MIDIAccess |
138 | 0 | // objects. We need to send connection updates to all living ports first, THEN |
139 | 0 | // we can send port list updates to all of the live MIDIAccess objects. We |
140 | 0 | // have to go in this order because if a port object is still held live but is |
141 | 0 | // disconnected, it needs to readd itself to its originating MIDIAccess object. |
142 | 0 | // Running SendPortList first would cause MIDIAccess to create a new MIDIPort |
143 | 0 | // object, which would conflict (i.e. old disconnected object != new object in |
144 | 0 | // port map, which is against spec). |
145 | 0 | for (auto& port : mPorts) { |
146 | 0 | if (port->MIDIPortInterface::Id() == aPortInfo.id()) { |
147 | 0 | port->SendUpdateStatus(MIDIPortDeviceState::Connected, |
148 | 0 | port->ConnectionState()); |
149 | 0 | } |
150 | 0 | } |
151 | 0 | if (mHasSentPortList) { |
152 | 0 | SendPortList(); |
153 | 0 | } |
154 | 0 | } |
155 | | |
156 | | void |
157 | | MIDIPlatformService::RemovePortInfo(MIDIPortInfo& aPortInfo) |
158 | 0 | { |
159 | 0 | AssertIsOnBackgroundThread(); |
160 | 0 | mPortInfo.RemoveElement(aPortInfo); |
161 | 0 | BroadcastState(aPortInfo, MIDIPortDeviceState::Disconnected); |
162 | 0 | if (mHasSentPortList) { |
163 | 0 | SendPortList(); |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | | StaticRefPtr<MIDIPlatformService> gMIDIPlatformService; |
168 | | |
169 | | //static |
170 | | bool |
171 | | MIDIPlatformService::IsRunning() |
172 | 0 | { |
173 | 0 | return gMIDIPlatformService != nullptr; |
174 | 0 | } |
175 | | |
176 | | void |
177 | | MIDIPlatformService::Close(mozilla::dom::MIDIPortParent *aPort) |
178 | 0 | { |
179 | 0 | AssertIsOnBackgroundThread(); |
180 | 0 | { |
181 | 0 | MutexAutoLock lock(mMessageQueueMutex); |
182 | 0 | MIDIMessageQueue* msgQueue = mMessageQueues.Get(aPort->MIDIPortInterface::Id()); |
183 | 0 | if (msgQueue) { |
184 | 0 | msgQueue->ClearAfterNow(); |
185 | 0 | } |
186 | 0 | } |
187 | 0 | // Send all messages before sending a close request |
188 | 0 | ScheduleSend(aPort->MIDIPortInterface::Id()); |
189 | 0 | // TODO We should probably have the send function schedule closing |
190 | 0 | ScheduleClose(aPort); |
191 | 0 | } |
192 | | |
193 | | //static |
194 | | MIDIPlatformService* |
195 | | MIDIPlatformService::Get() |
196 | 0 | { |
197 | 0 | // We should never touch the platform service in a child process. |
198 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
199 | 0 | AssertIsOnBackgroundThread(); |
200 | 0 | if (!IsRunning()) { |
201 | 0 | ErrorResult rv; |
202 | 0 | // Uncomment once we have an actual platform library to test. |
203 | 0 | // |
204 | 0 | // bool useTestService = false; |
205 | 0 | // rv = Preferences::GetRootBranch()->GetBoolPref("midi.testing", &useTestService); |
206 | 0 | gMIDIPlatformService = new TestMIDIPlatformService(); |
207 | 0 | gMIDIPlatformService->Init(); |
208 | 0 | } |
209 | 0 | return gMIDIPlatformService; |
210 | 0 | } |
211 | | |
212 | | void |
213 | | MIDIPlatformService::MaybeStop() |
214 | 0 | { |
215 | 0 | AssertIsOnBackgroundThread(); |
216 | 0 | if (!IsRunning()) { |
217 | 0 | // Service already stopped or never started. Exit. |
218 | 0 | return; |
219 | 0 | } |
220 | 0 | // If we have any ports or managers left, we should still be alive. |
221 | 0 | if (!mPorts.IsEmpty() || |
222 | 0 | !mManagers.IsEmpty()) { |
223 | 0 | return; |
224 | 0 | } |
225 | 0 | Stop(); |
226 | 0 | gMIDIPlatformService = nullptr; |
227 | 0 | } |
228 | | |
229 | | void |
230 | | MIDIPlatformService::AddManager(MIDIManagerParent* aManager) |
231 | 0 | { |
232 | 0 | AssertIsOnBackgroundThread(); |
233 | 0 | mManagers.AppendElement(aManager); |
234 | 0 | // Managers add themselves during construction. We have to wait for the protocol |
235 | 0 | // construction to finish before we send them a port list. The runnable calls |
236 | 0 | // SendPortList, which iterates through the live manager list, so this saves |
237 | 0 | // us from having to worry about Manager pointer validity at time of runnable |
238 | 0 | // execution. |
239 | 0 | nsCOMPtr<nsIRunnable> r(new SendPortListRunnable()); |
240 | 0 | NS_DispatchToCurrentThread(r); |
241 | 0 | } |
242 | | |
243 | | void |
244 | | MIDIPlatformService::RemoveManager(MIDIManagerParent* aManager) |
245 | 0 | { |
246 | 0 | AssertIsOnBackgroundThread(); |
247 | 0 | mManagers.RemoveElement(aManager); |
248 | 0 | MaybeStop(); |
249 | 0 | } |
250 | | |
251 | | void |
252 | | MIDIPlatformService::UpdateStatus(const nsAString& aPortId, |
253 | | const MIDIPortDeviceState& aDeviceState, |
254 | | const MIDIPortConnectionState& aConnectionState) |
255 | 0 | { |
256 | 0 | AssertIsOnBackgroundThread(); |
257 | 0 | for (auto port : mPorts) { |
258 | 0 | if (port->MIDIPortInterface::Id() == aPortId) { |
259 | 0 | port->SendUpdateStatus(aDeviceState, aConnectionState); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | | void |
265 | | MIDIPlatformService::GetMessages(const nsAString& aPortId, |
266 | | nsTArray<MIDIMessage>& aMsgs) |
267 | 0 | { |
268 | 0 | // Can run on either background thread or platform specific IO Thread. |
269 | 0 | { |
270 | 0 | MutexAutoLock lock(mMessageQueueMutex); |
271 | 0 | MIDIMessageQueue* msgQueue; |
272 | 0 | if (!mMessageQueues.Get(aPortId, &msgQueue)) { |
273 | 0 | return; |
274 | 0 | } |
275 | 0 | msgQueue->GetMessages(aMsgs); |
276 | 0 | } |
277 | 0 | } |
278 | | |
279 | | void |
280 | | MIDIPlatformService::GetMessagesBefore(const nsAString& aPortId, |
281 | | const TimeStamp& aTimeStamp, |
282 | | nsTArray<MIDIMessage>& aMsgs) |
283 | 0 | { |
284 | 0 | // Can run on either background thread or platform specific IO Thread. |
285 | 0 | { |
286 | 0 | MutexAutoLock lock(mMessageQueueMutex); |
287 | 0 | MIDIMessageQueue* msgQueue; |
288 | 0 | if (!mMessageQueues.Get(aPortId, &msgQueue)) { |
289 | 0 | return; |
290 | 0 | } |
291 | 0 | msgQueue->GetMessagesBefore(aTimeStamp, aMsgs); |
292 | 0 | } |
293 | 0 | } |