diff options
author | toyoshim <toyoshim@chromium.org> | 2014-10-23 00:17:59 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-23 07:18:17 +0000 |
commit | 71fcb150d9e2cb5e2f05069177ac611ac9c189c0 (patch) | |
tree | 5067da853f0f8b7d7b52a0fb632ad93e33701fe7 | |
parent | 7a0ddbb2ced7ed2117ae177773714968f66a66b5 (diff) | |
download | chromium_src-71fcb150d9e2cb5e2f05069177ac611ac9c189c0.zip chromium_src-71fcb150d9e2cb5e2f05069177ac611ac9c189c0.tar.gz chromium_src-71fcb150d9e2cb5e2f05069177ac611ac9c189c0.tar.bz2 |
Manage MIDI related objects and sessions' lifecycles correctly
Following changes are needed to maintain MIDIPortInfoList contents
consistent in each MidiMessageFilter instances when MidiManager
update the list asynchronously.
Essential changes:
- Call EndSession iff a session is open.
- Do not use client_id to communicate between browser and renderer.
Multiple clients are managed in a MidiMessageFilter existing in
each renderer.
Minor changes:
- Remove midi_manager_ checks in MIDIHost, and add CHECK insteads.
- Rename MidiManagerFilter::StartSession to MidiManagerFilter::AddClient
to be aligned with RemoveClient. A session can be shared with multiple
clients.
After this change, MidiManager::EndSession is called from each Renderer
and RendererHost at the right timing, e.g., GC collects the last MIDIAccess
reference in JavaScript. See, also http://crbug.com/424859.
BUG=424859
Review URL: https://codereview.chromium.org/662853003
Cr-Commit-Position: refs/heads/master@{#300851}
-rw-r--r-- | content/browser/media/midi_host.cc | 26 | ||||
-rw-r--r-- | content/browser/media/midi_host.h | 10 | ||||
-rw-r--r-- | content/common/media/midi_messages.h | 8 | ||||
-rw-r--r-- | content/renderer/media/midi_message_filter.cc | 265 | ||||
-rw-r--r-- | content/renderer/media/midi_message_filter.h | 58 | ||||
-rw-r--r-- | content/renderer/media/renderer_webmidiaccessor_impl.cc | 2 | ||||
-rw-r--r-- | media/midi/midi_manager.cc | 28 | ||||
-rw-r--r-- | media/midi/midi_manager.h | 12 | ||||
-rw-r--r-- | media/midi/midi_manager_unittest.cc | 37 | ||||
-rw-r--r-- | media/midi/midi_manager_usb_unittest.cc | 4 | ||||
-rw-r--r-- | media/midi/midi_result.h | 3 |
11 files changed, 240 insertions, 213 deletions
diff --git a/content/browser/media/midi_host.cc b/content/browser/media/midi_host.cc index 950cf48..fb4dcce 100644 --- a/content/browser/media/midi_host.cc +++ b/content/browser/media/midi_host.cc @@ -52,13 +52,16 @@ MidiHost::MidiHost(int renderer_process_id, media::MidiManager* midi_manager) : BrowserMessageFilter(MidiMsgStart), renderer_process_id_(renderer_process_id), has_sys_ex_permission_(false), + is_session_requested_(false), midi_manager_(midi_manager), sent_bytes_in_flight_(0), bytes_sent_since_last_acknowledgement_(0) { + CHECK(midi_manager_); } MidiHost::~MidiHost() { - if (midi_manager_) + // Close an open session, or abort opening a session. + if (is_session_requested_) midi_manager_->EndSession(this); } @@ -72,23 +75,21 @@ bool MidiHost::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(MidiHost, message) IPC_MESSAGE_HANDLER(MidiHostMsg_StartSession, OnStartSession) IPC_MESSAGE_HANDLER(MidiHostMsg_SendData, OnSendData) + IPC_MESSAGE_HANDLER(MidiHostMsg_EndSession, OnEndSession) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } -void MidiHost::OnStartSession(int client_id) { - if (midi_manager_) - midi_manager_->StartSession(this, client_id); +void MidiHost::OnStartSession() { + is_session_requested_ = true; + midi_manager_->StartSession(this); } void MidiHost::OnSendData(uint32 port, const std::vector<uint8>& data, double timestamp) { - if (!midi_manager_) - return; - if (data.empty()) return; @@ -117,7 +118,13 @@ void MidiHost::OnSendData(uint32 port, midi_manager_->DispatchSendMidiData(this, port, data, timestamp); } -void MidiHost::CompleteStartSession(int client_id, media::MidiResult result) { +void MidiHost::OnEndSession() { + is_session_requested_ = false; + midi_manager_->EndSession(this); +} + +void MidiHost::CompleteStartSession(media::MidiResult result) { + DCHECK(is_session_requested_); MidiPortInfoList input_ports; MidiPortInfoList output_ports; @@ -132,8 +139,7 @@ void MidiHost::CompleteStartSession(int client_id, media::MidiResult result) { CanSendMidiSysExMessage(renderer_process_id_); } - Send(new MidiMsg_SessionStarted(client_id, - result, + Send(new MidiMsg_SessionStarted(result, input_ports, output_ports)); } diff --git a/content/browser/media/midi_host.h b/content/browser/media/midi_host.h index 87be305..acaee52 100644 --- a/content/browser/media/midi_host.h +++ b/content/browser/media/midi_host.h @@ -11,6 +11,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/synchronization/lock.h" #include "content/common/content_export.h" #include "content/public/browser/browser_message_filter.h" #include "content/public/browser/browser_thread.h" @@ -35,7 +36,7 @@ class CONTENT_EXPORT MidiHost bool OnMessageReceived(const IPC::Message& message) override; // MidiManagerClient implementation. - void CompleteStartSession(int client_id, media::MidiResult result) override; + void CompleteStartSession(media::MidiResult result) override; void ReceiveMidiData(uint32 port, const uint8* data, size_t length, @@ -43,13 +44,15 @@ class CONTENT_EXPORT MidiHost void AccumulateMidiBytesSent(size_t n) override; // Start session to access MIDI hardware. - void OnStartSession(int client_id); + void OnStartSession(); // Data to be sent to a MIDI output port. void OnSendData(uint32 port, const std::vector<uint8>& data, double timestamp); + void OnEndSession(); + private: FRIEND_TEST_ALL_PREFIXES(MidiHostTest, IsValidWebMIDIData); friend class base::DeleteHelper<MidiHost>; @@ -70,6 +73,9 @@ class CONTENT_EXPORT MidiHost // messages. bool has_sys_ex_permission_; + // Represents if a session is requested to start. + bool is_session_requested_; + // |midi_manager_| talks to the platform-specific MIDI APIs. // It can be NULL if the platform (or our current implementation) // does not support MIDI. If not supported then a call to diff --git a/content/common/media/midi_messages.h b/content/common/media/midi_messages.h index da131397..c1375c3 100644 --- a/content/common/media/midi_messages.h +++ b/content/common/media/midi_messages.h @@ -43,18 +43,18 @@ IPC_MESSAGE_ROUTED2(MidiMsg_SysExPermissionApproved, // Messages for IPC between MidiMessageFilter and MidiHost. // Renderer request to browser for access to MIDI services. -IPC_MESSAGE_CONTROL1(MidiHostMsg_StartSession, - int /* client id */) +IPC_MESSAGE_CONTROL0(MidiHostMsg_StartSession) IPC_MESSAGE_CONTROL3(MidiHostMsg_SendData, uint32 /* port */, std::vector<uint8> /* data */, double /* timestamp */) +IPC_MESSAGE_CONTROL0(MidiHostMsg_EndSession) + // Messages sent from the browser to the renderer. -IPC_MESSAGE_CONTROL4(MidiMsg_SessionStarted, - int /* client id */, +IPC_MESSAGE_CONTROL3(MidiMsg_SessionStarted, media::MidiResult /* result */, media::MidiPortInfoList /* input ports */, media::MidiPortInfoList /* output ports */) diff --git a/content/renderer/media/midi_message_filter.cc b/content/renderer/media/midi_message_filter.cc index 165451a..8f08ff4 100644 --- a/content/renderer/media/midi_message_filter.cc +++ b/content/renderer/media/midi_message_filter.cc @@ -4,6 +4,8 @@ #include "content/renderer/media/midi_message_filter.h" +#include <algorithm> + #include "base/bind.h" #include "base/debug/trace_event.h" #include "base/message_loop/message_loop_proxy.h" @@ -22,17 +24,80 @@ static const size_t kMaxUnacknowledgedBytesSent = 10 * 1024 * 1024; // 10 MB. namespace content { +// TODO(crbug.com/425389): Rewrite this class as a RenderFrameObserver. 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) { + session_result_(media::MIDI_NOT_INITIALIZED), + unacknowledged_bytes_sent_(0u) { } MidiMessageFilter::~MidiMessageFilter() {} +void MidiMessageFilter::AddClient(blink::WebMIDIAccessorClient* client) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + TRACE_EVENT0("midi", "MidiMessageFilter::AddClient"); + clients_waiting_session_queue_.push_back(client); + if (session_result_ != media::MIDI_NOT_INITIALIZED) { + HandleClientAdded(session_result_); + } else if (clients_waiting_session_queue_.size() == 1u) { + io_message_loop_->PostTask(FROM_HERE, + base::Bind(&MidiMessageFilter::StartSessionOnIOThread, this)); + } +} + +void MidiMessageFilter::RemoveClient(blink::WebMIDIAccessorClient* client) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + clients_.erase(client); + ClientsQueue::iterator it = std::find(clients_waiting_session_queue_.begin(), + clients_waiting_session_queue_.end(), + client); + if (it != clients_waiting_session_queue_.end()) + clients_waiting_session_queue_.erase(it); + if (clients_.empty() && clients_waiting_session_queue_.empty()) { + session_result_ = media::MIDI_NOT_INITIALIZED; + io_message_loop_->PostTask(FROM_HERE, + base::Bind(&MidiMessageFilter::EndSessionOnIOThread, this)); + } +} + +void MidiMessageFilter::SendMidiData(uint32 port, + const uint8* data, + size_t length, + double timestamp) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + if ((kMaxUnacknowledgedBytesSent - unacknowledged_bytes_sent_) < length) { + // 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_ += length; + std::vector<uint8> v(data, data + length); + io_message_loop_->PostTask(FROM_HERE, base::Bind( + &MidiMessageFilter::SendMidiDataOnIOThread, this, port, v, timestamp)); +} + +void MidiMessageFilter::StartSessionOnIOThread() { + TRACE_EVENT0("midi", "MidiMessageFilter::StartSessionOnIOThread"); + DCHECK(io_message_loop_->BelongsToCurrentThread()); + Send(new MidiHostMsg_StartSession()); +} + +void MidiMessageFilter::SendMidiDataOnIOThread(uint32 port, + const std::vector<uint8>& data, + double timestamp) { + DCHECK(io_message_loop_->BelongsToCurrentThread()); + Send(new MidiHostMsg_SendData(port, data, timestamp)); +} + +void MidiMessageFilter::EndSessionOnIOThread() { + DCHECK(io_message_loop_->BelongsToCurrentThread()); + Send(new MidiHostMsg_EndSession()); +} + void MidiMessageFilter::Send(IPC::Message* message) { DCHECK(io_message_loop_->BelongsToCurrentThread()); if (!sender_) { @@ -61,7 +126,6 @@ void MidiMessageFilter::OnFilterAdded(IPC::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(); @@ -72,74 +136,47 @@ void MidiMessageFilter::OnChannelClosing() { 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); +void MidiMessageFilter::OnSessionStarted(media::MidiResult result, + MidiPortInfoList inputs, + MidiPortInfoList outputs) { + TRACE_EVENT0("midi", "MidiMessageFilter::OnSessionStarted"); + DCHECK(io_message_loop_->BelongsToCurrentThread()); + // TODO(toyoshim): |inputs_| and |outputs_| should not be updated on + // |io_message_loop_|. This should be fixed in a following change not to + // distribute MidiPortInfo via OnSessionStarted(). + // For now, this is safe because these are not updated later. + inputs_ = inputs; + outputs_ = outputs; + // Handle on the main JS thread. + main_message_loop_->PostTask( + FROM_HERE, + base::Bind(&MidiMessageFilter::HandleClientAdded, this, result)); } -// Received from browser. - -void MidiMessageFilter::OnSessionStarted( - int client_id, - media::MidiResult result, - MidiPortInfoList inputs, - MidiPortInfoList outputs) { +void MidiMessageFilter::OnDataReceived(uint32 port, + const std::vector<uint8>& data, + double timestamp) { + TRACE_EVENT0("midi", "MidiMessageFilter::OnDataReceived"); + DCHECK(io_message_loop_->BelongsToCurrentThread()); // Handle on the main JS thread. main_message_loop_->PostTask( FROM_HERE, - base::Bind(&MidiMessageFilter::HandleSessionStarted, this, - client_id, result, inputs, outputs)); + base::Bind(&MidiMessageFilter::HandleDataReceived, this, port, data, + timestamp)); } -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. - const bool active = true; - 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), - active); - } +void MidiMessageFilter::OnAcknowledgeSentData(size_t bytes_sent) { + DCHECK(io_message_loop_->BelongsToCurrentThread()); + main_message_loop_->PostTask( + FROM_HERE, + base::Bind(&MidiMessageFilter::HandleAckknowledgeSentData, this, + bytes_sent)); +} - 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), - active); - } - } +void MidiMessageFilter::HandleClientAdded(media::MidiResult result) { + TRACE_EVENT0("midi", "MidiMessageFilter::HandleClientAdded"); + DCHECK(main_message_loop_->BelongsToCurrentThread()); + session_result_ = result; std::string error; std::string message; switch (result) { @@ -158,82 +195,52 @@ void MidiMessageFilter::HandleSessionStarted( 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; + base::string16 error16 = base::UTF8ToUTF16(error); + base::string16 message16 = base::UTF8ToUTF16(message); + for (blink::WebMIDIAccessorClient* client : clients_waiting_session_queue_) { + if (result == media::MIDI_OK) { + // Add the client's input and output ports. + const bool active = true; + for (const auto& info : inputs_) { + client->didAddInputPort( + base::UTF8ToUTF16(info.id), + base::UTF8ToUTF16(info.manufacturer), + base::UTF8ToUTF16(info.name), + base::UTF8ToUTF16(info.version), + active); + } + + for (const auto& info : outputs_) { + client->didAddOutputPort( + base::UTF8ToUTF16(info.id), + base::UTF8ToUTF16(info.manufacturer), + base::UTF8ToUTF16(info.name), + base::UTF8ToUTF16(info.version), + active); + } + } + client->didStartSession(result == media::MIDI_OK, error16, message16); + clients_.insert(client); } - 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; + clients_waiting_session_queue_.clear(); } void MidiMessageFilter::HandleDataReceived(uint32 port, const std::vector<uint8>& data, double timestamp) { - DCHECK(!data.empty()); TRACE_EVENT0("midi", "MidiMessageFilter::HandleDataReceived"); + DCHECK(main_message_loop_->BelongsToCurrentThread()); + DCHECK(!data.empty()); - 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)); + for (blink::WebMIDIAccessorClient* client : clients_) + client->didReceiveMIDIData(port, &data[0], data.size(), 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)); +void MidiMessageFilter::HandleAckknowledgeSentData(size_t bytes_sent) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + DCHECK_GE(unacknowledged_bytes_sent_, bytes_sent); + if (unacknowledged_bytes_sent_ >= bytes_sent) + unacknowledged_bytes_sent_ -= bytes_sent; } } // namespace content diff --git a/content/renderer/media/midi_message_filter.h b/content/renderer/media/midi_message_filter.h index 4aa560a..34cee3d 100644 --- a/content/renderer/media/midi_message_filter.h +++ b/content/renderer/media/midi_message_filter.h @@ -5,7 +5,7 @@ #ifndef CONTENT_RENDERER_MEDIA_MIDI_MESSAGE_FILTER_H_ #define CONTENT_RENDERER_MEDIA_MIDI_MESSAGE_FILTER_H_ -#include <map> +#include <set> #include <vector> #include "base/memory/scoped_ptr.h" @@ -29,9 +29,7 @@ class CONTENT_EXPORT MidiMessageFilter : public IPC::MessageFilter { // Each client registers for MIDI access here. // If permission is granted, then the client's - // addInputPort() and addOutputPort() methods will be called, - // giving the client access to receive and send data. - void StartSession(blink::WebMIDIAccessorClient* client); + void AddClient(blink::WebMIDIAccessorClient* client); void RemoveClient(blink::WebMIDIAccessorClient* client); // A client will only be able to call this method if it has a suitable @@ -50,6 +48,14 @@ class CONTENT_EXPORT MidiMessageFilter : public IPC::MessageFilter { ~MidiMessageFilter() override; private: + void StartSessionOnIOThread(); + + void SendMidiDataOnIOThread(uint32 port, + const std::vector<uint8>& data, + double timestamp); + + void EndSessionOnIOThread(); + // Sends an IPC message using |sender_|. void Send(IPC::Message* message); @@ -61,8 +67,9 @@ class CONTENT_EXPORT MidiMessageFilter : public IPC::MessageFilter { // Called when the browser process has approved (or denied) access to // MIDI hardware. - void OnSessionStarted(int client_id, - media::MidiResult result, + // TODO(toyoshim): MidiPortInfoList objects should be notified separately + // port by port. + void OnSessionStarted(media::MidiResult result, media::MidiPortInfoList inputs, media::MidiPortInfoList outputs); @@ -77,22 +84,14 @@ class CONTENT_EXPORT MidiMessageFilter : public IPC::MessageFilter { // sending too much data before knowing how much has already been sent. void OnAcknowledgeSentData(size_t bytes_sent); - void HandleSessionStarted(int client_id, - media::MidiResult result, - media::MidiPortInfoList inputs, - media::MidiPortInfoList outputs); + // Following methods, Handle*, run on |main_message_loop_|. + void HandleClientAdded(media::MidiResult result); void HandleDataReceived(uint32 port, const std::vector<uint8>& data, double timestamp); - void StartSessionOnIOThread(int client_id); - - void SendMidiDataOnIOThread(uint32 port, - const std::vector<uint8>& data, - double timestamp); - - blink::WebMIDIAccessorClient* GetClientFromId(int client_id); + void HandleAckknowledgeSentData(size_t bytes_sent); // IPC sender for Send(); must only be accessed on |io_message_loop_|. IPC::Sender* sender_; @@ -103,15 +102,24 @@ class CONTENT_EXPORT MidiMessageFilter : public IPC::MessageFilter { // Main thread's message loop. scoped_refptr<base::MessageLoopProxy> main_message_loop_; + /* + * Notice: Following members are designed to be accessed only on + * |main_message_loop_|. + */ // Keeps track of all MIDI clients. - // We map client to "client id" used to track permission. - // When access has been approved, we add the input and output ports to - // the client, allowing it to actually receive and send MIDI data. - typedef std::map<blink::WebMIDIAccessorClient*, int> ClientsMap; - ClientsMap clients_; - - // Dishes out client ids. - int next_available_id_; + typedef std::set<blink::WebMIDIAccessorClient*> ClientsSet; + ClientsSet clients_; + + // Represents clients that are waiting for a session being open. + typedef std::vector<blink::WebMIDIAccessorClient*> ClientsQueue; + ClientsQueue clients_waiting_session_queue_; + + // Represents a result on starting a session. Can be accessed only on + media::MidiResult session_result_; + + // Holds MidiPortInfoList for input ports and output ports. + media::MidiPortInfoList inputs_; + media::MidiPortInfoList outputs_; size_t unacknowledged_bytes_sent_; diff --git a/content/renderer/media/renderer_webmidiaccessor_impl.cc b/content/renderer/media/renderer_webmidiaccessor_impl.cc index 75b6b972..b67f48a 100644 --- a/content/renderer/media/renderer_webmidiaccessor_impl.cc +++ b/content/renderer/media/renderer_webmidiaccessor_impl.cc @@ -21,7 +21,7 @@ RendererWebMIDIAccessorImpl::~RendererWebMIDIAccessorImpl() { } void RendererWebMIDIAccessorImpl::startSession() { - midi_message_filter()->StartSession(client_); + midi_message_filter()->AddClient(client_); } void RendererWebMIDIAccessorImpl::sendMIDIData( diff --git a/media/midi/midi_manager.cc b/media/midi/midi_manager.cc index c53eef4..b0439ec 100644 --- a/media/midi/midi_manager.cc +++ b/media/midi/midi_manager.cc @@ -26,7 +26,7 @@ MidiManager* MidiManager::Create() { } #endif -void MidiManager::StartSession(MidiManagerClient* client, int client_id) { +void MidiManager::StartSession(MidiManagerClient* client) { bool session_is_ready; bool session_needs_initialization = false; bool too_many_pending_clients_exist = false; @@ -34,6 +34,12 @@ void MidiManager::StartSession(MidiManagerClient* client, int client_id) { { base::AutoLock auto_lock(lock_); session_is_ready = initialized_; + if (clients_.find(client) != clients_.end() || + pending_clients_.find(client) != pending_clients_.end()) { + // Should not happen. But just in case the renderer is compromised. + NOTREACHED(); + return; + } if (!session_is_ready) { // Do not accept a new request if the pending client list contains too // many clients. @@ -43,7 +49,7 @@ void MidiManager::StartSession(MidiManagerClient* client, int client_id) { if (!too_many_pending_clients_exist) { // Call StartInitialization() only for the first request. session_needs_initialization = pending_clients_.empty(); - pending_clients_.insert(std::make_pair(client, client_id)); + pending_clients_.insert(client); } } } @@ -58,7 +64,7 @@ void MidiManager::StartSession(MidiManagerClient* client, int client_id) { } if (too_many_pending_clients_exist) { // Return an error immediately if there are too many requests. - client->CompleteStartSession(client_id, MIDI_INITIALIZATION_ERROR); + client->CompleteStartSession(MIDI_INITIALIZATION_ERROR); return; } // CompleteInitialization() will be called asynchronously when platform @@ -75,10 +81,12 @@ void MidiManager::StartSession(MidiManagerClient* client, int client_id) { clients_.insert(client); result = result_; } - client->CompleteStartSession(client_id, result); + client->CompleteStartSession(result); } void MidiManager::EndSession(MidiManagerClient* client) { + // At this point, |client| can be in the destruction process, and calling + // any method of |client| is dangerous. base::AutoLock auto_lock(lock_); clients_.erase(client); pending_clients_.erase(client); @@ -121,8 +129,8 @@ void MidiManager::ReceiveMidiData( double timestamp) { base::AutoLock auto_lock(lock_); - for (ClientList::iterator i = clients_.begin(); i != clients_.end(); ++i) - (*i)->ReceiveMidiData(port_index, data, length, timestamp); + for (MidiManagerClient* client : clients_) + client->ReceiveMidiData(port_index, data, length, timestamp); } void MidiManager::CompleteInitializationInternal(MidiResult result) { @@ -134,12 +142,10 @@ void MidiManager::CompleteInitializationInternal(MidiResult result) { initialized_ = true; result_ = result; - for (PendingClientMap::iterator it = pending_clients_.begin(); - it != pending_clients_.end(); - ++it) { + for (MidiManagerClient* client : pending_clients_) { if (result_ == MIDI_OK) - clients_.insert(it->first); - it->first->CompleteStartSession(it->second, result_); + clients_.insert(client); + client->CompleteStartSession(result_); } pending_clients_.clear(); } diff --git a/media/midi/midi_manager.h b/media/midi/midi_manager.h index 9fd7a21..1e2bcb8 100644 --- a/media/midi/midi_manager.h +++ b/media/midi/midi_manager.h @@ -5,7 +5,6 @@ #ifndef MEDIA_MIDI_MIDI_MANAGER_H_ #define MEDIA_MIDI_MIDI_MANAGER_H_ -#include <map> #include <set> #include <vector> @@ -32,7 +31,7 @@ class MEDIA_EXPORT MidiManagerClient { // CompleteStartSession() is called when platform dependent preparation is // finished. - virtual void CompleteStartSession(int client_id, MidiResult result) = 0; + virtual void CompleteStartSession(MidiResult result) = 0; // ReceiveMidiData() is called when MIDI data has been received from the // MIDI system. @@ -71,7 +70,7 @@ class MEDIA_EXPORT MidiManager { // Otherwise CompleteStartSession() is called with proper MidiResult code. // StartSession() and EndSession() can be called on the Chrome_IOThread. // CompleteStartSession() will be invoked on the same Chrome_IOThread. - void StartSession(MidiManagerClient* client, int client_id); + void StartSession(MidiManagerClient* client); // A client calls EndSession() to stop receiving MIDI data. void EndSession(MidiManagerClient* client); @@ -148,12 +147,11 @@ class MEDIA_EXPORT MidiManager { void CompleteInitializationInternal(MidiResult result); // Keeps track of all clients who wish to receive MIDI data. - typedef std::set<MidiManagerClient*> ClientList; - ClientList clients_; + typedef std::set<MidiManagerClient*> ClientSet; + ClientSet clients_; // Keeps track of all clients who are waiting for CompleteStartSession(). - typedef std::multimap<MidiManagerClient*, int> PendingClientMap; - PendingClientMap pending_clients_; + ClientSet pending_clients_; // Keeps a SingleThreadTaskRunner of the thread that calls StartSession in // order to invoke CompleteStartSession() on the thread. diff --git a/media/midi/midi_manager_unittest.cc b/media/midi/midi_manager_unittest.cc index 846e1bc..4a52ca6 100644 --- a/media/midi/midi_manager_unittest.cc +++ b/media/midi/midi_manager_unittest.cc @@ -54,16 +54,14 @@ class FakeMidiManager : public MidiManager { class FakeMidiManagerClient : public MidiManagerClient { public: - explicit FakeMidiManagerClient(int client_id) - : client_id_(client_id), - result_(MIDI_NOT_SUPPORTED), + FakeMidiManagerClient() + : result_(MIDI_NOT_SUPPORTED), wait_for_result_(true) {} ~FakeMidiManagerClient() override {} // MidiManagerClient implementation. - void CompleteStartSession(int client_id, MidiResult result) override { + void CompleteStartSession(MidiResult result) override { EXPECT_TRUE(wait_for_result_); - CHECK_EQ(client_id_, client_id); result_ = result; wait_for_result_ = false; } @@ -74,7 +72,6 @@ class FakeMidiManagerClient : public MidiManagerClient { double timestamp) override {} void AccumulateMidiBytesSent(size_t size) override {} - int client_id() const { return client_id_; } MidiResult result() const { return result_; } MidiResult WaitForResult() { @@ -86,7 +83,6 @@ class FakeMidiManagerClient : public MidiManagerClient { } private: - int client_id_; MidiResult result_; bool wait_for_result_; @@ -105,7 +101,7 @@ class MidiManagerTest : public ::testing::Test { EXPECT_FALSE(manager_->start_initialization_is_called_); EXPECT_EQ(0U, manager_->GetClientCount()); EXPECT_EQ(0U, manager_->GetPendingClientCount()); - manager_->StartSession(client, client->client_id()); + manager_->StartSession(client); EXPECT_EQ(0U, manager_->GetClientCount()); EXPECT_EQ(1U, manager_->GetPendingClientCount()); EXPECT_TRUE(manager_->start_initialization_is_called_); @@ -122,7 +118,7 @@ class MidiManagerTest : public ::testing::Test { // StartInitialization() should not be called for the second and later // sessions. manager_->start_initialization_is_called_ = false; - manager_->StartSession(client, client->client_id()); + manager_->StartSession(client); EXPECT_EQ(nth == 1, manager_->start_initialization_is_called_); manager_->start_initialization_is_called_ = true; } @@ -153,7 +149,7 @@ class MidiManagerTest : public ::testing::Test { TEST_F(MidiManagerTest, StartAndEndSession) { scoped_ptr<FakeMidiManagerClient> client; - client.reset(new FakeMidiManagerClient(0)); + client.reset(new FakeMidiManagerClient); StartTheFirstSession(client.get()); CompleteInitialization(MIDI_OK); @@ -163,7 +159,7 @@ TEST_F(MidiManagerTest, StartAndEndSession) { TEST_F(MidiManagerTest, StartAndEndSessionWithError) { scoped_ptr<FakeMidiManagerClient> client; - client.reset(new FakeMidiManagerClient(1)); + client.reset(new FakeMidiManagerClient); StartTheFirstSession(client.get()); CompleteInitialization(MIDI_INITIALIZATION_ERROR); @@ -175,9 +171,9 @@ TEST_F(MidiManagerTest, StartMultipleSessions) { scoped_ptr<FakeMidiManagerClient> client1; scoped_ptr<FakeMidiManagerClient> client2; scoped_ptr<FakeMidiManagerClient> client3; - client1.reset(new FakeMidiManagerClient(0)); - client2.reset(new FakeMidiManagerClient(1)); - client3.reset(new FakeMidiManagerClient(1)); + client1.reset(new FakeMidiManagerClient); + client2.reset(new FakeMidiManagerClient); + client3.reset(new FakeMidiManagerClient); StartTheFirstSession(client1.get()); StartTheNthSession(client2.get(), 2); @@ -199,16 +195,15 @@ TEST_F(MidiManagerTest, TooManyPendingSessions) { ScopedVector<FakeMidiManagerClient> many_existing_clients; many_existing_clients.resize(MidiManager::kMaxPendingClientCount); for (size_t i = 0; i < MidiManager::kMaxPendingClientCount; ++i) { - many_existing_clients[i] = new FakeMidiManagerClient(i); + many_existing_clients[i] = new FakeMidiManagerClient; StartTheNthSession(many_existing_clients[i], i + 1); } // Push the last client that should be rejected for too many pending requests. scoped_ptr<FakeMidiManagerClient> additional_client( - new FakeMidiManagerClient(MidiManager::kMaxPendingClientCount)); + new FakeMidiManagerClient); manager_->start_initialization_is_called_ = false; - manager_->StartSession(additional_client.get(), - additional_client->client_id()); + manager_->StartSession(additional_client.get()); EXPECT_FALSE(manager_->start_initialization_is_called_); EXPECT_EQ(MIDI_INITIALIZATION_ERROR, additional_client->result()); @@ -232,7 +227,7 @@ TEST_F(MidiManagerTest, AbortSession) { // A client starting a session can be destructed while an asynchronous // initialization is performed. scoped_ptr<FakeMidiManagerClient> client; - client.reset(new FakeMidiManagerClient(0)); + client.reset(new FakeMidiManagerClient); StartTheFirstSession(client.get()); EndSession(client.get(), 0, 0); @@ -246,10 +241,10 @@ TEST_F(MidiManagerTest, AbortSession) { TEST_F(MidiManagerTest, CreateMidiManager) { scoped_ptr<FakeMidiManagerClient> client; - client.reset(new FakeMidiManagerClient(0)); + client.reset(new FakeMidiManagerClient); scoped_ptr<MidiManager> manager(MidiManager::Create()); - manager->StartSession(client.get(), client->client_id()); + manager->StartSession(client.get()); MidiResult result = client->WaitForResult(); // This #ifdef needs to be identical to the one in media/midi/midi_manager.cc. diff --git a/media/midi/midi_manager_usb_unittest.cc b/media/midi/midi_manager_usb_unittest.cc index b4f939f..1eeae9e 100644 --- a/media/midi/midi_manager_usb_unittest.cc +++ b/media/midi/midi_manager_usb_unittest.cc @@ -78,7 +78,7 @@ class FakeMidiManagerClient : public MidiManagerClient { logger_(logger) {} ~FakeMidiManagerClient() override {} - void CompleteStartSession(int client_id, MidiResult result) override { + void CompleteStartSession(MidiResult result) override { complete_start_session_ = true; result_ = result; } @@ -159,7 +159,7 @@ class MidiManagerUsbTest : public ::testing::Test { protected: void Initialize() { client_.reset(new FakeMidiManagerClient(&logger_)); - manager_->StartSession(client_.get(), 0); + manager_->StartSession(client_.get()); } void Finalize() { diff --git a/media/midi/midi_result.h b/media/midi/midi_result.h index 1e10401..2fb58a4 100644 --- a/media/midi/midi_result.h +++ b/media/midi/midi_result.h @@ -9,7 +9,8 @@ namespace media { // Result codes for MIDI. enum MidiResult { - MIDI_OK, + MIDI_NOT_INITIALIZED = -1, + MIDI_OK = 0, MIDI_NOT_SUPPORTED, MIDI_INITIALIZATION_ERROR, |