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