root/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. WaitUntilAudioIsPlaying
  2. OnAudioControllers
  3. SetUpOnMainThread
  4. InvokeGetActiveSink
  5. InvokeGetSinks
  6. GetAudioDeviceNames
  7. GetIDInOrigin
  8. IN_PROC_BROWSER_TEST_F
  9. IN_PROC_BROWSER_TEST_F
  10. IN_PROC_BROWSER_TEST_F
  11. IN_PROC_BROWSER_TEST_F
  12. IN_PROC_BROWSER_TEST_F
  13. IN_PROC_BROWSER_TEST_F
  14. SetUp
  15. IN_PROC_BROWSER_TEST_F

// Copyright 2013 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 "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/media/webrtc_log_uploader.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::JSONWriter;
using content::RenderViewHost;
using content::WebContents;
using media::AudioDeviceNames;
using media::AudioManager;

namespace extensions {

using extension_function_test_utils::RunFunctionAndReturnError;
using extension_function_test_utils::RunFunctionAndReturnSingleResult;

class AudioWaitingExtensionTest : public ExtensionApiTest {
 protected:
  void WaitUntilAudioIsPlaying(WebContents* tab) {
    // Wait for audio to start playing. We gate this on there being one
    // or more AudioOutputController objects for our tab.
    bool audio_playing = false;
    for (size_t remaining_tries = 50; remaining_tries > 0; --remaining_tries) {
      tab->GetRenderViewHost()->GetAudioOutputControllers(
          base::Bind(OnAudioControllers, &audio_playing));
      base::MessageLoop::current()->RunUntilIdle();
      if (audio_playing)
        break;

      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
    }

    if (!audio_playing)
      FAIL() << "Audio did not start playing within ~5 seconds.";
  }

  // Used by the test above to wait until audio is playing.
  static void OnAudioControllers(
      bool* audio_playing,
      const RenderViewHost::AudioOutputControllerList& list) {
    if (!list.empty())
      *audio_playing = true;
  }
};

class WebrtcAudioPrivateTest : public AudioWaitingExtensionTest {
 public:
  WebrtcAudioPrivateTest()
      : enumeration_event_(false, false) {
  }

  virtual void SetUpOnMainThread() OVERRIDE {
    AudioWaitingExtensionTest::SetUpOnMainThread();
    // Needs to happen after chrome's schemes are added.
    source_url_ = GURL("chrome-extension://fakeid012345678/fakepage.html");
  }

 protected:
  std::string InvokeGetActiveSink(int tab_id) {
    base::ListValue parameters;
    parameters.AppendInteger(tab_id);
    std::string parameter_string;
    JSONWriter::Write(&parameters, &parameter_string);

    scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
        new WebrtcAudioPrivateGetActiveSinkFunction();
    function->set_source_url(source_url_);
    scoped_ptr<base::Value> result(
        RunFunctionAndReturnSingleResult(function.get(),
                                         parameter_string,
                                         browser()));
    std::string device_id;
    result->GetAsString(&device_id);
    return device_id;
  }

  scoped_ptr<base::Value> InvokeGetSinks(base::ListValue** sink_list) {
    scoped_refptr<WebrtcAudioPrivateGetSinksFunction> function =
        new WebrtcAudioPrivateGetSinksFunction();
    function->set_source_url(source_url_);

    scoped_ptr<base::Value> result(
        RunFunctionAndReturnSingleResult(function.get(), "[]", browser()));
    result->GetAsList(sink_list);
    return result.Pass();
  }

  // Synchronously (from the calling thread's point of view) runs the
  // given enumeration function on the device thread. On return,
  // |device_names| has been filled with the device names resulting
  // from that call.
  void GetAudioDeviceNames(
      void (AudioManager::*EnumerationFunc)(AudioDeviceNames*),
      AudioDeviceNames* device_names) {
    AudioManager* audio_manager = AudioManager::Get();

    if (!audio_manager->GetTaskRunner()->BelongsToCurrentThread()) {
      audio_manager->GetTaskRunner()->PostTask(
          FROM_HERE,
          base::Bind(&WebrtcAudioPrivateTest::GetAudioDeviceNames, this,
                     EnumerationFunc, device_names));
      enumeration_event_.Wait();
    } else {
      (audio_manager->*EnumerationFunc)(device_names);
      enumeration_event_.Signal();
    }
  }

  // Synchronously (from the calling thread's point of view) retrieve the
  // device id in the |origin| on the IO thread. On return,
  // |id_in_origin| contains the id |raw_device_id| is known by in
  // the origin.
  void GetIDInOrigin(content::ResourceContext* resource_context,
                     GURL origin,
                     const std::string& raw_device_id,
                     std::string* id_in_origin) {
    if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
      content::BrowserThread::PostTask(
          content::BrowserThread::IO, FROM_HERE,
          base::Bind(&WebrtcAudioPrivateTest::GetIDInOrigin,
                     this, resource_context, origin, raw_device_id,
                     id_in_origin));
      enumeration_event_.Wait();
    } else {
      *id_in_origin = content::GetHMACForMediaDeviceID(
          resource_context->GetMediaDeviceIDSalt(),
          origin,
          raw_device_id);
      enumeration_event_.Signal();
    }
  }

  // Event used to signal completion of enumeration.
  base::WaitableEvent enumeration_event_;

  GURL source_url_;
};

#if !defined(OS_MACOSX)
// http://crbug.com/334579
IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetSinks) {
  AudioDeviceNames devices;
  GetAudioDeviceNames(&AudioManager::GetAudioOutputDeviceNames, &devices);

  base::ListValue* sink_list = NULL;
  scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);

  std::string result_string;
  JSONWriter::Write(result.get(), &result_string);
  VLOG(2) << result_string;

  EXPECT_EQ(devices.size(), sink_list->GetSize());

  // Iterate through both lists in lockstep and compare. The order
  // should be identical.
  size_t ix = 0;
  AudioDeviceNames::const_iterator it = devices.begin();
  for (; ix < sink_list->GetSize() && it != devices.end();
       ++ix, ++it) {
    base::DictionaryValue* dict = NULL;
    sink_list->GetDictionary(ix, &dict);
    std::string sink_id;
    dict->GetString("sinkId", &sink_id);

    std::string expected_id;
    if (it->unique_id.empty() ||
        it->unique_id == media::AudioManagerBase::kDefaultDeviceId) {
      expected_id = media::AudioManagerBase::kDefaultDeviceId;
    } else {
      GetIDInOrigin(profile()->GetResourceContext(),
                    source_url_.GetOrigin(),
                    it->unique_id,
                    &expected_id);
    }

    EXPECT_EQ(expected_id, sink_id);
    std::string sink_label;
    dict->GetString("sinkLabel", &sink_label);
    EXPECT_EQ(it->device_name, sink_label);

    // TODO(joi): Verify the contents of these once we start actually
    // filling them in.
    EXPECT_TRUE(dict->HasKey("isDefault"));
    EXPECT_TRUE(dict->HasKey("isReady"));
    EXPECT_TRUE(dict->HasKey("sampleRate"));
  }
}
#endif  // OS_MACOSX

// This exercises the case where you have a tab with no active media
// stream and try to retrieve the currently active audio sink.
IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetActiveSinkNoMediaStream) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  int tab_id = ExtensionTabUtil::GetTabId(tab);
  base::ListValue parameters;
  parameters.AppendInteger(tab_id);
  std::string parameter_string;
  JSONWriter::Write(&parameters, &parameter_string);

  scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
      new WebrtcAudioPrivateGetActiveSinkFunction();
  function->set_source_url(source_url_);
  scoped_ptr<base::Value> result(
      RunFunctionAndReturnSingleResult(function.get(),
                                       parameter_string,
                                       browser()));

  std::string result_string;
  JSONWriter::Write(result.get(), &result_string);
  EXPECT_EQ("\"\"", result_string);
}

// This exercises the case where you have a tab with no active media
// stream and try to set the audio sink.
IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, SetActiveSinkNoMediaStream) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  int tab_id = ExtensionTabUtil::GetTabId(tab);
  base::ListValue parameters;
  parameters.AppendInteger(tab_id);
  parameters.AppendString("no such id");
  std::string parameter_string;
  JSONWriter::Write(&parameters, &parameter_string);

  scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
      new WebrtcAudioPrivateSetActiveSinkFunction();
  function->set_source_url(source_url_);
  std::string error(RunFunctionAndReturnError(function.get(),
                                              parameter_string,
                                              browser()));
  EXPECT_EQ(base::StringPrintf("No active stream for tab with id: %d.", tab_id),
            error);
}

IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAndSetWithMediaStream) {
  // First retrieve the list of all sinks, so that we can run a test
  // where we set the active sink to each of the different available
  // sinks in turn.
  base::ListValue* sink_list = NULL;
  scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);

  ASSERT_TRUE(StartEmbeddedTestServer());

  // Open a normal page that uses an audio sink.
  ui_test_utils::NavigateToURL(
      browser(),
      GURL(embedded_test_server()->GetURL("/extensions/loop_audio.html")));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  int tab_id = ExtensionTabUtil::GetTabId(tab);

  WaitUntilAudioIsPlaying(tab);

  std::string current_device = InvokeGetActiveSink(tab_id);
  VLOG(2) << "Before setting, current device: " << current_device;
  EXPECT_NE("", current_device);

  // Set to each of the other devices in turn.
  for (size_t ix = 0; ix < sink_list->GetSize(); ++ix) {
    base::DictionaryValue* dict = NULL;
    sink_list->GetDictionary(ix, &dict);
    std::string target_device;
    dict->GetString("sinkId", &target_device);

    base::ListValue parameters;
    parameters.AppendInteger(tab_id);
    parameters.AppendString(target_device);
    std::string parameter_string;
    JSONWriter::Write(&parameters, &parameter_string);

    scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
      new WebrtcAudioPrivateSetActiveSinkFunction();
    function->set_source_url(source_url_);
    scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
        function.get(), parameter_string, browser()));
    // The function was successful if the above invocation doesn't
    // fail. Just for kicks, also check that it returns no result.
    EXPECT_EQ(NULL, result.get());

    current_device = InvokeGetActiveSink(tab_id);
    VLOG(2) << "After setting to " << target_device
            << ", current device is " << current_device;
    EXPECT_EQ(target_device, current_device);
  }
}

IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAssociatedSink) {
  // Get the list of input devices. We can cheat in the unit test and
  // run this on the main thread since nobody else will be running at
  // the same time.
  AudioDeviceNames devices;
  GetAudioDeviceNames(&AudioManager::GetAudioInputDeviceNames, &devices);

  // Try to get an associated sink for each source.
  for (AudioDeviceNames::const_iterator device = devices.begin();
       device != devices.end();
       ++device) {
    scoped_refptr<WebrtcAudioPrivateGetAssociatedSinkFunction> function =
        new WebrtcAudioPrivateGetAssociatedSinkFunction();
    function->set_source_url(source_url_);

    std::string raw_device_id = device->unique_id;
    VLOG(2) << "Trying to find associated sink for device " << raw_device_id;
    std::string source_id_in_origin;
    GURL origin(GURL("http://www.google.com/").GetOrigin());
    GetIDInOrigin(profile()->GetResourceContext(),
                  origin,
                  raw_device_id,
                  &source_id_in_origin);

    base::ListValue parameters;
    parameters.AppendString(origin.spec());
    parameters.AppendString(source_id_in_origin);
    std::string parameter_string;
    JSONWriter::Write(&parameters, &parameter_string);

    scoped_ptr<base::Value> result(
        RunFunctionAndReturnSingleResult(function.get(),
                                         parameter_string,
                                         browser()));
    std::string result_string;
    JSONWriter::Write(result.get(), &result_string);
    VLOG(2) << "Results: " << result_string;
  }
}

IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, TriggerEvent) {
  WebrtcAudioPrivateEventService* service =
      WebrtcAudioPrivateEventService::GetFactoryInstance()->Get(profile());

  // Just trigger, without any extension listening.
  service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);

  // Now load our test extension and do it again.
  const extensions::Extension* extension = LoadExtension(
      test_data_dir_.AppendASCII("webrtc_audio_private_event_listener"));
  service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);

  // Check that the extension got the notification.
  std::string result = ExecuteScriptInBackgroundPage(extension->id(),
                                                     "reportIfGot()");
  EXPECT_EQ("true", result);
}

class HangoutServicesBrowserTest : public AudioWaitingExtensionTest {
 public:
  virtual void SetUp() OVERRIDE {
    // Make sure the Hangout Services component extension gets loaded.
    ComponentLoader::EnableBackgroundExtensionsForTesting();
    AudioWaitingExtensionTest::SetUp();
  }
};

#if defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
IN_PROC_BROWSER_TEST_F(HangoutServicesBrowserTest,
                       RunComponentExtensionTest) {
  // This runs the end-to-end JavaScript test for the Hangout Services
  // component extension, which uses the webrtcAudioPrivate API among
  // others.
  ASSERT_TRUE(StartEmbeddedTestServer());
  GURL url(embedded_test_server()->GetURL(
               "/extensions/hangout_services_test.html"));
  // The "externally connectable" extension permission doesn't seem to
  // like when we use 127.0.0.1 as the host, but using localhost works.
  std::string url_spec = url.spec();
  ReplaceFirstSubstringAfterOffset(&url_spec, 0, "127.0.0.1", "localhost");
  GURL localhost_url(url_spec);
  ui_test_utils::NavigateToURL(browser(), localhost_url);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  WaitUntilAudioIsPlaying(tab);

  // Override, i.e. disable, uploading. We don't want to try sending data to
  // servers when running the test. We don't bother about the contents of the
  // buffer |dummy|, that's tested in other tests.
  std::string dummy;
  g_browser_process->webrtc_log_uploader()->
      OverrideUploadWithBufferForTesting(&dummy);

  ASSERT_TRUE(content::ExecuteScript(tab, "browsertestRunAllTests();"));

  content::TitleWatcher title_watcher(tab, base::ASCIIToUTF16("success"));
  title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("failure"));
  base::string16 result = title_watcher.WaitAndGetTitle();
  EXPECT_EQ(base::ASCIIToUTF16("success"), result);

  g_browser_process->webrtc_log_uploader()->OverrideUploadWithBufferForTesting(
      NULL);
}
#endif  // defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */