blob: e6a7f8067beff37bea082b5ce503a6ef75567208 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/midi_message_filter.h"
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/media/midi_messages.h"
#include "content/renderer/render_thread_impl.h"
#include "ipc/ipc_logging.h"
using media::MidiPortInfoList;
using base::AutoLock;
// The maximum number of bytes which we're allowed to send to the browser
// before getting acknowledgement back from the browser that they've been
// successfully sent.
static const size_t kMaxUnacknowledgedBytesSent = 10 * 1024 * 1024; // 10 MB.
namespace content {
MidiMessageFilter::MidiMessageFilter(
const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
: sender_(NULL),
io_message_loop_(io_message_loop),
main_message_loop_(base::MessageLoopProxy::current()),
next_available_id_(0),
unacknowledged_bytes_sent_(0) {
}
MidiMessageFilter::~MidiMessageFilter() {}
void MidiMessageFilter::Send(IPC::Message* message) {
DCHECK(io_message_loop_->BelongsToCurrentThread());
if (!sender_) {
delete message;
} else {
sender_->Send(message);
}
}
bool MidiMessageFilter::OnMessageReceived(const IPC::Message& message) {
DCHECK(io_message_loop_->BelongsToCurrentThread());
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(MidiMessageFilter, message)
IPC_MESSAGE_HANDLER(MidiMsg_SessionStarted, OnSessionStarted)
IPC_MESSAGE_HANDLER(MidiMsg_DataReceived, OnDataReceived)
IPC_MESSAGE_HANDLER(MidiMsg_AcknowledgeSentData, OnAcknowledgeSentData)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void MidiMessageFilter::OnFilterAdded(IPC::Sender* sender) {
DCHECK(io_message_loop_->BelongsToCurrentThread());
sender_ = sender;
}
void MidiMessageFilter::OnFilterRemoved() {
DCHECK(io_message_loop_->BelongsToCurrentThread());
// Once removed, a filter will not be used again. At this time all
// delegates must be notified so they release their reference.
OnChannelClosing();
}
void MidiMessageFilter::OnChannelClosing() {
DCHECK(io_message_loop_->BelongsToCurrentThread());
sender_ = NULL;
}
void MidiMessageFilter::StartSession(blink::WebMIDIAccessorClient* client) {
// Generate and keep track of a "client id" which is sent to the browser
// to ask permission to talk to MIDI hardware.
// This id is handed back when we receive the answer in OnAccessApproved().
if (clients_.find(client) == clients_.end()) {
int client_id = next_available_id_++;
clients_[client] = client_id;
io_message_loop_->PostTask(FROM_HERE,
base::Bind(&MidiMessageFilter::StartSessionOnIOThread, this,
client_id));
}
}
void MidiMessageFilter::StartSessionOnIOThread(int client_id) {
Send(new MidiHostMsg_StartSession(client_id));
}
void MidiMessageFilter::RemoveClient(blink::WebMIDIAccessorClient* client) {
ClientsMap::iterator i = clients_.find(client);
if (i != clients_.end())
clients_.erase(i);
}
// Received from browser.
void MidiMessageFilter::OnSessionStarted(
int client_id,
media::MidiResult result,
MidiPortInfoList inputs,
MidiPortInfoList outputs) {
// Handle on the main JS thread.
main_message_loop_->PostTask(
FROM_HERE,
base::Bind(&MidiMessageFilter::HandleSessionStarted, this,
client_id, result, inputs, outputs));
}
void MidiMessageFilter::HandleSessionStarted(
int client_id,
media::MidiResult result,
MidiPortInfoList inputs,
MidiPortInfoList outputs) {
blink::WebMIDIAccessorClient* client = GetClientFromId(client_id);
if (!client)
return;
if (result == media::MIDI_OK) {
// Add the client's input and output ports.
for (size_t i = 0; i < inputs.size(); ++i) {
client->didAddInputPort(
base::UTF8ToUTF16(inputs[i].id),
base::UTF8ToUTF16(inputs[i].manufacturer),
base::UTF8ToUTF16(inputs[i].name),
base::UTF8ToUTF16(inputs[i].version));
}
for (size_t i = 0; i < outputs.size(); ++i) {
client->didAddOutputPort(
base::UTF8ToUTF16(outputs[i].id),
base::UTF8ToUTF16(outputs[i].manufacturer),
base::UTF8ToUTF16(outputs[i].name),
base::UTF8ToUTF16(outputs[i].version));
}
}
std::string error;
std::string message;
switch (result) {
case media::MIDI_OK:
break;
case media::MIDI_NOT_SUPPORTED:
error = "NotSupportedError";
break;
case media::MIDI_INITIALIZATION_ERROR:
error = "InvalidStateError";
message = "Platform dependent initialization failed.";
break;
default:
NOTREACHED();
error = "InvalidStateError";
message = "Unknown internal error occurred.";
break;
}
client->didStartSession(result == media::MIDI_OK, base::UTF8ToUTF16(error),
base::UTF8ToUTF16(message));
}
blink::WebMIDIAccessorClient*
MidiMessageFilter::GetClientFromId(int client_id) {
// Iterating like this seems inefficient, but in practice there generally
// will be very few clients (usually one). Additionally, this lookup
// usually happens one time during page load. So the performance hit is
// negligible.
for (ClientsMap::iterator i = clients_.begin(); i != clients_.end(); ++i) {
if ((*i).second == client_id)
return (*i).first;
}
return NULL;
}
void MidiMessageFilter::OnDataReceived(uint32 port,
const std::vector<uint8>& data,
double timestamp) {
TRACE_EVENT0("midi", "MidiMessageFilter::OnDataReceived");
main_message_loop_->PostTask(
FROM_HERE,
base::Bind(&MidiMessageFilter::HandleDataReceived, this,
port, data, timestamp));
}
void MidiMessageFilter::OnAcknowledgeSentData(size_t bytes_sent) {
DCHECK_GE(unacknowledged_bytes_sent_, bytes_sent);
if (unacknowledged_bytes_sent_ >= bytes_sent)
unacknowledged_bytes_sent_ -= bytes_sent;
}
void MidiMessageFilter::HandleDataReceived(uint32 port,
const std::vector<uint8>& data,
double timestamp) {
DCHECK(!data.empty());
TRACE_EVENT0("midi", "MidiMessageFilter::HandleDataReceived");
for (ClientsMap::iterator i = clients_.begin(); i != clients_.end(); ++i)
(*i).first->didReceiveMIDIData(port, &data[0], data.size(), timestamp);
}
void MidiMessageFilter::SendMidiData(uint32 port,
const uint8* data,
size_t length,
double timestamp) {
if (length > kMaxUnacknowledgedBytesSent) {
// TODO(toyoshim): buffer up the data to send at a later time.
// For now we're just dropping these bytes on the floor.
return;
}
std::vector<uint8> v(data, data + length);
io_message_loop_->PostTask(FROM_HERE,
base::Bind(&MidiMessageFilter::SendMidiDataOnIOThread, this,
port, v, timestamp));
}
void MidiMessageFilter::SendMidiDataOnIOThread(uint32 port,
const std::vector<uint8>& data,
double timestamp) {
size_t n = data.size();
if (n > kMaxUnacknowledgedBytesSent ||
unacknowledged_bytes_sent_ > kMaxUnacknowledgedBytesSent ||
n + unacknowledged_bytes_sent_ > kMaxUnacknowledgedBytesSent) {
// TODO(toyoshim): buffer up the data to send at a later time.
// For now we're just dropping these bytes on the floor.
return;
}
unacknowledged_bytes_sent_ += n;
// Send to the browser.
Send(new MidiHostMsg_SendData(port, data, timestamp));
}
} // namespace content