tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }