| // Copyright 2017 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/audio_ipc_factory.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/message_loop/message_loop.h" |
| #include "base/threading/thread.h" |
| #include "content/renderer/media/audio_message_filter.h" |
| #include "mojo/public/cpp/bindings/binding.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "mojo/public/cpp/system/message_pipe.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| |
| namespace content { |
| |
| namespace { |
| |
| const int kRenderFrameId = 0; |
| |
| std::unique_ptr<base::Thread> MakeIOThread() { |
| auto io_thread = base::MakeUnique<base::Thread>("test IO thread"); |
| base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); |
| CHECK(io_thread->StartWithOptions(thread_options)); |
| return io_thread; |
| } |
| |
| class FakeRemoteFactory : public mojom::RendererAudioOutputStreamFactory { |
| public: |
| FakeRemoteFactory() : binding_(this) {} |
| ~FakeRemoteFactory() override {} |
| |
| void RequestDeviceAuthorization( |
| media::mojom::AudioOutputStreamProviderRequest stream_provider, |
| int64_t session_id, |
| const std::string& device_id, |
| RequestDeviceAuthorizationCallback callback) override { |
| std::move(callback).Run( |
| media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, |
| media::AudioParameters::UnavailableDeviceParams(), std::string()); |
| EXPECT_FALSE(on_called_.is_null()); |
| std::move(on_called_).Run(); |
| } |
| |
| void SetOnCalledCallback(base::OnceClosure on_called) { |
| on_called_ = std::move(on_called); |
| } |
| |
| void Bind(mojo::ScopedMessagePipeHandle handle) { |
| EXPECT_FALSE(binding_.is_bound()); |
| binding_.Bind( |
| mojo::InterfaceRequest<mojom::RendererAudioOutputStreamFactory>( |
| std::move(handle))); |
| } |
| |
| private: |
| mojo::Binding<mojom::RendererAudioOutputStreamFactory> binding_; |
| base::OnceClosure on_called_; |
| }; |
| |
| class FakeAudioIPCDelegate : public media::AudioOutputIPCDelegate { |
| void OnError() override {} |
| void OnDeviceAuthorized(media::OutputDeviceStatus device_status, |
| const media::AudioParameters& output_params, |
| const std::string& matched_device_id) override {} |
| void OnStreamCreated(base::SharedMemoryHandle handle, |
| base::SyncSocket::Handle socket_handle, |
| int length) override {} |
| void OnIPCClosed() override {} |
| }; |
| |
| } // namespace |
| |
| class AudioIPCFactoryTest : public testing::Test { |
| public: |
| AudioIPCFactoryTest() {} |
| ~AudioIPCFactoryTest() override {} |
| |
| void RequestAuthorizationOnIOThread( |
| std::unique_ptr<media::AudioOutputIPC> output_ipc) { |
| output_ipc->RequestDeviceAuthorization(&fake_delegate, 0, "", |
| url::Origin()); |
| |
| output_ipc->CloseStream(); |
| } |
| |
| private: |
| FakeAudioIPCDelegate fake_delegate; |
| }; |
| |
| TEST_F(AudioIPCFactoryTest, CallFactoryFromIOThread) { |
| // This test makes sure that AudioIPCFactory correctly binds the |
| // RendererAudioOutputStreamFactoryPtr to the IO thread. |
| base::MessageLoop message_loop; |
| base::RunLoop run_loop; |
| auto io_thread = MakeIOThread(); |
| |
| FakeRemoteFactory remote_factory; |
| remote_factory.SetOnCalledCallback(run_loop.QuitWhenIdleClosure()); |
| |
| service_manager::InterfaceProvider interface_provider; |
| service_manager::InterfaceProvider::TestApi(&interface_provider) |
| .SetBinderForName(mojom::RendererAudioOutputStreamFactory::Name_, |
| base::BindRepeating(&FakeRemoteFactory::Bind, |
| base::Unretained(&remote_factory))); |
| |
| AudioIPCFactory ipc_factory(nullptr, io_thread->task_runner()); |
| |
| ipc_factory.MaybeRegisterRemoteFactory(kRenderFrameId, &interface_provider); |
| |
| // To make sure that the pointer stored in |ipc_factory| is connected to |
| // |remote_factory|, and also that it's bound to |io_thread|, we create an |
| // AudioOutputIPC object and request device authorization on the IO thread. |
| // This is supposed to call |remote_factory| on the main thread. |
| io_thread->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AudioIPCFactoryTest::RequestAuthorizationOnIOThread, |
| base::Unretained(this), |
| ipc_factory.CreateAudioOutputIPC(kRenderFrameId))); |
| |
| // Wait for call to |remote_factory|: |
| run_loop.Run(); |
| |
| ipc_factory.MaybeDeregisterRemoteFactory(0); |
| |
| io_thread.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(AudioIPCFactoryTest, SeveralFactories) { |
| // This test simulates having several frames being created and destructed. |
| base::MessageLoop message_loop; |
| auto io_thread = MakeIOThread(); |
| const int n_factories = 5; |
| |
| std::vector<service_manager::InterfaceProvider> interface_providers( |
| n_factories); |
| |
| std::vector<FakeRemoteFactory> remote_factories(n_factories); |
| |
| for (size_t i = 0; i < n_factories; i++) { |
| service_manager::InterfaceProvider::TestApi(&interface_providers[i]) |
| .SetBinderForName( |
| mojom::RendererAudioOutputStreamFactory::Name_, |
| base::BindRepeating(&FakeRemoteFactory::Bind, |
| base::Unretained(&remote_factories[i]))); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| AudioIPCFactory ipc_factory(nullptr, io_thread->task_runner()); |
| |
| for (size_t i = 0; i < n_factories; i++) { |
| ipc_factory.MaybeRegisterRemoteFactory(kRenderFrameId + i, |
| &interface_providers[i]); |
| } |
| |
| base::RunLoop run_loop; |
| remote_factories[0].SetOnCalledCallback(run_loop.QuitWhenIdleClosure()); |
| io_thread->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AudioIPCFactoryTest::RequestAuthorizationOnIOThread, |
| base::Unretained(this), |
| ipc_factory.CreateAudioOutputIPC(kRenderFrameId))); |
| run_loop.Run(); |
| |
| // Do some operation and make sure the internal state isn't messed up: |
| ipc_factory.MaybeDeregisterRemoteFactory(1); |
| |
| base::RunLoop run_loop2; |
| remote_factories[2].SetOnCalledCallback(run_loop2.QuitWhenIdleClosure()); |
| io_thread->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AudioIPCFactoryTest::RequestAuthorizationOnIOThread, |
| base::Unretained(this), |
| ipc_factory.CreateAudioOutputIPC(kRenderFrameId + 2))); |
| run_loop2.Run(); |
| |
| for (size_t i = 0; i < n_factories; i++) { |
| if (i == 1) |
| continue; |
| ipc_factory.MaybeDeregisterRemoteFactory(i); |
| } |
| |
| io_thread.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(AudioIPCFactoryTest, RegisterDeregisterBackToBack_Deregisters) { |
| // This test makes sure that calling Register... followed by Deregister... |
| // correctly sequences the registration before the deregistration. |
| base::MessageLoop message_loop; |
| auto io_thread = MakeIOThread(); |
| |
| FakeRemoteFactory remote_factory; |
| |
| service_manager::InterfaceProvider interface_provider; |
| service_manager::InterfaceProvider::TestApi(&interface_provider) |
| .SetBinderForName(mojom::RendererAudioOutputStreamFactory::Name_, |
| base::BindRepeating(&FakeRemoteFactory::Bind, |
| base::Unretained(&remote_factory))); |
| |
| AudioIPCFactory ipc_factory(nullptr, io_thread->task_runner()); |
| |
| ipc_factory.MaybeRegisterRemoteFactory(kRenderFrameId, &interface_provider); |
| ipc_factory.MaybeDeregisterRemoteFactory(kRenderFrameId); |
| // That there is no factory remaining at destruction is DCHECKed in the |
| // AudioIPCFactory destructor. |
| |
| base::RunLoop().RunUntilIdle(); |
| io_thread.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| } // namespace content |