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