gecko-dev/dom/midi/MIDIPlatformService.cpp
Kyle Machulis e1ca4ed006 Bug 1201590 - WebMIDI Parent/PBackground implementation; r=baku,padenot
MozReview-Commit-ID: Juk7azaIViw

--HG--
extra : rebase_source : 1daa19e385ba04fcfba1eb284442a64ae691a271
2017-11-15 11:19:59 -08:00

294 lines
8.0 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MIDIPlatformService.h"
#include "TestMIDIPlatformService.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/PMIDIManagerParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/dom/MIDIPortParent.h"
using namespace mozilla;
using namespace mozilla::dom;
MIDIPlatformService::MIDIPlatformService() :
mHasSentPortList(false),
mMessageQueueMutex("MIDIPlatformServce::mMessageQueueMutex")
{
}
MIDIPlatformService::~MIDIPlatformService()
{
}
void
MIDIPlatformService::CheckAndReceive(const nsAString& aPortId,
const nsTArray<MIDIMessage>& aMsgs)
{
AssertIsOnBackgroundThread();
for (auto& port : mPorts) {
// TODO Clean this up when we split input/output port arrays
if (port->MIDIPortInterface::Id() != aPortId ||
port->Type() != MIDIPortType::Input ||
port->ConnectionState() != MIDIPortConnectionState::Open) {
continue;
}
if (!port->SysexEnabled()) {
nsTArray<MIDIMessage> msgs;
for (auto& msg : aMsgs) {
if (!MIDIUtils::IsSysexMessage(msg)) {
msgs.AppendElement(msg);
}
}
Unused << port->SendReceive(msgs);
} else {
Unused << port->SendReceive(aMsgs);
}
}
}
void
MIDIPlatformService::AddPort(MIDIPortParent* aPort)
{
MOZ_ASSERT(aPort);
AssertIsOnBackgroundThread();
mPorts.AppendElement(aPort);
}
void
MIDIPlatformService::RemovePort(MIDIPortParent* aPort)
{
// This should only be called from the background thread, when a MIDIPort
// actor has been destroyed.
AssertIsOnBackgroundThread();
MOZ_ASSERT(aPort);
mPorts.RemoveElement(aPort);
MaybeStop();
}
void
MIDIPlatformService::BroadcastState(const MIDIPortInfo& aPortInfo,
const MIDIPortDeviceState& aState)
{
AssertIsOnBackgroundThread();
for (auto& p : mPorts) {
if (p->MIDIPortInterface::Id() == aPortInfo.id() &&
p->DeviceState() != aState) {
p->SendUpdateStatus(aState, p->ConnectionState());
}
}
}
void
MIDIPlatformService::QueueMessages(const nsAString& aId, nsTArray<MIDIMessage>& aMsgs)
{
AssertIsOnBackgroundThread();
{
MutexAutoLock lock(mMessageQueueMutex);
MIDIMessageQueue* msgQueue = mMessageQueues.LookupOrAdd(aId);
msgQueue->Add(aMsgs);
ScheduleSend(aId);
}
}
void
MIDIPlatformService::SendPortList()
{
AssertIsOnBackgroundThread();
mHasSentPortList = true;
MIDIPortList l;
for (auto& el : mPortInfo) {
l.ports().AppendElement(el);
}
for (auto& mgr : mManagers) {
Unused << mgr->SendMIDIPortListUpdate(l);
}
}
void
MIDIPlatformService::Clear(MIDIPortParent* aPort)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aPort);
{
MutexAutoLock lock(mMessageQueueMutex);
MIDIMessageQueue* msgQueue = mMessageQueues.Get(aPort->MIDIPortInterface::Id());
if (msgQueue) {
msgQueue->Clear();
}
}
}
void
MIDIPlatformService::AddPortInfo(MIDIPortInfo& aPortInfo)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(XRE_IsParentProcess());
mPortInfo.AppendElement(aPortInfo);
// ORDER MATTERS HERE.
//
// When MIDI hardware is disconnected, all open MIDIPort objects revert to a
// "pending" state, and they are removed from the port maps of MIDIAccess
// objects. We need to send connection updates to all living ports first, THEN
// we can send port list updates to all of the live MIDIAccess objects. We
// have to go in this order because if a port object is still held live but is
// disconnected, it needs to readd itself to its originating MIDIAccess object.
// Running SendPortList first would cause MIDIAccess to create a new MIDIPort
// object, which would conflict (i.e. old disconnected object != new object in
// port map, which is against spec).
for (auto& port : mPorts) {
if (port->MIDIPortInterface::Id() == aPortInfo.id()) {
port->SendUpdateStatus(MIDIPortDeviceState::Connected,
port->ConnectionState());
}
}
if (mHasSentPortList) {
SendPortList();
}
}
void
MIDIPlatformService::RemovePortInfo(MIDIPortInfo& aPortInfo)
{
AssertIsOnBackgroundThread();
mPortInfo.RemoveElement(aPortInfo);
BroadcastState(aPortInfo, MIDIPortDeviceState::Disconnected);
if (mHasSentPortList) {
SendPortList();
}
}
StaticRefPtr<MIDIPlatformService> gMIDIPlatformService;
//static
bool
MIDIPlatformService::IsRunning()
{
return gMIDIPlatformService != nullptr;
}
void
MIDIPlatformService::Close(mozilla::dom::MIDIPortParent *aPort)
{
AssertIsOnBackgroundThread();
{
MutexAutoLock lock(mMessageQueueMutex);
MIDIMessageQueue* msgQueue = mMessageQueues.Get(aPort->MIDIPortInterface::Id());
if (msgQueue) {
msgQueue->ClearAfterNow();
}
}
// Send all messages before sending a close request
ScheduleSend(aPort->MIDIPortInterface::Id());
// TODO We should probably have the send function schedule closing
ScheduleClose(aPort);
}
//static
MIDIPlatformService*
MIDIPlatformService::Get()
{
// We should never touch the platform service in a child process.
MOZ_ASSERT(XRE_IsParentProcess());
AssertIsOnBackgroundThread();
if (!IsRunning()) {
ErrorResult rv;
// Uncomment once we have an actual platform library to test.
//
// bool useTestService = false;
// rv = Preferences::GetRootBranch()->GetBoolPref("midi.testing", &useTestService);
gMIDIPlatformService = new TestMIDIPlatformService();
gMIDIPlatformService->Init();
}
return gMIDIPlatformService;
}
void
MIDIPlatformService::MaybeStop()
{
AssertIsOnBackgroundThread();
if (!IsRunning()) {
// Service already stopped or never started. Exit.
return;
}
// If we have any ports or managers left, we should still be alive.
if (!mPorts.IsEmpty() ||
!mManagers.IsEmpty()) {
return;
}
Stop();
gMIDIPlatformService = nullptr;
}
void
MIDIPlatformService::AddManager(MIDIManagerParent* aManager)
{
AssertIsOnBackgroundThread();
mManagers.AppendElement(aManager);
// Managers add themselves during construction. We have to wait for the protocol
// construction to finish before we send them a port list. The runnable calls
// SendPortList, which iterates through the live manager list, so this saves
// us from having to worry about Manager pointer validity at time of runnable
// execution.
nsCOMPtr<nsIRunnable> r(new SendPortListRunnable());
NS_DispatchToCurrentThread(r);
}
void
MIDIPlatformService::RemoveManager(MIDIManagerParent* aManager)
{
AssertIsOnBackgroundThread();
mManagers.RemoveElement(aManager);
MaybeStop();
}
void
MIDIPlatformService::UpdateStatus(const nsAString& aPortId,
const MIDIPortDeviceState& aDeviceState,
const MIDIPortConnectionState& aConnectionState)
{
AssertIsOnBackgroundThread();
for (auto port : mPorts) {
if (port->MIDIPortInterface::Id() == aPortId) {
port->SendUpdateStatus(aDeviceState, aConnectionState);
}
}
}
void
MIDIPlatformService::GetMessages(const nsAString& aPortId,
nsTArray<MIDIMessage>& aMsgs)
{
// Can run on either background thread or platform specific IO Thread.
{
MutexAutoLock lock(mMessageQueueMutex);
MIDIMessageQueue* msgQueue;
if (!mMessageQueues.Get(aPortId, &msgQueue)) {
return;
}
msgQueue->GetMessages(aMsgs);
}
}
void
MIDIPlatformService::GetMessagesBefore(const nsAString& aPortId,
const TimeStamp& aTimeStamp,
nsTArray<MIDIMessage>& aMsgs)
{
// Can run on either background thread or platform specific IO Thread.
{
MutexAutoLock lock(mMessageQueueMutex);
MIDIMessageQueue* msgQueue;
if (!mMessageQueues.Get(aPortId, &msgQueue)) {
return;
}
msgQueue->GetMessagesBefore(aTimeStamp, aMsgs);
}
}