diff --git a/content/browser/renderer_host/media/audio_input_device_manager.cc b/content/browser/renderer_host/media/audio_input_device_manager.cc index 2723decb20666..255dc5057bf0d 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager.cc +++ b/content/browser/renderer_host/media/audio_input_device_manager.cc @@ -110,6 +110,7 @@ base::UnguessableToken AudioInputDeviceManager::Open( // Generate a new id for this device. auto session_id = base::UnguessableToken::Create(); SendAudioLogMessage(GetOpenLogString(session_id, device)); + pending_open_sessions_.insert(session_id); // base::Unretained(this) is safe, because AudioInputDeviceManager is // destroyed not earlier than on the IO message loop destruction. @@ -143,8 +144,12 @@ void AudioInputDeviceManager::Close(const base::UnguessableToken& session_id) { DCHECK_CURRENTLY_ON(BrowserThread::IO); SendAudioLogMessage("Close({session_id=" + session_id.ToString() + "})"); auto device = GetDevice(session_id); - if (device == devices_.end()) + if (device == devices_.end()) { + // The asynchronous device query started by Open() may not have completed + // yet. Drop the session so that OpenedOnIOThread() does not register it. + pending_open_sessions_.erase(session_id); return; + } const blink::mojom::MediaStreamType stream_type = device->type; devices_.erase(device); @@ -165,6 +170,11 @@ void AudioInputDeviceManager::OpenedOnIOThread( DCHECK(!input_params || input_params->IsValid()); DCHECK(!matched_output_device_id || !matched_output_device_id->empty()); + if (!pending_open_sessions_.erase(session_id)) { + // The session was closed while the device query was in flight. + return; + } + SendAudioLogMessage("Opened({session_id=" + session_id.ToString() + "})"); blink::MediaStreamDevice media_stream_device(device.type, device.id, device.name); diff --git a/content/browser/renderer_host/media/audio_input_device_manager.h b/content/browser/renderer_host/media/audio_input_device_manager.h index a632e8bdc96af..4459034373da6 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager.h +++ b/content/browser/renderer_host/media/audio_input_device_manager.h @@ -16,6 +16,7 @@ #include #include +#include "base/containers/flat_set.h" #include "base/memory/raw_ptr.h" #include "base/observer_list.h" #include "base/threading/thread.h" @@ -74,6 +75,12 @@ class CONTENT_EXPORT AudioInputDeviceManager : public MediaStreamProvider { base::ObserverList::Unchecked listeners_; blink::MediaStreamDevices devices_; + // Sessions for which Open() has been called and the asynchronous device + // query is still in flight. A session is removed from this set either when + // OpenedOnIOThread() runs or when Close() is called for it before that + // happens, in which case OpenedOnIOThread() will discard the result. + base::flat_set pending_open_sessions_; + const raw_ptr audio_system_; }; diff --git a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc index 231e9b7dd416c..595afd5528fa3 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc +++ b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc @@ -28,6 +28,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/mediastream/media_stream_request.h" +using testing::_; using testing::InSequence; namespace content { @@ -378,4 +379,44 @@ TEST_F(AudioInputDeviceManagerNoDevicesTest, } } +// Closes a session while its asynchronous open is still pending. The pending +// open should be aborted and the session should never be registered. +TEST_F(AudioInputDeviceManagerNoDevicesTest, CloseWhileOpenIsPending) { + ASSERT_FALSE(devices_.empty()); + + base::UnguessableToken session_id = manager_->Open(devices_.front()); + manager_->Close(session_id); + + EXPECT_CALL(*audio_input_listener_, Opened(_, session_id)).Times(0); + WaitForOpenCompletion(); + + EXPECT_EQ(nullptr, manager_->GetOpenedDeviceById(session_id)); +} + +// Closes one of two pending sessions. The other session should still open +// normally. +TEST_F(AudioInputDeviceManagerNoDevicesTest, + CloseOneOfMultiplePendingSessions) { + ASSERT_GE(devices_.size(), 2u); + + base::UnguessableToken first_session_id = manager_->Open(devices_[0]); + base::UnguessableToken second_session_id = manager_->Open(devices_[1]); + manager_->Close(first_session_id); + + EXPECT_CALL(*audio_input_listener_, Opened(_, first_session_id)).Times(0); + EXPECT_CALL(*audio_input_listener_, + Opened(devices_[1].type, second_session_id)) + .Times(1); + WaitForOpenCompletion(); + + EXPECT_EQ(nullptr, manager_->GetOpenedDeviceById(first_session_id)); + EXPECT_NE(nullptr, manager_->GetOpenedDeviceById(second_session_id)); + + manager_->Close(second_session_id); + EXPECT_CALL(*audio_input_listener_, + Closed(devices_[1].type, second_session_id)) + .Times(1); + base::RunLoop().RunUntilIdle(); +} + } // namespace content