1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/renderer/media/midi_message_filter.h" 6 7 #include "base/bind.h" 8 #include "base/debug/trace_event.h" 9 #include "base/message_loop/message_loop_proxy.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "content/common/media/midi_messages.h" 12 #include "content/renderer/render_thread_impl.h" 13 #include "ipc/ipc_logging.h" 14 15 using media::MidiPortInfoList; 16 using base::AutoLock; 17 18 // The maximum number of bytes which we're allowed to send to the browser 19 // before getting acknowledgement back from the browser that they've been 20 // successfully sent. 21 static const size_t kMaxUnacknowledgedBytesSent = 10 * 1024 * 1024; // 10 MB. 22 23 namespace content { 24 25 MidiMessageFilter::MidiMessageFilter( 26 const scoped_refptr<base::MessageLoopProxy>& io_message_loop) 27 : sender_(NULL), 28 io_message_loop_(io_message_loop), 29 main_message_loop_(base::MessageLoopProxy::current()), 30 next_available_id_(0), 31 unacknowledged_bytes_sent_(0) { 32 } 33 34 MidiMessageFilter::~MidiMessageFilter() {} 35 36 void MidiMessageFilter::Send(IPC::Message* message) { 37 DCHECK(io_message_loop_->BelongsToCurrentThread()); 38 if (!sender_) { 39 delete message; 40 } else { 41 sender_->Send(message); 42 } 43 } 44 45 bool MidiMessageFilter::OnMessageReceived(const IPC::Message& message) { 46 DCHECK(io_message_loop_->BelongsToCurrentThread()); 47 bool handled = true; 48 IPC_BEGIN_MESSAGE_MAP(MidiMessageFilter, message) 49 IPC_MESSAGE_HANDLER(MidiMsg_SessionStarted, OnSessionStarted) 50 IPC_MESSAGE_HANDLER(MidiMsg_DataReceived, OnDataReceived) 51 IPC_MESSAGE_HANDLER(MidiMsg_AcknowledgeSentData, OnAcknowledgeSentData) 52 IPC_MESSAGE_UNHANDLED(handled = false) 53 IPC_END_MESSAGE_MAP() 54 return handled; 55 } 56 57 void MidiMessageFilter::OnFilterAdded(IPC::Sender* sender) { 58 DCHECK(io_message_loop_->BelongsToCurrentThread()); 59 sender_ = sender; 60 } 61 62 void MidiMessageFilter::OnFilterRemoved() { 63 DCHECK(io_message_loop_->BelongsToCurrentThread()); 64 65 // Once removed, a filter will not be used again. At this time all 66 // delegates must be notified so they release their reference. 67 OnChannelClosing(); 68 } 69 70 void MidiMessageFilter::OnChannelClosing() { 71 DCHECK(io_message_loop_->BelongsToCurrentThread()); 72 sender_ = NULL; 73 } 74 75 void MidiMessageFilter::StartSession(blink::WebMIDIAccessorClient* client) { 76 // Generate and keep track of a "client id" which is sent to the browser 77 // to ask permission to talk to MIDI hardware. 78 // This id is handed back when we receive the answer in OnAccessApproved(). 79 if (clients_.find(client) == clients_.end()) { 80 int client_id = next_available_id_++; 81 clients_[client] = client_id; 82 83 io_message_loop_->PostTask(FROM_HERE, 84 base::Bind(&MidiMessageFilter::StartSessionOnIOThread, this, 85 client_id)); 86 } 87 } 88 89 void MidiMessageFilter::StartSessionOnIOThread(int client_id) { 90 Send(new MidiHostMsg_StartSession(client_id)); 91 } 92 93 void MidiMessageFilter::RemoveClient(blink::WebMIDIAccessorClient* client) { 94 ClientsMap::iterator i = clients_.find(client); 95 if (i != clients_.end()) 96 clients_.erase(i); 97 } 98 99 // Received from browser. 100 101 void MidiMessageFilter::OnSessionStarted( 102 int client_id, 103 media::MidiResult result, 104 MidiPortInfoList inputs, 105 MidiPortInfoList outputs) { 106 // Handle on the main JS thread. 107 main_message_loop_->PostTask( 108 FROM_HERE, 109 base::Bind(&MidiMessageFilter::HandleSessionStarted, this, 110 client_id, result, inputs, outputs)); 111 } 112 113 void MidiMessageFilter::HandleSessionStarted( 114 int client_id, 115 media::MidiResult result, 116 MidiPortInfoList inputs, 117 MidiPortInfoList outputs) { 118 blink::WebMIDIAccessorClient* client = GetClientFromId(client_id); 119 if (!client) 120 return; 121 122 if (result == media::MIDI_OK) { 123 // Add the client's input and output ports. 124 for (size_t i = 0; i < inputs.size(); ++i) { 125 client->didAddInputPort( 126 base::UTF8ToUTF16(inputs[i].id), 127 base::UTF8ToUTF16(inputs[i].manufacturer), 128 base::UTF8ToUTF16(inputs[i].name), 129 base::UTF8ToUTF16(inputs[i].version)); 130 } 131 132 for (size_t i = 0; i < outputs.size(); ++i) { 133 client->didAddOutputPort( 134 base::UTF8ToUTF16(outputs[i].id), 135 base::UTF8ToUTF16(outputs[i].manufacturer), 136 base::UTF8ToUTF16(outputs[i].name), 137 base::UTF8ToUTF16(outputs[i].version)); 138 } 139 } 140 std::string error; 141 std::string message; 142 switch (result) { 143 case media::MIDI_OK: 144 break; 145 case media::MIDI_NOT_SUPPORTED: 146 error = "NotSupportedError"; 147 break; 148 case media::MIDI_INITIALIZATION_ERROR: 149 error = "InvalidStateError"; 150 message = "Platform dependent initialization failed."; 151 break; 152 default: 153 NOTREACHED(); 154 error = "InvalidStateError"; 155 message = "Unknown internal error occurred."; 156 break; 157 } 158 client->didStartSession(result == media::MIDI_OK, base::UTF8ToUTF16(error), 159 base::UTF8ToUTF16(message)); 160 } 161 162 blink::WebMIDIAccessorClient* 163 MidiMessageFilter::GetClientFromId(int client_id) { 164 // Iterating like this seems inefficient, but in practice there generally 165 // will be very few clients (usually one). Additionally, this lookup 166 // usually happens one time during page load. So the performance hit is 167 // negligible. 168 for (ClientsMap::iterator i = clients_.begin(); i != clients_.end(); ++i) { 169 if ((*i).second == client_id) 170 return (*i).first; 171 } 172 return NULL; 173 } 174 175 void MidiMessageFilter::OnDataReceived(uint32 port, 176 const std::vector<uint8>& data, 177 double timestamp) { 178 TRACE_EVENT0("midi", "MidiMessageFilter::OnDataReceived"); 179 180 main_message_loop_->PostTask( 181 FROM_HERE, 182 base::Bind(&MidiMessageFilter::HandleDataReceived, this, 183 port, data, timestamp)); 184 } 185 186 void MidiMessageFilter::OnAcknowledgeSentData(size_t bytes_sent) { 187 DCHECK_GE(unacknowledged_bytes_sent_, bytes_sent); 188 if (unacknowledged_bytes_sent_ >= bytes_sent) 189 unacknowledged_bytes_sent_ -= bytes_sent; 190 } 191 192 void MidiMessageFilter::HandleDataReceived(uint32 port, 193 const std::vector<uint8>& data, 194 double timestamp) { 195 DCHECK(!data.empty()); 196 TRACE_EVENT0("midi", "MidiMessageFilter::HandleDataReceived"); 197 198 for (ClientsMap::iterator i = clients_.begin(); i != clients_.end(); ++i) 199 (*i).first->didReceiveMIDIData(port, &data[0], data.size(), timestamp); 200 } 201 202 void MidiMessageFilter::SendMidiData(uint32 port, 203 const uint8* data, 204 size_t length, 205 double timestamp) { 206 if (length > kMaxUnacknowledgedBytesSent) { 207 // TODO(toyoshim): buffer up the data to send at a later time. 208 // For now we're just dropping these bytes on the floor. 209 return; 210 } 211 212 std::vector<uint8> v(data, data + length); 213 io_message_loop_->PostTask(FROM_HERE, 214 base::Bind(&MidiMessageFilter::SendMidiDataOnIOThread, this, 215 port, v, timestamp)); 216 } 217 218 void MidiMessageFilter::SendMidiDataOnIOThread(uint32 port, 219 const std::vector<uint8>& data, 220 double timestamp) { 221 size_t n = data.size(); 222 if (n > kMaxUnacknowledgedBytesSent || 223 unacknowledged_bytes_sent_ > kMaxUnacknowledgedBytesSent || 224 n + unacknowledged_bytes_sent_ > kMaxUnacknowledgedBytesSent) { 225 // TODO(toyoshim): buffer up the data to send at a later time. 226 // For now we're just dropping these bytes on the floor. 227 return; 228 } 229 230 unacknowledged_bytes_sent_ += n; 231 232 // Send to the browser. 233 Send(new MidiHostMsg_SendData(port, data, timestamp)); 234 } 235 236 } // namespace content 237