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::RenderViewHost;
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->GetRenderViewHost()->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 RenderViewHost::AudioOutputControllerList& list) {
70 *audio_playing = true;
74 class WebrtcAudioPrivateTest : public AudioWaitingExtensionTest {
76 WebrtcAudioPrivateTest()
77 : enumeration_event_(false, false),
78 source_url_("chrome-extension://fakeid012345678/fakepage.html") {
82 std::string InvokeGetActiveSink(int tab_id) {
83 base::ListValue parameters;
84 parameters.AppendInteger(tab_id);
85 std::string parameter_string;
86 JSONWriter::Write(¶meters, ¶meter_string);
88 scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
89 new WebrtcAudioPrivateGetActiveSinkFunction();
90 function->set_source_url(source_url_);
91 scoped_ptr<base::Value> result(
92 RunFunctionAndReturnSingleResult(function.get(),
95 std::string device_id;
96 result->GetAsString(&device_id);
100 scoped_ptr<base::Value> InvokeGetSinks(base::ListValue** sink_list) {
101 scoped_refptr<WebrtcAudioPrivateGetSinksFunction> function =
102 new WebrtcAudioPrivateGetSinksFunction();
103 function->set_source_url(source_url_);
105 scoped_ptr<base::Value> result(
106 RunFunctionAndReturnSingleResult(function.get(), "[]", browser()));
107 result->GetAsList(sink_list);
108 return result.Pass();
111 // Synchronously (from the calling thread's point of view) runs the
112 // given enumeration function on the device thread. On return,
113 // |device_names| has been filled with the device names resulting
115 void GetAudioDeviceNames(
116 void (AudioManager::*EnumerationFunc)(AudioDeviceNames*),
117 AudioDeviceNames* device_names) {
118 AudioManager* audio_manager = AudioManager::Get();
120 if (!audio_manager->GetTaskRunner()->BelongsToCurrentThread()) {
121 audio_manager->GetTaskRunner()->PostTask(
123 base::Bind(&WebrtcAudioPrivateTest::GetAudioDeviceNames, this,
124 EnumerationFunc, device_names));
125 enumeration_event_.Wait();
127 (audio_manager->*EnumerationFunc)(device_names);
128 enumeration_event_.Signal();
132 // Synchronously (from the calling thread's point of view) retrieve the
133 // device id in the |origin| on the IO thread. On return,
134 // |id_in_origin| contains the id |raw_device_id| is known by in
136 void GetIDInOrigin(content::ResourceContext* resource_context,
138 const std::string& raw_device_id,
139 std::string* id_in_origin) {
140 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
141 content::BrowserThread::PostTask(
142 content::BrowserThread::IO, FROM_HERE,
143 base::Bind(&WebrtcAudioPrivateTest::GetIDInOrigin,
144 this, resource_context, origin, raw_device_id,
146 enumeration_event_.Wait();
148 *id_in_origin = content::GetHMACForMediaDeviceID(
149 resource_context->GetMediaDeviceIDSalt(),
152 enumeration_event_.Signal();
156 // Event used to signal completion of enumeration.
157 base::WaitableEvent enumeration_event_;
162 #if !defined(OS_MACOSX)
163 // http://crbug.com/334579
164 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetSinks) {
165 AudioDeviceNames devices;
166 GetAudioDeviceNames(&AudioManager::GetAudioOutputDeviceNames, &devices);
168 base::ListValue* sink_list = NULL;
169 scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);
171 std::string result_string;
172 JSONWriter::Write(result.get(), &result_string);
173 VLOG(2) << result_string;
175 EXPECT_EQ(devices.size(), sink_list->GetSize());
177 // Iterate through both lists in lockstep and compare. The order
178 // should be identical.
180 AudioDeviceNames::const_iterator it = devices.begin();
181 for (; ix < sink_list->GetSize() && it != devices.end();
183 base::DictionaryValue* dict = NULL;
184 sink_list->GetDictionary(ix, &dict);
186 dict->GetString("sinkId", &sink_id);
188 std::string expected_id;
189 if (it->unique_id.empty() ||
190 it->unique_id == media::AudioManagerBase::kDefaultDeviceId) {
191 expected_id = media::AudioManagerBase::kDefaultDeviceId;
193 GetIDInOrigin(profile()->GetResourceContext(),
194 source_url_.GetOrigin(),
199 EXPECT_EQ(expected_id, sink_id);
200 std::string sink_label;
201 dict->GetString("sinkLabel", &sink_label);
202 EXPECT_EQ(it->device_name, sink_label);
204 // TODO(joi): Verify the contents of these once we start actually
206 EXPECT_TRUE(dict->HasKey("isDefault"));
207 EXPECT_TRUE(dict->HasKey("isReady"));
208 EXPECT_TRUE(dict->HasKey("sampleRate"));
213 // This exercises the case where you have a tab with no active media
214 // stream and try to retrieve the currently active audio sink.
215 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetActiveSinkNoMediaStream) {
216 WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
217 int tab_id = ExtensionTabUtil::GetTabId(tab);
218 base::ListValue parameters;
219 parameters.AppendInteger(tab_id);
220 std::string parameter_string;
221 JSONWriter::Write(¶meters, ¶meter_string);
223 scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
224 new WebrtcAudioPrivateGetActiveSinkFunction();
225 function->set_source_url(source_url_);
226 scoped_ptr<base::Value> result(
227 RunFunctionAndReturnSingleResult(function.get(),
231 std::string result_string;
232 JSONWriter::Write(result.get(), &result_string);
233 EXPECT_EQ("\"\"", result_string);
236 // This exercises the case where you have a tab with no active media
237 // stream and try to set the audio sink.
238 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, SetActiveSinkNoMediaStream) {
239 WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
240 int tab_id = ExtensionTabUtil::GetTabId(tab);
241 base::ListValue parameters;
242 parameters.AppendInteger(tab_id);
243 parameters.AppendString("no such id");
244 std::string parameter_string;
245 JSONWriter::Write(¶meters, ¶meter_string);
247 scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
248 new WebrtcAudioPrivateSetActiveSinkFunction();
249 function->set_source_url(source_url_);
250 std::string error(RunFunctionAndReturnError(function.get(),
253 EXPECT_EQ(base::StringPrintf("No active stream for tab with id: %d.", tab_id),
257 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAndSetWithMediaStream) {
258 // First retrieve the list of all sinks, so that we can run a test
259 // where we set the active sink to each of the different available
261 base::ListValue* sink_list = NULL;
262 scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);
264 ASSERT_TRUE(StartEmbeddedTestServer());
266 // Open a normal page that uses an audio sink.
267 ui_test_utils::NavigateToURL(
269 GURL(embedded_test_server()->GetURL("/extensions/loop_audio.html")));
271 WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
272 int tab_id = ExtensionTabUtil::GetTabId(tab);
274 WaitUntilAudioIsPlaying(tab);
276 std::string current_device = InvokeGetActiveSink(tab_id);
277 VLOG(2) << "Before setting, current device: " << current_device;
278 EXPECT_NE("", current_device);
280 // Set to each of the other devices in turn.
281 for (size_t ix = 0; ix < sink_list->GetSize(); ++ix) {
282 base::DictionaryValue* dict = NULL;
283 sink_list->GetDictionary(ix, &dict);
284 std::string target_device;
285 dict->GetString("sinkId", &target_device);
287 base::ListValue parameters;
288 parameters.AppendInteger(tab_id);
289 parameters.AppendString(target_device);
290 std::string parameter_string;
291 JSONWriter::Write(¶meters, ¶meter_string);
293 scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
294 new WebrtcAudioPrivateSetActiveSinkFunction();
295 function->set_source_url(source_url_);
296 scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
297 function.get(), parameter_string, browser()));
298 // The function was successful if the above invocation doesn't
299 // fail. Just for kicks, also check that it returns no result.
300 EXPECT_EQ(NULL, result.get());
302 current_device = InvokeGetActiveSink(tab_id);
303 VLOG(2) << "After setting to " << target_device
304 << ", current device is " << current_device;
305 EXPECT_EQ(target_device, current_device);
309 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAssociatedSink) {
310 // Get the list of input devices. We can cheat in the unit test and
311 // run this on the main thread since nobody else will be running at
313 AudioDeviceNames devices;
314 GetAudioDeviceNames(&AudioManager::GetAudioInputDeviceNames, &devices);
316 // Try to get an associated sink for each source.
317 for (AudioDeviceNames::const_iterator device = devices.begin();
318 device != devices.end();
320 scoped_refptr<WebrtcAudioPrivateGetAssociatedSinkFunction> function =
321 new WebrtcAudioPrivateGetAssociatedSinkFunction();
322 function->set_source_url(source_url_);
324 std::string raw_device_id = device->unique_id;
325 VLOG(2) << "Trying to find associated sink for device " << raw_device_id;
326 std::string source_id_in_origin;
327 GURL origin(GURL("http://www.google.com/").GetOrigin());
328 GetIDInOrigin(profile()->GetResourceContext(),
331 &source_id_in_origin);
333 base::ListValue parameters;
334 parameters.AppendString(origin.spec());
335 parameters.AppendString(source_id_in_origin);
336 std::string parameter_string;
337 JSONWriter::Write(¶meters, ¶meter_string);
339 scoped_ptr<base::Value> result(
340 RunFunctionAndReturnSingleResult(function.get(),
343 std::string result_string;
344 JSONWriter::Write(result.get(), &result_string);
345 VLOG(2) << "Results: " << result_string;
349 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, TriggerEvent) {
350 WebrtcAudioPrivateEventService* service =
351 WebrtcAudioPrivateEventService::GetFactoryInstance()->GetForProfile(
354 // Just trigger, without any extension listening.
355 service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
357 // Now load our test extension and do it again.
358 const extensions::Extension* extension = LoadExtension(
359 test_data_dir_.AppendASCII("webrtc_audio_private_event_listener"));
360 service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
362 // Check that the extension got the notification.
363 std::string result = ExecuteScriptInBackgroundPage(extension->id(),
365 EXPECT_EQ("true", result);
368 class HangoutServicesBrowserTest : public AudioWaitingExtensionTest {
370 virtual void SetUp() OVERRIDE {
371 // Make sure the Hangout Services component extension gets loaded.
372 ComponentLoader::EnableBackgroundExtensionsForTesting();
373 AudioWaitingExtensionTest::SetUp();
377 #if defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
378 IN_PROC_BROWSER_TEST_F(HangoutServicesBrowserTest,
379 RunComponentExtensionTest) {
380 // This runs the end-to-end JavaScript test for the Hangout Services
381 // component extension, which uses the webrtcAudioPrivate API among
383 ASSERT_TRUE(StartEmbeddedTestServer());
384 GURL url(embedded_test_server()->GetURL(
385 "/extensions/hangout_services_test.html"));
386 // The "externally connectable" extension permission doesn't seem to
387 // like when we use 127.0.0.1 as the host, but using localhost works.
388 std::string url_spec = url.spec();
389 ReplaceFirstSubstringAfterOffset(&url_spec, 0, "127.0.0.1", "localhost");
390 GURL localhost_url(url_spec);
391 ui_test_utils::NavigateToURL(browser(), localhost_url);
393 WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
394 WaitUntilAudioIsPlaying(tab);
396 // Override, i.e. disable, uploading. We don't want to try sending data to
397 // servers when running the test. We don't bother about the contents of the
398 // buffer |dummy|, that's tested in other tests.
400 g_browser_process->webrtc_log_uploader()->
401 OverrideUploadWithBufferForTesting(&dummy);
403 ASSERT_TRUE(content::ExecuteScript(tab, "browsertestRunAllTests();"));
405 content::TitleWatcher title_watcher(tab, base::ASCIIToUTF16("success"));
406 title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("failure"));
407 base::string16 result = title_watcher.WaitAndGetTitle();
408 EXPECT_EQ(base::ASCIIToUTF16("success"), result);
410 #endif // defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
412 } // namespace extensions