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