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