1 // Copyright 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.
5 #include "base/json/json_writer.h"
6 #include "base/strings/string_util.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/synchronization/waitable_event.h"
10 #include "base/threading/platform_thread.h"
11 #include "base/time/time.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
14 #include "chrome/browser/extensions/component_loader.h"
15 #include "chrome/browser/extensions/extension_apitest.h"
16 #include "chrome/browser/extensions/extension_function_test_utils.h"
17 #include "chrome/browser/extensions/extension_tab_util.h"
18 #include "chrome/browser/media/webrtc_log_uploader.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/test/base/in_process_browser_test.h"
22 #include "chrome/test/base/ui_test_utils.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/media_device_id.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/test/browser_test_utils.h"
27 #include "extensions/common/permissions/permission_set.h"
28 #include "extensions/common/permissions/permissions_data.h"
29 #include "media/audio/audio_manager.h"
30 #include "media/audio/audio_manager_base.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "testing/gtest/include/gtest/gtest.h"
34 using base::JSONWriter
;
35 using content::RenderProcessHost
;
36 using content::WebContents
;
37 using media::AudioDeviceNames
;
38 using media::AudioManager
;
40 namespace extensions
{
42 using extension_function_test_utils::RunFunctionAndReturnError
;
43 using extension_function_test_utils::RunFunctionAndReturnSingleResult
;
45 class AudioWaitingExtensionTest
: public ExtensionApiTest
{
47 void WaitUntilAudioIsPlaying(WebContents
* tab
) {
48 // Wait for audio to start playing. We gate this on there being one
49 // or more AudioOutputController objects for our tab.
50 bool audio_playing
= false;
51 for (size_t remaining_tries
= 50; remaining_tries
> 0; --remaining_tries
) {
52 tab
->GetRenderProcessHost()->GetAudioOutputControllers(
53 base::Bind(OnAudioControllers
, &audio_playing
));
54 base::MessageLoop::current()->RunUntilIdle();
58 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
62 FAIL() << "Audio did not start playing within ~5 seconds.";
65 // Used by the test above to wait until audio is playing.
66 static void OnAudioControllers(
68 const RenderProcessHost::AudioOutputControllerList
& list
) {
70 *audio_playing
= true;
74 class WebrtcAudioPrivateTest
: public AudioWaitingExtensionTest
{
76 WebrtcAudioPrivateTest()
77 : enumeration_event_(false, false) {
80 void SetUpOnMainThread() override
{
81 AudioWaitingExtensionTest::SetUpOnMainThread();
82 // Needs to happen after chrome's schemes are added.
83 source_url_
= GURL("chrome-extension://fakeid012345678/fakepage.html");
87 void AppendTabIdToRequestInfo(base::ListValue
* params
, int tab_id
) {
88 base::DictionaryValue
* request_info
= new base::DictionaryValue();
89 request_info
->SetInteger("tabId", tab_id
);
90 params
->Append(request_info
);
93 std::string
InvokeGetActiveSink(int tab_id
) {
94 base::ListValue parameters
;
95 AppendTabIdToRequestInfo(¶meters
, tab_id
);
96 std::string parameter_string
;
97 JSONWriter::Write(parameters
, ¶meter_string
);
99 scoped_refptr
<WebrtcAudioPrivateGetActiveSinkFunction
> function
=
100 new WebrtcAudioPrivateGetActiveSinkFunction();
101 function
->set_source_url(source_url_
);
102 scoped_ptr
<base::Value
> result(
103 RunFunctionAndReturnSingleResult(function
.get(),
106 std::string device_id
;
107 result
->GetAsString(&device_id
);
111 scoped_ptr
<base::Value
> InvokeGetSinks(base::ListValue
** sink_list
) {
112 scoped_refptr
<WebrtcAudioPrivateGetSinksFunction
> function
=
113 new WebrtcAudioPrivateGetSinksFunction();
114 function
->set_source_url(source_url_
);
116 scoped_ptr
<base::Value
> result(
117 RunFunctionAndReturnSingleResult(function
.get(), "[]", browser()));
118 result
->GetAsList(sink_list
);
119 return result
.Pass();
122 // Synchronously (from the calling thread's point of view) runs the
123 // given enumeration function on the device thread. On return,
124 // |device_names| has been filled with the device names resulting
126 void GetAudioDeviceNames(
127 void (AudioManager::*EnumerationFunc
)(AudioDeviceNames
*),
128 AudioDeviceNames
* device_names
) {
129 AudioManager
* audio_manager
= AudioManager::Get();
131 if (!audio_manager
->GetWorkerTaskRunner()->BelongsToCurrentThread()) {
132 audio_manager
->GetWorkerTaskRunner()->PostTask(
134 base::Bind(&WebrtcAudioPrivateTest::GetAudioDeviceNames
, this,
135 EnumerationFunc
, device_names
));
136 enumeration_event_
.Wait();
138 (audio_manager
->*EnumerationFunc
)(device_names
);
139 enumeration_event_
.Signal();
143 // Synchronously (from the calling thread's point of view) retrieve the
144 // device id in the |origin| on the IO thread. On return,
145 // |id_in_origin| contains the id |raw_device_id| is known by in
147 void GetIDInOrigin(content::ResourceContext
* resource_context
,
149 const std::string
& raw_device_id
,
150 std::string
* id_in_origin
) {
151 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
)) {
152 content::BrowserThread::PostTask(
153 content::BrowserThread::IO
, FROM_HERE
,
154 base::Bind(&WebrtcAudioPrivateTest::GetIDInOrigin
,
155 this, resource_context
, origin
, raw_device_id
,
157 enumeration_event_
.Wait();
159 *id_in_origin
= content::GetHMACForMediaDeviceID(
160 resource_context
->GetMediaDeviceIDSalt(),
163 enumeration_event_
.Signal();
167 // Event used to signal completion of enumeration.
168 base::WaitableEvent enumeration_event_
;
173 #if !defined(OS_MACOSX)
174 // http://crbug.com/334579
175 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, GetSinks
) {
176 AudioDeviceNames devices
;
177 GetAudioDeviceNames(&AudioManager::GetAudioOutputDeviceNames
, &devices
);
179 base::ListValue
* sink_list
= NULL
;
180 scoped_ptr
<base::Value
> result
= InvokeGetSinks(&sink_list
);
182 std::string result_string
;
183 JSONWriter::Write(*result
, &result_string
);
184 VLOG(2) << result_string
;
186 EXPECT_EQ(devices
.size(), sink_list
->GetSize());
188 // Iterate through both lists in lockstep and compare. The order
189 // should be identical.
191 AudioDeviceNames::const_iterator it
= devices
.begin();
192 for (; ix
< sink_list
->GetSize() && it
!= devices
.end();
194 base::DictionaryValue
* dict
= NULL
;
195 sink_list
->GetDictionary(ix
, &dict
);
197 dict
->GetString("sinkId", &sink_id
);
199 std::string expected_id
;
200 if (it
->unique_id
.empty() ||
201 it
->unique_id
== media::AudioManagerBase::kDefaultDeviceId
) {
202 expected_id
= media::AudioManagerBase::kDefaultDeviceId
;
204 GetIDInOrigin(profile()->GetResourceContext(),
205 source_url_
.GetOrigin(),
210 EXPECT_EQ(expected_id
, sink_id
);
211 std::string sink_label
;
212 dict
->GetString("sinkLabel", &sink_label
);
213 EXPECT_EQ(it
->device_name
, sink_label
);
215 // TODO(joi): Verify the contents of these once we start actually
217 EXPECT_TRUE(dict
->HasKey("isDefault"));
218 EXPECT_TRUE(dict
->HasKey("isReady"));
219 EXPECT_TRUE(dict
->HasKey("sampleRate"));
224 // This exercises the case where you have a tab with no active media
225 // stream and try to retrieve the currently active audio sink.
226 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, GetActiveSinkNoMediaStream
) {
227 WebContents
* tab
= browser()->tab_strip_model()->GetActiveWebContents();
228 int tab_id
= ExtensionTabUtil::GetTabId(tab
);
229 base::ListValue parameters
;
230 AppendTabIdToRequestInfo(¶meters
, tab_id
);
231 std::string parameter_string
;
232 JSONWriter::Write(parameters
, ¶meter_string
);
234 scoped_refptr
<WebrtcAudioPrivateGetActiveSinkFunction
> function
=
235 new WebrtcAudioPrivateGetActiveSinkFunction();
236 function
->set_source_url(source_url_
);
237 scoped_ptr
<base::Value
> result(
238 RunFunctionAndReturnSingleResult(function
.get(),
242 std::string result_string
;
243 JSONWriter::Write(*result
, &result_string
);
244 EXPECT_EQ("\"\"", result_string
);
247 // This exercises the case where you have a tab with no active media
248 // stream and try to set the audio sink.
249 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, SetActiveSinkNoMediaStream
) {
250 WebContents
* tab
= browser()->tab_strip_model()->GetActiveWebContents();
251 int tab_id
= ExtensionTabUtil::GetTabId(tab
);
252 base::ListValue parameters
;
253 AppendTabIdToRequestInfo(¶meters
, tab_id
);
254 parameters
.AppendString("no such id");
255 std::string parameter_string
;
256 JSONWriter::Write(parameters
, ¶meter_string
);
258 scoped_refptr
<WebrtcAudioPrivateSetActiveSinkFunction
> function
=
259 new WebrtcAudioPrivateSetActiveSinkFunction();
260 function
->set_source_url(source_url_
);
261 std::string
error(RunFunctionAndReturnError(function
.get(),
264 EXPECT_EQ(base::StringPrintf("No active stream for tabId %d", tab_id
),
268 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, GetAndSetWithMediaStream
) {
269 // First retrieve the list of all sinks, so that we can run a test
270 // where we set the active sink to each of the different available
272 base::ListValue
* sink_list
= NULL
;
273 scoped_ptr
<base::Value
> result
= InvokeGetSinks(&sink_list
);
275 ASSERT_TRUE(StartEmbeddedTestServer());
277 // Open a normal page that uses an audio sink.
278 ui_test_utils::NavigateToURL(
280 GURL(embedded_test_server()->GetURL("/extensions/loop_audio.html")));
282 WebContents
* tab
= browser()->tab_strip_model()->GetActiveWebContents();
283 int tab_id
= ExtensionTabUtil::GetTabId(tab
);
285 WaitUntilAudioIsPlaying(tab
);
287 std::string current_device
= InvokeGetActiveSink(tab_id
);
288 VLOG(2) << "Before setting, current device: " << current_device
;
289 EXPECT_NE("", current_device
);
291 // Set to each of the other devices in turn.
292 for (size_t ix
= 0; ix
< sink_list
->GetSize(); ++ix
) {
293 base::DictionaryValue
* dict
= NULL
;
294 sink_list
->GetDictionary(ix
, &dict
);
295 std::string target_device
;
296 dict
->GetString("sinkId", &target_device
);
298 base::ListValue parameters
;
299 AppendTabIdToRequestInfo(¶meters
, tab_id
);
300 parameters
.AppendString(target_device
);
301 std::string parameter_string
;
302 JSONWriter::Write(parameters
, ¶meter_string
);
304 scoped_refptr
<WebrtcAudioPrivateSetActiveSinkFunction
> function
=
305 new WebrtcAudioPrivateSetActiveSinkFunction();
306 function
->set_source_url(source_url_
);
307 scoped_ptr
<base::Value
> result(RunFunctionAndReturnSingleResult(
308 function
.get(), parameter_string
, browser()));
309 // The function was successful if the above invocation doesn't
310 // fail. Just for kicks, also check that it returns no result.
311 EXPECT_EQ(NULL
, result
.get());
313 current_device
= InvokeGetActiveSink(tab_id
);
314 VLOG(2) << "After setting to " << target_device
315 << ", current device is " << current_device
;
316 EXPECT_EQ(target_device
, current_device
);
320 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, GetAssociatedSink
) {
321 // Get the list of input devices. We can cheat in the unit test and
322 // run this on the main thread since nobody else will be running at
324 AudioDeviceNames devices
;
325 GetAudioDeviceNames(&AudioManager::GetAudioInputDeviceNames
, &devices
);
327 // Try to get an associated sink for each source.
328 for (AudioDeviceNames::const_iterator device
= devices
.begin();
329 device
!= devices
.end();
331 scoped_refptr
<WebrtcAudioPrivateGetAssociatedSinkFunction
> function
=
332 new WebrtcAudioPrivateGetAssociatedSinkFunction();
333 function
->set_source_url(source_url_
);
335 std::string raw_device_id
= device
->unique_id
;
336 VLOG(2) << "Trying to find associated sink for device " << raw_device_id
;
337 std::string source_id_in_origin
;
338 GURL
origin(GURL("http://www.google.com/").GetOrigin());
339 GetIDInOrigin(profile()->GetResourceContext(),
342 &source_id_in_origin
);
344 base::ListValue parameters
;
345 parameters
.AppendString(origin
.spec());
346 parameters
.AppendString(source_id_in_origin
);
347 std::string parameter_string
;
348 JSONWriter::Write(parameters
, ¶meter_string
);
350 scoped_ptr
<base::Value
> result(
351 RunFunctionAndReturnSingleResult(function
.get(),
354 std::string result_string
;
355 JSONWriter::Write(*result
, &result_string
);
356 VLOG(2) << "Results: " << result_string
;
360 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest
, TriggerEvent
) {
361 WebrtcAudioPrivateEventService
* service
=
362 WebrtcAudioPrivateEventService::GetFactoryInstance()->Get(profile());
364 // Just trigger, without any extension listening.
365 service
->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE
);
367 // Now load our test extension and do it again.
368 const extensions::Extension
* extension
= LoadExtension(
369 test_data_dir_
.AppendASCII("webrtc_audio_private_event_listener"));
370 service
->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE
);
372 // Check that the extension got the notification.
373 std::string result
= ExecuteScriptInBackgroundPage(extension
->id(),
375 EXPECT_EQ("true", result
);
378 class HangoutServicesBrowserTest
: public AudioWaitingExtensionTest
{
380 void SetUp() override
{
381 // Make sure the Hangout Services component extension gets loaded.
382 ComponentLoader::EnableBackgroundExtensionsForTesting();
383 AudioWaitingExtensionTest::SetUp();
387 #if defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
388 IN_PROC_BROWSER_TEST_F(HangoutServicesBrowserTest
,
389 RunComponentExtensionTest
) {
390 // This runs the end-to-end JavaScript test for the Hangout Services
391 // component extension, which uses the webrtcAudioPrivate API among
393 ASSERT_TRUE(StartEmbeddedTestServer());
394 GURL
url(embedded_test_server()->GetURL(
395 "/extensions/hangout_services_test.html"));
396 // The "externally connectable" extension permission doesn't seem to
397 // like when we use 127.0.0.1 as the host, but using localhost works.
398 std::string url_spec
= url
.spec();
399 base::ReplaceFirstSubstringAfterOffset(
400 &url_spec
, 0, "127.0.0.1", "localhost");
401 GURL
localhost_url(url_spec
);
402 ui_test_utils::NavigateToURL(browser(), localhost_url
);
404 WebContents
* tab
= browser()->tab_strip_model()->GetActiveWebContents();
405 WaitUntilAudioIsPlaying(tab
);
407 // Override, i.e. disable, uploading. We don't want to try sending data to
408 // servers when running the test. We don't bother about the contents of the
409 // buffer |dummy|, that's tested in other tests.
411 g_browser_process
->webrtc_log_uploader()->
412 OverrideUploadWithBufferForTesting(&dummy
);
414 ASSERT_TRUE(content::ExecuteScript(tab
, "browsertestRunAllTests();"));
416 content::TitleWatcher
title_watcher(tab
, base::ASCIIToUTF16("success"));
417 title_watcher
.AlsoWaitForTitle(base::ASCIIToUTF16("failure"));
418 base::string16 result
= title_watcher
.WaitAndGetTitle();
419 EXPECT_EQ(base::ASCIIToUTF16("success"), result
);
421 g_browser_process
->webrtc_log_uploader()->OverrideUploadWithBufferForTesting(
424 #endif // defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
426 } // namespace extensions