Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / midi / MIDIAccess.cpp
blobfa8ae514c29bc270a4dca6b38fcca4075e00d16b
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 && 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(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, 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(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, 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!");
191 // Set up port to listen for destruction of this access object.
192 mDestructionObservers.AddObserver(port);
194 // If we haven't resolved the promise for handing the MIDIAccess object to
195 // content, this means we're still populating the list of already connected
196 // devices. Don't fire events yet.
197 if (!mAccessPromise) {
198 FireConnectionEvent(port);
202 // For the MIDIAccess object, only worry about new connections, where we create
203 // MIDIPort objects. When a port is removed and the MIDIPortRemove event is
204 // received, that will be handled by the MIDIPort object itself, and it will
205 // request removal from MIDIAccess's maps.
206 void MIDIAccess::Notify(const MIDIPortList& aEvent) {
207 LOG("MIDIAcess::Notify");
208 if (!GetOwner()) {
209 // Do nothing if we've already been disconnected from the document.
210 return;
213 for (const auto& port : aEvent.ports()) {
214 // Something went very wrong. Warn and return.
215 ErrorResult rv;
216 MaybeCreateMIDIPort(port, rv);
217 if (rv.Failed()) {
218 if (!mAccessPromise) {
219 // We can't reject the promise so let's suppress the error instead
220 rv.SuppressException();
221 return;
223 mAccessPromise->MaybeReject(std::move(rv));
224 mAccessPromise = nullptr;
227 if (!mAccessPromise) {
228 return;
230 mAccessPromise->MaybeResolve(this);
231 mAccessPromise = nullptr;
234 JSObject* MIDIAccess::WrapObject(JSContext* aCx,
235 JS::Handle<JSObject*> aGivenProto) {
236 return MIDIAccess_Binding::Wrap(aCx, this, aGivenProto);
239 void MIDIAccess::RemovePortListener(MIDIAccessDestructionObserver* aObs) {
240 mDestructionObservers.RemoveObserver(aObs);
243 void MIDIAccess::DisconnectFromOwner() {
244 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
246 DOMEventTargetHelper::DisconnectFromOwner();
247 MIDIAccessManager::Get()->SendRefresh();
250 } // namespace mozilla::dom