Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / midi / MIDIAccess.cpp
blob5286b11d5a3651269f39b78c4039946f1c2b385f
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 "nsGlobalWindowInner.h"
25 #include "nsContentPermissionHelper.h"
26 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
27 #include "ipc/IPCMessageUtils.h"
28 #include "MIDILog.h"
30 namespace mozilla::dom {
32 NS_IMPL_CYCLE_COLLECTION_CLASS(MIDIAccess)
33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIAccess, DOMEventTargetHelper)
34 NS_IMPL_CYCLE_COLLECTION_TRACE_END
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MIDIAccess,
37 DOMEventTargetHelper)
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputMap)
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputMap)
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessPromise)
41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
43 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MIDIAccess,
44 DOMEventTargetHelper)
45 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputMap)
46 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputMap)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessPromise)
48 tmp->Shutdown();
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
51 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIAccess)
52 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
53 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
55 NS_IMPL_ADDREF_INHERITED(MIDIAccess, DOMEventTargetHelper)
56 NS_IMPL_RELEASE_INHERITED(MIDIAccess, DOMEventTargetHelper)
58 MIDIAccess::MIDIAccess(nsPIDOMWindowInner* aWindow, bool aSysexEnabled,
59 Promise* aAccessPromise)
60 : DOMEventTargetHelper(aWindow),
61 mInputMap(new MIDIInputMap(aWindow)),
62 mOutputMap(new MIDIOutputMap(aWindow)),
63 mSysexEnabled(aSysexEnabled),
64 mAccessPromise(aAccessPromise),
65 mHasShutdown(false) {
66 MOZ_ASSERT(aWindow);
67 MOZ_ASSERT(aAccessPromise);
68 KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
71 MIDIAccess::~MIDIAccess() { Shutdown(); }
73 void MIDIAccess::Shutdown() {
74 LOG("MIDIAccess::Shutdown");
75 if (mHasShutdown) {
76 return;
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 && mInputMap->Has(id)) {
93 MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, aPort->StableId(),
94 rv);
95 mInputMap->Remove(id);
96 } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
97 MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap,
98 aPort->StableId(), rv);
99 mOutputMap->Remove(id);
101 // Check to make sure Has()/Delete() calls haven't failed.
102 if (NS_WARN_IF(rv.Failed())) {
103 LOG("Inconsistency during FireConnectionEvent");
104 return;
106 } else {
107 // If we receive an event from a port that is not in one of our port maps,
108 // this means a port that was disconnected has been reconnected, with the
109 // port owner holding the object during that time, and we should add that
110 // port object to our maps again.
111 if (aPort->Type() == MIDIPortType::Input && !mInputMap->Has(id)) {
112 if (NS_WARN_IF(rv.Failed())) {
113 LOG("Input port not found");
114 return;
116 MIDIInputMap_Binding::MaplikeHelpers::Set(
117 mInputMap, aPort->StableId(), *(static_cast<MIDIInput*>(aPort)), rv);
118 if (NS_WARN_IF(rv.Failed())) {
119 LOG("Map Set failed for input port");
120 return;
122 mInputMap->Insert(id, aPort);
123 } else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
124 if (NS_WARN_IF(rv.Failed())) {
125 LOG("Output port not found");
126 return;
128 MIDIOutputMap_Binding::MaplikeHelpers::Set(
129 mOutputMap, aPort->StableId(), *(static_cast<MIDIOutput*>(aPort)),
130 rv);
131 if (NS_WARN_IF(rv.Failed())) {
132 LOG("Map set failed for output port");
133 return;
135 mOutputMap->Insert(id, aPort);
138 RefPtr<MIDIConnectionEvent> event =
139 MIDIConnectionEvent::Constructor(this, u"statechange"_ns, init);
140 DispatchTrustedEvent(event);
143 void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
144 ErrorResult& aRv) {
145 nsAutoString id(aInfo.id());
146 MIDIPortType type = static_cast<MIDIPortType>(aInfo.type());
147 RefPtr<MIDIPort> port;
148 if (type == MIDIPortType::Input) {
149 if (mInputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
150 // We already have the port in our map.
151 return;
153 port = MIDIInput::Create(GetOwnerWindow(), 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, port->StableId(), *(static_cast<MIDIInput*>(port.get())),
161 aRv);
162 if (NS_WARN_IF(aRv.Failed())) {
163 LOG("Coudld't set input port in map");
164 return;
166 mInputMap->Insert(id, port);
167 } else if (type == MIDIPortType::Output) {
168 if (mOutputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
169 // We already have the port in our map.
170 return;
172 port = MIDIOutput::Create(GetOwnerWindow(), 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, port->StableId(), *(static_cast<MIDIOutput*>(port.get())),
180 aRv);
181 if (NS_WARN_IF(aRv.Failed())) {
182 LOG("Coudld't set output port in map");
183 return;
185 mOutputMap->Insert(id, port);
186 } else {
187 // If we hit this, then we have some port that is neither input nor output.
188 // That is bad.
189 MOZ_CRASH("We shouldn't be here!");
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 if (!GetOwnerWindow()) {
207 // Do nothing if we've already been disconnected from the document.
208 return;
211 for (const auto& port : aEvent.ports()) {
212 // Something went very wrong. Warn and return.
213 ErrorResult rv;
214 MaybeCreateMIDIPort(port, rv);
215 if (rv.Failed()) {
216 if (!mAccessPromise) {
217 // We can't reject the promise so let's suppress the error instead
218 rv.SuppressException();
219 return;
221 mAccessPromise->MaybeReject(std::move(rv));
222 mAccessPromise = nullptr;
225 if (!mAccessPromise) {
226 return;
228 mAccessPromise->MaybeResolve(this);
229 mAccessPromise = nullptr;
232 JSObject* MIDIAccess::WrapObject(JSContext* aCx,
233 JS::Handle<JSObject*> aGivenProto) {
234 return MIDIAccess_Binding::Wrap(aCx, this, aGivenProto);
237 void MIDIAccess::DisconnectFromOwner() {
238 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
240 DOMEventTargetHelper::DisconnectFromOwner();
241 MIDIAccessManager::Get()->SendRefresh();
244 } // namespace mozilla::dom