tor-browser

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

MIDIAccess.cpp (8602B)


      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 "mozilla/dom/MIDIAccess.h"
      8 
      9 #include "MIDILog.h"
     10 #include "ipc/IPCMessageUtils.h"
     11 #include "mozilla/dom/Document.h"
     12 #include "mozilla/dom/MIDIAccessBinding.h"
     13 #include "mozilla/dom/MIDIAccessManager.h"
     14 #include "mozilla/dom/MIDIConnectionEvent.h"
     15 #include "mozilla/dom/MIDIInput.h"
     16 #include "mozilla/dom/MIDIInputMap.h"
     17 #include "mozilla/dom/MIDIInputMapBinding.h"
     18 #include "mozilla/dom/MIDIOptionsBinding.h"
     19 #include "mozilla/dom/MIDIOutput.h"
     20 #include "mozilla/dom/MIDIOutputMap.h"
     21 #include "mozilla/dom/MIDIOutputMapBinding.h"
     22 #include "mozilla/dom/MIDIPort.h"
     23 #include "mozilla/dom/MIDITypes.h"
     24 #include "mozilla/dom/PContent.h"
     25 #include "mozilla/dom/Promise.h"
     26 #include "nsContentPermissionHelper.h"
     27 #include "nsGlobalWindowInner.h"
     28 #include "nsISupportsImpl.h"  // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
     29 #include "nsPIDOMWindow.h"
     30 
     31 namespace mozilla::dom {
     32 
     33 NS_IMPL_CYCLE_COLLECTION_CLASS(MIDIAccess)
     34 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIAccess, DOMEventTargetHelper)
     35 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     36 
     37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MIDIAccess,
     38                                                  DOMEventTargetHelper)
     39  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputMap)
     40  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputMap)
     41  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessPromise)
     42 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     43 
     44 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MIDIAccess,
     45                                                DOMEventTargetHelper)
     46  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputMap)
     47  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputMap)
     48  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessPromise)
     49  tmp->Shutdown();
     50 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     51 
     52 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIAccess)
     53  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     54 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     55 
     56 NS_IMPL_ADDREF_INHERITED(MIDIAccess, DOMEventTargetHelper)
     57 NS_IMPL_RELEASE_INHERITED(MIDIAccess, DOMEventTargetHelper)
     58 
     59 MIDIAccess::MIDIAccess(nsPIDOMWindowInner* aWindow, bool aSysexEnabled,
     60                       Promise* aAccessPromise)
     61    : DOMEventTargetHelper(aWindow),
     62      mInputMap(new MIDIInputMap(aWindow)),
     63      mOutputMap(new MIDIOutputMap(aWindow)),
     64      mSysexEnabled(aSysexEnabled),
     65      mAccessPromise(aAccessPromise),
     66      mHasShutdown(false) {
     67  MOZ_ASSERT(aWindow);
     68  MOZ_ASSERT(aAccessPromise);
     69  KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
     70 }
     71 
     72 MIDIAccess::~MIDIAccess() { Shutdown(); }
     73 
     74 void MIDIAccess::Shutdown() {
     75  LOG("MIDIAccess::Shutdown");
     76  if (mHasShutdown) {
     77    return;
     78  }
     79  if (MIDIAccessManager::IsRunning()) {
     80    MIDIAccessManager::Get()->RemoveObserver(this);
     81  }
     82  mHasShutdown = true;
     83 }
     84 
     85 void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
     86  MOZ_ASSERT(aPort);
     87  MIDIConnectionEventInit init;
     88  init.mPort = aPort;
     89  nsAutoString id;
     90  aPort->GetId(id);
     91  ErrorResult rv;
     92  if (aPort->State() == MIDIPortDeviceState::Disconnected) {
     93    if (aPort->Type() == MIDIPortType::Input && mInputMap->Has(id)) {
     94      MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, aPort->StableId(),
     95                                                   rv);
     96      mInputMap->Remove(id);
     97    } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
     98      MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap,
     99                                                    aPort->StableId(), rv);
    100      mOutputMap->Remove(id);
    101    }
    102    // Check to make sure Has()/Delete() calls haven't failed.
    103    if (NS_WARN_IF(rv.Failed())) {
    104      LOG("Inconsistency during FireConnectionEvent");
    105      return;
    106    }
    107  } else {
    108    // If we receive an event from a port that is not in one of our port maps,
    109    // this means a port that was disconnected has been reconnected, with the
    110    // port owner holding the object during that time, and we should add that
    111    // port object to our maps again.
    112    if (aPort->Type() == MIDIPortType::Input && !mInputMap->Has(id)) {
    113      if (NS_WARN_IF(rv.Failed())) {
    114        LOG("Input port not found");
    115        return;
    116      }
    117      MIDIInputMap_Binding::MaplikeHelpers::Set(
    118          mInputMap, aPort->StableId(), *(static_cast<MIDIInput*>(aPort)), rv);
    119      if (NS_WARN_IF(rv.Failed())) {
    120        LOG("Map Set failed for input port");
    121        return;
    122      }
    123      mInputMap->Insert(id, aPort);
    124    } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
    125      if (NS_WARN_IF(rv.Failed())) {
    126        LOG("Output port not found");
    127        return;
    128      }
    129      MIDIOutputMap_Binding::MaplikeHelpers::Set(
    130          mOutputMap, aPort->StableId(), *(static_cast<MIDIOutput*>(aPort)),
    131          rv);
    132      if (NS_WARN_IF(rv.Failed())) {
    133        LOG("Map set failed for output port");
    134        return;
    135      }
    136      mOutputMap->Insert(id, aPort);
    137    }
    138  }
    139  RefPtr<MIDIConnectionEvent> event =
    140      MIDIConnectionEvent::Constructor(this, u"statechange"_ns, init);
    141  DispatchTrustedEvent(event);
    142 }
    143 
    144 void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
    145                                     ErrorResult& aRv) {
    146  nsAutoString id(aInfo.id());
    147  MIDIPortType type = static_cast<MIDIPortType>(aInfo.type());
    148  RefPtr<MIDIPort> port;
    149  if (type == MIDIPortType::Input) {
    150    if (mInputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
    151      // We already have the port in our map.
    152      return;
    153    }
    154    port = MIDIInput::Create(GetOwnerWindow(), this, aInfo, mSysexEnabled);
    155    if (NS_WARN_IF(!port)) {
    156      LOG("Couldn't create input port");
    157      aRv.Throw(NS_ERROR_FAILURE);
    158      return;
    159    }
    160    MIDIInputMap_Binding::MaplikeHelpers::Set(
    161        mInputMap, port->StableId(), *(static_cast<MIDIInput*>(port.get())),
    162        aRv);
    163    if (NS_WARN_IF(aRv.Failed())) {
    164      LOG("Coudld't set input port in map");
    165      return;
    166    }
    167    mInputMap->Insert(id, port);
    168  } else if (type == MIDIPortType::Output) {
    169    if (mOutputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
    170      // We already have the port in our map.
    171      return;
    172    }
    173    port = MIDIOutput::Create(GetOwnerWindow(), this, aInfo, mSysexEnabled);
    174    if (NS_WARN_IF(!port)) {
    175      LOG("Couldn't create output port");
    176      aRv.Throw(NS_ERROR_FAILURE);
    177      return;
    178    }
    179    MIDIOutputMap_Binding::MaplikeHelpers::Set(
    180        mOutputMap, port->StableId(), *(static_cast<MIDIOutput*>(port.get())),
    181        aRv);
    182    if (NS_WARN_IF(aRv.Failed())) {
    183      LOG("Coudld't set output port in map");
    184      return;
    185    }
    186    mOutputMap->Insert(id, port);
    187  } else {
    188    // If we hit this, then we have some port that is neither input nor output.
    189    // That is bad.
    190    MOZ_CRASH("We shouldn't be here!");
    191  }
    192 
    193  // If we haven't resolved the promise for handing the MIDIAccess object to
    194  // content, this means we're still populating the list of already connected
    195  // devices. Don't fire events yet.
    196  if (!mAccessPromise) {
    197    FireConnectionEvent(port);
    198  }
    199 }
    200 
    201 // For the MIDIAccess object, only worry about new connections, where we create
    202 // MIDIPort objects. When a port is removed and the MIDIPortRemove event is
    203 // received, that will be handled by the MIDIPort object itself, and it will
    204 // request removal from MIDIAccess's maps.
    205 void MIDIAccess::Notify(const MIDIPortList& aEvent) {
    206  LOG("MIDIAccess::Notify");
    207  if (!GetOwnerWindow()) {
    208    // Do nothing if we've already been disconnected from the document.
    209    return;
    210  }
    211 
    212  for (const auto& port : aEvent.ports()) {
    213    // Something went very wrong. Warn and return.
    214    ErrorResult rv;
    215    MaybeCreateMIDIPort(port, rv);
    216    if (rv.Failed()) {
    217      if (!mAccessPromise) {
    218        // We can't reject the promise so let's suppress the error instead
    219        rv.SuppressException();
    220        return;
    221      }
    222      mAccessPromise->MaybeReject(std::move(rv));
    223      mAccessPromise = nullptr;
    224    }
    225  }
    226  if (!mAccessPromise) {
    227    return;
    228  }
    229  mAccessPromise->MaybeResolve(this);
    230  mAccessPromise = nullptr;
    231 }
    232 
    233 JSObject* MIDIAccess::WrapObject(JSContext* aCx,
    234                                 JS::Handle<JSObject*> aGivenProto) {
    235  return MIDIAccess_Binding::Wrap(aCx, this, aGivenProto);
    236 }
    237 
    238 void MIDIAccess::DisconnectFromOwner() {
    239  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
    240 
    241  DOMEventTargetHelper::DisconnectFromOwner();
    242  MIDIAccessManager::Get()->SendRefresh();
    243 }
    244 
    245 }  // namespace mozilla::dom