tor-browser

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

MIDIAccessManager.cpp (5625B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MIDIAccessManager.h"
      8 
      9 #include "mozilla/ClearOnShutdown.h"
     10 #include "mozilla/StaticPrefs_midi.h"
     11 #include "mozilla/dom/Document.h"
     12 #include "mozilla/dom/FeaturePolicyUtils.h"
     13 #include "mozilla/dom/MIDIAccess.h"
     14 #include "mozilla/dom/MIDIManagerChild.h"
     15 #include "mozilla/dom/MIDIPermissionRequest.h"
     16 #include "mozilla/dom/Promise.h"
     17 #include "mozilla/ipc/BackgroundChild.h"
     18 #include "mozilla/ipc/Endpoint.h"
     19 #include "mozilla/ipc/PBackgroundChild.h"
     20 #include "nsIGlobalObject.h"
     21 
     22 using namespace mozilla::ipc;
     23 
     24 namespace mozilla::dom {
     25 
     26 namespace {
     27 // Singleton object for MIDIAccessManager
     28 StaticRefPtr<MIDIAccessManager> gMIDIAccessManager;
     29 }  // namespace
     30 
     31 MIDIAccessManager::MIDIAccessManager() : mHasPortList(false), mChild(nullptr) {}
     32 
     33 MIDIAccessManager::~MIDIAccessManager() = default;
     34 
     35 // static
     36 MIDIAccessManager* MIDIAccessManager::Get() {
     37  if (!gMIDIAccessManager) {
     38    gMIDIAccessManager = new MIDIAccessManager();
     39    ClearOnShutdown(&gMIDIAccessManager);
     40  }
     41  return gMIDIAccessManager;
     42 }
     43 
     44 // static
     45 bool MIDIAccessManager::IsRunning() { return !!gMIDIAccessManager; }
     46 
     47 already_AddRefed<Promise> MIDIAccessManager::RequestMIDIAccess(
     48    nsPIDOMWindowInner* aWindow, const MIDIOptions& aOptions,
     49    ErrorResult& aRv) {
     50  MOZ_ASSERT(NS_IsMainThread());
     51  MOZ_ASSERT(aWindow);
     52  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aWindow);
     53  RefPtr<Promise> p = Promise::Create(go, aRv);
     54  if (NS_WARN_IF(aRv.Failed())) {
     55    return nullptr;
     56  }
     57  nsCOMPtr<Document> doc = aWindow->GetDoc();
     58  if (NS_WARN_IF(!doc)) {
     59    aRv.Throw(NS_ERROR_FAILURE);
     60    return nullptr;
     61  }
     62 
     63 #ifndef MOZ_WEBMIDI_MIDIR_IMPL
     64  if (!StaticPrefs::midi_testing()) {
     65    // If we don't have a MIDI implementation and testing is disabled we can't
     66    // allow accessing WebMIDI. However we don't want to return something
     67    // different from a normal rejection because we don't want websites to use
     68    // the error as a way to fingerprint users, so we throw a security error
     69    // as if the request had been rejected by the user.
     70    aRv.ThrowSecurityError("Access not allowed");
     71    return nullptr;
     72  }
     73 #endif
     74 
     75  if (!FeaturePolicyUtils::IsFeatureAllowed(doc, u"midi"_ns)) {
     76    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     77    return nullptr;
     78  }
     79 
     80  nsCOMPtr<nsIRunnable> permRunnable =
     81      new MIDIPermissionRequest(aWindow, p, aOptions);
     82  aRv = NS_DispatchToMainThread(permRunnable);
     83  if (NS_WARN_IF(aRv.Failed())) {
     84    return nullptr;
     85  }
     86  return p.forget();
     87 }
     88 
     89 bool MIDIAccessManager::AddObserver(Observer<MIDIPortList>* aObserver) {
     90  // Add observer before we start the service, otherwise we can end up with
     91  // device lists being received before we have observers to send them to.
     92  mChangeObservers.AddObserver(aObserver);
     93 
     94  if (!mChild) {
     95    StartActor();
     96  } else {
     97    mChild->SendRefresh();
     98  }
     99 
    100  return true;
    101 }
    102 
    103 // Sets up the actor to talk to the parent.
    104 //
    105 // We Bootstrap the actor manually rather than using a constructor so that
    106 // we can bind the parent endpoint to a dedicated task queue.
    107 void MIDIAccessManager::StartActor() {
    108  MOZ_ASSERT(NS_IsMainThread());
    109  MOZ_ASSERT(!mChild);
    110 
    111  // Grab PBackground.
    112  ::mozilla::ipc::PBackgroundChild* pBackground =
    113      BackgroundChild::GetOrCreateForCurrentThread();
    114 
    115  // Create the endpoints and bind the one on the child side.
    116  Endpoint<PMIDIManagerParent> parentEndpoint;
    117  Endpoint<PMIDIManagerChild> childEndpoint;
    118  MOZ_ALWAYS_SUCCEEDS(
    119      PMIDIManager::CreateEndpoints(&parentEndpoint, &childEndpoint));
    120  mChild = new MIDIManagerChild();
    121  MOZ_ALWAYS_TRUE(childEndpoint.Bind(mChild));
    122 
    123  // Kick over to the parent to connect things over there.
    124  pBackground->SendCreateMIDIManager(std::move(parentEndpoint));
    125 }
    126 
    127 void MIDIAccessManager::RemoveObserver(Observer<MIDIPortList>* aObserver) {
    128  mChangeObservers.RemoveObserver(aObserver);
    129  if (mChangeObservers.Length() == 0) {
    130    // If we're out of listeners, go ahead and shut down. Make sure to cleanup
    131    // the IPDL protocol also.
    132    if (mChild) {
    133      mChild->Shutdown();
    134      mChild = nullptr;
    135    }
    136    gMIDIAccessManager = nullptr;
    137  }
    138 }
    139 
    140 void MIDIAccessManager::SendRefresh() {
    141  if (mChild) {
    142    mChild->SendRefresh();
    143  }
    144 }
    145 
    146 void MIDIAccessManager::CreateMIDIAccess(nsPIDOMWindowInner* aWindow,
    147                                         bool aNeedsSysex, Promise* aPromise) {
    148  MOZ_ASSERT(aWindow);
    149  MOZ_ASSERT(aPromise);
    150  RefPtr<MIDIAccess> a(new MIDIAccess(aWindow, aNeedsSysex, aPromise));
    151  if (NS_WARN_IF(!AddObserver(a))) {
    152    aPromise->MaybeReject(NS_ERROR_FAILURE);
    153    return;
    154  }
    155  if (!mHasPortList) {
    156    // Hold the access object until we get a connected device list.
    157    mAccessHolder.AppendElement(a);
    158  } else {
    159    // If we already have a port list, just send it to the MIDIAccess object now
    160    // so it can prepopulate its device list and resolve the promise.
    161    a->Notify(mPortList);
    162  }
    163 }
    164 
    165 void MIDIAccessManager::Update(const MIDIPortList& aPortList) {
    166  mPortList = aPortList;
    167  mChangeObservers.Broadcast(aPortList);
    168  if (!mHasPortList) {
    169    mHasPortList = true;
    170    // Now that we've broadcast the already-connected port list, content
    171    // should manage the lifetime of the MIDIAccess object, so we can clear the
    172    // keep-alive array.
    173    mAccessHolder.Clear();
    174  }
    175 }
    176 
    177 }  // namespace mozilla::dom