Coverage Report

Created: 2018-09-25 14:53

/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
}