summaryrefslogtreecommitdiff
path: root/chromium/content/browser/media
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/media')
-rw-r--r--chromium/content/browser/media/OWNERS11
-rw-r--r--chromium/content/browser/media/encrypted_media_browsertest.cc310
-rw-r--r--chromium/content/browser/media/media_browsertest.cc245
-rw-r--r--chromium/content/browser/media/media_browsertest.h35
-rw-r--r--chromium/content/browser/media/media_internals.cc136
-rw-r--r--chromium/content/browser/media/media_internals.h96
-rw-r--r--chromium/content/browser/media/media_internals_handler.cc46
-rw-r--r--chromium/content/browser/media/media_internals_handler.h43
-rw-r--r--chromium/content/browser/media/media_internals_proxy.cc182
-rw-r--r--chromium/content/browser/media/media_internals_proxy.h90
-rw-r--r--chromium/content/browser/media/media_internals_ui.cc53
-rw-r--r--chromium/content/browser/media/media_internals_ui.h23
-rw-r--r--chromium/content/browser/media/media_internals_unittest.cc134
-rw-r--r--chromium/content/browser/media/media_source_browsertest.cc57
-rw-r--r--chromium/content/browser/media/webrtc_browsertest.cc301
-rw-r--r--chromium/content/browser/media/webrtc_identity_store.cc307
-rw-r--r--chromium/content/browser/media/webrtc_identity_store.h114
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_backend.cc546
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_backend.h111
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_unittest.cc278
-rw-r--r--chromium/content/browser/media/webrtc_internals.cc243
-rw-r--r--chromium/content/browser/media/webrtc_internals.h117
-rw-r--r--chromium/content/browser/media/webrtc_internals_browsertest.cc709
-rw-r--r--chromium/content/browser/media/webrtc_internals_message_handler.cc78
-rw-r--r--chromium/content/browser/media/webrtc_internals_message_handler.h45
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui.cc44
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui.h23
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui_observer.h28
-rw-r--r--chromium/content/browser/media/webrtc_internals_unittest.cc158
29 files changed, 4563 insertions, 0 deletions
diff --git a/chromium/content/browser/media/OWNERS b/chromium/content/browser/media/OWNERS
new file mode 100644
index 00000000000..d132d0e6061
--- /dev/null
+++ b/chromium/content/browser/media/OWNERS
@@ -0,0 +1,11 @@
+acolwell@chromium.org
+dalecurtis@chromium.org
+ddorwin@chromium.org
+fischman@chromium.org
+scherkus@chromium.org
+shadi@chromium.org
+tommi@chromium.org
+vrk@chromium.org
+wjia@chromium.org
+xhwang@chromium.org
+xians@chromium.org
diff --git a/chromium/content/browser/media/encrypted_media_browsertest.cc b/chromium/content/browser/media/encrypted_media_browsertest.cc
new file mode 100644
index 00000000000..ce5dc4e6ba0
--- /dev/null
+++ b/chromium/content/browser/media/encrypted_media_browsertest.cc
@@ -0,0 +1,310 @@
+// Copyright (c) 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/command_line.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "content/browser/media/media_browsertest.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+
+#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
+
+#if defined(WIDEVINE_CDM_AVAILABLE) && defined(OS_LINUX)
+#include <gnu/libc-version.h>
+#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(OS_LINUX)
+
+#if defined(ENABLE_PEPPER_CDMS)
+// Platform-specific filename relative to the chrome executable.
+static const char kClearKeyCdmAdapterFileName[] =
+#if defined(OS_MACOSX)
+ "clearkeycdmadapter.plugin";
+#elif defined(OS_WIN)
+ "clearkeycdmadapter.dll";
+#elif defined(OS_POSIX)
+ "libclearkeycdmadapter.so";
+#endif
+#endif // defined(ENABLE_PEPPER_CDMS)
+
+// Available key systems.
+static const char kClearKeyKeySystem[] = "webkit-org.w3.clearkey";
+static const char kExternalClearKeyKeySystem[] =
+ "org.chromium.externalclearkey";
+
+// Supported media types.
+static const char kWebMAudioOnly[] = "audio/webm; codecs=\"vorbis\"";
+static const char kWebMVideoOnly[] = "video/webm; codecs=\"vp8\"";
+static const char kWebMAudioVideo[] = "video/webm; codecs=\"vorbis, vp8\"";
+static const char kMP4AudioOnly[] = "audio/mp4; codecs=\"mp4a.40.2\"";
+static const char kMP4VideoOnly[] = "video/mp4; codecs=\"avc1.4D4041\"";
+
+// Common test expectations.
+static const char kKeyError[] = "KEYERROR";
+
+// The type of video src used to load media.
+enum SrcType {
+ SRC,
+ MSE
+};
+
+namespace content {
+
+// Tests encrypted media playback with a combination of parameters:
+// - char*: Key system name.
+// - bool: True to load media using MSE, otherwise use src.
+class EncryptedMediaTest : public content::MediaBrowserTest,
+ public testing::WithParamInterface<std::tr1::tuple<const char*, SrcType> > {
+ public:
+ void TestSimplePlayback(const char* encrypted_media, const char* media_type,
+ const std::tr1::tuple<const char*, SrcType> test_params,
+ const char* expectation) {
+ const char* key_system = std::tr1::get<0>(test_params);
+ SrcType src_type = std::tr1::get<1>(test_params);
+ RunEncryptedMediaTest("encrypted_media_player.html", encrypted_media,
+ media_type, key_system, src_type, expectation);
+ }
+
+ void TestMSESimplePlayback(const char* encrypted_media,
+ const char* media_type, const char* key_system,
+ const char* expectation) {
+ RunEncryptedMediaTest("encrypted_media_player.html", encrypted_media,
+ media_type, key_system, MSE, expectation);
+ }
+
+ void TestFrameSizeChange(
+ const std::tr1::tuple<const char*, SrcType> test_params,
+ const char* expectation) {
+ const char* key_system = std::tr1::get<0>(test_params);
+ SrcType src_type = std::tr1::get<1>(test_params);
+ RunEncryptedMediaTest("encrypted_frame_size_change.html",
+ "frame_size_change-av-enc-v.webm", kWebMAudioVideo,
+ key_system, src_type, expectation);
+ }
+
+ void TestConfigChange(const char* key_system, const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("keysystem", key_system));
+ query_params.push_back(std::make_pair("runencrypted", "1"));
+ RunMediaTestPage("mse_config_change.html", &query_params, expectation,
+ true);
+ }
+
+ void RunEncryptedMediaTest(const char* html_page, const char* media_file,
+ const char* media_type, const char* key_system,
+ SrcType src_type, const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("mediafile", media_file));
+ query_params.push_back(std::make_pair("mediatype", media_type));
+ query_params.push_back(std::make_pair("keysystem", key_system));
+ if (src_type == MSE)
+ query_params.push_back(std::make_pair("usemse", "1"));
+ RunMediaTestPage(html_page, &query_params, expectation, true);
+ }
+
+ protected:
+#if defined(ENABLE_PEPPER_CDMS)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ RegisterPepperCdm(command_line, kClearKeyCdmAdapterFileName,
+ kExternalClearKeyKeySystem);
+ }
+
+ virtual void RegisterPepperCdm(CommandLine* command_line,
+ const std::string& adapter_name,
+ const std::string& key_system) {
+ // Append the switch to register the Clear Key CDM Adapter.
+ base::FilePath plugin_dir;
+ EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &plugin_dir));
+ base::FilePath plugin_lib = plugin_dir.AppendASCII(adapter_name);
+ EXPECT_TRUE(base::PathExists(plugin_lib));
+ base::FilePath::StringType pepper_plugin = plugin_lib.value();
+ pepper_plugin.append(FILE_PATH_LITERAL("#CDM#0.1.0.0;"));
+#if defined(OS_WIN)
+ pepper_plugin.append(ASCIIToWide(GetPepperType(key_system)));
+#else
+ pepper_plugin.append(GetPepperType(key_system));
+#endif
+ command_line->AppendSwitchNative(switches::kRegisterPepperPlugins,
+ pepper_plugin);
+ }
+
+ // Adapted from key_systems.cc.
+ std::string GetPepperType(const std::string& key_system) {
+ if (key_system == kExternalClearKeyKeySystem)
+ return "application/x-ppapi-clearkey-cdm";
+#if defined(WIDEVINE_CDM_AVAILABLE)
+ if (key_system == kWidevineKeySystem)
+ return "application/x-ppapi-widevine-cdm";
+#endif // WIDEVINE_CDM_AVAILABLE
+
+ NOTREACHED();
+ return "";
+ }
+#endif // defined(ENABLE_PEPPER_CDMS)
+};
+
+#if defined(WIDEVINE_CDM_AVAILABLE)
+class WVEncryptedMediaTest : public EncryptedMediaTest {
+ public:
+ // Tests that the following happen after trying to play encrypted media:
+ // - webkitneedkey event is fired.
+ // - webkitGenerateKeyRequest() does not fail.
+ // - webkitkeymessage is fired
+ // - webkitAddKey() generates a WebKitKeyError since no real WV key is added.
+ void TestMSESimplePlayback(const char* encrypted_media,
+ const char* media_type, const char* key_system) {
+ // TODO(shadi): Remove after bots upgrade to precise.
+ // Don't run on lucid bots since the CDM is not compatible (glibc < 2.14)
+#if defined(OS_LINUX)
+ if (strcmp(gnu_get_libc_version(), "2.11.1") == 0) {
+ LOG(INFO) << "Skipping test; not supported on glibc version: "
+ << gnu_get_libc_version();
+ return;
+ }
+#endif // defined(OS_LINUX)
+ EncryptedMediaTest::TestMSESimplePlayback(encrypted_media, media_type,
+ key_system, kKeyError);
+ bool receivedKeyMessage = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(video.receivedKeyMessage);",
+ &receivedKeyMessage));
+ ASSERT_TRUE(receivedKeyMessage);
+ }
+
+ protected:
+#if defined(ENABLE_PEPPER_CDMS)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ RegisterPepperCdm(command_line, kWidevineCdmAdapterFileName,
+ kWidevineKeySystem);
+ }
+#endif // defined(ENABLE_PEPPER_CDMS)
+};
+#endif // defined(WIDEVINE_CDM_AVAILABLE)
+
+INSTANTIATE_TEST_CASE_P(ClearKey, EncryptedMediaTest,
+ ::testing::Combine(
+ ::testing::Values(kClearKeyKeySystem), ::testing::Values(SRC, MSE)));
+
+// External Clear Key is currently only used on platforms that use Pepper CDMs.
+#if defined(ENABLE_PEPPER_CDMS)
+INSTANTIATE_TEST_CASE_P(ExternalClearKey, EncryptedMediaTest,
+ ::testing::Combine(
+ ::testing::Values(kExternalClearKeyKeySystem),
+ ::testing::Values(SRC, MSE)));
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, ConfigChangeVideo_ExternalClearKey) {
+ TestConfigChange(kExternalClearKeyKeySystem, kEnded);
+}
+#endif // defined(ENABLE_PEPPER_CDMS)
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, ConfigChangeVideo_ClearKey) {
+ TestConfigChange(kClearKeyKeySystem, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, InvalidKeySystem) {
+ TestMSESimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo,
+ "com.example.invalid",
+ "GENERATE_KEY_REQUEST_EXCEPTION");
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioOnly_WebM) {
+ TestSimplePlayback("bear-a-enc_a.webm", kWebMAudioOnly, GetParam(), kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioClearVideo_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_a.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoAudio_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_WebM) {
+ TestSimplePlayback("bear-320x240-v-enc_v.webm", kWebMVideoOnly, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoClearAudio_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_v.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, FrameChangeVideo) {
+ // Times out on Windows XP. http://crbug.com/171937
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return;
+#endif
+ TestFrameSizeChange(GetParam(), kEnded);
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_MP4) {
+ std::tr1::tuple<const char*, SrcType> test_params = GetParam();
+ // MP4 without MSE is not support yet, http://crbug.com/170793.
+ if (std::tr1::get<1>(test_params) != MSE) {
+ LOG(INFO) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
+ return;
+ }
+ TestMSESimplePlayback("bear-640x360-v_frag-cenc.mp4", kMP4VideoOnly,
+ std::tr1::get<0>(test_params), kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioOnly_MP4) {
+ std::tr1::tuple<const char*, SrcType> test_params = GetParam();
+ // MP4 without MSE is not support yet, http://crbug.com/170793.
+ if (std::tr1::get<1>(test_params) != MSE) {
+ LOG(INFO) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
+ return;
+ }
+ TestMSESimplePlayback("bear-640x360-a_frag-cenc.mp4", kMP4AudioOnly,
+ std::tr1::get<0>(test_params), kEnded);
+}
+#endif
+
+// Run only when WV CDM is available.
+#if defined(WIDEVINE_CDM_AVAILABLE)
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioOnly_WebM) {
+ TestMSESimplePlayback("bear-a-enc_a.webm", kWebMAudioOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioClearVideo_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_a.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoAudio_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoOnly_WebM) {
+ TestMSESimplePlayback("bear-320x240-v-enc_v.webm", kWebMVideoOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoClearAudio_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_v.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoOnly_MP4) {
+ TestMSESimplePlayback("bear-640x360-v_frag-cenc.mp4", kMP4VideoOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioOnly_MP4) {
+ TestMSESimplePlayback("bear-640x360-a_frag-cenc.mp4", kMP4AudioOnly,
+ kWidevineKeySystem);
+}
+#endif // defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+#endif // defined(WIDEVINE_CDM_AVAILABLE)
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_browsertest.cc b/chromium/content/browser/media/media_browsertest.cc
new file mode 100644
index 00000000000..1d830354c9e
--- /dev/null
+++ b/chromium/content/browser/media/media_browsertest.cc
@@ -0,0 +1,245 @@
+// Copyright (c) 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 "content/browser/media/media_browsertest.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test_utils.h"
+
+// TODO(wolenetz): Fix Media.YUV* tests on MSVS 2012 x64. crbug.com/180074
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) && _MSC_VER == 1700
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+namespace content {
+
+// Common test results.
+const char MediaBrowserTest::kEnded[] = "ENDED";
+const char MediaBrowserTest::kError[] = "ERROR";
+const char MediaBrowserTest::kFailed[] = "FAILED";
+
+void MediaBrowserTest::SetUp() {
+ // TODO(danakj): The GPU Video Decoder needs real GL bindings.
+ // crbug.com/269087
+ UseRealGLBindings();
+
+ ContentBrowserTest::SetUp();
+}
+
+void MediaBrowserTest::RunMediaTestPage(
+ const char* html_page, std::vector<StringPair>* query_params,
+ const char* expected, bool http) {
+ GURL gurl;
+ std::string query = "";
+ if (query_params != NULL && !query_params->empty()) {
+ std::vector<StringPair>::const_iterator itr = query_params->begin();
+ query = base::StringPrintf("%s=%s", itr->first, itr->second);
+ ++itr;
+ for (; itr != query_params->end(); ++itr) {
+ query.append(base::StringPrintf("&%s=%s", itr->first, itr->second));
+ }
+ }
+ if (http) {
+ ASSERT_TRUE(test_server()->Start());
+ gurl = test_server()->GetURL(
+ base::StringPrintf("files/media/%s?%s", html_page, query.c_str()));
+ } else {
+ base::FilePath test_file_path = GetTestFilePath("media", html_page);
+ gurl = GetFileUrlWithQuery(test_file_path, query);
+ }
+ RunTest(gurl, expected);
+}
+
+void MediaBrowserTest::RunTest(const GURL& gurl, const char* expected) {
+ const string16 kExpected = ASCIIToUTF16(expected);
+ DVLOG(1) << "Running test URL: " << gurl;
+ TitleWatcher title_watcher(shell()->web_contents(), kExpected);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kEnded));
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kError));
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kFailed));
+ NavigateToURL(shell(), gurl);
+
+ string16 final_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(kExpected, final_title);
+}
+
+// Tests playback and seeking of an audio or video file over file or http based
+// on a test parameter. Test starts with playback, then, after X seconds or the
+// ended event fires, seeks near end of file; see player.html for details. The
+// test completes when either the last 'ended' or an 'error' event fires.
+class MediaTest : public testing::WithParamInterface<bool>,
+ public MediaBrowserTest {
+ public:
+ // Play specified audio over http:// or file:// depending on |http| setting.
+ void PlayAudio(const char* media_file, bool http) {
+ PlayMedia("audio", media_file, http);
+ }
+
+ // Play specified video over http:// or file:// depending on |http| setting.
+ void PlayVideo(const char* media_file, bool http) {
+ PlayMedia("video", media_file, http);
+ }
+
+ // Run specified color format test with the expected result.
+ void RunColorFormatTest(const char* media_file, const char* expected) {
+ base::FilePath test_file_path = GetTestFilePath("media", "blackwhite.html");
+ RunTest(GetFileUrlWithQuery(test_file_path, media_file), expected);
+ }
+
+ void PlayMedia(const char* tag, const char* media_file, bool http) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair(tag, media_file));
+ RunMediaTestPage("player.html", &query_params, kEnded, http);
+ }
+};
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearTheora) {
+ PlayVideo("bear.ogv", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentTheora) {
+ PlayVideo("bear_silent.ogv", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWebm) {
+ PlayVideo("bear.webm", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentWebm) {
+ PlayVideo("bear_silent.webm", GetParam());
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMp4) {
+ PlayVideo("bear.mp4", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentMp4) {
+ PlayVideo("bear_silent.mp4", GetParam());
+}
+
+// While we support the big endian (be) PCM codecs on Chromium, Quicktime seems
+// to be the only creator of this format and only for .mov files.
+// TODO(dalecurtis/ihf): Find or create some .wav test cases for "be" format.
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMovPcmS16be) {
+ PlayVideo("bear_pcm_s16be.mov", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMovPcmS24be) {
+ PlayVideo("bear_pcm_s24be.mov", GetParam());
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Mpeg4) {
+ PlayVideo("bear_mpeg4_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Mpeg4Asp) {
+ PlayVideo("bear_mpeg4asp_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Divx) {
+ PlayVideo("bear_divx_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear3gpAacH264) {
+ PlayVideo("bear_h264_aac.3gp", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear3gpAmrnbMpeg4) {
+ PlayVideo("bear_mpeg4_amrnb.3gp", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavGsmms) {
+ PlayAudio("bear_gsm_ms.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavMulaw) {
+ PlayAudio("bear_mulaw.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearFlac) {
+ PlayAudio("bear.flac", GetParam());
+}
+#endif
+#endif
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm) {
+ PlayAudio("bear_pcm.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm3kHz) {
+ PlayAudio("bear_3kHz.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm192kHz) {
+ PlayAudio("bear_192kHz.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoTulipWebm) {
+ PlayVideo("tulip2.webm", GetParam());
+}
+
+// Covers tear-down when navigating away as opposed to browser exiting.
+IN_PROC_BROWSER_TEST_F(MediaTest, Navigate) {
+ PlayVideo("bear.ogv", false);
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+INSTANTIATE_TEST_CASE_P(File, MediaTest, ::testing::Values(false));
+INSTANTIATE_TEST_CASE_P(Http, MediaTest, ::testing::Values(true));
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pTheora)) {
+ RunColorFormatTest("yuv420p.ogv", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv422pTheora)) {
+ RunColorFormatTest("yuv422p.ogv", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv444pTheora)) {
+ // TODO(scherkus): Support YUV444 http://crbug.com/104711
+ RunColorFormatTest("yuv424p.ogv", "ERROR");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pVp8)) {
+ RunColorFormatTest("yuv420p.webm", "ENDED");
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pH264)) {
+ RunColorFormatTest("yuv420p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuvj420pH264)) {
+ RunColorFormatTest("yuvj420p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv422pH264)) {
+ RunColorFormatTest("yuv422p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv444pH264)) {
+ // TODO(scherkus): Support YUV444 http://crbug.com/104711
+ RunColorFormatTest("yuv444p.mp4", "ERROR");
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(MediaTest, Yuv420pMpeg4) {
+ RunColorFormatTest("yuv420p.avi", "ENDED");
+}
+#endif
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_browsertest.h b/chromium/content/browser/media/media_browsertest.h
new file mode 100644
index 00000000000..013283b1ed6
--- /dev/null
+++ b/chromium/content/browser/media/media_browsertest.h
@@ -0,0 +1,35 @@
+// 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 "content/test/content_browser_test.h"
+
+namespace content {
+
+// Class used to automate running media related browser tests. The functions
+// assume that media files are located under files/media/ folder known to
+// the test http server.
+class MediaBrowserTest : public ContentBrowserTest {
+ public:
+ static const char kEnded[];
+ static const char kError[];
+ static const char kFailed[];
+
+ typedef std::pair<const char*, const char*> StringPair;
+
+ virtual void SetUp() OVERRIDE;
+
+ // Runs a html page with a list of URL query parameters.
+ // If http is true, the test starts a local http test server to load the test
+ // page, otherwise a local file URL is loaded inside the content shell.
+ // It uses RunTest() to check for expected test output.
+ void RunMediaTestPage(const char* html_page,
+ std::vector<StringPair>* query_params,
+ const char* expected, bool http);
+
+ // Opens a URL and waits for the document title to match either one of the
+ // default strings or the expected string.
+ void RunTest(const GURL& gurl, const char* expected);
+};
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals.cc b/chromium/content/browser/media/media_internals.cc
new file mode 100644
index 00000000000..273eeaeaf9f
--- /dev/null
+++ b/chromium/content/browser/media/media_internals.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 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/browser/media/media_internals.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_ui.h"
+#include "media/base/media_log.h"
+#include "media/base/media_log_event.h"
+
+namespace content {
+
+MediaInternals* MediaInternals::GetInstance() {
+ return Singleton<MediaInternals>::get();
+}
+
+MediaInternals::~MediaInternals() {}
+
+void MediaInternals::OnDeleteAudioStream(void* host, int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ std::string stream = base::StringPrintf("audio_streams.%p:%d",
+ host, stream_id);
+ DeleteItem(stream);
+}
+
+void MediaInternals::OnSetAudioStreamPlaying(
+ void* host, int stream_id, bool playing) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "playing", new base::FundamentalValue(playing));
+}
+
+void MediaInternals::OnSetAudioStreamStatus(
+ void* host, int stream_id, const std::string& status) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "status", new base::StringValue(status));
+}
+
+void MediaInternals::OnSetAudioStreamVolume(
+ void* host, int stream_id, double volume) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "volume", new base::FundamentalValue(volume));
+}
+
+void MediaInternals::OnMediaEvents(
+ int render_process_id, const std::vector<media::MediaLogEvent>& events) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Notify observers that |event| has occured.
+ for (std::vector<media::MediaLogEvent>::const_iterator event = events.begin();
+ event != events.end(); ++event) {
+ base::DictionaryValue dict;
+ dict.SetInteger("renderer", render_process_id);
+ dict.SetInteger("player", event->id);
+ dict.SetString("type", media::MediaLog::EventTypeToString(event->type));
+
+ int64 ticks = event->time.ToInternalValue();
+ double ticks_millis =
+ ticks / static_cast<double>(base::Time::kMicrosecondsPerMillisecond);
+
+ dict.SetDouble("ticksMillis", ticks_millis);
+ dict.Set("params", event->params.DeepCopy());
+ SendUpdate("media.onMediaEvent", &dict);
+ }
+}
+
+void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
+ update_callbacks_.push_back(callback);
+}
+
+void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
+ for (size_t i = 0; i < update_callbacks_.size(); ++i) {
+ if (update_callbacks_[i].Equals(callback)) {
+ update_callbacks_.erase(update_callbacks_.begin() + i);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void MediaInternals::SendEverything() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ SendUpdate("media.onReceiveEverything", &data_);
+}
+
+MediaInternals::MediaInternals() {
+}
+
+void MediaInternals::UpdateAudioStream(void* host,
+ int stream_id,
+ const std::string& property,
+ base::Value* value) {
+ std::string stream = base::StringPrintf("audio_streams.%p:%d",
+ host, stream_id);
+ UpdateItem("media.addAudioStream", stream, property, value);
+}
+
+void MediaInternals::DeleteItem(const std::string& item) {
+ data_.Remove(item, NULL);
+ scoped_ptr<base::Value> value(new base::StringValue(item));
+ SendUpdate("media.onItemDeleted", value.get());
+}
+
+void MediaInternals::UpdateItem(
+ const std::string& update_fn, const std::string& id,
+ const std::string& property, base::Value* value) {
+ base::DictionaryValue* item_properties;
+ if (!data_.GetDictionary(id, &item_properties)) {
+ item_properties = new base::DictionaryValue();
+ data_.Set(id, item_properties);
+ item_properties->SetString("id", id);
+ }
+ item_properties->Set(property, value);
+ SendUpdate(update_fn, item_properties);
+}
+
+void MediaInternals::SendUpdate(const std::string& function,
+ base::Value* value) {
+ // Only bother serializing the update to JSON if someone is watching.
+ if (update_callbacks_.empty())
+ return;
+
+ std::vector<const base::Value*> args;
+ args.push_back(value);
+ string16 update = WebUI::GetJavascriptCall(function, args);
+ for (size_t i = 0; i < update_callbacks_.size(); i++)
+ update_callbacks_[i].Run(update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals.h b/chromium/content/browser/media/media_internals.h
new file mode 100644
index 00000000000..4a4d2efc7d1
--- /dev/null
+++ b/chromium/content/browser/media/media_internals.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "content/common/content_export.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace media {
+struct MediaLogEvent;
+}
+
+namespace content {
+
+// This class stores information about currently active media.
+// It's constructed on the UI thread but all of its methods are called on the IO
+// thread.
+class CONTENT_EXPORT MediaInternals {
+ public:
+ virtual ~MediaInternals();
+
+ static MediaInternals* GetInstance();
+
+ // The following methods are virtual for gmock.
+
+ // Called when an audio stream is deleted.
+ virtual void OnDeleteAudioStream(void* host, int stream_id);
+
+ // Called when an audio stream is set to playing or paused.
+ virtual void OnSetAudioStreamPlaying(void* host, int stream_id,
+ bool playing);
+
+ // Called when the status of an audio stream is set to "created", "closed", or
+ // "error".
+ virtual void OnSetAudioStreamStatus(void* host, int stream_id,
+ const std::string& status);
+
+ // Called when the volume of an audio stream is set.
+ virtual void OnSetAudioStreamVolume(void* host, int stream_id,
+ double volume);
+
+ // Called when a MediaEvent occurs.
+ virtual void OnMediaEvents(int render_process_id,
+ const std::vector<media::MediaLogEvent>& events);
+
+ // Called with the update string.
+ typedef base::Callback<void(const string16&)> UpdateCallback;
+
+ // Add/remove update callbacks (see above).
+ void AddUpdateCallback(const UpdateCallback& callback);
+ void RemoveUpdateCallback(const UpdateCallback& callback);
+ void SendEverything();
+
+ private:
+ friend class MockMediaInternals;
+ friend class MediaInternalsTest;
+ friend struct DefaultSingletonTraits<MediaInternals>;
+
+ MediaInternals();
+
+ // Sets |property| of an audio stream to |value| and notifies observers.
+ // (host, stream_id) is a unique id for the audio stream.
+ // |host| will never be dereferenced.
+ void UpdateAudioStream(void* host, int stream_id,
+ const std::string& property, base::Value* value);
+
+ // Removes |item| from |data_|.
+ void DeleteItem(const std::string& item);
+
+ // Sets data_.id.property = value and notifies attached UIs using update_fn.
+ // id may be any depth, e.g. "video.decoders.1.2.3"
+ void UpdateItem(const std::string& update_fn, const std::string& id,
+ const std::string& property, base::Value* value);
+
+ // Calls javascript |function|(|value|) on each attached UI.
+ void SendUpdate(const std::string& function, base::Value* value);
+
+ base::DictionaryValue data_;
+
+ std::vector<UpdateCallback> update_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternals);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
diff --git a/chromium/content/browser/media/media_internals_handler.cc b/chromium/content/browser/media/media_internals_handler.cc
new file mode 100644
index 00000000000..1ea6b04c663
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_handler.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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/browser/media/media_internals_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/values.h"
+#include "content/browser//media/media_internals_proxy.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+MediaInternalsMessageHandler::MediaInternalsMessageHandler()
+ : proxy_(new MediaInternalsProxy()) {}
+
+MediaInternalsMessageHandler::~MediaInternalsMessageHandler() {
+ proxy_->Detach();
+}
+
+void MediaInternalsMessageHandler::RegisterMessages() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ proxy_->Attach(this);
+
+ web_ui()->RegisterMessageCallback("getEverything",
+ base::Bind(&MediaInternalsMessageHandler::OnGetEverything,
+ base::Unretained(this)));
+}
+
+void MediaInternalsMessageHandler::OnGetEverything(
+ const base::ListValue* list) {
+ proxy_->GetEverything();
+}
+
+void MediaInternalsMessageHandler::OnUpdate(const string16& update) {
+ // Don't try to execute JavaScript in a RenderView that no longer exists.
+ RenderViewHost* host = web_ui()->GetWebContents()->GetRenderViewHost();
+ if (host)
+ host->ExecuteJavascriptInWebFrame(string16(), update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_handler.h b/chromium/content/browser/media/media_internals_handler.h
new file mode 100644
index 00000000000..4833aee285f
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_handler.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+class MediaInternalsProxy;
+
+// This class handles messages to and from MediaInternalsUI.
+// It does all its work on the IO thread through the proxy below.
+class MediaInternalsMessageHandler : public WebUIMessageHandler {
+ public:
+ MediaInternalsMessageHandler();
+ virtual ~MediaInternalsMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // Javascript message handlers.
+ void OnGetEverything(const base::ListValue* list);
+
+ // MediaInternals message handlers.
+ void OnUpdate(const string16& update);
+
+ private:
+ scoped_refptr<MediaInternalsProxy> proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsMessageHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
diff --git a/chromium/content/browser/media/media_internals_proxy.cc b/chromium/content/browser/media/media_internals_proxy.cc
new file mode 100644
index 00000000000..d0e0622b41b
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_proxy.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2012 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/browser/media/media_internals_proxy.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/media/media_internals_handler.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+static const int kMediaInternalsProxyEventDelayMilliseconds = 100;
+
+static const net::NetLog::EventType kNetEventTypeFilter[] = {
+ net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL,
+ net::NetLog::TYPE_SPARSE_READ,
+ net::NetLog::TYPE_SPARSE_WRITE,
+ net::NetLog::TYPE_URL_REQUEST_START_JOB,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+};
+
+MediaInternalsProxy::MediaInternalsProxy() {
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+void MediaInternalsProxy::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ RenderProcessHost* process = Source<RenderProcessHost>(source).ptr();
+ CallJavaScriptFunctionOnUIThread("media.onRendererTerminated",
+ new base::FundamentalValue(process->GetID()));
+}
+
+void MediaInternalsProxy::Attach(MediaInternalsMessageHandler* handler) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ handler_ = handler;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::ObserveMediaInternalsOnIOThread, this));
+}
+
+void MediaInternalsProxy::Detach() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ handler_ = NULL;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &MediaInternalsProxy::StopObservingMediaInternalsOnIOThread, this));
+}
+
+void MediaInternalsProxy::GetEverything() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Ask MediaInternals for all its data.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::GetEverythingOnIOThread, this));
+
+ // Send the page names for constants.
+ CallJavaScriptFunctionOnUIThread("media.onReceiveConstants", GetConstants());
+}
+
+void MediaInternalsProxy::OnUpdate(const string16& update) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::UpdateUIOnUIThread, this, update));
+}
+
+void MediaInternalsProxy::OnAddEntry(const net::NetLog::Entry& entry) {
+ bool is_event_interesting = false;
+ for (size_t i = 0; i < arraysize(kNetEventTypeFilter); i++) {
+ if (entry.type() == kNetEventTypeFilter[i]) {
+ is_event_interesting = true;
+ break;
+ }
+ }
+
+ if (!is_event_interesting)
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::AddNetEventOnUIThread, this,
+ entry.ToValue()));
+}
+
+MediaInternalsProxy::~MediaInternalsProxy() {}
+
+base::Value* MediaInternalsProxy::GetConstants() {
+ base::DictionaryValue* event_phases = new base::DictionaryValue();
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_NONE),
+ net::NetLog::PHASE_NONE);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_BEGIN),
+ net::NetLog::PHASE_BEGIN);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_END),
+ net::NetLog::PHASE_END);
+
+ base::DictionaryValue* constants = new base::DictionaryValue();
+ constants->Set("eventTypes", net::NetLog::GetEventTypesAsValue());
+ constants->Set("eventPhases", event_phases);
+
+ return constants;
+}
+
+void MediaInternalsProxy::ObserveMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ update_callback_ = base::Bind(&MediaInternalsProxy::OnUpdate,
+ base::Unretained(this));
+ MediaInternals::GetInstance()->AddUpdateCallback(update_callback_);
+ if (GetContentClient()->browser()->GetNetLog()) {
+ net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
+ net_log->AddThreadSafeObserver(this, net::NetLog::LOG_ALL_BUT_BYTES);
+ }
+}
+
+void MediaInternalsProxy::StopObservingMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ MediaInternals::GetInstance()->RemoveUpdateCallback(update_callback_);
+ if (GetContentClient()->browser()->GetNetLog()) {
+ net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
+ net_log->RemoveThreadSafeObserver(this);
+ }
+}
+
+void MediaInternalsProxy::GetEverythingOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ MediaInternals::GetInstance()->SendEverything();
+}
+
+void MediaInternalsProxy::UpdateUIOnUIThread(const string16& update) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Don't forward updates to a destructed UI.
+ if (handler_)
+ handler_->OnUpdate(update);
+}
+
+void MediaInternalsProxy::AddNetEventOnUIThread(base::Value* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Send the updates to the page in kMediaInternalsProxyEventDelayMilliseconds
+ // if an update is not already pending.
+ if (!pending_net_updates_) {
+ pending_net_updates_.reset(new base::ListValue());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MediaInternalsProxy::SendNetEventsOnUIThread, this),
+ base::TimeDelta::FromMilliseconds(
+ kMediaInternalsProxyEventDelayMilliseconds));
+ }
+ pending_net_updates_->Append(entry);
+}
+
+void MediaInternalsProxy::SendNetEventsOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ CallJavaScriptFunctionOnUIThread("media.onNetUpdate",
+ pending_net_updates_.release());
+}
+
+void MediaInternalsProxy::CallJavaScriptFunctionOnUIThread(
+ const std::string& function, base::Value* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<base::Value> args_value(args);
+ std::vector<const base::Value*> args_vector;
+ args_vector.push_back(args_value.get());
+ string16 update = WebUI::GetJavascriptCall(function, args_vector);
+ UpdateUIOnUIThread(update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_proxy.h b/chromium/content/browser/media/media_internals_proxy.h
new file mode 100644
index 00000000000..5d173b3e738
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_proxy.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/browser/media/media_internals.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "net/base/net_log.h"
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace content {
+class MediaInternalsMessageHandler;
+
+// This class is a proxy between MediaInternals (on the IO thread) and
+// MediaInternalsMessageHandler (on the UI thread).
+// It is ref_counted to ensure that it completes all pending Tasks on both
+// threads before destruction.
+class MediaInternalsProxy
+ : public base::RefCountedThreadSafe<
+ MediaInternalsProxy,
+ BrowserThread::DeleteOnUIThread>,
+ public net::NetLog::ThreadSafeObserver,
+ public NotificationObserver {
+ public:
+ MediaInternalsProxy();
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Register a Handler and start receiving callbacks from MediaInternals.
+ void Attach(MediaInternalsMessageHandler* handler);
+
+ // Unregister the same and stop receiving callbacks.
+ void Detach();
+
+ // Have MediaInternals send all the data it has.
+ void GetEverything();
+
+ // MediaInternals callback. Called on the IO thread.
+ void OnUpdate(const string16& update);
+
+ // net::NetLog::ThreadSafeObserver implementation. Callable from any thread:
+ virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE;
+
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
+ friend class base::DeleteHelper<MediaInternalsProxy>;
+ virtual ~MediaInternalsProxy();
+
+ // Build a dictionary mapping constant names to values.
+ base::Value* GetConstants();
+
+ void ObserveMediaInternalsOnIOThread();
+ void StopObservingMediaInternalsOnIOThread();
+ void GetEverythingOnIOThread();
+ void UpdateUIOnUIThread(const string16& update);
+
+ // Put |entry| on a list of events to be sent to the page.
+ void AddNetEventOnUIThread(base::Value* entry);
+
+ // Send all pending events to the page.
+ void SendNetEventsOnUIThread();
+
+ // Call a JavaScript function on the page. Takes ownership of |args|.
+ void CallJavaScriptFunctionOnUIThread(const std::string& function,
+ base::Value* args);
+
+ MediaInternalsMessageHandler* handler_;
+ scoped_ptr<base::ListValue> pending_net_updates_;
+ NotificationRegistrar registrar_;
+ MediaInternals::UpdateCallback update_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsProxy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
diff --git a/chromium/content/browser/media/media_internals_ui.cc b/chromium/content/browser/media/media_internals_ui.cc
new file mode 100644
index 00000000000..83638148bf9
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_ui.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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/browser/media/media_internals_ui.h"
+
+#include "base/command_line.h"
+#include "content/browser/media/media_internals_handler.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+
+namespace content {
+namespace {
+
+WebUIDataSource* CreateMediaInternalsHTMLSource() {
+ WebUIDataSource* source =
+ WebUIDataSource::Create(kChromeUIMediaInternalsHost);
+
+ source->SetJsonPath("strings.js");
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableNewMediaInternals)) {
+ source->AddResourcePath("media_internals.js", IDR_MEDIA_INTERNALS_NEW_JS);
+ source->SetDefaultResource(IDR_MEDIA_INTERNALS_NEW_HTML);
+ return source;
+ }
+
+ source->AddResourcePath("media_internals.js", IDR_MEDIA_INTERNALS_JS);
+ source->SetDefaultResource(IDR_MEDIA_INTERNALS_HTML);
+ return source;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MediaInternalsUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+MediaInternalsUI::MediaInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new MediaInternalsMessageHandler());
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateMediaInternalsHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_ui.h b/chromium/content/browser/media/media_internals_ui.h
new file mode 100644
index 00000000000..f7a17a6765a
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+// The implementation for the chrome://media-internals page.
+class MediaInternalsUI : public WebUIController {
+ public:
+ explicit MediaInternalsUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
diff --git a/chromium/content/browser/media/media_internals_unittest.cc b/chromium/content/browser/media/media_internals_unittest.cc
new file mode 100644
index 00000000000..58c8f91dc68
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 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/browser/media/media_internals.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class MockObserverBaseClass {
+ public:
+ ~MockObserverBaseClass() {}
+ virtual void OnUpdate(const string16& javascript) = 0;
+};
+
+class MockMediaInternalsObserver : public MockObserverBaseClass {
+ public:
+ virtual ~MockMediaInternalsObserver() {}
+ MOCK_METHOD1(OnUpdate, void(const string16& javascript));
+};
+
+} // namespace
+
+class MediaInternalsTest : public testing::Test {
+ public:
+ MediaInternalsTest() : io_thread_(BrowserThread::IO, &loop_) {}
+ base::DictionaryValue* data() {
+ return &internals_->data_;
+ }
+
+ void DeleteItem(const std::string& item) {
+ internals_->DeleteItem(item);
+ }
+
+ void UpdateItem(const std::string& item, const std::string& property,
+ base::Value* value) {
+ internals_->UpdateItem(std::string(), item, property, value);
+ }
+
+ void SendUpdate(const std::string& function, base::Value* value) {
+ internals_->SendUpdate(function, value);
+ }
+
+ protected:
+ virtual void SetUp() {
+ internals_.reset(new MediaInternals());
+ }
+
+ base::MessageLoop loop_;
+ TestBrowserThread io_thread_;
+ scoped_ptr<MediaInternals> internals_;
+};
+
+TEST_F(MediaInternalsTest, UpdateAddsNewItem) {
+ UpdateItem("some.item", "testing", new base::FundamentalValue(true));
+ bool testing = false;
+ std::string id;
+
+ EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
+ EXPECT_TRUE(testing);
+
+ EXPECT_TRUE(data()->GetString("some.item.id", &id));
+ EXPECT_EQ(id, "some.item");
+}
+
+TEST_F(MediaInternalsTest, UpdateModifiesExistingItem) {
+ UpdateItem("some.item", "testing", new base::FundamentalValue(true));
+ UpdateItem("some.item", "value", new base::FundamentalValue(5));
+ UpdateItem("some.item", "testing", new base::FundamentalValue(false));
+ bool testing = true;
+ int value = 0;
+ std::string id;
+
+ EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
+ EXPECT_FALSE(testing);
+
+ EXPECT_TRUE(data()->GetInteger("some.item.value", &value));
+ EXPECT_EQ(value, 5);
+
+ EXPECT_TRUE(data()->GetString("some.item.id", &id));
+ EXPECT_EQ(id, "some.item");
+}
+
+TEST_F(MediaInternalsTest, ObserversReceiveNotifications) {
+ scoped_ptr<MockMediaInternalsObserver> observer(
+ new MockMediaInternalsObserver());
+
+ EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(1);
+
+ MediaInternals::UpdateCallback callback = base::Bind(
+ &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
+
+ internals_->AddUpdateCallback(callback);
+ SendUpdate("fn", data());
+}
+
+TEST_F(MediaInternalsTest, RemovedObserversReceiveNoNotifications) {
+ scoped_ptr<MockMediaInternalsObserver> observer(
+ new MockMediaInternalsObserver());
+
+ EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(0);
+
+ MediaInternals::UpdateCallback callback = base::Bind(
+ &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
+
+ internals_->AddUpdateCallback(callback);
+ internals_->RemoveUpdateCallback(callback);
+ SendUpdate("fn", data());
+}
+
+TEST_F(MediaInternalsTest, DeleteRemovesItem) {
+ base::Value* out;
+
+ UpdateItem("some.item", "testing", base::Value::CreateNullValue());
+ EXPECT_TRUE(data()->Get("some.item", &out));
+ EXPECT_TRUE(data()->Get("some", &out));
+
+ DeleteItem("some.item");
+ EXPECT_FALSE(data()->Get("some.item", &out));
+ EXPECT_TRUE(data()->Get("some", &out));
+
+ DeleteItem("some");
+ EXPECT_FALSE(data()->Get("some.item", &out));
+ EXPECT_FALSE(data()->Get("some", &out));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_source_browsertest.cc b/chromium/content/browser/media/media_source_browsertest.cc
new file mode 100644
index 00000000000..bd119ddff07
--- /dev/null
+++ b/chromium/content/browser/media/media_source_browsertest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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/command_line.h"
+#include "content/browser/media/media_browsertest.h"
+#include "content/public/common/content_switches.h"
+
+// Common media types.
+static const char kWebMAudioOnly[] = "audio/webm; codecs=\"vorbis\"";
+static const char kWebMVideoOnly[] = "video/webm; codecs=\"vp8\"";
+static const char kWebMAudioVideo[] = "video/webm; codecs=\"vorbis, vp8\"";
+
+namespace content {
+
+class MediaSourceTest : public content::MediaBrowserTest {
+ public:
+ void TestSimplePlayback(const char* media_file, const char* media_type,
+ const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("mediafile", media_file));
+ query_params.push_back(std::make_pair("mediatype", media_type));
+ RunMediaTestPage("media_source_player.html", &query_params, expectation,
+ true);
+ }
+
+#if defined(OS_ANDROID)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(
+ switches::kDisableGestureRequirementForMediaPlayback);
+ }
+#endif
+};
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_VideoAudio_WebM) {
+ TestSimplePlayback("bear-320x240.webm", kWebMAudioVideo, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_VideoOnly_WebM) {
+ TestSimplePlayback("bear-320x240-video-only.webm", kWebMVideoOnly, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_AudioOnly_WebM) {
+ TestSimplePlayback("bear-320x240-audio-only.webm", kWebMAudioOnly, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_Type_Error) {
+ TestSimplePlayback("bear-320x240-video-only.webm", kWebMAudioOnly, kError);
+}
+
+// Flaky test crbug.com/246308
+// Test changed to skip checks resulting in flakiness. Proper fix still needed.
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, ConfigChangeVideo) {
+ RunMediaTestPage("mse_config_change.html", NULL, kEnded, true);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_browsertest.cc b/chromium/content/browser/media/webrtc_browsertest.cc
new file mode 100644
index 00000000000..7cf48b0ad77
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_browsertest.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2012 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/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace {
+
+std::string GenerateGetUserMediaCall(int min_width,
+ int max_width,
+ int min_height,
+ int max_height,
+ int min_frame_rate,
+ int max_frame_rate) {
+ return base::StringPrintf(
+ "getUserMedia({video: {mandatory: {minWidth: %d, maxWidth: %d, "
+ "minHeight: %d, maxHeight: %d, minFrameRate: %d, maxFrameRate: %d}, "
+ "optional: []}});",
+ min_width,
+ max_width,
+ min_height,
+ max_height,
+ min_frame_rate,
+ max_frame_rate);
+}
+}
+
+namespace content {
+
+class WebrtcBrowserTest: public ContentBrowserTest {
+ public:
+ WebrtcBrowserTest() {}
+ virtual ~WebrtcBrowserTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // We need fake devices in this test since we want to run on naked VMs. We
+ // assume these switches are set by default in content_browsertests.
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+
+ // The video playback will not work without a GPU, so force its use here.
+ // This may not be available on all VMs though.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ protected:
+ bool ExecuteJavascript(const std::string& javascript) {
+ return ExecuteScript(shell()->web_contents(), javascript);
+ }
+
+ void ExpectTitle(const std::string& expected_title) const {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+};
+
+// These tests will all make a getUserMedia call with different constraints and
+// see that the success callback is called. If the error callback is called or
+// none of the callbacks are called the tests will simply time out and fail.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetVideoStreamAndStop) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMedia({video: true});"));
+
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetAudioAndVideoStreamAndStop) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMedia({video: true, audio: true});"));
+
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetAudioAndVideoStreamAndClone) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMediaAndClone();"));
+
+ ExpectTitle("OK");
+}
+
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CanSetupVideoCall DISABLED_CanSetupVideoCall
+#else
+#define MAYBE_CanSetupVideoCall CanSetupVideoCall
+#endif
+
+// These tests will make a complete PeerConnection-based call and verify that
+// video is playing for the call.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CanSetupVideoCall) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("call({video: true});"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux, see http://crbug.com/240376
+#define MAYBE_CanSetupAudioAndVideoCall DISABLED_CanSetupAudioAndVideoCall
+#else
+#define MAYBE_CanSetupAudioAndVideoCall CanSetupAudioAndVideoCall
+#endif
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CanSetupAudioAndVideoCall) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("call({video: true, audio: true});"));
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MANUAL_CanSetupCallAndSendDtmf) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("callAndSendDtmf('123,abc');"));
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest,
+ CanMakeEmptyCallThenAddStreamsAndRenegotiate) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ const char* kJavascript =
+ "callEmptyThenAddOneStreamAndRenegotiate({video: true, audio: true});";
+ EXPECT_TRUE(ExecuteJavascript(kJavascript));
+ ExpectTitle("OK");
+}
+
+// This test will make a complete PeerConnection-based call but remove the
+// MSID and bundle attribute from the initial offer to verify that
+// video is playing for the call even if the initiating client don't support
+// MSID. http://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02
+#if defined(OS_WIN) && defined(USE_AURA)
+// Disabled for win7_aura, see http://crbug.com/235089.
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ DISABLED_CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux, see http://crbug.com/240373
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ DISABLED_CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#else
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#endif
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest,
+ MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithoutMsidAndBundle();"));
+ ExpectTitle("OK");
+}
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, CallWithDataOnly) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataOnly();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithDataAndMedia DISABLED_CallWithDataAndMedia
+#else
+#define MAYBE_CallWithDataAndMedia CallWithDataAndMedia
+#endif
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel and audio and video tracks.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithDataAndMedia) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataAndMedia();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithDataAndLaterAddMedia DISABLED_CallWithDataAndLaterAddMedia
+#else
+#define MAYBE_CallWithDataAndLaterAddMedia CallWithDataAndLaterAddMedia
+#endif
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel and later add an audio and video track.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithDataAndLaterAddMedia) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataAndLaterAddMedia();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithNewVideoMediaStream DISABLED_CallWithNewVideoMediaStream
+#else
+#define MAYBE_CallWithNewVideoMediaStream CallWithNewVideoMediaStream
+#endif
+
+// This test will make a PeerConnection-based call and send a new Video
+// MediaStream that has been created based on a MediaStream created with
+// getUserMedia.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithNewVideoMediaStream) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithNewVideoMediaStream();"));
+ ExpectTitle("OK");
+}
+
+// This test will make a PeerConnection-based call and send a new Video
+// MediaStream that has been created based on a MediaStream created with
+// getUserMedia. When video is flowing, the VideoTrack is removed and an
+// AudioTrack is added instead.
+// TODO(phoglund): This test is manual since not all buildbots has an audio
+// input.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MANUAL_CallAndModifyStream) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("callWithNewVideoMediaStreamLaterSwitchToAudio();"));
+ ExpectTitle("OK");
+}
+
+// This test calls getUserMedia in sequence with different constraints.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, TestGetUserMediaConstraints) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+
+ std::vector<std::string> list_of_get_user_media_calls;
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(320, 320, 180, 180, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(320, 320, 240, 240, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(640, 640, 360, 360, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(640, 640, 480, 480, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(960, 960, 720, 720, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(1280, 1280, 720, 720, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(1920, 1920, 1080, 1080, 30, 30));
+
+ for (std::vector<std::string>::iterator const_iterator =
+ list_of_get_user_media_calls.begin();
+ const_iterator != list_of_get_user_media_calls.end();
+ ++const_iterator) {
+ DVLOG(1) << "Calling getUserMedia: " << *const_iterator;
+ NavigateToURL(shell(), url);
+ EXPECT_TRUE(ExecuteJavascript(*const_iterator));
+ ExpectTitle("OK");
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, AddTwoMediaStreamsToOnePC) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("addTwoMediaStreamsToOneConnection();"));
+ ExpectTitle("OK");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store.cc b/chromium/content/browser/media/webrtc_identity_store.cc
new file mode 100644
index 00000000000..67a25851fde
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store.cc
@@ -0,0 +1,307 @@
+// 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 "content/browser/media/webrtc_identity_store.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/threading/worker_pool.h"
+#include "content/browser/media/webrtc_identity_store_backend.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_util.h"
+#include "url/gurl.h"
+
+namespace content {
+
+struct WebRTCIdentityRequestResult {
+ WebRTCIdentityRequestResult(int error,
+ const std::string& certificate,
+ const std::string& private_key)
+ : error(error), certificate(certificate), private_key(private_key) {}
+
+ int error;
+ std::string certificate;
+ std::string private_key;
+};
+
+// Generates a new identity using |common_name| and returns the result in
+// |result|.
+static void GenerateIdentityWorker(const std::string& common_name,
+ WebRTCIdentityRequestResult* result) {
+ result->error = net::OK;
+ int serial_number = base::RandInt(0, std::numeric_limits<int>::max());
+
+ scoped_ptr<crypto::RSAPrivateKey> key(crypto::RSAPrivateKey::Create(1024));
+ if (!key.get()) {
+ DLOG(ERROR) << "Unable to create key pair for client";
+ result->error = net::ERR_KEY_GENERATION_FAILED;
+ return;
+ }
+
+ base::Time now = base::Time::Now();
+ bool success =
+ net::x509_util::CreateSelfSignedCert(key.get(),
+ "CN=" + common_name,
+ serial_number,
+ now,
+ now + base::TimeDelta::FromDays(30),
+ &result->certificate);
+ if (!success) {
+ DLOG(ERROR) << "Unable to create x509 cert for client";
+ result->error = net::ERR_SELF_SIGNED_CERT_GENERATION_FAILED;
+ return;
+ }
+
+ std::vector<uint8> private_key_info;
+ if (!key->ExportPrivateKey(&private_key_info)) {
+ DLOG(ERROR) << "Unable to export private key";
+ result->error = net::ERR_PRIVATE_KEY_EXPORT_FAILED;
+ return;
+ }
+
+ result->private_key =
+ std::string(private_key_info.begin(), private_key_info.end());
+}
+
+class WebRTCIdentityRequestHandle;
+
+// The class represents an identity request internal to WebRTCIdentityStore.
+// It has a one-to-many mapping to the external version of the request,
+// WebRTCIdentityRequestHandle, i.e. multiple identical external requests are
+// combined into one internal request.
+// It's deleted automatically when the request is completed.
+class WebRTCIdentityRequest {
+ public:
+ WebRTCIdentityRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name)
+ : origin_(origin),
+ identity_name_(identity_name),
+ common_name_(common_name) {}
+
+ void Cancel(WebRTCIdentityRequestHandle* handle) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (callbacks_.find(handle) == callbacks_.end())
+ return;
+ callbacks_.erase(handle);
+ }
+
+ private:
+ friend class WebRTCIdentityStore;
+
+ void AddCallback(WebRTCIdentityRequestHandle* handle,
+ const WebRTCIdentityStore::CompletionCallback& callback) {
+ DCHECK(callbacks_.find(handle) == callbacks_.end());
+ callbacks_[handle] = callback;
+ }
+
+ // This method deletes "this" and no one should access it after the request
+ // completes.
+ // We do not use base::Owned to tie its lifetime to the callback for
+ // WebRTCIdentityStoreBackend::FindIdentity, because it needs to live longer
+ // than that if the identity does not exist in DB.
+ void Post(const WebRTCIdentityRequestResult& result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ for (CallbackMap::iterator it = callbacks_.begin(); it != callbacks_.end();
+ ++it)
+ it->second.Run(result.error, result.certificate, result.private_key);
+ delete this;
+ }
+
+ GURL origin_;
+ std::string identity_name_;
+ std::string common_name_;
+ typedef std::map<WebRTCIdentityRequestHandle*,
+ WebRTCIdentityStore::CompletionCallback> CallbackMap;
+ CallbackMap callbacks_;
+};
+
+// The class represents an identity request which calls back to the external
+// client when the request completes.
+// Its lifetime is tied with the Callback held by the corresponding
+// WebRTCIdentityRequest.
+class WebRTCIdentityRequestHandle {
+ public:
+ WebRTCIdentityRequestHandle(
+ WebRTCIdentityStore* store,
+ const WebRTCIdentityStore::CompletionCallback& callback)
+ : store_(store), request_(NULL), callback_(callback) {}
+
+ private:
+ friend class WebRTCIdentityStore;
+
+ // Cancel the request. Does nothing if the request finished or was already
+ // cancelled.
+ void Cancel() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!request_)
+ return;
+
+ callback_.Reset();
+ WebRTCIdentityRequest* request = request_;
+ request_ = NULL;
+ // "this" will be deleted after the following call, because "this" is
+ // owned by the Callback held by |request|.
+ request->Cancel(this);
+ }
+
+ void OnRequestStarted(WebRTCIdentityRequest* request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(request);
+ request_ = request;
+ }
+
+ void OnRequestComplete(int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(request_);
+ request_ = NULL;
+ base::ResetAndReturn(&callback_).Run(error, certificate, private_key);
+ }
+
+ WebRTCIdentityStore* store_;
+ WebRTCIdentityRequest* request_;
+ WebRTCIdentityStore::CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityRequestHandle);
+};
+
+WebRTCIdentityStore::WebRTCIdentityStore(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : task_runner_(base::WorkerPool::GetTaskRunner(true)),
+ backend_(new WebRTCIdentityStoreBackend(path, policy)) {}
+
+WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); }
+
+base::Closure WebRTCIdentityStore::RequestIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const CompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ WebRTCIdentityRequest* request =
+ FindRequest(origin, identity_name, common_name);
+ // If there is no identical request in flight, create a new one, queue it,
+ // and make the backend request.
+ if (!request) {
+ request = new WebRTCIdentityRequest(origin, identity_name, common_name);
+ // |request| will delete itself after the result is posted.
+ if (!backend_->FindIdentity(
+ origin,
+ identity_name,
+ common_name,
+ base::Bind(
+ &WebRTCIdentityStore::BackendFindCallback, this, request))) {
+ // Bail out if the backend failed to start the task.
+ delete request;
+ return base::Closure();
+ }
+ in_flight_requests_.push_back(request);
+ }
+
+ WebRTCIdentityRequestHandle* handle =
+ new WebRTCIdentityRequestHandle(this, callback);
+
+ request->AddCallback(
+ handle,
+ base::Bind(&WebRTCIdentityRequestHandle::OnRequestComplete,
+ base::Owned(handle)));
+ handle->OnRequestStarted(request);
+ return base::Bind(&WebRTCIdentityRequestHandle::Cancel,
+ base::Unretained(handle));
+}
+
+void WebRTCIdentityStore::DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ backend_->DeleteBetween(delete_begin, delete_end, callback);
+}
+
+void WebRTCIdentityStore::SetTaskRunnerForTesting(
+ const scoped_refptr<base::TaskRunner>& task_runner) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ task_runner_ = task_runner;
+}
+
+void WebRTCIdentityStore::BackendFindCallback(WebRTCIdentityRequest* request,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (error == net::OK) {
+ DVLOG(2) << "Identity found in DB.";
+ WebRTCIdentityRequestResult result(error, certificate, private_key);
+ PostRequestResult(request, result);
+ return;
+ }
+ // Generate a new identity if not found in the DB.
+ WebRTCIdentityRequestResult* result =
+ new WebRTCIdentityRequestResult(0, "", "");
+ if (!task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GenerateIdentityWorker, request->common_name_, result),
+ base::Bind(&WebRTCIdentityStore::GenerateIdentityCallback,
+ this,
+ request,
+ base::Owned(result)))) {
+ // Completes the request with error if failed to post the task.
+ WebRTCIdentityRequestResult result(net::ERR_UNEXPECTED, "", "");
+ PostRequestResult(request, result);
+ }
+}
+
+void WebRTCIdentityStore::GenerateIdentityCallback(
+ WebRTCIdentityRequest* request,
+ WebRTCIdentityRequestResult* result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (result->error == net::OK) {
+ DVLOG(2) << "New identity generated and added to the backend.";
+ backend_->AddIdentity(request->origin_,
+ request->identity_name_,
+ request->common_name_,
+ result->certificate,
+ result->private_key);
+ }
+ PostRequestResult(request, *result);
+}
+
+void WebRTCIdentityStore::PostRequestResult(
+ WebRTCIdentityRequest* request,
+ const WebRTCIdentityRequestResult& result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Removes the in flight request from the queue.
+ for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
+ if (in_flight_requests_[i] == request) {
+ in_flight_requests_.erase(in_flight_requests_.begin() + i);
+ break;
+ }
+ }
+ // |request| will be deleted after this call.
+ request->Post(result);
+}
+
+// Find an identical request from the in flight requests.
+WebRTCIdentityRequest* WebRTCIdentityStore::FindRequest(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name) {
+ for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
+ if (in_flight_requests_[i]->origin_ == origin &&
+ in_flight_requests_[i]->identity_name_ == identity_name &&
+ in_flight_requests_[i]->common_name_ == common_name) {
+ return in_flight_requests_[i];
+ }
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store.h b/chromium/content/browser/media/webrtc_identity_store.h
new file mode 100644
index 00000000000..c2b523e2dfe
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store.h
@@ -0,0 +1,114 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class TaskRunner;
+} // namespace base
+
+namespace quota {
+class SpecialStoragePolicy;
+} // namespace quota
+
+namespace content {
+class WebRTCIdentityRequest;
+struct WebRTCIdentityRequestResult;
+class WebRTCIdentityStoreBackend;
+class WebRTCIdentityStoreTest;
+
+// A class for creating and fetching DTLS identities, i.e. the private key and
+// the self-signed certificate.
+// It can be created/destroyed on any thread, but the public methods must be
+// called on the IO thread.
+class CONTENT_EXPORT WebRTCIdentityStore
+ : public base::RefCountedThreadSafe<WebRTCIdentityStore> {
+ public:
+ typedef base::Callback<void(int error,
+ const std::string& certificate,
+ const std::string& private_key)>
+ CompletionCallback;
+
+ // If |path| is empty, nothing will be saved to disk.
+ WebRTCIdentityStore(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy);
+
+ // Retrieve the cached DTLS private key and certificate, i.e. identity, for
+ // the |origin| and |identity_name| pair, or generate a new identity using
+ // |common_name| if such an identity does not exist.
+ // If the given |common_name| is different from the common name in the cached
+ // identity that has the same origin and identity_name, a new private key and
+ // a new certificate will be generated, overwriting the old one.
+ //
+ // |origin| is the origin of the DTLS connection;
+ // |identity_name| is used to identify an identity within an origin; it is
+ // opaque to WebRTCIdentityStore and remains private to the caller, i.e. not
+ // present in the certificate;
+ // |common_name| is the common name used to generate the certificate and will
+ // be shared with the peer of the DTLS connection. Identities created for
+ // different origins or different identity names may have the same common
+ // name.
+ // |callback| is the callback to return the result as DER strings.
+ //
+ // Returns the Closure used to cancel the request if the request is accepted.
+ // The Closure can only be called before the request completes.
+ virtual base::Closure RequestIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const CompletionCallback& callback);
+
+ // Delete the identities created between |delete_begin| and |delete_end|.
+ // |callback| will be called when the operation is done.
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ protected:
+ // Only virtual to allow subclassing for test mock.
+ virtual ~WebRTCIdentityStore();
+
+ private:
+ friend class base::RefCountedThreadSafe<WebRTCIdentityStore>;
+ friend class WebRTCIdentityStoreTest;
+
+ void SetTaskRunnerForTesting(
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ void BackendFindCallback(WebRTCIdentityRequest* request,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key);
+ void GenerateIdentityCallback(WebRTCIdentityRequest* request,
+ WebRTCIdentityRequestResult* result);
+ WebRTCIdentityRequest* FindRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name);
+ void PostRequestResult(WebRTCIdentityRequest* request,
+ const WebRTCIdentityRequestResult& result);
+
+ // The TaskRunner for doing work on a worker thread.
+ scoped_refptr<base::TaskRunner> task_runner_;
+ // Weak references of the in flight requests. Used to join identical external
+ // requests.
+ std::vector<WebRTCIdentityRequest*> in_flight_requests_;
+
+ scoped_refptr<WebRTCIdentityStoreBackend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
diff --git a/chromium/content/browser/media/webrtc_identity_store_backend.cc b/chromium/content/browser/media/webrtc_identity_store_backend.cc
new file mode 100644
index 00000000000..9ec73e92f69
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_backend.cc
@@ -0,0 +1,546 @@
+// 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 "content/browser/media/webrtc_identity_store_backend.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/net_errors.h"
+#include "sql/error_delegate_util.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "url/gurl.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+namespace content {
+
+static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store";
+
+static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
+ FILE_PATH_LITERAL("WebRTCIdentityStore");
+
+// Initializes the identity table, returning true on success.
+static bool InitDB(sql::Connection* db) {
+ if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
+ if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
+ return true;
+ if (!db->Execute("DROP TABLE webrtc_identity_store"))
+ return false;
+ }
+
+ return db->Execute(
+ "CREATE TABLE webrtc_identity_store"
+ " ("
+ "origin TEXT NOT NULL,"
+ "identity_name TEXT NOT NULL,"
+ "common_name TEXT NOT NULL,"
+ "certificate BLOB NOT NULL,"
+ "private_key BLOB NOT NULL,"
+ "creation_time INTEGER)");
+}
+
+struct WebRTCIdentityStoreBackend::IdentityKey {
+ IdentityKey(const GURL& origin, const std::string& identity_name)
+ : origin(origin), identity_name(identity_name) {}
+
+ bool operator<(const IdentityKey& other) const {
+ return origin < other.origin ||
+ (origin == other.origin && identity_name < other.identity_name);
+ }
+
+ GURL origin;
+ std::string identity_name;
+};
+
+struct WebRTCIdentityStoreBackend::Identity {
+ Identity(const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key)
+ : common_name(common_name),
+ certificate(certificate),
+ private_key(private_key),
+ creation_time(base::Time::Now().ToInternalValue()) {}
+
+ Identity(const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key,
+ int64 creation_time)
+ : common_name(common_name),
+ certificate(certificate),
+ private_key(private_key),
+ creation_time(creation_time) {}
+
+ std::string common_name;
+ std::string certificate;
+ std::string private_key;
+ int64 creation_time;
+};
+
+struct WebRTCIdentityStoreBackend::PendingFindRequest {
+ PendingFindRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback)
+ : origin(origin),
+ identity_name(identity_name),
+ common_name(common_name),
+ callback(callback) {}
+
+ ~PendingFindRequest() {}
+
+ GURL origin;
+ std::string identity_name;
+ std::string common_name;
+ FindIdentityCallback callback;
+};
+
+// The class encapsulates the database operations. All members except ctor and
+// dtor should be accessed on the DB thread.
+// It can be created/destroyed on any thread.
+class WebRTCIdentityStoreBackend::SqlLiteStorage
+ : public base::RefCountedThreadSafe<SqlLiteStorage> {
+ public:
+ SqlLiteStorage(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : special_storage_policy_(policy) {
+ if (!path.empty())
+ path_ = path.Append(kWebRTCIdentityStoreDirectory);
+ }
+
+ void Load(IdentityMap* out_map);
+ void Close();
+ void AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void DeleteIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<SqlLiteStorage>;
+
+ enum OperationType {
+ ADD_IDENTITY,
+ DELETE_IDENTITY
+ };
+ struct PendingOperation {
+ PendingOperation(OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity)
+ : type(type),
+ origin(origin),
+ identity_name(identity_name),
+ identity(identity) {}
+
+ OperationType type;
+ GURL origin;
+ std::string identity_name;
+ Identity identity;
+ };
+ typedef std::vector<PendingOperation*> PendingOperationList;
+
+ virtual ~SqlLiteStorage() {}
+ void OnDatabaseError(int error, sql::Statement* stmt);
+ void BatchOperation(OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void Commit();
+
+ // The file path of the DB. Empty if temporary.
+ base::FilePath path_;
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ scoped_ptr<sql::Connection> db_;
+ // Batched DB operations pending to commit.
+ PendingOperationList pending_operations_;
+
+ DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
+};
+
+WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
+ const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : state_(NOT_STARTED),
+ sql_lite_storage_(new SqlLiteStorage(path, policy)) {}
+
+bool WebRTCIdentityStoreBackend::FindIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (state_ == CLOSED)
+ return false;
+
+ if (state_ != LOADED) {
+ // Queues the request to wait for the DB to load.
+ pending_find_requests_.push_back(
+ new PendingFindRequest(origin, identity_name, common_name, callback));
+ if (state_ == LOADING)
+ return true;
+
+ DCHECK_EQ(state_, NOT_STARTED);
+
+ // Kick off loading the DB.
+ scoped_ptr<IdentityMap> out_map(new IdentityMap());
+ base::Closure task(
+ base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
+ // |out_map| will be NULL after this call.
+ if (BrowserThread::PostTaskAndReply(
+ BrowserThread::DB,
+ FROM_HERE,
+ task,
+ base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
+ this,
+ base::Passed(&out_map)))) {
+ state_ = LOADING;
+ return true;
+ }
+ // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
+ }
+
+ IdentityKey key(origin, identity_name);
+ IdentityMap::iterator iter = identities_.find(key);
+ if (iter != identities_.end() && iter->second.common_name == common_name) {
+ // Identity found.
+ return BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback,
+ net::OK,
+ iter->second.certificate,
+ iter->second.private_key));
+ }
+
+ return BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
+}
+
+void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (state_ == CLOSED)
+ return;
+
+ // If there is an existing identity for the same origin and identity_name,
+ // delete it.
+ IdentityKey key(origin, identity_name);
+ Identity identity(common_name, certificate, private_key);
+
+ if (identities_.find(key) != identities_.end()) {
+ if (!BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::DeleteIdentity,
+ sql_lite_storage_,
+ origin,
+ identity_name,
+ identities_.find(key)->second)))
+ return;
+ }
+ identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
+
+ BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::AddIdentity,
+ sql_lite_storage_,
+ origin,
+ identity_name,
+ identity));
+}
+
+void WebRTCIdentityStoreBackend::Close() {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&WebRTCIdentityStoreBackend::Close, this));
+ return;
+ }
+
+ if (state_ == CLOSED)
+ return;
+
+ state_ = CLOSED;
+ BrowserThread::PostTask(
+ BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
+}
+
+void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Delete the in-memory cache.
+ IdentityMap::iterator it = identities_.begin();
+ while (it != identities_.end()) {
+ if (it->second.creation_time >= delete_begin.ToInternalValue() &&
+ it->second.creation_time <= delete_end.ToInternalValue())
+ identities_.erase(it++);
+ else
+ it++;
+ }
+
+ BrowserThread::PostTaskAndReply(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::DeleteBetween,
+ sql_lite_storage_,
+ delete_begin,
+ delete_end,
+ callback),
+ callback);
+}
+
+WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
+
+void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state_ = LOADED;
+ identities_.swap(*out_map);
+
+ for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
+ FindIdentity(pending_find_requests_[i]->origin,
+ pending_find_requests_[i]->identity_name,
+ pending_find_requests_[i]->common_name,
+ pending_find_requests_[i]->callback);
+ delete pending_find_requests_[i];
+ }
+ pending_find_requests_.clear();
+}
+
+//
+// Implementation of SqlLiteStorage.
+//
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ DCHECK(!db_.get());
+
+ // Ensure the parent directory for storing certs is created before reading
+ // from it.
+ const base::FilePath dir = path_.DirName();
+ if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) {
+ DLOG(ERROR) << "Unable to open DB file path.";
+ return;
+ }
+
+ db_.reset(new sql::Connection());
+
+ db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
+
+ if (!db_->Open(path_)) {
+ DLOG(ERROR) << "Unable to open DB.";
+ db_.reset();
+ return;
+ }
+
+ if (!InitDB(db_.get())) {
+ DLOG(ERROR) << "Unable to init DB.";
+ db_.reset();
+ return;
+ }
+
+ db_->Preload();
+
+ // Slurp all the identities into the out_map.
+ sql::Statement stmt(db_->GetUniqueStatement(
+ "SELECT origin, identity_name, common_name, "
+ "certificate, private_key, creation_time "
+ "FROM webrtc_identity_store"));
+ CHECK(stmt.is_valid());
+
+ while (stmt.Step()) {
+ IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
+ std::string common_name(stmt.ColumnString(2));
+ std::string cert, private_key;
+ stmt.ColumnBlobAsString(3, &cert);
+ stmt.ColumnBlobAsString(4, &private_key);
+ int64 creation_time = stmt.ColumnInt64(5);
+ std::pair<IdentityMap::iterator, bool> result =
+ out_map->insert(std::pair<IdentityKey, Identity>(
+ key, Identity(common_name, cert, private_key, creation_time)));
+ DCHECK(result.second);
+ }
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ Commit();
+ db_.reset();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+
+ // Do not add for session only origins.
+ if (special_storage_policy_.get() &&
+ !special_storage_policy_->IsStorageProtected(origin) &&
+ special_storage_policy_->IsStorageSessionOnly(origin)) {
+ return;
+ }
+ BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+ BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
+ int error,
+ sql::Statement* stmt) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!sql::IsErrorCatastrophic(error))
+ return;
+ db_->RazeAndClose();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
+ OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Commit every 30 seconds.
+ static const base::TimeDelta kCommitInterval(
+ base::TimeDelta::FromSeconds(30));
+ // Commit right away if we have more than 512 outstanding operations.
+ static const size_t kCommitAfterBatchSize = 512;
+
+ // We do a full copy of the cert here, and hopefully just here.
+ scoped_ptr<PendingOperation> operation(
+ new PendingOperation(type, origin, identity_name, identity));
+
+ pending_operations_.push_back(operation.release());
+
+ if (pending_operations_.size() == 1) {
+ // We've gotten our first entry for this batch, fire off the timer.
+ BrowserThread::PostDelayedTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Commit, this),
+ kCommitInterval);
+ } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
+ // We've reached a big enough batch, fire off a commit now.
+ BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Commit, this));
+ }
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Maybe an old timer fired or we are already Close()'ed.
+ if (!db_.get() || pending_operations_.empty())
+ return;
+
+ sql::Statement add_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "INSERT INTO webrtc_identity_store "
+ "(origin, identity_name, common_name, certificate,"
+ " private_key, creation_time) VALUES"
+ " (?,?,?,?,?,?)"));
+
+ CHECK(add_stmt.is_valid());
+
+ sql::Statement del_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
+
+ CHECK(del_stmt.is_valid());
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ DLOG(ERROR) << "Failed to begin the transaction.";
+ return;
+ }
+
+ for (PendingOperationList::iterator it = pending_operations_.begin();
+ it != pending_operations_.end();
+ ++it) {
+ scoped_ptr<PendingOperation> po(*it);
+ switch (po->type) {
+ case ADD_IDENTITY: {
+ add_stmt.Reset(true);
+ add_stmt.BindString(0, po->origin.spec());
+ add_stmt.BindString(1, po->identity_name);
+ add_stmt.BindString(2, po->identity.common_name);
+ const std::string& cert = po->identity.certificate;
+ add_stmt.BindBlob(3, cert.data(), cert.size());
+ const std::string& private_key = po->identity.private_key;
+ add_stmt.BindBlob(4, private_key.data(), private_key.size());
+ add_stmt.BindInt64(5, po->identity.creation_time);
+ CHECK(add_stmt.Run());
+ break;
+ }
+ case DELETE_IDENTITY:
+ del_stmt.Reset(true);
+ del_stmt.BindString(0, po->origin.spec());
+ add_stmt.BindString(1, po->identity_name);
+ CHECK(del_stmt.Run());
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ transaction.Commit();
+ pending_operations_.clear();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+
+ // Commit pending operations first.
+ Commit();
+
+ sql::Statement del_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "DELETE FROM webrtc_identity_store"
+ " WHERE creation_time >= ? AND creation_time <= ?"));
+ CHECK(del_stmt.is_valid());
+
+ del_stmt.BindInt64(0, delete_begin.ToInternalValue());
+ del_stmt.BindInt64(1, delete_end.ToInternalValue());
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ DLOG(ERROR) << "Failed to begin the transaction.";
+ return;
+ }
+
+ CHECK(del_stmt.Run());
+ transaction.Commit();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store_backend.h b/chromium/content/browser/media/webrtc_identity_store_backend.h
new file mode 100644
index 00000000000..ab4e1ed7e1e
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_backend.h
@@ -0,0 +1,111 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
+
+#include <map>
+#include <string>
+
+#include "base/time/time.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace quota {
+class SpecialStoragePolicy;
+} // namespace quota
+
+namespace content {
+
+// This class represents a persistent cache of WebRTC identities.
+// It can be created/destroyed/Close() on any thread. All other members should
+// be accessed on the IO thread.
+class WebRTCIdentityStoreBackend
+ : public base::RefCountedThreadSafe<WebRTCIdentityStoreBackend> {
+ public:
+ typedef base::Callback<void(int error,
+ const std::string& certificate,
+ const std::string& private_key)>
+ FindIdentityCallback;
+
+ // No data is saved on disk if |path| is empty.
+ WebRTCIdentityStoreBackend(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy);
+
+ // Finds the identity with |origin|, |identity_name|, and |common_name| from
+ // the DB.
+ // |origin| is the origin of the identity;
+ // |identity_name| is used to identify an identity within an origin;
+ // |common_name| is the common name used to generate the certificate;
+ // |callback| is the callback to return the find result.
+ // Returns true if |callback| will be called.
+ // Should be called on the IO thread.
+ bool FindIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback);
+
+ // Adds the identity to the DB and overwrites any existing identity having the
+ // same origin and identity_name.
+ // |origin| is the origin of the identity;
+ // |identity_name| is used to identify an identity within an origin;
+ // |common_name| is the common name used to generate the certificate;
+ // |certificate| is the DER string of the certificate;
+ // |private_key| is the DER string of the private key.
+ // Should be called on the IO thread.
+ void AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key);
+
+ // Commits all pending DB operations and closes the DB connection. Any API
+ // call after this will fail.
+ // Can be called on any thread.
+ void Close();
+
+ // Delete the data created between |delete_begin| and |delete_end|.
+ // Should be called on the IO thread.
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<WebRTCIdentityStoreBackend>;
+ class SqlLiteStorage;
+ enum LoadingState {
+ NOT_STARTED,
+ LOADING,
+ LOADED,
+ CLOSED,
+ };
+ struct PendingFindRequest;
+ struct IdentityKey;
+ struct Identity;
+ typedef std::map<IdentityKey, Identity> IdentityMap;
+
+ ~WebRTCIdentityStoreBackend();
+
+ void OnLoaded(scoped_ptr<IdentityMap> out_map);
+
+ // In-memory copy of the identities.
+ IdentityMap identities_;
+ // "Find identity" requests waiting for the DB to load.
+ std::vector<PendingFindRequest*> pending_find_requests_;
+ // The persistent storage loading state.
+ LoadingState state_;
+ // The persistent storage of identities.
+ scoped_refptr<SqlLiteStorage> sql_lite_storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStoreBackend);
+};
+}
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
diff --git a/chromium/content/browser/media/webrtc_identity_store_unittest.cc b/chromium/content/browser/media/webrtc_identity_store_unittest.cc
new file mode 100644
index 00000000000..81c25d7f63f
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_unittest.cc
@@ -0,0 +1,278 @@
+// 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/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "content/browser/media/webrtc_identity_store.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// TODO(jiayl): the tests fail on Android since the openssl version of
+// CreateSelfSignedCert is not implemented. We should mock out this dependency
+// and remove the if-defined.
+#if !defined(OS_ANDROID)
+
+static const char* kFakeOrigin = "http://foo.com";
+static const char* kFakeIdentityName1 = "name1";
+static const char* kFakeIdentityName2 = "name2";
+static const char* kFakeCommonName1 = "cname1";
+static const char* kFakeCommonName2 = "cname2";
+
+static void OnRequestCompleted(bool* completed,
+ std::string* out_cert,
+ std::string* out_key,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ ASSERT_EQ(net::OK, error);
+ ASSERT_NE("", certificate);
+ ASSERT_NE("", private_key);
+ *completed = true;
+ *out_cert = certificate;
+ *out_key = private_key;
+}
+
+class WebRTCIdentityStoreTest : public testing::Test {
+ public:
+ WebRTCIdentityStoreTest()
+ : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP |
+ TestBrowserThreadBundle::REAL_DB_THREAD),
+ pool_owner_(
+ new base::SequencedWorkerPoolOwner(3, "WebRTCIdentityStoreTest")),
+ webrtc_identity_store_(
+ new WebRTCIdentityStore(base::FilePath(), NULL)) {
+ webrtc_identity_store_->SetTaskRunnerForTesting(pool_owner_->pool());
+ }
+
+ virtual ~WebRTCIdentityStoreTest() {
+ pool_owner_->pool()->Shutdown();
+ }
+
+ void RunUntilIdle() {
+ RunAllPendingInMessageLoop(BrowserThread::DB);
+ RunAllPendingInMessageLoop(BrowserThread::IO);
+ pool_owner_->pool()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ base::Closure RequestIdentityAndRunUtilIdle(const std::string& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ bool* completed,
+ std::string* certificate,
+ std::string* private_key) {
+ base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity(
+ GURL(origin),
+ identity_name,
+ common_name,
+ base::Bind(&OnRequestCompleted, completed, certificate, private_key));
+ EXPECT_FALSE(cancel_callback.is_null());
+ RunUntilIdle();
+ return cancel_callback;
+ }
+
+ protected:
+ TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_;
+ scoped_refptr<WebRTCIdentityStore> webrtc_identity_store_;
+};
+
+TEST_F(WebRTCIdentityStoreTest, RequestIdentity) {
+ bool completed = false;
+ std::string dummy;
+ base::Closure cancel_callback =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed,
+ &dummy,
+ &dummy);
+ EXPECT_TRUE(completed);
+}
+
+TEST_F(WebRTCIdentityStoreTest, CancelRequest) {
+ bool completed = false;
+ std::string dummy;
+ base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback.is_null());
+ cancel_callback.Run();
+
+ RunUntilIdle();
+ EXPECT_FALSE(completed);
+}
+
+TEST_F(WebRTCIdentityStoreTest, ConcurrentUniqueRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string dummy;
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName2,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ RunUntilIdle();
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, DifferentCommonNameReturnNewIdentity) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName2,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_NE(cert_1, cert_2);
+ EXPECT_NE(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, SerialIdenticalRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_EQ(cert_1, cert_2);
+ EXPECT_EQ(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, ConcurrentIdenticalRequestsJoined) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ RunUntilIdle();
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_EQ(cert_1, cert_2);
+ EXPECT_EQ(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, CancelOneOfIdenticalRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ cancel_callback_1.Run();
+
+ RunUntilIdle();
+ EXPECT_FALSE(completed_1);
+ EXPECT_TRUE(completed_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, DeleteDataAndGenerateNewIdentity) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ // Generate the first identity.
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ // Clear the data and the second request should return a new identity.
+ webrtc_identity_store_->DeleteBetween(
+ base::Time(), base::Time::Now(), base::Bind(&base::DoNothing));
+ RunUntilIdle();
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_NE(cert_1, cert_2);
+ EXPECT_NE(key_1, key_2);
+}
+
+#endif
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals.cc b/chromium/content/browser/media/webrtc_internals.cc
new file mode 100644
index 00000000000..f8bcdc3d321
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals.h"
+
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+
+using base::ProcessId;
+using std::string;
+
+namespace content {
+
+namespace {
+// Makes sure that |dict| has a ListValue under path "log".
+static base::ListValue* EnsureLogList(base::DictionaryValue* dict) {
+ base::ListValue* log = NULL;
+ if (!dict->GetList("log", &log)) {
+ log = new base::ListValue();
+ if (log)
+ dict->Set("log", log);
+ }
+ return log;
+}
+
+} // namespace
+
+WebRTCInternals::WebRTCInternals() : is_recording_rtp_(false) {
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+
+ BrowserChildProcessObserver::Add(this);
+}
+
+WebRTCInternals::~WebRTCInternals() {
+ BrowserChildProcessObserver::Remove(this);
+}
+
+WebRTCInternals* WebRTCInternals::GetInstance() {
+ return Singleton<WebRTCInternals>::get();
+}
+
+void WebRTCInternals::OnAddPeerConnection(int render_process_id,
+ ProcessId pid,
+ int lid,
+ const string& url,
+ const string& servers,
+ const string& constraints) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (!dict)
+ return;
+
+ dict->SetInteger("rid", render_process_id);
+ dict->SetInteger("pid", static_cast<int>(pid));
+ dict->SetInteger("lid", lid);
+ dict->SetString("servers", servers);
+ dict->SetString("constraints", constraints);
+ dict->SetString("url", url);
+ peer_connection_data_.Append(dict);
+
+ if (observers_.size() > 0)
+ SendUpdate("addPeerConnection", dict);
+}
+
+void WebRTCInternals::OnRemovePeerConnection(ProcessId pid, int lid) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
+ base::DictionaryValue* dict = NULL;
+ peer_connection_data_.GetDictionary(i, &dict);
+
+ int this_pid = 0;
+ int this_lid = 0;
+ dict->GetInteger("pid", &this_pid);
+ dict->GetInteger("lid", &this_lid);
+
+ if (this_pid != static_cast<int>(pid) || this_lid != lid)
+ continue;
+
+ peer_connection_data_.Remove(i, NULL);
+
+ if (observers_.size() > 0) {
+ base::DictionaryValue id;
+ id.SetInteger("pid", static_cast<int>(pid));
+ id.SetInteger("lid", lid);
+ SendUpdate("removePeerConnection", &id);
+ }
+ break;
+ }
+}
+
+void WebRTCInternals::OnUpdatePeerConnection(
+ ProcessId pid, int lid, const string& type, const string& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
+ base::DictionaryValue* record = NULL;
+ peer_connection_data_.GetDictionary(i, &record);
+
+ int this_pid = 0, this_lid = 0;
+ record->GetInteger("pid", &this_pid);
+ record->GetInteger("lid", &this_lid);
+
+ if (this_pid != static_cast<int>(pid) || this_lid != lid)
+ continue;
+
+ // Append the update to the end of the log.
+ base::ListValue* log = EnsureLogList(record);
+ if (!log)
+ return;
+
+ base::DictionaryValue* log_entry = new base::DictionaryValue();
+ if (!log_entry)
+ return;
+
+ log_entry->SetString("type", type);
+ log_entry->SetString("value", value);
+ log->Append(log_entry);
+
+ if (observers_.size() > 0) {
+ base::DictionaryValue update;
+ update.SetInteger("pid", static_cast<int>(pid));
+ update.SetInteger("lid", lid);
+ update.SetString("type", type);
+ update.SetString("value", value);
+
+ SendUpdate("updatePeerConnection", &update);
+ }
+ return;
+ }
+}
+
+void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid,
+ const base::ListValue& value) {
+ if (observers_.size() == 0)
+ return;
+
+ base::DictionaryValue dict;
+ dict.SetInteger("pid", static_cast<int>(pid));
+ dict.SetInteger("lid", lid);
+
+ base::ListValue* list = value.DeepCopy();
+ if (!list)
+ return;
+
+ dict.Set("reports", list);
+
+ SendUpdate("addStats", &dict);
+}
+
+void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ observers_.AddObserver(observer);
+}
+
+void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ observers_.RemoveObserver(observer);
+}
+
+void WebRTCInternals::SendAllUpdates() {
+ if (observers_.size() > 0)
+ SendUpdate("updateAllPeerConnections", &peer_connection_data_);
+}
+
+void WebRTCInternals::StartRtpRecording() {
+ if (!is_recording_rtp_) {
+ is_recording_rtp_ = true;
+ // TODO(justinlin): start RTP recording.
+ }
+}
+
+void WebRTCInternals::StopRtpRecording() {
+ if (is_recording_rtp_) {
+ is_recording_rtp_ = false;
+ // TODO(justinlin): stop RTP recording.
+ }
+}
+
+void WebRTCInternals::SendUpdate(const string& command, base::Value* value) {
+ DCHECK_GT(observers_.size(), (size_t)0);
+
+ FOR_EACH_OBSERVER(WebRTCInternalsUIObserver,
+ observers_,
+ OnUpdate(command, value));
+}
+
+void WebRTCInternals::BrowserChildProcessCrashed(
+ const ChildProcessData& data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ OnRendererExit(data.id);
+}
+
+void WebRTCInternals::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ OnRendererExit(Source<RenderProcessHost>(source)->GetID());
+}
+
+void WebRTCInternals::OnRendererExit(int render_process_id) {
+ // Iterates from the end of the list to remove the PeerConnections created
+ // by the exitting renderer.
+ for (int i = peer_connection_data_.GetSize() - 1; i >= 0; --i) {
+ base::DictionaryValue* record = NULL;
+ peer_connection_data_.GetDictionary(i, &record);
+
+ int this_rid = 0;
+ record->GetInteger("rid", &this_rid);
+
+ if (this_rid == render_process_id) {
+ if (observers_.size() > 0) {
+ int lid = 0, pid = 0;
+ record->GetInteger("lid", &lid);
+ record->GetInteger("pid", &pid);
+
+ base::DictionaryValue update;
+ update.SetInteger("lid", lid);
+ update.SetInteger("pid", pid);
+ SendUpdate("removePeerConnection", &update);
+ }
+ peer_connection_data_.Remove(i, NULL);
+ }
+ }
+}
+
+// TODO(justlin): Calls this method as necessary to update the recording status
+// UI.
+void WebRTCInternals::SendRtpRecordingUpdate() {
+ DCHECK(is_recording_rtp_);
+ base::DictionaryValue update;
+ // TODO(justinlin): Fill in |update| with values as appropriate.
+ SendUpdate("updateDumpStatus", &update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals.h b/chromium/content/browser/media/webrtc_internals.h
new file mode 100644
index 00000000000..58e9a9fbb9b
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals.h
@@ -0,0 +1,117 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
+
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "base/process/process.h"
+#include "base/values.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_child_process_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace content {
+class WebRTCInternalsUIObserver;
+
+// This is a singleton class running in the browser UI thread.
+// It collects peer connection infomation from the renderers,
+// forwards the data to WebRTCInternalsUIObserver and
+// sends data collecting commands to the renderers.
+class CONTENT_EXPORT WebRTCInternals : public BrowserChildProcessObserver,
+ public NotificationObserver {
+ public:
+ static WebRTCInternals* GetInstance();
+
+ // This method is called when a PeerConnection is created.
+ // |render_process_id| is the id of the render process (not OS pid), which is
+ // needed because we might not be able to get the OS process id when the
+ // render process terminates and we want to clean up.
+ // |pid| is the renderer process id, |lid| is the renderer local id used to
+ // identify a PeerConnection, |url| is the url of the tab owning the
+ // PeerConnection, |servers| is the servers configuration, |constraints| is
+ // the media constraints used to initialize the PeerConnection.
+ void OnAddPeerConnection(int render_process_id,
+ base::ProcessId pid,
+ int lid,
+ const std::string& url,
+ const std::string& servers,
+ const std::string& constraints);
+
+ // This method is called when PeerConnection is destroyed.
+ // |pid| is the renderer process id, |lid| is the renderer local id.
+ void OnRemovePeerConnection(base::ProcessId pid, int lid);
+
+ // This method is called when a PeerConnection is updated.
+ // |pid| is the renderer process id, |lid| is the renderer local id,
+ // |type| is the update type, |value| is the detail of the update.
+ void OnUpdatePeerConnection(base::ProcessId pid,
+ int lid,
+ const std::string& type,
+ const std::string& value);
+
+ // This method is called when results from PeerConnectionInterface::GetStats
+ // are available. |pid| is the renderer process id, |lid| is the renderer
+ // local id, |value| is the list of stats reports.
+ void OnAddStats(base::ProcessId pid, int lid, const base::ListValue& value);
+
+ // Methods for adding or removing WebRTCInternalsUIObserver.
+ void AddObserver(WebRTCInternalsUIObserver *observer);
+ void RemoveObserver(WebRTCInternalsUIObserver *observer);
+
+ // Sends all update data to the observers.
+ void SendAllUpdates();
+
+ // Tells the renderer processes to start or stop recording RTP packets.
+ void StartRtpRecording();
+ void StopRtpRecording();
+
+ private:
+ friend struct DefaultSingletonTraits<WebRTCInternals>;
+
+ WebRTCInternals();
+ virtual ~WebRTCInternals();
+
+ void SendUpdate(const std::string& command, base::Value* value);
+
+ // BrowserChildProcessObserver implementation.
+ virtual void BrowserChildProcessCrashed(
+ const ChildProcessData& data) OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Called when a renderer exits (including crashes).
+ void OnRendererExit(int render_process_id);
+
+ void SendRtpRecordingUpdate();
+
+ ObserverList<WebRTCInternalsUIObserver> observers_;
+
+ // |peer_connection_data_| is a list containing all the PeerConnection
+ // updates.
+ // Each item of the list represents the data for one PeerConnection, which
+ // contains these fields:
+ // "pid" -- processId of the renderer that creates the PeerConnection.
+ // "lid" -- local Id assigned to the PeerConnection.
+ // "url" -- url of the web page that created the PeerConnection.
+ // "servers" and "constraints" -- server configuration and media constraints
+ // used to initialize the PeerConnection respectively.
+ // "log" -- a ListValue contains all the updates for the PeerConnection. Each
+ // list item is a DictionaryValue containing "type" and "value", both of which
+ // are strings.
+ base::ListValue peer_connection_data_;
+
+ NotificationRegistrar registrar_;
+
+ bool is_recording_rtp_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
diff --git a/chromium/content/browser/media/webrtc_internals_browsertest.cc b/chromium/content/browser/media/webrtc_internals_browsertest.cc
new file mode 100644
index 00000000000..71bbe53ed9f
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_browsertest.cc
@@ -0,0 +1,709 @@
+// Copyright (c) 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/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using std::string;
+namespace content {
+
+struct SsrcEntry {
+ string GetSsrcAttributeString() const {
+ std::stringstream ss;
+ ss << "a=ssrc:" << id;
+ std::map<string, string>::const_iterator iter;
+ for (iter = properties.begin(); iter != properties.end(); ++iter) {
+ ss << " " << iter->first << ":" << iter->second;
+ }
+ return ss.str();
+ }
+
+ string GetAsJSON() const {
+ std::stringstream ss;
+ ss << "{";
+ std::map<string, string>::const_iterator iter;
+ for (iter = properties.begin(); iter != properties.end(); ++iter) {
+ if (iter != properties.begin())
+ ss << ",";
+ ss << "\"" << iter->first << "\":\"" << iter->second << "\"";
+ }
+ ss << "}";
+ return ss.str();
+ }
+
+ string id;
+ std::map<string, string> properties;
+};
+
+struct EventEntry {
+ string type;
+ string value;
+};
+
+struct StatsUnit {
+ string GetString() const {
+ std::stringstream ss;
+ ss << "{timestamp:" << timestamp << ", values:[";
+ std::map<string, string>::const_iterator iter;
+ for (iter = values.begin(); iter != values.end(); ++iter) {
+ ss << "'" << iter->first << "','" << iter->second << "',";
+ }
+ ss << "]}";
+ return ss.str();
+ }
+
+ int64 timestamp;
+ std::map<string, string> values;
+};
+
+struct StatsEntry {
+ string type;
+ string id;
+ StatsUnit stats;
+};
+
+typedef std::map<string, std::vector<string> > StatsMap;
+
+class PeerConnectionEntry {
+ public:
+ PeerConnectionEntry(int pid, int lid) : pid_(pid), lid_(lid) {}
+
+ void AddEvent(const string& type, const string& value) {
+ EventEntry entry = {type, value};
+ events_.push_back(entry);
+ }
+
+ string getIdString() const {
+ std::stringstream ss;
+ ss << pid_ << "-" << lid_;
+ return ss.str();
+ }
+
+ string getLogIdString() const {
+ std::stringstream ss;
+ ss << pid_ << "-" << lid_ << "-update-log";
+ return ss.str();
+ }
+
+ string getAllUpdateString() const {
+ std::stringstream ss;
+ ss << "{pid:" << pid_ << ", lid:" << lid_ << ", log:[";
+ for (size_t i = 0; i < events_.size(); ++i) {
+ ss << "{type:'" << events_[i].type <<
+ "', value:'" << events_[i].value << "'},";
+ }
+ ss << "]}";
+ return ss.str();
+ }
+
+ int pid_;
+ int lid_;
+ std::vector<EventEntry> events_;
+ // This is a record of the history of stats value reported for each stats
+ // report id (e.g. ssrc-1234) for each stats name (e.g. framerate).
+ // It a 2-D map with each map entry is a vector of reported values.
+ // It is used to verify the graph data series.
+ std::map<string, StatsMap> stats_;
+};
+
+static const int64 FAKE_TIME_STAMP = 3600000;
+
+class WebRTCInternalsBrowserTest: public ContentBrowserTest {
+ public:
+ WebRTCInternalsBrowserTest() {}
+ virtual ~WebRTCInternalsBrowserTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ // We need fake devices in this test since we want to run on naked VMs. We
+ // assume these switches are set by default in content_browsertests.
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+ }
+
+ protected:
+ bool ExecuteJavascript(const string& javascript) {
+ return ExecuteScript(shell()->web_contents(), javascript);
+ }
+
+ void ExpectTitle(const std::string& expected_title) const {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+
+ // Execute the javascript of addPeerConnection.
+ void ExecuteAddPeerConnectionJs(const PeerConnectionEntry& pc) {
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << ", " <<
+ "url:'u', servers:'s', constraints:'c'}";
+ ASSERT_TRUE(ExecuteJavascript("addPeerConnection(" + ss.str() + ");"));
+ }
+
+ // Execute the javascript of removePeerConnection.
+ void ExecuteRemovePeerConnectionJs(const PeerConnectionEntry& pc) {
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << "}";
+
+ ASSERT_TRUE(ExecuteJavascript("removePeerConnection(" + ss.str() + ");"));
+ }
+
+ // Verifies that the DOM element with id |id| exists.
+ void VerifyElementWithId(const string& id) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send($('" + id + "') != null);",
+ &result));
+ EXPECT_TRUE(result);
+ }
+
+ // Verifies that the DOM element with id |id| does not exist.
+ void VerifyNoElementWithId(const string& id) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send($('" + id + "') == null);",
+ &result));
+ EXPECT_TRUE(result);
+ }
+
+ // Verifies that DOM for |pc| is correctly created with the right content.
+ void VerifyPeerConnectionEntry(const PeerConnectionEntry& pc) {
+ VerifyElementWithId(pc.getIdString());
+ if (pc.events_.size() == 0)
+ return;
+
+ string log_id = pc.getLogIdString();
+ VerifyElementWithId(log_id);
+ string result;
+ for (size_t i = 0; i < pc.events_.size(); ++i) {
+ std::stringstream ss;
+ ss << "var row = $('" << log_id << "').rows[" << (i + 1) << "];"
+ "var cell = row.lastChild;"
+ "window.domAutomationController.send(cell.firstChild.textContent);";
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(), ss.str(), &result));
+ EXPECT_EQ(pc.events_[i].type + pc.events_[i].value, result);
+ }
+ }
+
+ // Executes the javascript of updatePeerConnection and verifies the result.
+ void ExecuteAndVerifyUpdatePeerConnection(
+ PeerConnectionEntry& pc, const string& type, const string& value) {
+ pc.AddEvent(type, value);
+
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ <<
+ ", type:'" << type << "', value:'" << value << "'}";
+ ASSERT_TRUE(ExecuteJavascript("updatePeerConnection(" + ss.str() + ")"));
+
+ VerifyPeerConnectionEntry(pc);
+ }
+
+ // Execute addStats and verifies that the stats table has the right content.
+ void ExecuteAndVerifyAddStats(
+ PeerConnectionEntry& pc, const string& type, const string& id,
+ StatsUnit& stats) {
+ StatsEntry entry = {type, id, stats};
+
+ // Adds each new value to the map of stats history.
+ std::map<string, string>::iterator iter;
+ for (iter = stats.values.begin(); iter != stats.values.end(); iter++) {
+ pc.stats_[id][iter->first].push_back(iter->second);
+ }
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ","
+ "reports:[" << "{id:'" << id << "', type:'" << type << "', "
+ "stats:" << stats.GetString() << "}]}";
+
+ ASSERT_TRUE(ExecuteJavascript("addStats(" + ss.str() + ")"));
+ VerifyStatsTable(pc, entry);
+ }
+
+
+ // Verifies that the stats table has the right content.
+ void VerifyStatsTable(const PeerConnectionEntry& pc,
+ const StatsEntry& report) {
+ string table_id =
+ pc.getIdString() + "-table-" + report.id;
+ VerifyElementWithId(table_id);
+
+ std::map<string, string>::const_iterator iter;
+ for (iter = report.stats.values.begin();
+ iter != report.stats.values.end(); iter++) {
+ VerifyStatsTableRow(table_id, iter->first, iter->second);
+ }
+ }
+
+ // Verifies that the row named as |name| of the stats table |table_id| has
+ // the correct content as |name| : |value|.
+ void VerifyStatsTableRow(const string& table_id,
+ const string& name,
+ const string& value) {
+ VerifyElementWithId(table_id + "-" + name);
+
+ string result;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "var row = $('" + table_id + "-" + name + "');"
+ "var name = row.cells[0].textContent;"
+ "var value = row.cells[1].textContent;"
+ "window.domAutomationController.send(name + ':' + value)",
+ &result));
+ EXPECT_EQ(name + ":" + value, result);
+ }
+
+ // Verifies that the graph data series consistent with pc.stats_.
+ void VerifyStatsGraph(const PeerConnectionEntry& pc) {
+ std::map<string, StatsMap>::const_iterator stream_iter;
+ for (stream_iter = pc.stats_.begin();
+ stream_iter != pc.stats_.end(); stream_iter++) {
+ StatsMap::const_iterator stats_iter;
+ for (stats_iter = stream_iter->second.begin();
+ stats_iter != stream_iter->second.end();
+ stats_iter++) {
+ string graph_id = stream_iter->first + "-" + stats_iter->first;
+ for (size_t i = 0; i < stats_iter->second.size(); ++i) {
+ float number;
+ std::stringstream stream(stats_iter->second[i]);
+ stream >> number;
+ if (stream.fail())
+ continue;
+ VerifyGraphDataPoint(
+ pc.getIdString(), graph_id, i, stats_iter->second[i]);
+ }
+ }
+ }
+ }
+
+ // Verifies that the graph data point at index |index| has value |value|.
+ void VerifyGraphDataPoint(const string& pc_id, const string& graph_id,
+ int index, const string& value) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "graphViews['" + pc_id + "-" + graph_id + "'] != null)",
+ &result));
+ EXPECT_TRUE(result);
+
+ std::stringstream ss;
+ ss << "var dp = peerConnectionDataStore['" << pc_id << "']"
+ ".getDataSeries('" << graph_id << "').dataPoints_[" << index << "];"
+ "window.domAutomationController.send(dp.value.toString())";
+ string actual_value;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(), ss.str(), &actual_value));
+ EXPECT_EQ(value, actual_value);
+ }
+
+ // Get the JSON string of the ssrc info from the page.
+ string GetSsrcInfo(const string& ssrc_id) {
+ string result;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send(JSON.stringify("
+ "ssrcInfoManager.streamInfoContainer_['" + ssrc_id + "']))",
+ &result));
+ return result;
+ }
+
+ int GetSsrcInfoBlockCount(Shell* shell) {
+ int count = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ shell->web_contents(),
+ "window.domAutomationController.send("
+ "document.getElementsByClassName("
+ "ssrcInfoManager.SSRC_INFO_BLOCK_CLASS).length);",
+ &count));
+ return count;
+ }
+
+ // Verifies |dump| contains |peer_connection_number| peer connection dumps,
+ // each containing |update_number| updates and |stats_number| stats tables.
+ void VerifyPageDumpStructure(base::Value* dump,
+ int peer_connection_number,
+ int update_number,
+ int stats_number) {
+ EXPECT_NE((base::Value*)NULL, dump);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, dump->GetType());
+
+ base::DictionaryValue* dict_dump =
+ static_cast<base::DictionaryValue*>(dump);
+ EXPECT_EQ((size_t) peer_connection_number, dict_dump->size());
+
+ base::DictionaryValue::Iterator it(*dict_dump);
+ for (; !it.IsAtEnd(); it.Advance()) {
+ base::Value* value = NULL;
+ dict_dump->Get(it.key(), &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+ base::DictionaryValue* pc_dump =
+ static_cast<base::DictionaryValue*>(value);
+ EXPECT_TRUE(pc_dump->HasKey("updateLog"));
+ EXPECT_TRUE(pc_dump->HasKey("stats"));
+
+ // Verifies the number of updates.
+ pc_dump->Get("updateLog", &value);
+ EXPECT_EQ(base::Value::TYPE_LIST, value->GetType());
+ base::ListValue* list = static_cast<base::ListValue*>(value);
+ EXPECT_EQ((size_t) update_number, list->GetSize());
+
+ // Verifies the number of stats tables.
+ pc_dump->Get("stats", &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+ base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
+ EXPECT_EQ((size_t) stats_number, dict->size());
+ }
+ }
+
+ // Verifies |dump| contains the correct statsTable and statsDataSeries for
+ // |pc|.
+ void VerifyStatsDump(base::Value* dump,
+ const PeerConnectionEntry& pc,
+ const string& report_type,
+ const string& report_id,
+ const StatsUnit& stats) {
+ EXPECT_NE((base::Value*)NULL, dump);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, dump->GetType());
+
+ base::DictionaryValue* dict_dump =
+ static_cast<base::DictionaryValue*>(dump);
+ base::Value* value = NULL;
+ dict_dump->Get(pc.getIdString(), &value);
+ base::DictionaryValue* pc_dump = static_cast<base::DictionaryValue*>(value);
+
+ // Verifies there is one data series per stats name.
+ value = NULL;
+ pc_dump->Get("stats", &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+
+ base::DictionaryValue* dataSeries =
+ static_cast<base::DictionaryValue*>(value);
+ EXPECT_EQ(stats.values.size(), dataSeries->size());
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, AddAndRemovePeerConnection) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ // Add two PeerConnections and then remove them.
+ PeerConnectionEntry pc_1(1, 0);
+ ExecuteAddPeerConnectionJs(pc_1);
+ VerifyPeerConnectionEntry(pc_1);
+
+ PeerConnectionEntry pc_2(2, 1);
+ ExecuteAddPeerConnectionJs(pc_2);
+ VerifyPeerConnectionEntry(pc_2);
+
+ ExecuteRemovePeerConnectionJs(pc_1);
+ VerifyNoElementWithId(pc_1.getIdString());
+ VerifyPeerConnectionEntry(pc_2);
+
+ ExecuteRemovePeerConnectionJs(pc_2);
+ VerifyNoElementWithId(pc_2.getIdString());
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, UpdateAllPeerConnections) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc_0(1, 0);
+ pc_0.AddEvent("e1", "v1");
+ pc_0.AddEvent("e2", "v2");
+ PeerConnectionEntry pc_1(1, 1);
+ pc_1.AddEvent("e3", "v3");
+ pc_1.AddEvent("e4", "v4");
+ string pc_array = "[" + pc_0.getAllUpdateString() + ", " +
+ pc_1.getAllUpdateString() + "]";
+ EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+ VerifyPeerConnectionEntry(pc_0);
+ VerifyPeerConnectionEntry(pc_1);
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, UpdatePeerConnection) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ // Add one PeerConnection and send one update.
+ PeerConnectionEntry pc_1(1, 0);
+ ExecuteAddPeerConnectionJs(pc_1);
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_1, "e1", "v1");
+
+ // Add another PeerConnection and send two updates.
+ PeerConnectionEntry pc_2(1, 1);
+ ExecuteAddPeerConnectionJs(pc_2);
+
+ SsrcEntry ssrc1, ssrc2;
+ ssrc1.id = "ssrcid1";
+ ssrc1.properties["msid"] = "mymsid";
+ ssrc2.id = "ssrcid2";
+ ssrc2.properties["label"] = "mylabel";
+ ssrc2.properties["cname"] = "mycname";
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_2, "setRemoteDescription",
+ ssrc1.GetSsrcAttributeString());
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_2, "setLocalDescription",
+ ssrc2.GetSsrcAttributeString());
+
+ EXPECT_EQ(ssrc1.GetAsJSON(), GetSsrcInfo(ssrc1.id));
+ EXPECT_EQ(ssrc2.GetAsJSON(), GetSsrcInfo(ssrc2.id));
+
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["ssrc"] = ssrc1.id;
+ ExecuteAndVerifyAddStats(pc_2, "ssrc", "dummyId", stats);
+ EXPECT_GT(GetSsrcInfoBlockCount(shell()), 0);
+}
+
+// Tests that adding random named stats updates the dataSeries and graphs.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, AddStats) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ const string type = "ssrc";
+ const string id = "ssrc-1234";
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["trackId"] = "abcd";
+ stats.values["bitrate"] = "2000";
+ stats.values["framerate"] = "30";
+
+ // Add new stats and verify the stats table and graphs.
+ ExecuteAndVerifyAddStats(pc, type, id, stats);
+ VerifyStatsGraph(pc);
+
+ // Update existing stats and verify the stats table and graphs.
+ stats.values["bitrate"] = "2001";
+ stats.values["framerate"] = "31";
+ ExecuteAndVerifyAddStats(pc, type, id, stats);
+ VerifyStatsGraph(pc);
+}
+
+// Tests that the bandwidth estimation values are drawn on a single graph.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, BweCompoundGraph) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["googAvailableSendBandwidth"] = "1000000";
+ stats.values["googTargetEncBitrate"] = "1000";
+ stats.values["googActualEncBitrate"] = "1000000";
+ stats.values["googRetransmitBitrate"] = "10";
+ stats.values["googTransmitBitrate"] = "1000000";
+ const string stats_type = "bwe";
+ const string stats_id = "videobwe";
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ string graph_id =
+ pc.getIdString() + "-" + stats_id + "-bweCompound";
+ bool result = false;
+ // Verify that the bweCompound graph exists.
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ " graphViews['" + graph_id + "'] != null)",
+ &result));
+ EXPECT_TRUE(result);
+
+ // Verify that the bweCompound graph contains multiple dataSeries.
+ int count = 0;
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ " graphViews['" + graph_id + "'].getDataSeriesCount())",
+ &count));
+ EXPECT_EQ((int)stats.values.size(), count);
+}
+
+// Tests that the total packet/byte count is converted to count per second,
+// and the converted data is drawn.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, ConvertedGraphs) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ const string stats_type = "s";
+ const string stats_id = "1";
+ const int num_converted_stats = 4;
+ const string stats_names[] =
+ {"packetsSent", "bytesSent", "packetsReceived", "bytesReceived"};
+ const string converted_names[] =
+ {"packetsSentPerSecond", "bitsSentPerSecond",
+ "packetsReceivedPerSecond", "bitsReceivedPerSecond"};
+ const string first_value = "1000";
+ const string second_value = "2000";
+ const string converted_values[] = {"1000", "8000", "1000", "8000"};
+
+ // Send the first data point.
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ for (int i = 0; i < num_converted_stats; ++i)
+ stats.values[stats_names[i]] = first_value;
+
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ // Send the second data point at 1000ms after the first data point.
+ stats.timestamp += 1000;
+ for (int i = 0; i < num_converted_stats; ++i)
+ stats.values[stats_names[i]] = second_value;
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ // Verifies the graph data matches converted_values.
+ for (int i = 0; i < num_converted_stats; ++i) {
+ VerifyGraphDataPoint(pc.getIdString(), stats_id + "-" + converted_names[i],
+ 1, converted_values[i]);
+ }
+}
+
+// Timing out on ARM linux bot: http://crbug.com/238490
+// Disabling due to failure on Linux, Mac, Win: http://crbug.com/272413
+// Sanity check of the page content under a real PeerConnection call.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest,
+ DISABLED_WithRealPeerConnectionCall) {
+ // Start a peerconnection call in the first window.
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+ ASSERT_TRUE(ExecuteJavascript("call({video:true});"));
+ ExpectTitle("OK");
+
+ // Open webrtc-internals in the second window.
+ GURL url2("chrome://webrtc-internals");
+ Shell* shell2 = CreateBrowser();
+ NavigateToURL(shell2, url2);
+
+ const int NUMBER_OF_PEER_CONNECTIONS = 2;
+
+ // Verifies the number of peerconnections.
+ int count = 0;
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send("
+ "$('peer-connections-list').getElementsByTagName('li').length);",
+ &count));
+ EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
+
+ // Verifies the the event tables.
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table').length);",
+ &count));
+ EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table')[0].rows.length);",
+ &count));
+ EXPECT_GT(count, 1);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table')[1].rows.length);",
+ &count));
+ EXPECT_GT(count, 1);
+
+ // Wait until the stats table containers are created.
+ count = 0;
+ while (count != NUMBER_OF_PEER_CONNECTIONS) {
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send("
+ "$('peer-connections-list').getElementsByClassName("
+ "'stats-table-container').length);",
+ &count));
+ }
+
+ // Verifies each stats table having more than one rows.
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell2->web_contents(),
+ "var tableContainers = $('peer-connections-list')"
+ ".getElementsByClassName('stats-table-container');"
+ "var result = true;"
+ "for (var i = 0; i < tableContainers.length && result; ++i) {"
+ "var tables = tableContainers[i].getElementsByTagName('table');"
+ "for (var j = 0; j < tables.length && result; ++j) {"
+ "result = (tables[j].rows.length > 1);"
+ "}"
+ "if (!result) {"
+ "console.log(tableContainers[i].innerHTML);"
+ "}"
+ "}"
+ "window.domAutomationController.send(result);",
+ &result));
+
+ EXPECT_TRUE(result);
+
+ count = GetSsrcInfoBlockCount(shell2);
+ EXPECT_GT(count, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, CreatePageDump) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc_0(1, 0);
+ pc_0.AddEvent("e1", "v1");
+ pc_0.AddEvent("e2", "v2");
+ PeerConnectionEntry pc_1(1, 1);
+ pc_1.AddEvent("e3", "v3");
+ pc_1.AddEvent("e4", "v4");
+ string pc_array =
+ "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
+ EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+
+ // Verifies the peer connection data store can be created without stats.
+ string dump_json;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "JSON.stringify(peerConnectionDataStore));",
+ &dump_json));
+ scoped_ptr<base::Value> dump;
+ dump.reset(base::JSONReader::Read(dump_json));
+ VerifyPageDumpStructure(dump.get(),
+ 2 /*peer_connection_number*/,
+ 2 /*update_number*/,
+ 0 /*stats_number*/);
+
+ // Adds a stats report.
+ const string type = "dummy";
+ const string id = "1234";
+ StatsUnit stats = { FAKE_TIME_STAMP };
+ stats.values["bitrate"] = "2000";
+ stats.values["framerate"] = "30";
+ ExecuteAndVerifyAddStats(pc_0, type, id, stats);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "JSON.stringify(peerConnectionDataStore));",
+ &dump_json));
+ dump.reset(base::JSONReader::Read(dump_json));
+ VerifyStatsDump(dump.get(), pc_0, type, id, stats);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_message_handler.cc b/chromium/content/browser/media/webrtc_internals_message_handler.cc
new file mode 100644
index 00000000000..2ba75227380
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_message_handler.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals_message_handler.h"
+
+#include "content/browser/media/webrtc_internals.h"
+#include "content/common/media/peer_connection_tracker_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+WebRTCInternalsMessageHandler::WebRTCInternalsMessageHandler() {
+ WebRTCInternals::GetInstance()->AddObserver(this);
+}
+
+WebRTCInternalsMessageHandler::~WebRTCInternalsMessageHandler() {
+ WebRTCInternals::GetInstance()->RemoveObserver(this);
+}
+
+void WebRTCInternalsMessageHandler::RegisterMessages() {
+ web_ui()->RegisterMessageCallback("getAllUpdates",
+ base::Bind(&WebRTCInternalsMessageHandler::OnGetAllUpdates,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("getAllStats",
+ base::Bind(&WebRTCInternalsMessageHandler::OnGetAllStats,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("startRtpRecording",
+ base::Bind(&WebRTCInternalsMessageHandler::OnStartRtpRecording,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("stopRtpRecording",
+ base::Bind(&WebRTCInternalsMessageHandler::OnStopRtpRecording,
+ base::Unretained(this)));
+}
+
+void WebRTCInternalsMessageHandler::OnGetAllUpdates(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->SendAllUpdates();
+}
+
+void WebRTCInternalsMessageHandler::OnGetAllStats(const base::ListValue* list) {
+ for (RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ i.GetCurrentValue()->Send(new PeerConnectionTracker_GetAllStats());
+ }
+}
+
+void WebRTCInternalsMessageHandler::OnStartRtpRecording(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->StartRtpRecording();
+}
+
+void WebRTCInternalsMessageHandler::OnStopRtpRecording(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->StopRtpRecording();
+}
+
+void WebRTCInternalsMessageHandler::OnUpdate(const std::string& command,
+ const base::Value* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::vector<const base::Value*> args_vector;
+ args_vector.push_back(args);
+ string16 update = WebUI::GetJavascriptCall(command, args_vector);
+
+ RenderViewHost* host = web_ui()->GetWebContents()->GetRenderViewHost();
+ if (host)
+ host->ExecuteJavascriptInWebFrame(string16(), update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_message_handler.h b/chromium/content/browser/media/webrtc_internals_message_handler.h
new file mode 100644
index 00000000000..ee2059a646d
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_message_handler.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace content {
+
+// This class handles messages to and from WebRTCInternalsUI.
+// It delegates all its work to WebRTCInternalsProxy on the IO thread.
+class WebRTCInternalsMessageHandler : public WebUIMessageHandler,
+ public WebRTCInternalsUIObserver{
+ public:
+ WebRTCInternalsMessageHandler();
+ virtual ~WebRTCInternalsMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // WebRTCInternalsUIObserver override.
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* args) OVERRIDE;
+
+ // Javascript message handler.
+ void OnGetAllUpdates(const base::ListValue* list);
+ void OnGetAllStats(const base::ListValue* list);
+ void OnStartRtpRecording(const base::ListValue* list);
+ void OnStopRtpRecording(const base::ListValue* list);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRTCInternalsMessageHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
diff --git a/chromium/content/browser/media/webrtc_internals_ui.cc b/chromium/content/browser/media/webrtc_internals_ui.cc
new file mode 100644
index 00000000000..5c4adc591a9
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals_ui.h"
+
+#include "content/browser/media/webrtc_internals_message_handler.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+
+namespace content {
+namespace {
+
+WebUIDataSource* CreateWebRTCInternalsHTMLSource() {
+ WebUIDataSource* source =
+ WebUIDataSource::Create(kChromeUIWebRTCInternalsHost);
+
+ source->SetJsonPath("strings.js");
+ source->AddResourcePath("webrtc_internals.js", IDR_WEBRTC_INTERNALS_JS);
+ source->SetDefaultResource(IDR_WEBRTC_INTERNALS_HTML);
+ return source;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// WebRTCInternalsUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+WebRTCInternalsUI::WebRTCInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new WebRTCInternalsMessageHandler());
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateWebRTCInternalsHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_ui.h b/chromium/content/browser/media/webrtc_internals_ui.h
new file mode 100644
index 00000000000..29745b18681
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+// The implementation for the chrome://webrtc-internals page.
+class WebRTCInternalsUI : public WebUIController {
+ public:
+ explicit WebRTCInternalsUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRTCInternalsUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
diff --git a/chromium/content/browser/media/webrtc_internals_ui_observer.h b/chromium/content/browser/media/webrtc_internals_ui_observer.h
new file mode 100644
index 00000000000..8d2c60e63b8
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui_observer.h
@@ -0,0 +1,28 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
+
+#include <string>
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace content {
+
+// Implement this interface to receive WebRTCInternals updates.
+class WebRTCInternalsUIObserver {
+ public:
+ virtual ~WebRTCInternalsUIObserver() {}
+
+ // This is called on the browser IO thread.
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* args) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
diff --git a/chromium/content/browser/media/webrtc_internals_unittest.cc b/chromium/content/browser/media/webrtc_internals_unittest.cc
new file mode 100644
index 00000000000..a00ccb9877c
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 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/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/browser/media/webrtc_internals.h"
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+static const std::string kContraints = "c";
+static const std::string kServers = "s";
+static const std::string kUrl = "u";
+
+class MockWebRTCInternalsProxy : public WebRTCInternalsUIObserver {
+ public:
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* value) OVERRIDE {
+ command_ = command;
+ value_.reset(value->DeepCopy());
+ }
+
+ std::string command() {
+ return command_;
+ }
+
+ base::Value* value() {
+ return value_.get();
+ }
+
+ private:
+ std::string command_;
+ scoped_ptr<base::Value> value_;
+};
+
+class WebRTCInternalsTest : public testing::Test {
+ public:
+ WebRTCInternalsTest() : io_thread_(BrowserThread::UI, &io_loop_) {}
+
+ protected:
+ std::string ExpectedInfo(std::string prefix,
+ std::string id,
+ std::string suffix) {
+ static const std::string kstatic_part1 = std::string(
+ "{\"constraints\":\"c\",");
+ static const std::string kstatic_part2 = std::string(
+ ",\"servers\":\"s\",\"url\":\"u\"}");
+ return prefix + kstatic_part1 + id + kstatic_part2 + suffix;
+ }
+
+ base::MessageLoop io_loop_;
+ TestBrowserThread io_thread_;
+};
+
+} // namespace
+
+TEST_F(WebRTCInternalsTest, AddRemoveObserver) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 3, 4, kUrl, kServers, kContraints);
+ EXPECT_EQ("", observer->command());
+
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(3, 4);
+}
+
+TEST_F(WebRTCInternalsTest, SendAddPeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+ EXPECT_EQ("addPeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ std::string value;
+ EXPECT_TRUE(dict->GetString("url", &value));
+ EXPECT_EQ(kUrl, value);
+ EXPECT_TRUE(dict->GetString("servers", &value));
+ EXPECT_EQ(kServers, value);
+ EXPECT_TRUE(dict->GetString("constraints", &value));
+ EXPECT_EQ(kContraints, value);
+
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+}
+
+TEST_F(WebRTCInternalsTest, SendRemovePeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+ EXPECT_EQ("removePeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+}
+
+TEST_F(WebRTCInternalsTest, SendUpdatePeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+
+ const std::string update_type = "fakeType";
+ const std::string update_value = "fakeValue";
+ WebRTCInternals::GetInstance()->OnUpdatePeerConnection(
+ 1, 2, update_type, update_value);
+
+ EXPECT_EQ("updatePeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ std::string value;
+ EXPECT_TRUE(dict->GetString("type", &value));
+ EXPECT_EQ(update_type, value);
+ EXPECT_TRUE(dict->GetString("value", &value));
+ EXPECT_EQ(update_value, value);
+
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+}
+
+} // namespace content