MIDIPort.cpp (7473B)
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/MIDIPort.h" 8 9 #include "MIDILog.h" 10 #include "mozilla/dom/Document.h" 11 #include "mozilla/dom/MIDIAccess.h" 12 #include "mozilla/dom/MIDIConnectionEvent.h" 13 #include "mozilla/dom/MIDIPortChild.h" 14 #include "mozilla/dom/MIDITypes.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/ipc/BackgroundChild.h" 17 #include "mozilla/ipc/Endpoint.h" 18 #include "mozilla/ipc/PBackgroundChild.h" 19 #include "nsContentUtils.h" 20 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR 21 22 using namespace mozilla::ipc; 23 24 namespace mozilla::dom { 25 26 NS_IMPL_CYCLE_COLLECTION_INHERITED(MIDIPort, DOMEventTargetHelper, 27 mMIDIAccessParent, mOpeningPromise, 28 mClosingPromise) 29 30 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIPort, DOMEventTargetHelper) 31 NS_IMPL_CYCLE_COLLECTION_TRACE_END 32 33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIPort) 34 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 35 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 36 37 NS_IMPL_ADDREF_INHERITED(MIDIPort, DOMEventTargetHelper) 38 NS_IMPL_RELEASE_INHERITED(MIDIPort, DOMEventTargetHelper) 39 40 MIDIPort::MIDIPort(nsPIDOMWindowInner* aWindow) 41 : DOMEventTargetHelper(aWindow), mKeepAlive(false) { 42 MOZ_ASSERT(aWindow); 43 44 if (Document* aDoc = aWindow->GetExtantDoc()) { 45 aDoc->DisallowBFCaching(); 46 } 47 } 48 49 MIDIPort::~MIDIPort() { 50 if (Port()) { 51 // If the IPC port channel is still alive at this point, it means we're 52 // probably CC'ing this port object. Send the shutdown message to also clean 53 // up the IPC channel. 54 Port()->SendShutdown(); 55 } 56 } 57 58 bool MIDIPort::Initialize(const MIDIPortInfo& aPortInfo, bool aSysexEnabled, 59 MIDIAccess* aMIDIAccessParent) { 60 MOZ_ASSERT(aMIDIAccessParent); 61 nsCOMPtr<Document> document = GetDocumentIfCurrent(); 62 if (!document) { 63 return false; 64 } 65 66 nsCOMPtr<nsIURI> uri = document->GetDocumentURI(); 67 if (!uri) { 68 return false; 69 } 70 71 nsAutoCString origin; 72 nsresult rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin); 73 if (NS_FAILED(rv)) { 74 return false; 75 } 76 RefPtr<MIDIPortChild> port = 77 new MIDIPortChild(aPortInfo, aSysexEnabled, this); 78 if (NS_FAILED(port->GenerateStableId(origin))) { 79 return false; 80 } 81 PBackgroundChild* b = BackgroundChild::GetForCurrentThread(); 82 MOZ_ASSERT(b, 83 "Should always have a valid BackgroundChild when creating a port " 84 "object!"); 85 86 // Create the endpoints and bind the one on the child side. 87 Endpoint<PMIDIPortParent> parentEndpoint; 88 Endpoint<PMIDIPortChild> childEndpoint; 89 MOZ_ALWAYS_SUCCEEDS( 90 PMIDIPort::CreateEndpoints(&parentEndpoint, &childEndpoint)); 91 MOZ_ALWAYS_TRUE(childEndpoint.Bind(port)); 92 93 if (!b->SendCreateMIDIPort(std::move(parentEndpoint), aPortInfo, 94 aSysexEnabled)) { 95 return false; 96 } 97 98 mMIDIAccessParent = aMIDIAccessParent; 99 mPortHolder.Init(port.forget()); 100 LOG("MIDIPort::Initialize (%s, %s)", 101 NS_ConvertUTF16toUTF8(Port()->Name()).get(), 102 GetEnumString(Port()->Type()).get()); 103 return true; 104 } 105 106 void MIDIPort::UnsetIPCPort() { 107 LOG("MIDIPort::UnsetIPCPort (%s, %s)", 108 NS_ConvertUTF16toUTF8(Port()->Name()).get(), 109 GetEnumString(Port()->Type()).get()); 110 mPortHolder.Clear(); 111 } 112 113 void MIDIPort::GetId(nsString& aRetVal) const { 114 MOZ_ASSERT(Port()); 115 aRetVal = Port()->StableId(); 116 } 117 118 void MIDIPort::GetManufacturer(nsString& aRetVal) const { 119 MOZ_ASSERT(Port()); 120 aRetVal = Port()->Manufacturer(); 121 } 122 123 void MIDIPort::GetName(nsString& aRetVal) const { 124 MOZ_ASSERT(Port()); 125 aRetVal = Port()->Name(); 126 } 127 128 void MIDIPort::GetVersion(nsString& aRetVal) const { 129 MOZ_ASSERT(Port()); 130 aRetVal = Port()->Version(); 131 } 132 133 MIDIPortType MIDIPort::Type() const { 134 MOZ_ASSERT(Port()); 135 return Port()->Type(); 136 } 137 138 MIDIPortConnectionState MIDIPort::Connection() const { 139 MOZ_ASSERT(Port()); 140 return Port()->ConnectionState(); 141 } 142 143 MIDIPortDeviceState MIDIPort::State() const { 144 MOZ_ASSERT(Port()); 145 return Port()->DeviceState(); 146 } 147 148 bool MIDIPort::SysexEnabled() const { 149 MOZ_ASSERT(Port()); 150 return Port()->SysexEnabled(); 151 } 152 153 already_AddRefed<Promise> MIDIPort::Open(ErrorResult& aError) { 154 LOG("MIDIPort::Open"); 155 MOZ_ASSERT(Port()); 156 if (mOpeningPromise) { 157 return do_AddRef(mOpeningPromise); 158 } 159 RefPtr<Promise> p = Promise::Create(GetOwnerGlobal(), aError); 160 if (aError.Failed()) { 161 return nullptr; 162 } 163 mOpeningPromise = p; 164 Port()->SendOpen(); 165 return p.forget(); 166 } 167 168 already_AddRefed<Promise> MIDIPort::Close(ErrorResult& aError) { 169 LOG("MIDIPort::Close"); 170 MOZ_ASSERT(Port()); 171 if (mClosingPromise) { 172 return do_AddRef(mClosingPromise); 173 } 174 RefPtr<Promise> p = Promise::Create(GetOwnerGlobal(), aError); 175 if (aError.Failed()) { 176 return nullptr; 177 } 178 mClosingPromise = p; 179 Port()->SendClose(); 180 return p.forget(); 181 } 182 183 void MIDIPort::FireStateChangeEvent() { 184 if (!GetOwnerWindow()) { 185 return; // Ignore changes once we've been disconnected from the owner 186 } 187 188 StateChange(); 189 190 MOZ_ASSERT(Port()); 191 if (Port()->ConnectionState() == MIDIPortConnectionState::Open || 192 Port()->ConnectionState() == MIDIPortConnectionState::Pending) { 193 if (mOpeningPromise) { 194 mOpeningPromise->MaybeResolve(this); 195 mOpeningPromise = nullptr; 196 } 197 } else if (Port()->ConnectionState() == MIDIPortConnectionState::Closed) { 198 if (mOpeningPromise) { 199 mOpeningPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR); 200 mOpeningPromise = nullptr; 201 } 202 if (mClosingPromise) { 203 mClosingPromise->MaybeResolve(this); 204 mClosingPromise = nullptr; 205 } 206 } 207 208 if (Port()->DeviceState() == MIDIPortDeviceState::Connected && 209 Port()->ConnectionState() == MIDIPortConnectionState::Pending) { 210 Port()->SendOpen(); 211 } 212 213 if (Port()->ConnectionState() == MIDIPortConnectionState::Open || 214 (Port()->DeviceState() == MIDIPortDeviceState::Connected && 215 Port()->ConnectionState() == MIDIPortConnectionState::Pending)) { 216 KeepAliveOnStatechange(); 217 } else { 218 DontKeepAliveOnStatechange(); 219 } 220 221 // Fire MIDIAccess events first so that the port is no longer in the port 222 // maps. 223 if (mMIDIAccessParent) { 224 mMIDIAccessParent->FireConnectionEvent(this); 225 } 226 227 MIDIConnectionEventInit init; 228 init.mPort = this; 229 RefPtr<MIDIConnectionEvent> event( 230 MIDIConnectionEvent::Constructor(this, u"statechange"_ns, init)); 231 DispatchTrustedEvent(event); 232 } 233 234 void MIDIPort::StateChange() {} 235 236 void MIDIPort::Receive(const nsTArray<MIDIMessage>& aMsg) { 237 MOZ_CRASH("We should never get here!"); 238 } 239 240 void MIDIPort::DisconnectFromOwner() { 241 if (Port()) { 242 Port()->SendClose(); 243 } 244 DontKeepAliveOnStatechange(); 245 246 DOMEventTargetHelper::DisconnectFromOwner(); 247 } 248 249 void MIDIPort::KeepAliveOnStatechange() { 250 if (!mKeepAlive) { 251 mKeepAlive = true; 252 KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); 253 } 254 } 255 256 void MIDIPort::DontKeepAliveOnStatechange() { 257 if (mKeepAlive) { 258 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); 259 mKeepAlive = false; 260 } 261 } 262 263 const nsString& MIDIPort::StableId() { return Port()->StableId(); } 264 265 } // namespace mozilla::dom