MIDIPlatformService.cpp (8659B)
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 9 #include "MIDIMessageQueue.h" 10 #include "TestMIDIPlatformService.h" 11 #ifdef MOZ_WEBMIDI_MIDIR_IMPL 12 # include "midirMIDIPlatformService.h" 13 #endif // MOZ_WEBMIDI_MIDIR_IMPL 14 #include "mozilla/ErrorResult.h" 15 #include "mozilla/StaticPrefs_midi.h" 16 #include "mozilla/StaticPtr.h" 17 #include "mozilla/dom/MIDIManagerParent.h" 18 #include "mozilla/dom/MIDIPlatformRunnables.h" 19 #include "mozilla/dom/MIDIPortParent.h" 20 #include "mozilla/dom/MIDIUtils.h" 21 #include "mozilla/dom/PMIDIManagerParent.h" 22 #include "mozilla/ipc/BackgroundParent.h" 23 24 using namespace mozilla; 25 using namespace mozilla::dom; 26 27 MIDIPlatformService::MIDIPlatformService() 28 : mHasSentPortList(false), 29 mMessageQueueMutex("MIDIPlatformServce::mMessageQueueMutex") {} 30 31 MIDIPlatformService::~MIDIPlatformService() = default; 32 33 void MIDIPlatformService::CheckAndReceive(const nsAString& aPortId, 34 const nsTArray<MIDIMessage>& aMsgs) { 35 AssertThread(); 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) { 41 continue; 42 } 43 if (!port->SysexEnabled()) { 44 nsTArray<MIDIMessage> msgs; 45 for (const auto& msg : aMsgs) { 46 if (!MIDIUtils::IsSysexMessage(msg)) { 47 msgs.AppendElement(msg); 48 } 49 } 50 (void)port->SendReceive(msgs); 51 } else { 52 (void)port->SendReceive(aMsgs); 53 } 54 } 55 } 56 57 void MIDIPlatformService::AddPort(MIDIPortParent* aPort) { 58 MOZ_ASSERT(aPort); 59 AssertThread(); 60 mPorts.AppendElement(aPort); 61 } 62 63 void MIDIPlatformService::RemovePort(MIDIPortParent* aPort) { 64 // This should only be called from the background thread, when a MIDIPort 65 // actor has been destroyed. 66 AssertThread(); 67 MOZ_ASSERT(aPort); 68 mPorts.RemoveElement(aPort); 69 MaybeStop(); 70 } 71 72 void MIDIPlatformService::BroadcastState(const MIDIPortInfo& aPortInfo, 73 const MIDIPortDeviceState& aState) { 74 AssertThread(); 75 for (auto& p : mPorts) { 76 if (p->MIDIPortInterface::Id() == aPortInfo.id() && 77 p->DeviceState() != aState) { 78 p->SendUpdateStatus(aState, p->ConnectionState()); 79 } 80 } 81 } 82 83 void MIDIPlatformService::QueueMessages(const nsAString& aId, 84 nsTArray<MIDIMessage>& aMsgs) { 85 AssertThread(); 86 { 87 MutexAutoLock lock(mMessageQueueMutex); 88 MIDIMessageQueue* msgQueue = mMessageQueues.GetOrInsertNew(aId); 89 msgQueue->Add(aMsgs); 90 } 91 92 ScheduleSend(aId); 93 } 94 95 void MIDIPlatformService::SendPortList() { 96 AssertThread(); 97 mHasSentPortList = true; 98 MIDIPortList l; 99 for (auto& el : mPortInfo) { 100 l.ports().AppendElement(el); 101 } 102 for (auto& mgr : mManagers) { 103 (void)mgr->SendMIDIPortListUpdate(l); 104 } 105 } 106 107 void MIDIPlatformService::Clear(MIDIPortParent* aPort) { 108 AssertThread(); 109 MOZ_ASSERT(aPort); 110 { 111 MutexAutoLock lock(mMessageQueueMutex); 112 MIDIMessageQueue* msgQueue = 113 mMessageQueues.Get(aPort->MIDIPortInterface::Id()); 114 if (msgQueue) { 115 msgQueue->Clear(); 116 } 117 } 118 } 119 120 void MIDIPlatformService::AddPortInfo(MIDIPortInfo& aPortInfo) { 121 AssertThread(); 122 MOZ_ASSERT(XRE_IsParentProcess()); 123 124 mPortInfo.AppendElement(aPortInfo); 125 126 // ORDER MATTERS HERE. 127 // 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()); 141 } 142 } 143 if (mHasSentPortList) { 144 SendPortList(); 145 } 146 } 147 148 void MIDIPlatformService::RemovePortInfo(MIDIPortInfo& aPortInfo) { 149 AssertThread(); 150 mPortInfo.RemoveElement(aPortInfo); 151 BroadcastState(aPortInfo, MIDIPortDeviceState::Disconnected); 152 if (mHasSentPortList) { 153 SendPortList(); 154 } 155 } 156 157 StaticRefPtr<nsISerialEventTarget> gMIDITaskQueue; 158 159 // static 160 void MIDIPlatformService::InitStatics() { 161 nsCOMPtr<nsISerialEventTarget> queue; 162 MOZ_ALWAYS_SUCCEEDS( 163 NS_CreateBackgroundTaskQueue("MIDITaskQueue", getter_AddRefs(queue))); 164 gMIDITaskQueue = queue.forget(); 165 ClearOnShutdown(&gMIDITaskQueue); 166 } 167 168 // static 169 nsISerialEventTarget* MIDIPlatformService::OwnerThread() { 170 return gMIDITaskQueue; 171 } 172 173 StaticRefPtr<MIDIPlatformService> gMIDIPlatformService; 174 175 // static 176 bool MIDIPlatformService::IsRunning() { 177 return gMIDIPlatformService != nullptr; 178 } 179 180 void MIDIPlatformService::Close(mozilla::dom::MIDIPortParent* aPort) { 181 AssertThread(); 182 { 183 MutexAutoLock lock(mMessageQueueMutex); 184 MIDIMessageQueue* msgQueue = 185 mMessageQueues.Get(aPort->MIDIPortInterface::Id()); 186 if (msgQueue) { 187 msgQueue->ClearAfterNow(); 188 } 189 } 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); 194 } 195 196 // static 197 MIDIPlatformService* MIDIPlatformService::Get() { 198 // We should never touch the platform service in a child process. 199 MOZ_ASSERT(XRE_IsParentProcess()); 200 AssertThread(); 201 if (!IsRunning()) { 202 if (StaticPrefs::midi_testing()) { 203 gMIDIPlatformService = new TestMIDIPlatformService(); 204 } 205 #ifdef MOZ_WEBMIDI_MIDIR_IMPL 206 else { 207 gMIDIPlatformService = new midirMIDIPlatformService(); 208 } 209 #endif // MOZ_WEBMIDI_MIDIR_IMPL 210 gMIDIPlatformService->Init(); 211 } 212 return gMIDIPlatformService; 213 } 214 215 void MIDIPlatformService::MaybeStop() { 216 AssertThread(); 217 if (!IsRunning()) { 218 // Service already stopped or never started. Exit. 219 return; 220 } 221 // If we have any ports or managers left, we should still be alive. 222 if (!mPorts.IsEmpty() || !mManagers.IsEmpty()) { 223 return; 224 } 225 Stop(); 226 gMIDIPlatformService = nullptr; 227 } 228 229 void MIDIPlatformService::AddManager(MIDIManagerParent* aManager) { 230 AssertThread(); 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()); 239 } 240 241 void MIDIPlatformService::RemoveManager(MIDIManagerParent* aManager) { 242 AssertThread(); 243 mManagers.RemoveElement(aManager); 244 MaybeStop(); 245 } 246 247 void MIDIPlatformService::UpdateStatus( 248 MIDIPortParent* aPort, const MIDIPortDeviceState& aDeviceState, 249 const MIDIPortConnectionState& aConnectionState) { 250 AssertThread(); 251 aPort->SendUpdateStatus(aDeviceState, aConnectionState); 252 } 253 254 void MIDIPlatformService::GetMessages(const nsAString& aPortId, 255 nsTArray<MIDIMessage>& aMsgs) { 256 // Can run on either background thread or platform specific IO Thread. 257 { 258 MutexAutoLock lock(mMessageQueueMutex); 259 MIDIMessageQueue* msgQueue; 260 if (!mMessageQueues.Get(aPortId, &msgQueue)) { 261 return; 262 } 263 msgQueue->GetMessages(aMsgs); 264 } 265 } 266 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. 271 { 272 MutexAutoLock lock(mMessageQueueMutex); 273 MIDIMessageQueue* msgQueue; 274 if (!mMessageQueues.Get(aPortId, &msgQueue)) { 275 return; 276 } 277 msgQueue->GetMessagesBefore(aTimeStamp, aMsgs); 278 } 279 }