summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/media
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/media')
-rw-r--r--chromium/chrome/browser/media/webrtc/DEPS25
-rw-r--r--chromium/chrome/browser/media/webrtc/OWNERS24
-rw-r--r--chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc168
-rw-r--r--chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h96
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc598
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h105
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc254
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc198
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc166
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h54
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc96
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc186
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_base.h102
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h28
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker.cc10
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker.h88
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc109
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h91
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc154
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc9
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h33
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc91
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h35
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc34
-rw-r--r--chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h51
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler.cc270
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler.h89
-rw-r--r--chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc286
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc88
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h43
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc108
-rw-r--r--chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h53
-rw-r--r--chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h26
-rw-r--r--chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc493
-rw-r--r--chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h209
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc470
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h136
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc101
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h46
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc136
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc54
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h26
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc605
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h138
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc963
-rw-r--r--chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc168
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc407
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc554
-rw-r--r--chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc324
-rw-r--r--chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/rtp_dump_type.h12
-rw-r--r--chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc109
-rw-r--r--chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h52
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h57
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm246
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h36
-rw-r--r--chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm196
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc92
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc163
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h31
-rw-r--r--chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc372
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc216
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary.h85
-rw-r--r--chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc167
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc259
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc344
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc651
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h257
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc180
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h67
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc338
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h44
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc96
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc99
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc423
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h121
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc1086
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h429
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc1013
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h543
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc655
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc36
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h43
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc40
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h44
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc252
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h98
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc1376
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h487
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc4934
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc98
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h82
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc383
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h148
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc366
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc196
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc360
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc78
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc309
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc42
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h47
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc633
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h236
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc316
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_util.cc36
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_log_util.h15
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc589
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h242
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc336
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h139
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc432
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc451
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h148
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc375
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc68
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc391
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc539
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h149
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc497
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc163
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc367
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc118
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util.h14
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc21
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm78
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc17
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_win.cc40
-rw-r--r--chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc74
131 files changed, 33136 insertions, 0 deletions
diff --git a/chromium/chrome/browser/media/webrtc/DEPS b/chromium/chrome/browser/media/webrtc/DEPS
new file mode 100644
index 00000000000..e1fc475bafa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ "+media/webrtc",
+ "+services/audio/public/cpp",
+ "+third_party/libyuv",
+ "+third_party/webrtc",
+]
+
+specific_include_rules = {
+ # TODO(mash): Fix these. https://crbug.com/723880
+ "desktop_capture_access_handler\.cc": [
+ "+ash/shell.h",
+ ],
+ "desktop_media_list_ash\.cc": [
+ "+ash/shell.h",
+ "+ash/wm/desks/desks_util.h",
+ ],
+ # TODO(mash): Fix. https://crbug.com/855147
+ "media_capture_devices_dispatcher\.cc": [
+ "+ash/shell.h",
+ ],
+ # TODO(mash): Remove. https://crbug.com/723880
+ ".*_unittest\.cc": [
+ "+ash/test/ash_test_base.h",
+ ]
+}
diff --git a/chromium/chrome/browser/media/webrtc/OWNERS b/chromium/chrome/browser/media/webrtc/OWNERS
new file mode 100644
index 00000000000..1d63dd49b92
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/OWNERS
@@ -0,0 +1,24 @@
+sergeyu@chromium.org
+tommi@chromium.org
+guidou@chromium.org
+
+# For WebRTC desktop capturer related changes only
+braveyao@chromium.org
+
+# For WebRTC browser tests.
+per-file *webrtc*browsertest*=hbos@chromium.org
+per-file *webrtc*browsertest*=phoglund@chromium.org
+
+# For changes related to the tab media indicators.
+per-file media_stream_capture_indicator*=miu@chromium.org
+
+# For permissions related code.
+per-file media_stream_device*=raymes@chromium.org
+per-file media_permission*=raymes@chromium.org
+
+# For WebRTC event logging code.
+per-file webrtc_event_log_*=eladalon@chromium.org
+
+per-file *permission_context*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
+
+# COMPONENT: Blink>WebRTC
diff --git a/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc
new file mode 100644
index 00000000000..44ee73b6ae7
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.cc
@@ -0,0 +1,168 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/audio_debug_recordings_handler.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/system_connector.h"
+#include "media/audio/audio_debug_recording_session.h"
+#include "services/audio/public/cpp/debug_recording_session_factory.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+using content::BrowserThread;
+
+// Keys used to attach handler to the RenderProcessHost
+const char AudioDebugRecordingsHandler::kAudioDebugRecordingsHandlerKey[] =
+ "kAudioDebugRecordingsHandlerKey";
+
+namespace {
+
+// Returns a path name to be used as prefix for audio debug recordings files.
+base::FilePath GetAudioDebugRecordingsPrefixPath(
+ const base::FilePath& directory,
+ uint64_t audio_debug_recordings_id) {
+ static const char kAudioDebugRecordingsFilePrefix[] = "AudioDebugRecordings.";
+ return directory.AppendASCII(kAudioDebugRecordingsFilePrefix +
+ base::NumberToString(audio_debug_recordings_id));
+}
+
+base::FilePath GetLogDirectoryAndEnsureExists(
+ content::BrowserContext* browser_context) {
+ base::FilePath log_dir_path =
+ webrtc_logging::TextLogList::GetWebRtcLogDirectoryForBrowserContextPath(
+ browser_context->GetPath());
+ base::File::Error error;
+ if (!base::CreateDirectoryAndGetError(log_dir_path, &error)) {
+ DLOG(ERROR) << "Could not create WebRTC log directory, error: " << error;
+ return base::FilePath();
+ }
+ return log_dir_path;
+}
+
+} // namespace
+
+AudioDebugRecordingsHandler::AudioDebugRecordingsHandler(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), current_audio_debug_recordings_id_(0) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context_);
+}
+
+AudioDebugRecordingsHandler::~AudioDebugRecordingsHandler() {}
+
+void AudioDebugRecordingsHandler::StartAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ base::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(&GetLogDirectoryAndEnsureExists, browser_context_),
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStartAudioDebugRecordings,
+ this, host, delay, callback, error_callback));
+}
+
+void AudioDebugRecordingsHandler::StopAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const bool is_manual_stop = true;
+ base::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(&GetLogDirectoryAndEnsureExists, browser_context_),
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStopAudioDebugRecordings,
+ this, host, is_manual_stop,
+ current_audio_debug_recordings_id_, callback,
+ error_callback));
+}
+
+void AudioDebugRecordingsHandler::DoStartAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (audio_debug_recording_session_) {
+ error_callback.Run("Audio debug recordings already in progress");
+ return;
+ }
+
+ base::FilePath prefix_path = GetAudioDebugRecordingsPrefixPath(
+ log_directory, ++current_audio_debug_recordings_id_);
+ host->EnableAudioDebugRecordings(prefix_path);
+
+ audio_debug_recording_session_ = audio::CreateAudioDebugRecordingSession(
+ prefix_path, content::GetSystemConnector()->Clone());
+
+ if (delay.is_zero()) {
+ const bool is_stopped = false, is_manual_stop = false;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+ return;
+ }
+
+ const bool is_manual_stop = false;
+ base::PostDelayedTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&AudioDebugRecordingsHandler::DoStopAudioDebugRecordings,
+ this, host, is_manual_stop,
+ current_audio_debug_recordings_id_, callback,
+ error_callback, log_directory),
+ delay);
+}
+
+void AudioDebugRecordingsHandler::DoStopAudioDebugRecordings(
+ content::RenderProcessHost* host,
+ bool is_manual_stop,
+ uint64_t audio_debug_recordings_id,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_LE(audio_debug_recordings_id, current_audio_debug_recordings_id_);
+
+ base::FilePath prefix_path = GetAudioDebugRecordingsPrefixPath(
+ log_directory, audio_debug_recordings_id);
+ // Prevent an old posted StopAudioDebugRecordings() call to stop a newer dump.
+ // This could happen in a sequence like:
+ // Start(10); // Start dump 1. Post Stop() to run after 10 seconds.
+ // Stop(); // Manually stop dump 1 before 10 seconds;
+ // Start(20); // Start dump 2. Posted Stop() for 1 should not stop dump 2.
+ if (audio_debug_recordings_id < current_audio_debug_recordings_id_) {
+ const bool is_stopped = false;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+ return;
+ }
+
+ if (!audio_debug_recording_session_) {
+ error_callback.Run("No audio debug recording in progress");
+ return;
+ }
+
+ audio_debug_recording_session_.reset();
+
+ host->DisableAudioDebugRecordings();
+
+ const bool is_stopped = true;
+ callback.Run(prefix_path.AsUTF8Unsafe(), is_stopped, is_manual_stop);
+}
diff --git a/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h
new file mode 100644
index 00000000000..4725e565594
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/audio_debug_recordings_handler.h
@@ -0,0 +1,96 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+namespace media {
+class AudioDebugRecordingSession;
+}
+
+// AudioDebugRecordingsHandler provides an interface to start and stop
+// AudioDebugRecordings, including WebRTC AEC dumps. Lives on the UI thread.
+class AudioDebugRecordingsHandler
+ : public base::RefCountedThreadSafe<AudioDebugRecordingsHandler> {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+ typedef base::Callback<void(const std::string&)> RecordingErrorCallback;
+ typedef base::Callback<void(const std::string&, bool, bool)>
+ RecordingDoneCallback;
+
+ // Key used to attach the handler to the RenderProcessHost
+ static const char kAudioDebugRecordingsHandlerKey[];
+
+ explicit AudioDebugRecordingsHandler(
+ content::BrowserContext* browser_context);
+
+ // Starts an audio debug recording. The recording lasts the given |delay|,
+ // unless |delay| is zero, in which case recording will continue until
+ // StopAudioDebugRecordings() is explicitly invoked.
+ // |callback| is invoked once recording stops. If |delay| is zero
+ // |callback| is invoked once recording starts.
+ // If a recording was already in progress, |error_callback| is invoked instead
+ // of |callback|.
+ void StartAudioDebugRecordings(content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback);
+
+ // Stops an audio debug recording. |callback| is invoked once recording
+ // stops. If no recording was in progress, |error_callback| is invoked instead
+ // of |callback|.
+ void StopAudioDebugRecordings(content::RenderProcessHost* host,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<AudioDebugRecordingsHandler>;
+
+ virtual ~AudioDebugRecordingsHandler();
+
+ // Helper for starting audio debug recordings.
+ void DoStartAudioDebugRecordings(content::RenderProcessHost* host,
+ base::TimeDelta delay,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory);
+
+ // Helper for stopping audio debug recordings.
+ void DoStopAudioDebugRecordings(content::RenderProcessHost* host,
+ bool is_manual_stop,
+ uint64_t audio_debug_recordings_id,
+ const RecordingDoneCallback& callback,
+ const RecordingErrorCallback& error_callback,
+ const base::FilePath& log_directory);
+
+ // The browser context associated with our renderer process.
+ content::BrowserContext* const browser_context_;
+
+ // This counter allows saving each debug recording in separate files.
+ uint64_t current_audio_debug_recordings_id_;
+
+ // Used for controlling debug recordings.
+ std::unique_ptr<media::AudioDebugRecordingSession>
+ audio_debug_recording_session_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDebugRecordingsHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_AUDIO_DEBUG_RECORDINGS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
new file mode 100644
index 00000000000..7e52d84fa42
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -0,0 +1,598 @@
+// Copyright 2015 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 "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/screen_capture_notification_ui.h"
+#include "chrome/browser/ui/simple_message_box.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/desktop_streams_registry.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/switches.h"
+#include "net/base/url_util.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/origin.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/shell.h"
+#include "ui/base/ui_base_features.h"
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#endif // defined(OS_MACOSX)
+
+using content::BrowserThread;
+
+namespace {
+
+// Helper to get title of the calling application shown in the screen capture
+// notification.
+base::string16 GetApplicationTitle(content::WebContents* web_contents,
+ const extensions::Extension* extension) {
+ // Use extension name as title for extensions and host/origin for drive-by
+ // web.
+ std::string title;
+ if (extension) {
+ title = extension->name();
+ return base::UTF8ToUTF16(title);
+ }
+ GURL url = web_contents->GetURL();
+ title = content::IsOriginSecure(url) ? net::GetHostAndOptionalPort(url)
+ : url.GetOrigin().spec();
+ return base::UTF8ToUTF16(title);
+}
+
+// Returns whether an on-screen notification should appear after desktop capture
+// is approved for |extension|. Component extensions do not display a
+// notification.
+bool ShouldDisplayNotification(const extensions::Extension* extension) {
+ return !(extension &&
+ (extension->location() == extensions::Manifest::COMPONENT ||
+ extension->location() == extensions::Manifest::EXTERNAL_COMPONENT));
+}
+
+#if !defined(OS_ANDROID)
+// Find browser or app window from a given |web_contents|.
+gfx::NativeWindow FindParentWindowForWebContents(
+ content::WebContents* web_contents) {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (browser && browser->window())
+ return browser->window()->GetNativeWindow();
+
+ const extensions::AppWindowRegistry::AppWindowList& window_list =
+ extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext())
+ ->app_windows();
+ for (auto iter = window_list.begin(); iter != window_list.end(); ++iter) {
+ if ((*iter)->web_contents() == web_contents)
+ return (*iter)->GetNativeWindow();
+ }
+
+ return NULL;
+}
+#endif
+
+} // namespace
+
+// Holds pending request information so that we display one picker UI at a time
+// for each content::WebContents.
+struct DesktopCaptureAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension)
+ : picker(std::move(picker)),
+ request(request),
+ callback(std::move(callback)),
+ extension(extension) {}
+ ~PendingAccessRequest() = default;
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+ content::MediaStreamRequest request;
+ content::MediaResponseCallback callback;
+ const extensions::Extension* extension;
+};
+
+DesktopCaptureAccessHandler::DesktopCaptureAccessHandler()
+ : picker_factory_(new DesktopMediaPickerFactoryImpl()),
+ display_notification_(true) {
+ AddNotificationObserver();
+}
+
+DesktopCaptureAccessHandler::DesktopCaptureAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory)
+ : picker_factory_(std::move(picker_factory)), display_notification_(false) {
+ AddNotificationObserver();
+}
+
+DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() = default;
+
+void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ DCHECK_EQ(request.video_type,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
+
+ UpdateExtensionTrusted(request, extension);
+
+ bool loopback_audio_supported = false;
+#if defined(USE_CRAS) || defined(OS_WIN)
+ // Currently loopback audio capture is supported only on Windows and ChromeOS.
+ loopback_audio_supported = true;
+#endif
+
+ bool screen_capture_enabled =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableUserMediaScreenCapturing) ||
+ MediaCaptureDevicesDispatcher::IsOriginForCasting(
+ request.security_origin) ||
+ IsExtensionWhitelistedForScreenCapture(extension) ||
+ IsBuiltInExtension(request.security_origin);
+
+ const bool origin_is_secure =
+ content::IsOriginSecure(request.security_origin) ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowHttpScreenCapture);
+
+ // If basic conditions (screen capturing is enabled and origin is secure)
+ // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
+ // it after checking permission.
+ // TODO(grunell): It would be good to change this result for something else,
+ // probably a new one.
+ blink::mojom::MediaStreamRequestResult result =
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE;
+
+ // Approve request only when the following conditions are met:
+ // 1. Screen capturing is enabled via command line switch or white-listed for
+ // the given origin.
+ // 2. Request comes from a page with a secure origin or from an extension.
+ if (screen_capture_enabled && origin_is_secure) {
+ // Get title of the calling application prior to showing the message box.
+ // chrome::ShowQuestionMessageBox() starts a nested run loop which may
+ // allow |web_contents| to be destroyed on the UI thread before the messag
+ // box is closed. See http://crbug.com/326690.
+ base::string16 application_title =
+ GetApplicationTitle(web_contents, extension);
+#if !defined(OS_ANDROID)
+ gfx::NativeWindow parent_window =
+ FindParentWindowForWebContents(web_contents);
+#else
+ gfx::NativeWindow parent_window = NULL;
+#endif
+ web_contents = NULL;
+
+ // Some extensions do not require user approval, because they provide their
+ // own user approval UI.
+ bool is_approved = IsDefaultApproved(extension);
+ if (!is_approved) {
+ base::string16 application_name =
+ base::UTF8ToUTF16(request.security_origin.spec());
+ if (extension)
+ application_name = base::UTF8ToUTF16(extension->name());
+ base::string16 confirmation_text = l10n_util::GetStringFUTF16(
+ request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE
+ ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
+ : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
+ application_name);
+ chrome::MessageBoxResult result = chrome::ShowQuestionMessageBox(
+ parent_window,
+ l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
+ confirmation_text);
+ is_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
+ }
+
+ if (is_approved) {
+ content::DesktopMediaID screen_id;
+#if defined(OS_CHROMEOS)
+ screen_id = content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_SCREEN,
+ ash::Shell::Get()->GetPrimaryRootWindow());
+#else // defined(OS_CHROMEOS)
+ screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ webrtc::kFullDesktopScreenId);
+#endif // !defined(OS_CHROMEOS)
+
+ bool capture_audio =
+ (request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
+ loopback_audio_supported);
+
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, screen_id,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
+ capture_audio, request.disable_local_echo, display_notification,
+ application_title, application_title);
+ DCHECK(!devices.empty());
+ }
+
+ // The only case when devices can be empty is if the user has denied
+ // permission.
+ result = devices.empty()
+ ? blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED
+ : blink::mojom::MediaStreamRequestResult::OK;
+ }
+
+ std::move(callback).Run(devices, result, std::move(ui));
+}
+
+bool DesktopCaptureAccessHandler::IsDefaultApproved(
+ const extensions::Extension* extension) {
+ return extension &&
+ (extension->location() == extensions::Manifest::COMPONENT ||
+ extension->location() == extensions::Manifest::EXTERNAL_COMPONENT ||
+ IsExtensionWhitelistedForScreenCapture(extension));
+}
+
+bool DesktopCaptureAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
+}
+
+bool DesktopCaptureAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void DesktopCaptureAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ if (request.video_type !=
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+
+ if (request.request_type == blink::MEDIA_DEVICE_UPDATE) {
+ ProcessChangeSourceRequest(web_contents, request, std::move(callback),
+ extension);
+ return;
+ }
+
+ // If the device id wasn't specified then this is a screen capture request
+ // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
+ if (request.requested_video_device_id.empty()) {
+#if defined(OS_MACOSX)
+ if (system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
+ nullptr);
+ return;
+ }
+#endif
+ ProcessScreenCaptureAccessRequest(web_contents, request,
+ std::move(callback), extension);
+ return;
+ }
+
+ // Resolve DesktopMediaID for the specified device id.
+ content::DesktopMediaID media_id;
+ // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
+ // RenderFrame IDs once the desktop capture extension API implementation is
+ // fixed. http://crbug.com/304341
+ content::WebContents* const web_contents_for_stream =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(request.render_process_id,
+ request.render_frame_id));
+ content::RenderFrameHost* const main_frame =
+ web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL;
+ if (main_frame) {
+ media_id =
+ content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
+ request.requested_video_device_id,
+ main_frame->GetProcess()->GetID(), main_frame->GetRoutingID(),
+ url::Origin::Create(request.security_origin), nullptr,
+ content::kRegistryStreamTypeDesktop);
+ }
+
+ // Received invalid device id.
+ if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+#if defined(OS_MACOSX)
+ if (media_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS &&
+ system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
+ nullptr);
+ return;
+ }
+#endif
+
+ bool loopback_audio_supported = false;
+#if defined(USE_CRAS) || defined(OS_WIN)
+ // Currently loopback audio capture is supported only on Windows and ChromeOS.
+ loopback_audio_supported = true;
+#endif
+
+ // This value essentially from the checkbox on picker window, so it
+ // corresponds to user permission.
+ const bool audio_permitted = media_id.audio_share;
+
+ // This value essentially from whether getUserMedia requests audio stream.
+ const bool audio_requested =
+ request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
+
+ // This value shows for a given capture type, whether the system or our code
+ // can support audio sharing. Currently audio is only supported for screen and
+ // tab/webcontents capture streams.
+ const bool audio_supported =
+ (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
+ loopback_audio_supported) ||
+ media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS;
+
+ const bool check_audio_permission =
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kDisableDesktopCaptureAudio);
+ const bool capture_audio =
+ (check_audio_permission ? audio_permitted : true) && audio_requested &&
+ audio_supported;
+
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, capture_audio,
+ request.disable_local_echo, display_notification,
+ GetApplicationTitle(web_contents, extension),
+ GetApplicationTitle(web_contents, extension));
+ UpdateExtensionTrusted(request, extension);
+ std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK,
+ std::move(ui));
+}
+
+void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+
+ if (!base::FeatureList::IsEnabled(
+ features::kDesktopCaptureTabSharingInfobar) ||
+ request.requested_video_device_id.empty()) {
+ picker = picker_factory_->CreatePicker();
+ if (!picker) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+ }
+
+ RequestsQueue& queue = pending_requests_[web_contents];
+ queue.push_back(std::make_unique<PendingAccessRequest>(
+ std::move(picker), request, std::move(callback), extension));
+ // If this is the only request then pop picker UI.
+ if (queue.size() == 1)
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DesktopCaptureAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (state != content::MEDIA_REQUEST_STATE_DONE &&
+ state != content::MEDIA_REQUEST_STATE_CLOSING) {
+ return;
+ }
+
+ if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ DeletePendingAccessRequest(render_process_id, render_frame_id,
+ page_request_id);
+ }
+ CaptureAccessHandlerBase::UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type, state);
+
+ // This method only gets called with the above checked states when all
+ // requests are to be canceled. Therefore, we don't need to process the
+ // next queued request.
+}
+
+void DesktopCaptureAccessHandler::ProcessQueuedAccessRequest(
+ const RequestsQueue& queue,
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ const PendingAccessRequest& pending_request = *queue.front();
+
+ if (!pending_request.picker) {
+ DCHECK(!pending_request.request.requested_video_device_id.empty());
+ content::WebContentsMediaCaptureId web_contents_id;
+ if (content::WebContentsMediaCaptureId::Parse(
+ pending_request.request.requested_video_device_id,
+ &web_contents_id)) {
+ content::DesktopMediaID media_id(
+ content::DesktopMediaID::TYPE_WEB_CONTENTS,
+ content::DesktopMediaID::kNullId, web_contents_id);
+ media_id.audio_share = pending_request.request.audio_type !=
+ blink::mojom::MediaStreamType::NO_SERVICE;
+ OnPickerDialogResults(web_contents, media_id);
+ return;
+ }
+ }
+
+ std::vector<content::DesktopMediaID::Type> media_types = {
+ content::DesktopMediaID::TYPE_WEB_CONTENTS};
+ auto source_lists = picker_factory_->CreateMediaList(media_types);
+
+ DesktopMediaPicker::DoneCallback done_callback =
+ base::BindOnce(&DesktopCaptureAccessHandler::OnPickerDialogResults,
+ base::Unretained(this), web_contents);
+ DesktopMediaPicker::Params picker_params;
+ picker_params.web_contents = web_contents;
+ gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
+ picker_params.context = parent_window;
+ picker_params.parent = parent_window;
+ picker_params.app_name =
+ GetApplicationTitle(web_contents, pending_request.extension);
+ picker_params.target_name = picker_params.app_name;
+ picker_params.request_audio = (pending_request.request.audio_type ==
+ blink::mojom::MediaStreamType::NO_SERVICE)
+ ? false
+ : true;
+ pending_request.picker->Show(picker_params, std::move(source_lists),
+ std::move(done_callback));
+
+ // Focus on the tab with the picker for easy access.
+ if (auto* delegate = web_contents->GetDelegate())
+ delegate->ActivateContents(web_contents);
+}
+
+void DesktopCaptureAccessHandler::OnPickerDialogResults(
+ content::WebContents* web_contents,
+ content::DesktopMediaID media_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(web_contents);
+
+ auto it = pending_requests_.find(web_contents);
+ if (it == pending_requests_.end())
+ return;
+ RequestsQueue& queue = it->second;
+ if (queue.empty()) {
+ // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
+ // need to do anything.
+ return;
+ }
+
+ PendingAccessRequest& pending_request = *queue.front();
+ blink::MediaStreamDevices devices;
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ const extensions::Extension* extension = pending_request.extension;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (media_id.is_null()) {
+ request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ } else {
+ request_result = blink::mojom::MediaStreamRequestResult::OK;
+ // Determine if the extension is required to display a notification.
+ const bool display_notification =
+ display_notification_ && ShouldDisplayNotification(extension);
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id, pending_request.request.video_type,
+ pending_request.request.audio_type, media_id.audio_share,
+ pending_request.request.disable_local_echo, display_notification,
+ GetApplicationTitle(web_contents, extension),
+ GetApplicationTitle(web_contents, extension));
+ }
+
+ std::move(pending_request.callback)
+ .Run(devices, request_result, std::move(ui));
+ queue.pop_front();
+
+ if (!queue.empty())
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DesktopCaptureAccessHandler::AddNotificationObserver() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+void DesktopCaptureAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
+
+void DesktopCaptureAccessHandler::DeletePendingAccessRequest(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id) {
+ for (auto& queue_it : pending_requests_) {
+ RequestsQueue& queue = queue_it.second;
+ for (auto it = queue.begin(); it != queue.end(); ++it) {
+ const PendingAccessRequest& pending_request = **it;
+ if (pending_request.request.render_process_id == render_process_id &&
+ pending_request.request.render_frame_id == render_frame_id &&
+ pending_request.request.page_request_id == page_request_id) {
+ queue.erase(it);
+ return;
+ }
+ }
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h
new file mode 100644
index 00000000000..e97d6318179
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler.h
@@ -0,0 +1,105 @@
+// Copyright 2015 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "chrome/browser/media/capture_access_handler_base.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace extensions {
+class Extension;
+}
+
+// MediaAccessHandler for DesktopCapture API requests that originate from
+// getUserMedia() calls. Note that getDisplayMedia() calls are handled in
+// DisplayMediaAccessHandler.
+class DesktopCaptureAccessHandler : public CaptureAccessHandlerBase,
+ public content::NotificationObserver {
+ public:
+ DesktopCaptureAccessHandler();
+ explicit DesktopCaptureAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory);
+ ~DesktopCaptureAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ friend class DesktopCaptureAccessHandlerTest;
+
+ struct PendingAccessRequest;
+ using RequestsQueue =
+ base::circular_deque<std::unique_ptr<PendingAccessRequest>>;
+ using RequestsQueues = base::flat_map<content::WebContents*, RequestsQueue>;
+
+ void ProcessScreenCaptureAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+
+ // Returns whether desktop capture is always approved for |extension|.
+ // Currently component extensions and some whitelisted extensions are default
+ // approved.
+ static bool IsDefaultApproved(const extensions::Extension* extension);
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+ void AddNotificationObserver();
+
+ // Methods for handling source change request, e.g. bringing up the picker to
+ // select a new source within the current desktop sharing session.
+ void ProcessChangeSourceRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+ void ProcessQueuedAccessRequest(const RequestsQueue& queue,
+ content::WebContents* web_contents);
+ void OnPickerDialogResults(content::WebContents* web_contents,
+ content::DesktopMediaID source);
+ void DeletePendingAccessRequest(int render_process_id,
+ int render_frame_id,
+ int page_request_id);
+
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory_;
+ bool display_notification_;
+ RequestsQueues pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopCaptureAccessHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
new file mode 100644
index 00000000000..70ed3a0c4c8
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
@@ -0,0 +1,254 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+class DesktopCaptureAccessHandlerTest : public ChromeRenderViewHostTestHarness {
+ public:
+ DesktopCaptureAccessHandlerTest() {}
+ ~DesktopCaptureAccessHandlerTest() override {}
+
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+ auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
+ picker_factory_ = picker_factory.get();
+ access_handler_ = std::make_unique<DesktopCaptureAccessHandler>(
+ std::move(picker_factory));
+ }
+
+ void ProcessRequest(
+ const content::DesktopMediaID& fake_desktop_media_id_response,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ blink::MediaStreamRequestType request_type,
+ bool request_audio) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, request_audio /* expect_audio */,
+ fake_desktop_media_id_response /* selected_source */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ blink::mojom::MediaStreamType audio_type =
+ request_audio ? blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
+ : blink::mojom::MediaStreamType::NO_SERVICE;
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, request_type, std::string(),
+ std::string(), audio_type,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+
+ base::RunLoop wait_loop;
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop, request_result, devices_result);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ wait_loop.Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+
+ access_handler_.reset();
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ }
+
+ void NotifyWebContentsDestroyed() {
+ access_handler_->Observe(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<content::WebContents>(web_contents()),
+ content::NotificationDetails());
+ }
+
+ const DesktopCaptureAccessHandler::RequestsQueues& GetRequestQueues() {
+ return access_handler_->pending_requests_;
+ }
+
+ protected:
+ FakeDesktopMediaPickerFactory* picker_factory_;
+ std::unique_ptr<DesktopCaptureAccessHandler> access_handler_;
+};
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceWithoutAudioRequestPermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId),
+ &result, &devices, blink::MEDIA_DEVICE_UPDATE,
+ false /*request_audio*/);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceWithAudioRequestPermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId,
+ true /* audio_share */),
+ &result, &devices, blink::MEDIA_DEVICE_UPDATE,
+ true /* request_audio */);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(2u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
+ devices[1].type);
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourcePermissionDenied) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(), &result, &devices,
+ blink::MEDIA_DEVICE_UPDATE, false /*request audio*/);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
+ EXPECT_EQ(0u, devices.size());
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest,
+ ChangeSourceUpdateMediaRequestStateWithClosing) {
+ const int render_process_id = 0;
+ const int render_frame_id = 0;
+ const int page_request_id = 0;
+ const blink::mojom::MediaStreamType stream_type =
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ render_process_id, render_frame_id, page_request_id,
+ GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, std::string(),
+ std::string(), blink::mojom::MediaStreamType::NO_SERVICE, stream_type,
+ false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ access_handler_->UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type,
+ content::MEDIA_REQUEST_STATE_CLOSING);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(0u, queue_it->second.size());
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ access_handler_.reset();
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceWebContentsDestroyed) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ NotifyWebContentsDestroyed();
+ EXPECT_EQ(0u, GetRequestQueues().size());
+ access_handler_.reset();
+}
+
+TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceMultipleRequests) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId) /* selected_source */},
+ {false /* expect_screens */, false /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::kNullId) /* selected_source */}};
+ const size_t kTestFlagCount = 2;
+ picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
+
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ base::RunLoop wait_loop[kTestFlagCount];
+ for (size_t i = 0; i < kTestFlagCount; ++i) {
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop[i], &result, &devices);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ }
+ wait_loop[0].Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+
+ blink::MediaStreamDevice first_device = devices[0];
+ EXPECT_TRUE(test_flags[1].picker_created);
+ EXPECT_FALSE(test_flags[1].picker_deleted);
+ wait_loop[1].Run();
+ EXPECT_TRUE(test_flags[1].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_FALSE(devices[0].IsSameDevice(first_device));
+
+ access_handler_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
new file mode 100644
index 00000000000..76fede3233e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
@@ -0,0 +1,198 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+
+#include <string>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/ui/screen_capture_notification_ui.h"
+#include "chrome/browser/ui/tab_sharing/tab_sharing_ui.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/audio_device_description.h"
+#include "media/mojo/mojom/display_media_information.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+media::mojom::DisplayMediaInformationPtr
+DesktopMediaIDToDisplayMediaInformation(
+ const content::DesktopMediaID& media_id) {
+ media::mojom::DisplayCaptureSurfaceType display_surface =
+ media::mojom::DisplayCaptureSurfaceType::MONITOR;
+ bool logical_surface = true;
+ media::mojom::CursorCaptureType cursor =
+ media::mojom::CursorCaptureType::NEVER;
+#if defined(USE_AURA)
+ const bool uses_aura =
+ media_id.window_id != content::DesktopMediaID::kNullId ? true : false;
+#else
+ const bool uses_aura = false;
+#endif // defined(USE_AURA)
+ switch (media_id.type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR;
+ cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
+ : media::mojom::CursorCaptureType::ALWAYS;
+ break;
+ case content::DesktopMediaID::TYPE_WINDOW:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW;
+ cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
+ : media::mojom::CursorCaptureType::ALWAYS;
+ break;
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER;
+ cursor = media::mojom::CursorCaptureType::MOTION;
+ break;
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ }
+
+ return media::mojom::DisplayMediaInformation::New(display_surface,
+ logical_surface, cursor);
+}
+
+base::string16 GetStopSharingUIString(
+ const base::string16& application_title,
+ const base::string16& registered_extension_name,
+ bool capture_audio,
+ content::DesktopMediaID::Type capture_type) {
+ if (!capture_audio) {
+ if (application_title == registered_extension_name) {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_WINDOW:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ NOTREACHED();
+ }
+ } else {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WINDOW:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ NOTREACHED();
+ }
+ }
+ } else { // The case with audio
+ if (application_title == registered_extension_name) {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+ application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+ application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ case content::DesktopMediaID::TYPE_WINDOW:
+ NOTREACHED();
+ }
+ } else {
+ switch (capture_type) {
+ case content::DesktopMediaID::TYPE_SCREEN:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+ registered_extension_name, application_title);
+ case content::DesktopMediaID::TYPE_NONE:
+ case content::DesktopMediaID::TYPE_WINDOW:
+ NOTREACHED();
+ }
+ }
+ }
+ return base::string16();
+}
+
+} // namespace
+
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+ content::WebContents* web_contents,
+ blink::MediaStreamDevices* devices,
+ const content::DesktopMediaID& media_id,
+ blink::mojom::MediaStreamType devices_video_type,
+ blink::mojom::MediaStreamType devices_audio_type,
+ bool capture_audio,
+ bool disable_local_echo,
+ bool display_notification,
+ const base::string16& application_title,
+ const base::string16& registered_extension_name) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ DVLOG(2) << __func__ << ": media_id " << media_id.ToString()
+ << ", capture_audio " << capture_audio << ", disable_local_echo "
+ << disable_local_echo << ", display_notification "
+ << display_notification << ", application_title "
+ << application_title << ", extension_name "
+ << registered_extension_name;
+
+ // Add selected desktop source to the list.
+ auto device = blink::MediaStreamDevice(
+ devices_video_type, media_id.ToString(), media_id.ToString());
+ device.display_media_info = DesktopMediaIDToDisplayMediaInformation(media_id);
+ devices->push_back(device);
+ if (capture_audio) {
+ if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) {
+ content::WebContentsMediaCaptureId web_id = media_id.web_contents_id;
+ web_id.disable_local_echo = disable_local_echo;
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type, web_id.ToString(), "Tab audio"));
+ } else if (disable_local_echo) {
+ // Use the special loopback device ID for system audio capture.
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type,
+ media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
+ "System Audio"));
+ } else {
+ // Use the special loopback device ID for system audio capture.
+ devices->push_back(blink::MediaStreamDevice(
+ devices_audio_type,
+ media::AudioDeviceDescription::kLoopbackInputDeviceId,
+ "System Audio"));
+ }
+ }
+
+ // If required, register to display the notification for stream capture.
+ std::unique_ptr<MediaStreamUI> notification_ui;
+ if (display_notification) {
+ if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
+ base::FeatureList::IsEnabled(
+ features::kDesktopCaptureTabSharingInfobar)) {
+ notification_ui = TabSharingUI::Create(media_id, application_title);
+ } else {
+ notification_ui = ScreenCaptureNotificationUI::Create(
+ GetStopSharingUIString(application_title, registered_extension_name,
+ capture_audio, media_id.type));
+ }
+ }
+
+ return MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents, *devices, std::move(notification_ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h
new file mode 100644
index 00000000000..2bdfe18cd21
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_capture_devices_util.h
@@ -0,0 +1,31 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+
+#include <memory>
+
+#include "base/strings/string_util.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+// Helper to get list of media stream devices for desktop capture in |devices|.
+// Registers to display notification if |display_notification| is true.
+// Returns an instance of MediaStreamUI to be passed to content layer.
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+ content::WebContents* web_contents,
+ blink::MediaStreamDevices* devices,
+ const content::DesktopMediaID& media_id,
+ blink::mojom::MediaStreamType devices_video_type,
+ blink::mojom::MediaStreamType devices_audio_type,
+ bool capture_audio,
+ bool disable_local_echo,
+ bool display_notification,
+ const base::string16& application_title,
+ const base::string16& registered_extension_name);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc
new file mode 100644
index 00000000000..8f69f01efaa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc
@@ -0,0 +1,166 @@
+// 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 "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+
+#include <utility>
+
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/shell.h"
+#include "ash/wm/desks/desks_util.h"
+#include "base/bind.h"
+#include "chrome/grit/generated_resources.h"
+#include "media/base/video_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/snapshot/snapshot.h"
+
+using content::DesktopMediaID;
+
+namespace {
+
+// Update the list twice per second.
+const int kDefaultDesktopMediaListUpdatePeriod = 500;
+
+} // namespace
+
+DesktopMediaListAsh::DesktopMediaListAsh(content::DesktopMediaID::Type type)
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultDesktopMediaListUpdatePeriod)) {
+ DCHECK(type == content::DesktopMediaID::TYPE_SCREEN ||
+ type == content::DesktopMediaID::TYPE_WINDOW);
+ type_ = type;
+}
+
+DesktopMediaListAsh::~DesktopMediaListAsh() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DesktopMediaListAsh::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(pending_window_capture_requests_, 0);
+
+ std::vector<SourceDescription> new_sources;
+ EnumerateSources(&new_sources, update_thumnails);
+ UpdateSourcesList(new_sources);
+ OnRefreshMaybeComplete();
+}
+
+void DesktopMediaListAsh::EnumerateWindowsForRoot(
+ std::vector<DesktopMediaListAsh::SourceDescription>* sources,
+ bool update_thumnails,
+ aura::Window* root_window,
+ int container_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ aura::Window* container = ash::Shell::GetContainer(root_window, container_id);
+ if (!container)
+ return;
+ // The |container| has all the top-level windows in reverse order, e.g. the
+ // most top-level window is at the end. So iterate children reversely to make
+ // sure |sources| is in the expected order.
+ for (aura::Window::Windows::const_reverse_iterator it =
+ container->children().rbegin();
+ it != container->children().rend(); ++it) {
+ if (!(*it)->IsVisible() || !(*it)->CanFocus())
+ continue;
+ content::DesktopMediaID id = content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_WINDOW, *it);
+ if (id.window_id == view_dialog_id_.window_id)
+ continue;
+ SourceDescription window_source(id, (*it)->GetTitle());
+ sources->push_back(window_source);
+
+ if (update_thumnails)
+ CaptureThumbnail(window_source.id, *it);
+ }
+}
+
+void DesktopMediaListAsh::EnumerateSources(
+ std::vector<DesktopMediaListAsh::SourceDescription>* sources,
+ bool update_thumnails) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
+
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ if (type_ == content::DesktopMediaID::TYPE_SCREEN) {
+ SourceDescription screen_source(
+ content::DesktopMediaID::RegisterNativeWindow(
+ content::DesktopMediaID::TYPE_SCREEN, root_windows[i]),
+ root_windows[i]->GetTitle());
+
+ if (root_windows[i] == ash::Shell::GetPrimaryRootWindow())
+ sources->insert(sources->begin(), screen_source);
+ else
+ sources->push_back(screen_source);
+
+ if (screen_source.name.empty()) {
+ if (root_windows.size() > 1) {
+ // 'Screen' in 'Screen 1, Screen 2, etc ' might be inflected in some
+ // languages depending on the number although rather unlikely. To be
+ // safe, use the plural format.
+ // TODO(jshin): Revert to GetStringFUTF16Int (with native digits)
+ // if none of UI languages inflects 'Screen' in this context.
+ screen_source.name = l10n_util::GetPluralStringFUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
+ static_cast<int>(i + 1));
+ } else {
+ screen_source.name = l10n_util::GetStringUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
+ }
+ }
+
+ if (update_thumnails)
+ CaptureThumbnail(screen_source.id, root_windows[i]);
+ } else {
+ // The list of desks containers depends on whether the Virtual Desks
+ // feature is enabled or not.
+ for (int desk_id : ash::desks_util::GetDesksContainersIds())
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ desk_id);
+
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ ash::kShellWindowId_AlwaysOnTopContainer);
+ EnumerateWindowsForRoot(sources, update_thumnails, root_windows[i],
+ ash::kShellWindowId_PipContainer);
+ }
+ }
+}
+
+void DesktopMediaListAsh::CaptureThumbnail(content::DesktopMediaID id,
+ aura::Window* window) {
+ gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(thumbnail_size_), window_rect.size());
+
+ ++pending_window_capture_requests_;
+ ui::GrabWindowSnapshotAndScaleAsync(
+ window, window_rect, scaled_rect.size(),
+ base::Bind(&DesktopMediaListAsh::OnThumbnailCaptured,
+ weak_factory_.GetWeakPtr(), id));
+}
+
+void DesktopMediaListAsh::OnThumbnailCaptured(content::DesktopMediaID id,
+ gfx::Image image) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ UpdateSourceThumbnail(id, image.AsImageSkia());
+
+ --pending_window_capture_requests_;
+ DCHECK_GE(pending_window_capture_requests_, 0);
+
+ OnRefreshMaybeComplete();
+}
+
+void DesktopMediaListAsh::OnRefreshMaybeComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (pending_window_capture_requests_ == 0) {
+ // Once we've finished capturing all windows, notify the caller, which will
+ // post a task for the next list update if necessary.
+ OnRefreshComplete();
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h
new file mode 100644
index 00000000000..e8450edc102
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash.h
@@ -0,0 +1,54 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
+
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+#include "content/public/browser/desktop_media_id.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Image;
+}
+
+// Implementation of DesktopMediaList that shows native screens and
+// native windows.
+class DesktopMediaListAsh : public DesktopMediaListBase {
+ public:
+ explicit DesktopMediaListAsh(content::DesktopMediaID::Type type);
+ ~DesktopMediaListAsh() override;
+
+ private:
+ // Override from DesktopMediaListBase.
+ void Refresh(bool update_thumnails) override;
+ void EnumerateWindowsForRoot(
+ std::vector<DesktopMediaListAsh::SourceDescription>* windows,
+ bool update_thumnails,
+ aura::Window* root_window,
+ int container_id);
+ void EnumerateSources(
+ std::vector<DesktopMediaListAsh::SourceDescription>* windows,
+ bool update_thumnails);
+ void CaptureThumbnail(content::DesktopMediaID id, aura::Window* window);
+ void OnThumbnailCaptured(content::DesktopMediaID id, gfx::Image image);
+ void OnRefreshMaybeComplete();
+
+ int pending_window_capture_requests_ = 0;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<DesktopMediaListAsh> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListAsh);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_ASH_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc
new file mode 100644
index 00000000000..dfc973b1989
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_ash_unittest.cc
@@ -0,0 +1,96 @@
+// 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 "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "chrome/test/base/chrome_ash_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+
+int kThumbnailSize = 100;
+
+using testing::AtLeast;
+using testing::DoDefault;
+
+class MockDesktopMediaListObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+};
+
+class DesktopMediaListAshTest : public ChromeAshTestBase {
+ public:
+ DesktopMediaListAshTest() {}
+ ~DesktopMediaListAshTest() override {}
+
+ void TearDown() override {
+ // Reset the unique_ptr so the list stops refreshing.
+ list_.reset();
+ ChromeAshTestBase::TearDown();
+ }
+
+ void CreateList(content::DesktopMediaID::Type type) {
+ list_.reset(new DesktopMediaListAsh(type));
+ list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
+
+ // Set update period to reduce the time it takes to run tests.
+ list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
+ }
+
+ protected:
+ MockDesktopMediaListObserver observer_;
+ std::unique_ptr<DesktopMediaListAsh> list_;
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListAshTest);
+};
+
+ACTION(QuitMessageLoop) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
+}
+
+TEST_F(DesktopMediaListAshTest, ScreenOnly) {
+ CreateList(content::DesktopMediaID::TYPE_SCREEN);
+
+ std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop())
+ .WillRepeatedly(DoDefault());
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+}
+
+TEST_F(DesktopMediaListAshTest, WindowOnly) {
+ CreateList(content::DesktopMediaID::TYPE_WINDOW);
+
+ std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop())
+ .WillRepeatedly(DoDefault());
+ EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+ window.reset();
+ base::RunLoop().Run();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc
new file mode 100644
index 00000000000..9d9b7435044
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.cc
@@ -0,0 +1,186 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/desktop_media_list_base.h"
+
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/gfx/image/image.h"
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+DesktopMediaListBase::DesktopMediaListBase(base::TimeDelta update_period)
+ : update_period_(update_period) {}
+
+DesktopMediaListBase::~DesktopMediaListBase() {}
+
+void DesktopMediaListBase::SetUpdatePeriod(base::TimeDelta period) {
+ DCHECK(!observer_);
+ update_period_ = period;
+}
+
+void DesktopMediaListBase::SetThumbnailSize(const gfx::Size& thumbnail_size) {
+ thumbnail_size_ = thumbnail_size;
+}
+
+void DesktopMediaListBase::SetViewDialogWindowId(DesktopMediaID dialog_id) {
+ view_dialog_id_ = dialog_id;
+}
+
+void DesktopMediaListBase::StartUpdating(DesktopMediaListObserver* observer) {
+ DCHECK(!observer_);
+ observer_ = observer;
+
+ // Process sources previously discovered by a call to Update().
+ if (observer_) {
+ for (size_t i = 0; i < sources_.size(); i++) {
+ observer_->OnSourceAdded(this, i);
+ }
+ }
+
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = base::BindOnce(&DesktopMediaListBase::ScheduleNextRefresh,
+ weak_factory_.GetWeakPtr());
+ Refresh(true);
+}
+
+void DesktopMediaListBase::Update(UpdateCallback callback) {
+ DCHECK(sources_.empty());
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = std::move(callback);
+ Refresh(false);
+}
+
+int DesktopMediaListBase::GetSourceCount() const {
+ return sources_.size();
+}
+
+const DesktopMediaList::Source& DesktopMediaListBase::GetSource(
+ int index) const {
+ DCHECK_GE(index, 0);
+ DCHECK_LT(index, static_cast<int>(sources_.size()));
+ return sources_[index];
+}
+
+DesktopMediaID::Type DesktopMediaListBase::GetMediaListType() const {
+ return type_;
+}
+
+DesktopMediaListBase::SourceDescription::SourceDescription(
+ DesktopMediaID id,
+ const base::string16& name)
+ : id(id), name(name) {}
+
+void DesktopMediaListBase::UpdateSourcesList(
+ const std::vector<SourceDescription>& new_sources) {
+ typedef std::set<DesktopMediaID> SourceSet;
+ SourceSet new_source_set;
+ for (size_t i = 0; i < new_sources.size(); ++i) {
+ new_source_set.insert(new_sources[i].id);
+ }
+ // Iterate through the old sources to find the removed sources.
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
+ sources_.erase(sources_.begin() + i);
+ if (observer_)
+ observer_->OnSourceRemoved(this, i);
+ --i;
+ }
+ }
+ // Iterate through the new sources to find the added sources.
+ if (new_sources.size() > sources_.size()) {
+ SourceSet old_source_set;
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ old_source_set.insert(sources_[i].id);
+ }
+
+ for (size_t i = 0; i < new_sources.size(); ++i) {
+ if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
+ sources_.insert(sources_.begin() + i, Source());
+ sources_[i].id = new_sources[i].id;
+ sources_[i].name = new_sources[i].name;
+ if (observer_)
+ observer_->OnSourceAdded(this, i);
+ }
+ }
+ }
+ DCHECK_EQ(new_sources.size(), sources_.size());
+
+ // Find the moved/changed sources.
+ size_t pos = 0;
+ while (pos < sources_.size()) {
+ if (!(sources_[pos].id == new_sources[pos].id)) {
+ // Find the source that should be moved to |pos|, starting from |pos + 1|
+ // of |sources_|, because entries before |pos| should have been sorted.
+ size_t old_pos = pos + 1;
+ for (; old_pos < sources_.size(); ++old_pos) {
+ if (sources_[old_pos].id == new_sources[pos].id)
+ break;
+ }
+ DCHECK(sources_[old_pos].id == new_sources[pos].id);
+
+ // Move the source from |old_pos| to |pos|.
+ Source temp = sources_[old_pos];
+ sources_.erase(sources_.begin() + old_pos);
+ sources_.insert(sources_.begin() + pos, temp);
+
+ if (observer_)
+ observer_->OnSourceMoved(this, old_pos, pos);
+ }
+
+ if (sources_[pos].name != new_sources[pos].name) {
+ sources_[pos].name = new_sources[pos].name;
+ if (observer_)
+ observer_->OnSourceNameChanged(this, pos);
+ }
+ ++pos;
+ }
+}
+
+void DesktopMediaListBase::UpdateSourceThumbnail(DesktopMediaID id,
+ const gfx::ImageSkia& image) {
+ // Unlike other methods that check can_refresh(), this one won't cause
+ // OnRefreshComplete() to be called, but the caller is expected to schedule a
+ // call to OnRefreshComplete() after this method has been called as many times
+ // as needed, so the check is still valid.
+ DCHECK(can_refresh());
+
+ for (size_t i = 0; i < sources_.size(); ++i) {
+ if (sources_[i].id == id) {
+ sources_[i].thumbnail = image;
+ if (observer_)
+ observer_->OnSourceThumbnailChanged(this, i);
+ break;
+ }
+ }
+}
+
+// static
+uint32_t DesktopMediaListBase::GetImageHash(const gfx::Image& image) {
+ SkBitmap bitmap = image.AsBitmap();
+ uint32_t value = base::Hash(bitmap.getPixels(), bitmap.computeByteSize());
+ return value;
+}
+
+void DesktopMediaListBase::OnRefreshComplete() {
+ DCHECK(refresh_callback_);
+ std::move(refresh_callback_).Run();
+}
+
+void DesktopMediaListBase::ScheduleNextRefresh() {
+ DCHECK(!refresh_callback_);
+ refresh_callback_ = base::BindOnce(&DesktopMediaListBase::ScheduleNextRefresh,
+ weak_factory_.GetWeakPtr());
+ base::PostDelayedTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&DesktopMediaListBase::Refresh,
+ weak_factory_.GetWeakPtr(), true),
+ update_period_);
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h
new file mode 100644
index 00000000000..3c09ec3111d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_base.h
@@ -0,0 +1,102 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "content/public/browser/desktop_media_id.h"
+
+namespace gfx {
+class Image;
+}
+
+// Thumbnail size is 100*100 pixels
+static const int kDefaultThumbnailSize = 100;
+
+// Base class for DesktopMediaList implementations. Implements logic shared
+// between implementations. Specifically it's responsible for keeping current
+// list of sources and calling the observer when the list changes.
+//
+// TODO(crbug.com/987001): Consider renaming this class.
+class DesktopMediaListBase : public DesktopMediaList {
+ public:
+ explicit DesktopMediaListBase(base::TimeDelta update_period);
+ ~DesktopMediaListBase() override;
+
+ // DesktopMediaList interface.
+ void SetUpdatePeriod(base::TimeDelta period) override;
+ void SetThumbnailSize(const gfx::Size& thumbnail_size) override;
+ void SetViewDialogWindowId(content::DesktopMediaID dialog_id) override;
+ void StartUpdating(DesktopMediaListObserver* observer) override;
+ void Update(UpdateCallback callback) override;
+ int GetSourceCount() const override;
+ const Source& GetSource(int index) const override;
+ content::DesktopMediaID::Type GetMediaListType() const override;
+
+ static uint32_t GetImageHash(const gfx::Image& image);
+
+ protected:
+ using RefreshCallback = UpdateCallback;
+
+ struct SourceDescription {
+ SourceDescription(content::DesktopMediaID id, const base::string16& name);
+
+ content::DesktopMediaID id;
+ base::string16 name;
+ };
+
+ // Before this method is called, |refresh_callback_| must be non-null, and
+ // after it completes (usually asychnonrously), |refresh_callback_| must be
+ // null. Since |refresh_callback_| is private, subclasses can check this
+ // condition by calling can_refresh().
+ virtual void Refresh(bool update_thumnails) = 0;
+
+ // Update source media list to observer.
+ void UpdateSourcesList(const std::vector<SourceDescription>& new_sources);
+
+ // Update a thumbnail to observer.
+ void UpdateSourceThumbnail(content::DesktopMediaID id,
+ const gfx::ImageSkia& image);
+
+ // Called when a refresh is complete. Invokes |refresh_callback_| unless it
+ // is null. Postcondition: |refresh_callback_| is null.
+ void OnRefreshComplete();
+
+ bool can_refresh() const { return !refresh_callback_.is_null(); }
+
+ // Size of thumbnails generated by the model.
+ gfx::Size thumbnail_size_ =
+ gfx::Size(kDefaultThumbnailSize, kDefaultThumbnailSize);
+
+ // ID of the hosting dialog.
+ content::DesktopMediaID view_dialog_id_ =
+ content::DesktopMediaID(content::DesktopMediaID::TYPE_NONE, -1);
+
+ // Desktop media type of the list.
+ content::DesktopMediaID::Type type_ = content::DesktopMediaID::TYPE_NONE;
+
+ private:
+ // Post a task for next list update.
+ void ScheduleNextRefresh();
+
+ // Time interval between mode updates.
+ base::TimeDelta update_period_;
+
+ // Current list of sources.
+ std::vector<Source> sources_;
+
+ // The observer passed to StartUpdating().
+ DesktopMediaListObserver* observer_ = nullptr;
+
+ // Called when a refresh operation completes.
+ RefreshCallback refresh_callback_;
+
+ base::WeakPtrFactory<DesktopMediaListBase> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaListBase);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_BASE_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h b/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h
new file mode 100644
index 00000000000..ad7f766a36b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_list_observer.h
@@ -0,0 +1,28 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
+
+class DesktopMediaList;
+
+// Interface implemented by the desktop media picker dialog to receive
+// notifications about changes in DesktopMediaList.
+class DesktopMediaListObserver {
+ public:
+ // TODO(jrw): None of the |list| parameters below seem to be used. Consider
+ // removing them.
+ virtual void OnSourceAdded(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceRemoved(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceMoved(DesktopMediaList* list,
+ int old_index,
+ int new_index) = 0;
+ virtual void OnSourceNameChanged(DesktopMediaList* list, int index) = 0;
+ virtual void OnSourceThumbnailChanged(DesktopMediaList* list, int index) = 0;
+
+ protected:
+ virtual ~DesktopMediaListObserver() {}
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_LIST_OBSERVER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc
new file mode 100644
index 00000000000..32b31d5f579
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker.cc
@@ -0,0 +1,10 @@
+// Copyright 2018 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 "desktop_media_picker.h"
+
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+
+DesktopMediaPicker::Params::Params() = default;
+DesktopMediaPicker::Params::~Params() = default;
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker.h
new file mode 100644
index 00000000000..afcb60351dd
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker.h
@@ -0,0 +1,88 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/native_widget_types.h"
+
+class DesktopMediaList;
+
+namespace content {
+class WebContents;
+}
+
+// Abstract interface for desktop media picker UI. It's used by Desktop Media
+// API and by ARC to let user choose a desktop media source.
+//
+// TODO(crbug.com/987001): Rename this class.
+class DesktopMediaPicker {
+ public:
+ using DoneCallback = base::OnceCallback<void(content::DesktopMediaID id)>;
+
+ struct Params {
+ Params();
+ ~Params();
+
+ // WebContents this picker is relative to, can be null.
+ content::WebContents* web_contents = nullptr;
+ // The context whose root window is used for dialog placement, cannot be
+ // null for Aura.
+ gfx::NativeWindow context = nullptr;
+ // Parent window the dialog is relative to, only used on Mac.
+ gfx::NativeWindow parent = nullptr;
+ // The modality used for showing the dialog.
+ ui::ModalType modality = ui::ModalType::MODAL_TYPE_CHILD;
+ // The name used in the dialog for what is requesting the picker to be
+ // shown.
+ base::string16 app_name;
+ // Can be the same as target_name. If it is not then this is used in the
+ // dialog for what is specific target within the app_name is requesting the
+ // picker.
+ base::string16 target_name;
+ // Whether audio capture should be shown as an option in the picker.
+ bool request_audio = false;
+ // Whether audio capture option should be approved by default if shown.
+ bool approve_audio_by_default = true;
+ // This flag controls the behvior in the case where the picker is invoked to
+ // select a screen and there is only one screen available. If true, the
+ // dialog is bypassed entirely and the screen is automatically selected.
+ // This behavior is disabled by default because in addition to letting the
+ // user select a desktop, the desktop picker also serves to prevent the
+ // screen screen from being shared without the user's explicit consent.
+ bool select_only_screen = false;
+ };
+
+ // Creates default implementation of DesktopMediaPicker for the current
+ // platform.
+ static std::unique_ptr<DesktopMediaPicker> Create();
+
+ DesktopMediaPicker() {}
+ virtual ~DesktopMediaPicker() {}
+
+ // Shows dialog with list of desktop media sources (screens, windows, tabs)
+ // provided by |sources_lists|.
+ // Dialog window will call |done_callback| when user chooses one of the
+ // sources or closes the dialog.
+ virtual void Show(const Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPicker);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc
new file mode 100644
index 00000000000..e026c204534
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.cc
@@ -0,0 +1,109 @@
+// Copyright 2019 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 "chrome/browser/media/webrtc/desktop_media_picker_controller.h"
+
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/grit/chromium_strings.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/desktop_streams_registry.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "desktop_media_picker.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/switches.h"
+#include "ui/base/l10n/l10n_util.h"
+
+DesktopMediaPickerController::DesktopMediaPickerController(
+ DesktopMediaPickerFactory* picker_factory)
+ : picker_factory_(picker_factory
+ ? picker_factory
+ : DesktopMediaPickerFactoryImpl::GetInstance()) {}
+
+DesktopMediaPickerController::~DesktopMediaPickerController() = default;
+
+void DesktopMediaPickerController::Show(
+ const Params& params,
+ const std::vector<content::DesktopMediaID::Type>& sources,
+ DoneCallback done_callback) {
+ DCHECK(!base::Contains(sources, content::DesktopMediaID::TYPE_NONE));
+ DCHECK(!done_callback_);
+
+ done_callback_ = std::move(done_callback);
+ params_ = params;
+
+ Observe(params.web_contents);
+
+ // Keep same order as the input |sources| and avoid duplicates.
+ source_lists_ = picker_factory_->CreateMediaList(sources);
+ if (source_lists_.empty()) {
+ OnPickerDialogResults("At least one source type must be specified.", {});
+ return;
+ }
+
+ if (params.select_only_screen && sources.size() == 1 &&
+ sources[0] == content::DesktopMediaID::TYPE_SCREEN) {
+ // Try to bypass the picker dialog if possible.
+ DCHECK(source_lists_.size() == 1);
+ auto* source_list = source_lists_[0].get();
+ source_list->Update(
+ base::BindOnce(&DesktopMediaPickerController::OnInitialMediaListFound,
+ base::Unretained(this)));
+ } else {
+ ShowPickerDialog();
+ }
+}
+
+void DesktopMediaPickerController::WebContentsDestroyed() {
+ OnPickerDialogResults(std::string(), content::DesktopMediaID());
+}
+
+void DesktopMediaPickerController::OnInitialMediaListFound() {
+ DCHECK(params_.select_only_screen);
+ DCHECK(source_lists_.size() == 1);
+ auto* source_list = source_lists_[0].get();
+ if (source_list->GetSourceCount() == 1) {
+ OnPickerDialogResults({}, source_list->GetSource(0).id);
+ return;
+ }
+
+ ShowPickerDialog();
+}
+
+void DesktopMediaPickerController::ShowPickerDialog() {
+ picker_ = picker_factory_->CreatePicker();
+ if (!picker_) {
+ OnPickerDialogResults(
+ "Desktop Capture API is not yet implemented for this platform.", {});
+ return;
+ }
+
+ picker_->Show(params_, std::move(source_lists_),
+ base::Bind(&DesktopMediaPickerController::OnPickerDialogResults,
+ base::Unretained(this), std::string()));
+}
+
+void DesktopMediaPickerController::OnPickerDialogResults(
+ const std::string& err,
+ content::DesktopMediaID source) {
+ DCHECK(done_callback_);
+ std::move(done_callback_).Run(err, source);
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h
new file mode 100644
index 00000000000..5bdd0ecdf82
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller.h
@@ -0,0 +1,91 @@
+// Copyright 2019 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/base/ui_base_types.h"
+
+class DesktopMediaList;
+class DesktopMediaPickerFactory;
+
+// The main entry point for the desktop picker dialog box, which prompts the
+// user to select a desktop or an application window whose content will be made
+// available as a video stream.
+//
+// TODO(crbug.com/987001): Rename this class. Consider merging with
+// DesktopMediaPickerViews and naming the merged class just DesktopMediaPicker.
+class DesktopMediaPickerController : private content::WebContentsObserver {
+ public:
+ using Params = DesktopMediaPicker::Params;
+
+ // Callback for desktop selection results. There are three possible cases:
+ //
+ // - If |err| is non-empty, it contains an error message regarding why the
+ // dialog could not be displayed, and the value of |id| should not be used.
+ //
+ // - If |err| is empty and id.is_null() is true, the user canceled the dialog.
+ //
+ // - Otherwise, |id| represents the user's selection.
+ using DoneCallback = base::OnceCallback<void(const std::string& err,
+ content::DesktopMediaID id)>;
+
+ explicit DesktopMediaPickerController(
+ DesktopMediaPickerFactory* picker_factory = nullptr);
+ DesktopMediaPickerController(const DesktopMediaPickerController&) = delete;
+ DesktopMediaPickerController& operator=(const DesktopMediaPickerController&) =
+ delete;
+ ~DesktopMediaPickerController() override;
+
+ // Show the desktop picker dialog using the parameters specified by |params|,
+ // with the possible selections restricted to those included in |sources|. If
+ // an error is detected synchronously, it is reported by returning an error
+ // string. Otherwise, the return value is nullopt, and the closure passed as
+ // |done_callback| is called when the dialog is closed. If the dialog is
+ // canceled, the argument to |done_callback| will be an instance of
+ // DesktopMediaID whose is_null() method returns true.
+ //
+ // As a special case, if |params.select_only_screen| is true, and the only
+ // selection type is TYPE_SCREEN, and there is only one screen,
+ // |done_callback| is called immediately with the screen's ID, and the dialog
+ // is not shown. This option must be used with care, because even when the
+ // dialog has only one option to select, the dialog itself helps prevent the
+ // user for accidentally sharing their screen and gives them the option to
+ // prevent their screen from being shared.
+ //
+ // Note that |done_callback| is called only if the dialog completes normally.
+ // If an instance of this class is destroyed while the dialog is visible, the
+ // dialog will be cleaned up, but |done_callback| will not be invoked.
+ void Show(const Params& params,
+ const std::vector<content::DesktopMediaID::Type>& sources,
+ DoneCallback done_callback);
+
+ // content::WebContentsObserver overrides.
+ void WebContentsDestroyed() override;
+
+ private:
+ void OnInitialMediaListFound();
+ void ShowPickerDialog();
+ // This function is responsible to call |done_callback_| and after running the
+ // callback |this| might be destroyed. Do **not** access fields after calling
+ // this function.
+ void OnPickerDialogResults(const std::string& err,
+ content::DesktopMediaID source);
+
+ Params params_;
+ DoneCallback done_callback_;
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists_;
+ std::unique_ptr<DesktopMediaPicker> picker_;
+ DesktopMediaPickerFactory* picker_factory_;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc
new file mode 100644
index 00000000000..545d149d069
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_controller_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2019 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 <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/mock_callback.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_controller.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::ByMove;
+using testing::InvokeArgument;
+using testing::Ne;
+using testing::Return;
+using testing::ReturnRef;
+using testing::WithArg;
+
+class MockDesktopMediaPicker : public DesktopMediaPicker {
+ public:
+ MOCK_METHOD3(Show,
+ void(const Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback));
+};
+
+class MockDesktopMediaList : public DesktopMediaList {
+ public:
+ MOCK_METHOD1(SetUpdatePeriod, void(base::TimeDelta period));
+ MOCK_METHOD1(SetThumbnailSize, void(const gfx::Size& thumbnail_size));
+ MOCK_METHOD1(SetViewDialogWindowId, void(content::DesktopMediaID dialog_id));
+ MOCK_METHOD1(StartUpdating, void(DesktopMediaListObserver* observer));
+ MOCK_METHOD1(Update, void(UpdateCallback callback));
+ MOCK_CONST_METHOD0(GetSourceCount, int());
+ MOCK_CONST_METHOD1(GetSource, Source&(int index));
+ MOCK_CONST_METHOD0(GetMediaListType, content::DesktopMediaID::Type());
+};
+
+class MockDesktopMediaPickerFactory : public DesktopMediaPickerFactory {
+ public:
+ MOCK_METHOD0(CreatePicker, std::unique_ptr<DesktopMediaPicker>());
+ MOCK_METHOD1(CreateMediaList,
+ std::vector<std::unique_ptr<DesktopMediaList>>(
+ const std::vector<content::DesktopMediaID::Type>& types));
+};
+
+class DesktopMediaPickerControllerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ON_CALL(factory_, CreatePicker).WillByDefault([this]() {
+ return std::unique_ptr<DesktopMediaPicker>(std::move(picker_));
+ });
+ ON_CALL(factory_, CreateMediaList).WillByDefault([this](const auto& types) {
+ std::vector<std::unique_ptr<DesktopMediaList>> lists;
+ lists.push_back(std::move(media_list_));
+ return lists;
+ });
+ }
+
+ protected:
+ DesktopMediaPickerController::Params picker_params_;
+ base::MockCallback<DesktopMediaPickerController::DoneCallback> done_;
+ std::vector<content::DesktopMediaID::Type> source_types_{
+ content::DesktopMediaID::TYPE_SCREEN};
+ content::DesktopMediaID media_id_{content::DesktopMediaID::TYPE_SCREEN, 42};
+ std::unique_ptr<MockDesktopMediaPicker> picker_ =
+ std::make_unique<MockDesktopMediaPicker>();
+ std::unique_ptr<MockDesktopMediaList> media_list_ =
+ std::make_unique<MockDesktopMediaList>();
+ MockDesktopMediaPickerFactory factory_;
+};
+
+// Test that the picker dialog is shown and the selected media ID is returned.
+TEST_F(DesktopMediaPickerControllerTest, ShowPicker) {
+ EXPECT_CALL(factory_, CreatePicker());
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", media_id_));
+ EXPECT_CALL(*picker_, Show)
+ .WillOnce(WithArg<2>([&](DesktopMediaPicker::DoneCallback cb) {
+ std::move(cb).Run(media_id_);
+ }));
+ EXPECT_CALL(*media_list_, Update).Times(0);
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that a null result is returned in response to WebContentsDestroyed().
+TEST_F(DesktopMediaPickerControllerTest, WebContentsDestroyed) {
+ EXPECT_CALL(factory_, CreatePicker());
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", content::DesktopMediaID()));
+ EXPECT_CALL(*picker_, Show);
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+ controller.WebContentsDestroyed();
+}
+
+// Test that the picker dialog can be bypassed.
+TEST_F(DesktopMediaPickerControllerTest, ShowSingleScreen) {
+ picker_params_.select_only_screen = true;
+
+ DesktopMediaList::Source source;
+ source.id = media_id_;
+ source.name = base::ASCIIToUTF16("fake name");
+
+ EXPECT_CALL(factory_, CreatePicker()).Times(0);
+ EXPECT_CALL(factory_, CreateMediaList(source_types_));
+ EXPECT_CALL(done_, Run("", source.id));
+ EXPECT_CALL(*picker_, Show).Times(0);
+ EXPECT_CALL(*media_list_, Update)
+ .WillOnce(
+ [](DesktopMediaList::UpdateCallback cb) { std::move(cb).Run(); });
+ EXPECT_CALL(*media_list_, GetSourceCount)
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(1));
+ EXPECT_CALL(*media_list_, GetSource(0))
+ .Times(AnyNumber())
+ .WillRepeatedly(ReturnRef(source));
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that an error is reported when no sources are found.
+TEST_F(DesktopMediaPickerControllerTest, EmptySourceList) {
+ EXPECT_CALL(factory_, CreateMediaList)
+ .WillOnce(
+ Return(ByMove(std::vector<std::unique_ptr<DesktopMediaList>>())));
+ EXPECT_CALL(done_, Run(Ne(""), content::DesktopMediaID()));
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
+
+// Test that an error is reported when no picker can be created.
+TEST_F(DesktopMediaPickerControllerTest, NoPicker) {
+ EXPECT_CALL(factory_, CreatePicker)
+ .WillOnce(Return(ByMove(std::unique_ptr<DesktopMediaPicker>())));
+ EXPECT_CALL(done_, Run(Ne(""), content::DesktopMediaID()));
+ EXPECT_CALL(factory_, CreateMediaList).Times(AnyNumber());
+
+ DesktopMediaPickerController controller(&factory_);
+ controller.Show(picker_params_, source_types_, done_.Get());
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc
new file mode 100644
index 00000000000..cbb3e722374
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.cc
@@ -0,0 +1,9 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+
+DesktopMediaPickerFactory::DesktopMediaPickerFactory() = default;
+
+DesktopMediaPickerFactory::~DesktopMediaPickerFactory() = default;
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h
new file mode 100644
index 00000000000..0e393e91a4d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory.h
@@ -0,0 +1,33 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Interface for factory creating DesktopMediaList and DesktopMediaPicker
+// instances.
+class DesktopMediaPickerFactory {
+ public:
+ virtual ~DesktopMediaPickerFactory();
+
+ virtual std::unique_ptr<DesktopMediaPicker> CreatePicker() = 0;
+ virtual std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) = 0;
+
+ protected:
+ DesktopMediaPickerFactory();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerFactory);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc
new file mode 100644
index 00000000000..fa7d872810a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+
+#include "base/no_destructor.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "content/public/browser/desktop_capture.h"
+
+DesktopMediaPickerFactoryImpl::DesktopMediaPickerFactoryImpl() = default;
+
+DesktopMediaPickerFactoryImpl::~DesktopMediaPickerFactoryImpl() = default;
+
+// static
+DesktopMediaPickerFactoryImpl* DesktopMediaPickerFactoryImpl::GetInstance() {
+ static base::NoDestructor<DesktopMediaPickerFactoryImpl> impl;
+ return impl.get();
+}
+
+std::unique_ptr<DesktopMediaPicker>
+DesktopMediaPickerFactoryImpl::CreatePicker() {
+// DesktopMediaPicker is implemented only for Windows, OSX and Aura Linux
+// builds.
+#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX)
+ return DesktopMediaPicker::Create();
+#else
+ return nullptr;
+#endif
+}
+
+std::vector<std::unique_ptr<DesktopMediaList>>
+DesktopMediaPickerFactoryImpl::CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) {
+ // Keep same order as the input |sources| and avoid duplicates.
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists;
+ bool have_screen_list = false;
+ bool have_window_list = false;
+ bool have_tab_list = false;
+ for (auto source_type : types) {
+ switch (source_type) {
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ case content::DesktopMediaID::TYPE_SCREEN: {
+ if (have_screen_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> screen_list;
+#if defined(OS_CHROMEOS)
+ screen_list = std::make_unique<DesktopMediaListAsh>(
+ content::DesktopMediaID::TYPE_SCREEN);
+#else // !defined(OS_CHROMEOS)
+ screen_list = std::make_unique<NativeDesktopMediaList>(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::desktop_capture::CreateScreenCapturer());
+#endif // !defined(OS_CHROMEOS)
+ have_screen_list = true;
+ source_lists.push_back(std::move(screen_list));
+ break;
+ }
+ case content::DesktopMediaID::TYPE_WINDOW: {
+ if (have_window_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> window_list;
+#if defined(OS_CHROMEOS)
+ window_list = std::make_unique<DesktopMediaListAsh>(
+ content::DesktopMediaID::TYPE_WINDOW);
+#else // !defined(OS_CHROMEOS)
+ window_list = std::make_unique<NativeDesktopMediaList>(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::desktop_capture::CreateWindowCapturer());
+#endif // !defined(OS_CHROMEOS)
+ have_window_list = true;
+ source_lists.push_back(std::move(window_list));
+ break;
+ }
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS: {
+ if (have_tab_list)
+ continue;
+ std::unique_ptr<DesktopMediaList> tab_list =
+ std::make_unique<TabDesktopMediaList>();
+ have_tab_list = true;
+ source_lists.push_back(std::move(tab_list));
+ break;
+ }
+ }
+ }
+ return source_lists;
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h
new file mode 100644
index 00000000000..4888b591445
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h
@@ -0,0 +1,35 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Factory creating DesktopMediaList and DesktopMediaPicker instances.
+class DesktopMediaPickerFactoryImpl : public DesktopMediaPickerFactory {
+ public:
+ DesktopMediaPickerFactoryImpl();
+ ~DesktopMediaPickerFactoryImpl() override;
+
+ // Get the lazy initialized instance of the factory.
+ static DesktopMediaPickerFactoryImpl* GetInstance();
+
+ // DesktopMediaPickerFactory implementation
+ // Can return |nullptr| if platform doesn't support DesktopMediaPicker.
+ std::unique_ptr<DesktopMediaPicker> CreatePicker() override;
+ std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerFactoryImpl);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_FACTORY_IMPL_H_
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc
new file mode 100644
index 00000000000..1f3a18f8268
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.cc
@@ -0,0 +1,34 @@
+// Copyright 2019 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 "chrome/browser/media/webrtc/desktop_media_picker_manager.h"
+
+#include "base/no_destructor.h"
+
+// static
+DesktopMediaPickerManager* DesktopMediaPickerManager::Get() {
+ static base::NoDestructor<DesktopMediaPickerManager> instance;
+ return instance.get();
+}
+
+DesktopMediaPickerManager::DesktopMediaPickerManager() = default;
+DesktopMediaPickerManager::~DesktopMediaPickerManager() = default;
+
+void DesktopMediaPickerManager::AddObserver(DialogObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DesktopMediaPickerManager::RemoveObserver(DialogObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void DesktopMediaPickerManager::OnShowDialog() {
+ for (auto& observer : observers_)
+ observer.OnDialogOpened();
+}
+
+void DesktopMediaPickerManager::OnHideDialog() {
+ for (auto& observer : observers_)
+ observer.OnDialogClosed();
+}
diff --git a/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h
new file mode 100644
index 00000000000..391994156e6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/desktop_media_picker_manager.h
@@ -0,0 +1,51 @@
+// Copyright 2019 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 CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}
+
+// A singleton that acts as a rendezvous for dialog observers to register and
+// the dialog managers/delegates to post their activities.
+// TODO(crbug/953495): Merge this into DesktopMediaPickerFactoryImpl.
+class DesktopMediaPickerManager {
+ public:
+ class DialogObserver : public base::CheckedObserver {
+ public:
+ // Called when a media dialog is opened/shown.
+ virtual void OnDialogOpened() = 0;
+
+ // Called when a media dialog is closed/hidden.
+ virtual void OnDialogClosed() = 0;
+ };
+
+ static DesktopMediaPickerManager* Get();
+
+ // For the observers
+ void AddObserver(DialogObserver* observer);
+ void RemoveObserver(DialogObserver* observer);
+
+ // For the notifiers
+ void OnShowDialog();
+ void OnHideDialog();
+
+ private:
+ friend base::NoDestructor<DesktopMediaPickerManager>;
+
+ DesktopMediaPickerManager();
+ ~DesktopMediaPickerManager(); // Never called.
+
+ base::ObserverList<DesktopMediaPickerManager::DialogObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerManager);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_MEDIA_PICKER_MANAGER_H_
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc b/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc
new file mode 100644
index 00000000000..7e63e08ccf9
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -0,0 +1,270 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#endif
+
+// Holds pending request information so that we display one picker UI at a time
+// for each content::WebContents.
+struct DisplayMediaAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : picker(std::move(picker)),
+ request(request),
+ callback(std::move(callback)) {}
+ ~PendingAccessRequest() = default;
+
+ std::unique_ptr<DesktopMediaPicker> picker;
+ content::MediaStreamRequest request;
+ content::MediaResponseCallback callback;
+};
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler()
+ : picker_factory_(new DesktopMediaPickerFactoryImpl()) {
+ AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+ bool display_notification)
+ : display_notification_(display_notification),
+ picker_factory_(std::move(picker_factory)) {
+ AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::~DisplayMediaAccessHandler() = default;
+
+bool DisplayMediaAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType stream_type,
+ const extensions::Extension* extension) {
+ return stream_type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+ // This class handles MEDIA_DISPLAY_AUDIO_CAPTURE as well, but only if it is
+ // accompanied by MEDIA_DISPLAY_VIDEO_CAPTURE request as per spec.
+ // https://w3c.github.io/mediacapture-screen-share/#mediadevices-additions
+ // 5.1 MediaDevices Additions
+ // "The user agent MUST reject audio-only requests."
+}
+
+bool DisplayMediaAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void DisplayMediaAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+#if defined(OS_MACOSX)
+ // Do not allow picker UI to be shown on a page that isn't in the foreground
+ // in Mac, because the UI implementation in Mac pops a window over any content
+ // which might be confusing for the users. See https://crbug.com/1407733 for
+ // details.
+ // TODO(emircan): Remove this once Mac UI doesn't use a window.
+ if (web_contents->GetVisibility() != content::Visibility::VISIBLE) {
+ LOG(ERROR) << "Do not allow getDisplayMedia() on a backgrounded page.";
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+#endif // defined(OS_MACOSX)
+
+ std::unique_ptr<DesktopMediaPicker> picker = picker_factory_->CreatePicker();
+ if (!picker) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+
+ RequestsQueue& queue = pending_requests_[web_contents];
+ queue.push_back(std::make_unique<PendingAccessRequest>(
+ std::move(picker), request, std::move(callback)));
+ // If this is the only request then pop picker UI.
+ if (queue.size() == 1)
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (state != content::MEDIA_REQUEST_STATE_DONE &&
+ state != content::MEDIA_REQUEST_STATE_CLOSING) {
+ return;
+ }
+
+ if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ DeletePendingAccessRequest(render_process_id, render_frame_id,
+ page_request_id);
+ }
+ CaptureAccessHandlerBase::UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, stream_type, state);
+
+ // This method only gets called with the above checked states when all
+ // requests are to be canceled. Therefore, we don't need to process the
+ // next queued request.
+}
+
+void DisplayMediaAccessHandler::ProcessQueuedAccessRequest(
+ const RequestsQueue& queue,
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ const PendingAccessRequest& pending_request = *queue.front();
+ UpdateTrusted(pending_request.request, false /* is_trusted */);
+
+ std::vector<content::DesktopMediaID::Type> media_types = {
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::TYPE_WEB_CONTENTS};
+ auto source_lists = picker_factory_->CreateMediaList(media_types);
+
+ DesktopMediaPicker::DoneCallback done_callback =
+ base::BindOnce(&DisplayMediaAccessHandler::OnPickerDialogResults,
+ base::Unretained(this), web_contents);
+ DesktopMediaPicker::Params picker_params;
+ picker_params.web_contents = web_contents;
+ gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
+ picker_params.context = parent_window;
+ picker_params.parent = parent_window;
+ picker_params.app_name = url_formatter::FormatUrlForSecurityDisplay(
+ web_contents->GetLastCommittedURL(),
+ url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+ picker_params.target_name = picker_params.app_name;
+ picker_params.request_audio =
+ pending_request.request.audio_type ==
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
+ picker_params.approve_audio_by_default = false;
+ pending_request.picker->Show(picker_params, std::move(source_lists),
+ std::move(done_callback));
+}
+
+void DisplayMediaAccessHandler::OnPickerDialogResults(
+ content::WebContents* web_contents,
+ content::DesktopMediaID media_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(web_contents);
+
+ auto it = pending_requests_.find(web_contents);
+ if (it == pending_requests_.end())
+ return;
+ RequestsQueue& queue = it->second;
+ if (queue.empty()) {
+ // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
+ // need to do anything.
+ return;
+ }
+
+ PendingAccessRequest& pending_request = *queue.front();
+ blink::MediaStreamDevices devices;
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (media_id.is_null()) {
+ request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ } else {
+ request_result = blink::mojom::MediaStreamRequestResult::OK;
+#if defined(OS_MACOSX)
+ // Check screen capture permissions on Mac if necessary.
+ if ((media_id.type == content::DesktopMediaID::TYPE_SCREEN ||
+ media_id.type == content::DesktopMediaID::TYPE_WINDOW) &&
+ system_media_permissions::CheckSystemScreenCapturePermission() !=
+ system_media_permissions::SystemPermission::kAllowed) {
+ request_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ }
+#endif
+ if (request_result == blink::mojom::MediaStreamRequestResult::OK) {
+ const auto& visible_url = url_formatter::FormatUrlForSecurityDisplay(
+ web_contents->GetLastCommittedURL(),
+ url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+ ui = GetDevicesForDesktopCapture(
+ web_contents, &devices, media_id,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
+ media_id.audio_share, false /* disable_local_echo */,
+ display_notification_, visible_url, visible_url);
+ }
+ }
+
+ std::move(pending_request.callback)
+ .Run(devices, request_result, std::move(ui));
+ queue.pop_front();
+
+ if (!queue.empty())
+ ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::AddNotificationObserver() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+void DisplayMediaAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
+
+void DisplayMediaAccessHandler::DeletePendingAccessRequest(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id) {
+ for (auto& queue_it : pending_requests_) {
+ RequestsQueue& queue = queue_it.second;
+ for (auto it = queue.begin(); it != queue.end(); ++it) {
+ const PendingAccessRequest& pending_request = **it;
+ if (pending_request.request.render_process_id == render_process_id &&
+ pending_request.request.render_frame_id == render_frame_id &&
+ pending_request.request.page_request_id == page_request_id) {
+ queue.erase(it);
+ return;
+ }
+ }
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler.h b/chromium/chrome/browser/media/webrtc/display_media_access_handler.h
new file mode 100644
index 00000000000..8ae226f2324
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler.h
@@ -0,0 +1,89 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "chrome/browser/media/capture_access_handler_base.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace extensions {
+class Extension;
+}
+
+// MediaAccessHandler for getDisplayMedia API, see
+// https://w3c.github.io/mediacapture-screen-share.
+class DisplayMediaAccessHandler : public CaptureAccessHandlerBase,
+ public content::NotificationObserver {
+ public:
+ DisplayMediaAccessHandler();
+ DisplayMediaAccessHandler(
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+ bool display_notification);
+ ~DisplayMediaAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType stream_type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ friend class DisplayMediaAccessHandlerTest;
+
+ struct PendingAccessRequest;
+ using RequestsQueue =
+ base::circular_deque<std::unique_ptr<PendingAccessRequest>>;
+ using RequestsQueues = base::flat_map<content::WebContents*, RequestsQueue>;
+
+ void ProcessQueuedAccessRequest(const RequestsQueue& queue,
+ content::WebContents* web_contents);
+
+ void OnPickerDialogResults(content::WebContents* web_contents,
+ content::DesktopMediaID source);
+
+ void AddNotificationObserver();
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ void DeletePendingAccessRequest(int render_process_id,
+ int render_frame_id,
+ int page_request_id);
+
+ bool display_notification_ = true;
+ std::unique_ptr<DesktopMediaPickerFactory> picker_factory_;
+ RequestsQueues pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayMediaAccessHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
new file mode 100644
index 00000000000..0fa5aa3bfbe
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -0,0 +1,286 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+class DisplayMediaAccessHandlerTest : public ChromeRenderViewHostTestHarness {
+ public:
+ DisplayMediaAccessHandlerTest() {}
+ ~DisplayMediaAccessHandlerTest() override {}
+
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+ auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
+ picker_factory_ = picker_factory.get();
+ access_handler_ = std::make_unique<DisplayMediaAccessHandler>(
+ std::move(picker_factory), false /* display_notification */);
+ }
+
+ void ProcessRequest(
+ const content::DesktopMediaID& fake_desktop_media_id_response,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ bool request_audio) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, request_audio,
+ fake_desktop_media_id_response /* selected_source */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(),
+ request_audio ? blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE
+ : blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+
+ base::RunLoop wait_loop;
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop, request_result, devices_result);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ wait_loop.Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+
+ access_handler_.reset();
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ }
+
+ void NotifyWebContentsDestroyed() {
+ access_handler_->Observe(
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<content::WebContents>(web_contents()),
+ content::NotificationDetails());
+ }
+
+ const DisplayMediaAccessHandler::RequestsQueues& GetRequestQueues() {
+ return access_handler_->pending_requests_;
+ }
+
+ protected:
+ FakeDesktopMediaPickerFactory* picker_factory_;
+ std::unique_ptr<DisplayMediaAccessHandler> access_handler_;
+};
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionGiven) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId),
+ &result, &devices, false /* request_audio */);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ return;
+ }
+#endif
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_TRUE(devices[0].display_media_info.has_value());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionGivenToRequestWithAudio) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ content::DesktopMediaID fake_media_id(content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId,
+ true /* audio_share */);
+ ProcessRequest(fake_media_id, &result, &devices, true /* request_audio */);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ return;
+ }
+#endif
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(2u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_TRUE(devices[0].display_media_info.has_value());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
+ devices[1].type);
+ EXPECT_TRUE(devices[1].input.IsValid());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionDenied) {
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ ProcessRequest(content::DesktopMediaID(), &result, &devices,
+ true /* request_audio */);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
+ EXPECT_EQ(0u, devices.size());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, UpdateMediaRequestStateWithClosing) {
+ const int render_process_id = 0;
+ const int render_frame_id = 0;
+ const int page_request_id = 0;
+ const blink::mojom::MediaStreamType video_stream_type =
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+ const blink::mojom::MediaStreamType audio_stream_type =
+ blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, true /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ render_process_id, render_frame_id, page_request_id,
+ GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), audio_stream_type, video_stream_type,
+ false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ access_handler_->UpdateMediaRequestState(
+ render_process_id, render_frame_id, page_request_id, video_stream_type,
+ content::MEDIA_REQUEST_STATE_CLOSING);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(0u, queue_it->second.size());
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+ access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, WebContentsDestroyed) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(), true /* cancelled */}};
+ picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback;
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_EQ(1u, GetRequestQueues().size());
+ auto queue_it = GetRequestQueues().find(web_contents());
+ EXPECT_TRUE(queue_it != GetRequestQueues().end());
+ EXPECT_EQ(1u, queue_it->second.size());
+
+ NotifyWebContentsDestroyed();
+ EXPECT_EQ(0u, GetRequestQueues().size());
+ access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, MultipleRequests) {
+ FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN,
+ content::DesktopMediaID::kFakeId) /* selected_source */},
+ {true /* expect_screens */, true /* expect_windows*/,
+ true /* expect_tabs */, false /* expect_audio */,
+ content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_WINDOW,
+ content::DesktopMediaID::kNullId) /* selected_source */}};
+ const size_t kTestFlagCount = 2;
+ picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
+
+ blink::mojom::MediaStreamRequestResult result;
+ blink::MediaStreamDevices devices;
+ base::RunLoop wait_loop[kTestFlagCount];
+ for (size_t i = 0; i < kTestFlagCount; ++i) {
+ content::MediaStreamRequest request(
+ 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_GENERATE_STREAM,
+ std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, false);
+ content::MediaResponseCallback callback = base::BindOnce(
+ [](base::RunLoop* wait_loop,
+ blink::mojom::MediaStreamRequestResult* request_result,
+ blink::MediaStreamDevices* devices_result,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ *request_result = result;
+ *devices_result = devices;
+ wait_loop->Quit();
+ },
+ &wait_loop[i], &result, &devices);
+ access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+ nullptr /* extension */);
+ }
+ wait_loop[0].Run();
+ EXPECT_TRUE(test_flags[0].picker_created);
+ EXPECT_TRUE(test_flags[0].picker_deleted);
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default.
+ if (base::mac::IsAtLeastOS10_15()) {
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ result);
+ access_handler_.reset();
+ return;
+ }
+#endif
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+
+ blink::MediaStreamDevice first_device = devices[0];
+ EXPECT_TRUE(test_flags[1].picker_created);
+ EXPECT_FALSE(test_flags[1].picker_deleted);
+ wait_loop[1].Run();
+ EXPECT_TRUE(test_flags[1].picker_deleted);
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
+ EXPECT_EQ(1u, devices.size());
+ EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
+ devices[0].type);
+ EXPECT_FALSE(devices[0].IsSameDevice(first_device));
+
+ access_handler_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc
new file mode 100644
index 00000000000..ee71126dc3e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.cc
@@ -0,0 +1,88 @@
+// 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 "chrome/browser/media/webrtc/fake_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
+#include "ui/gfx/skia_util.h"
+
+using content::DesktopMediaID;
+
+FakeDesktopMediaList::FakeDesktopMediaList(DesktopMediaID::Type type)
+ : observer_(NULL), type_(type) {}
+FakeDesktopMediaList::~FakeDesktopMediaList() {}
+
+void FakeDesktopMediaList::AddSource(int id) {
+ AddSourceByFullMediaID(
+ content::DesktopMediaID(DesktopMediaID::TYPE_WINDOW, id));
+}
+
+void FakeDesktopMediaList::AddSourceByFullMediaID(
+ content::DesktopMediaID media_id) {
+ Source source;
+ source.id = media_id;
+ source.name = base::NumberToString16(media_id.id);
+
+ sources_.push_back(source);
+ observer_->OnSourceAdded(this, sources_.size() - 1);
+}
+
+void FakeDesktopMediaList::RemoveSource(int index) {
+ sources_.erase(sources_.begin() + index);
+ observer_->OnSourceRemoved(this, index);
+}
+
+void FakeDesktopMediaList::MoveSource(int old_index, int new_index) {
+ Source source = sources_[old_index];
+ sources_.erase(sources_.begin() + old_index);
+ sources_.insert(sources_.begin() + new_index, source);
+ observer_->OnSourceMoved(this, old_index, new_index);
+}
+
+void FakeDesktopMediaList::SetSourceThumbnail(int index) {
+ sources_[index].thumbnail = thumbnail_;
+ observer_->OnSourceThumbnailChanged(this, index);
+}
+
+void FakeDesktopMediaList::SetSourceName(int index, base::string16 name) {
+ sources_[index].name = name;
+ observer_->OnSourceNameChanged(this, index);
+}
+
+void FakeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {}
+
+void FakeDesktopMediaList::SetThumbnailSize(const gfx::Size& thumbnail_size) {}
+
+void FakeDesktopMediaList::SetViewDialogWindowId(
+ content::DesktopMediaID dialog_id) {}
+
+void FakeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
+ observer_ = observer;
+
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(150, 150);
+ bitmap.eraseARGB(255, 0, 255, 0);
+ thumbnail_ = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+}
+
+void FakeDesktopMediaList::Update(UpdateCallback callback) {
+ std::move(callback).Run();
+}
+
+int FakeDesktopMediaList::GetSourceCount() const {
+ return sources_.size();
+}
+
+const DesktopMediaList::Source& FakeDesktopMediaList::GetSource(
+ int index) const {
+ return sources_[index];
+}
+
+DesktopMediaID::Type FakeDesktopMediaList::GetMediaListType() const {
+ return type_;
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h
new file mode 100644
index 00000000000..d9ed43891a3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_list.h
@@ -0,0 +1,43 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
+
+#include <vector>
+
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+
+class FakeDesktopMediaList : public DesktopMediaList {
+ public:
+ explicit FakeDesktopMediaList(content::DesktopMediaID::Type type);
+ ~FakeDesktopMediaList() override;
+
+ void AddSource(int id);
+ void AddSourceByFullMediaID(content::DesktopMediaID media_id);
+ void RemoveSource(int index);
+ void MoveSource(int old_index, int new_index);
+ void SetSourceThumbnail(int index);
+ void SetSourceName(int index, base::string16 name);
+
+ // DesktopMediaList implementation:
+ void SetUpdatePeriod(base::TimeDelta period) override;
+ void SetThumbnailSize(const gfx::Size& thumbnail_size) override;
+ void SetViewDialogWindowId(content::DesktopMediaID dialog_id) override;
+ void StartUpdating(DesktopMediaListObserver* observer) override;
+ void Update(UpdateCallback callback) override;
+ int GetSourceCount() const override;
+ const Source& GetSource(int index) const override;
+ content::DesktopMediaID::Type GetMediaListType() const override;
+
+ private:
+ std::vector<Source> sources_;
+ DesktopMediaListObserver* observer_;
+ gfx::ImageSkia thumbnail_;
+ const content::DesktopMediaID::Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
new file mode 100644
index 00000000000..99a474ce324
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
@@ -0,0 +1,108 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class FakeDesktopMediaPicker : public DesktopMediaPicker {
+ public:
+ explicit FakeDesktopMediaPicker(
+ FakeDesktopMediaPickerFactory::TestFlags* expectation)
+ : expectation_(expectation) {
+ expectation_->picker_created = true;
+ }
+ ~FakeDesktopMediaPicker() override { expectation_->picker_deleted = true; }
+
+ // DesktopMediaPicker interface.
+ void Show(const DesktopMediaPicker::Params& params,
+ std::vector<std::unique_ptr<DesktopMediaList>> source_lists,
+ DoneCallback done_callback) override {
+ bool show_screens = false;
+ bool show_windows = false;
+ bool show_tabs = false;
+
+ for (auto& source_list : source_lists) {
+ switch (source_list->GetMediaListType()) {
+ case content::DesktopMediaID::TYPE_NONE:
+ break;
+ case content::DesktopMediaID::TYPE_SCREEN:
+ show_screens = true;
+ break;
+ case content::DesktopMediaID::TYPE_WINDOW:
+ show_windows = true;
+ break;
+ case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+ show_tabs = true;
+ break;
+ }
+ }
+ EXPECT_EQ(expectation_->expect_screens, show_screens);
+ EXPECT_EQ(expectation_->expect_windows, show_windows);
+ EXPECT_EQ(expectation_->expect_tabs, show_tabs);
+ EXPECT_EQ(expectation_->expect_audio, params.request_audio);
+ EXPECT_EQ(params.modality, ui::ModalType::MODAL_TYPE_CHILD);
+
+ if (!expectation_->cancelled) {
+ // Post a task to call the callback asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FakeDesktopMediaPicker::CallCallback,
+ weak_factory_.GetWeakPtr(), std::move(done_callback)));
+ } else {
+ // If we expect the dialog to be cancelled then store the callback to
+ // retain reference to the callback handler.
+ done_callback_ = std::move(done_callback);
+ }
+ }
+
+ private:
+ void CallCallback(DoneCallback done_callback) {
+ std::move(done_callback).Run(expectation_->selected_source);
+ }
+
+ FakeDesktopMediaPickerFactory::TestFlags* expectation_;
+ DoneCallback done_callback_;
+
+ base::WeakPtrFactory<FakeDesktopMediaPicker> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaPicker);
+};
+
+FakeDesktopMediaPickerFactory::FakeDesktopMediaPickerFactory() = default;
+
+FakeDesktopMediaPickerFactory::~FakeDesktopMediaPickerFactory() = default;
+
+void FakeDesktopMediaPickerFactory::SetTestFlags(TestFlags* test_flags,
+ int tests_count) {
+ test_flags_ = test_flags;
+ tests_count_ = tests_count;
+ current_test_ = 0;
+}
+
+std::unique_ptr<DesktopMediaPicker>
+FakeDesktopMediaPickerFactory::CreatePicker() {
+ EXPECT_LE(current_test_, tests_count_);
+ if (current_test_ >= tests_count_)
+ return std::unique_ptr<DesktopMediaPicker>();
+ ++current_test_;
+ return std::unique_ptr<DesktopMediaPicker>(
+ new FakeDesktopMediaPicker(test_flags_ + current_test_ - 1));
+}
+
+std::vector<std::unique_ptr<DesktopMediaList>>
+FakeDesktopMediaPickerFactory::CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) {
+ EXPECT_LE(current_test_, tests_count_);
+ std::vector<std::unique_ptr<DesktopMediaList>> media_lists;
+ for (auto source_type : types)
+ media_lists.emplace_back(new FakeDesktopMediaList(source_type));
+ return media_lists;
+}
diff --git a/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
new file mode 100644
index 00000000000..57ace04ddcc
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
@@ -0,0 +1,53 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+
+// Used in tests to supply fake picker.
+class FakeDesktopMediaPickerFactory : public DesktopMediaPickerFactory {
+ public:
+ struct TestFlags {
+ bool expect_screens = false;
+ bool expect_windows = false;
+ bool expect_tabs = false;
+ bool expect_audio = false;
+ content::DesktopMediaID selected_source;
+ bool cancelled = false;
+
+ // Following flags are set by FakeDesktopMediaPicker when it's created and
+ // deleted.
+ bool picker_created = false;
+ bool picker_deleted = false;
+ };
+
+ FakeDesktopMediaPickerFactory();
+ ~FakeDesktopMediaPickerFactory() override;
+
+ // |test_flags| are expected to outlive the factory.
+ void SetTestFlags(TestFlags* test_flags, int tests_count);
+
+ // DesktopMediaPickerFactory implementation
+ std::unique_ptr<DesktopMediaPicker> CreatePicker() override;
+ std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
+ const std::vector<content::DesktopMediaID::Type>& types) override;
+
+ private:
+ TestFlags* test_flags_;
+ int tests_count_;
+ int current_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDesktopMediaPickerFactory);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_FAKE_DESKTOP_MEDIA_PICKER_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h b/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h
new file mode 100644
index 00000000000..cd5c578d604
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_authorization_wrapper_mac.h
@@ -0,0 +1,26 @@
+// Copyright 2019 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 CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
+
+#import <Foundation/NSString.h>
+
+#include "base/callback_forward.h"
+
+namespace system_media_permissions {
+
+class MediaAuthorizationWrapper {
+ public:
+ virtual ~MediaAuthorizationWrapper() {}
+
+ virtual NSInteger AuthorizationStatusForMediaType(NSString* media_type) = 0;
+ virtual void RequestAccessForMediaType(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) = 0;
+};
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_AUTHORIZATION_WRAPPER_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
new file mode 100644
index 00000000000..954ce697768
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -0,0 +1,493 @@
+// 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 "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/media_capture_devices.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/buildflags/buildflags.h"
+#include "extensions/common/constants.h"
+#include "media/base/media_switches.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+#include "ash/shell.h"
+#include "chrome/browser/media/chromeos_login_media_access_handler.h"
+#include "chrome/browser/media/public_session_media_access_handler.h"
+#include "chrome/browser/media/public_session_tab_capture_access_handler.h"
+#endif // defined(OS_CHROMEOS)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/media/extension_media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
+#include "chrome/browser/media/webrtc/tab_capture_access_handler.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+using blink::MediaStreamDevices;
+using content::BrowserThread;
+using content::MediaCaptureDevices;
+
+namespace {
+
+// Finds a device in |devices| that has |device_id|, or NULL if not found.
+const blink::MediaStreamDevice* FindDeviceWithId(
+ const blink::MediaStreamDevices& devices,
+ const std::string& device_id) {
+ auto iter = devices.begin();
+ for (; iter != devices.end(); ++iter) {
+ if (iter->id == device_id) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
+content::WebContents* WebContentsFromIds(int render_process_id,
+ int render_frame_id) {
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id));
+ return web_contents;
+}
+
+} // namespace
+
+MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
+ return base::Singleton<MediaCaptureDevicesDispatcher>::get();
+}
+
+MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
+ : is_device_enumeration_disabled_(false),
+ media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if !defined(OS_ANDROID)
+ media_access_handlers_.push_back(
+ std::make_unique<DisplayMediaAccessHandler>());
+#endif // defined(OS_ANDROID)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#if defined(OS_CHROMEOS)
+ media_access_handlers_.push_back(
+ std::make_unique<ChromeOSLoginMediaAccessHandler>());
+ // Wrapper around ExtensionMediaAccessHandler used in Public Sessions.
+ media_access_handlers_.push_back(
+ std::make_unique<PublicSessionMediaAccessHandler>());
+#else
+ media_access_handlers_.push_back(
+ std::make_unique<ExtensionMediaAccessHandler>());
+#endif
+ media_access_handlers_.push_back(
+ std::make_unique<DesktopCaptureAccessHandler>());
+#if defined(OS_CHROMEOS)
+ // Wrapper around TabCaptureAccessHandler used in Public Sessions.
+ media_access_handlers_.push_back(
+ std::make_unique<PublicSessionTabCaptureAccessHandler>());
+#else
+ media_access_handlers_.push_back(std::make_unique<TabCaptureAccessHandler>());
+#endif
+#endif
+ media_access_handlers_.push_back(
+ std::make_unique<PermissionBubbleMediaAccessHandler>());
+}
+
+MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
+
+void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterStringPref(prefs::kDefaultAudioCaptureDevice,
+ std::string());
+ registry->RegisterStringPref(prefs::kDefaultVideoCaptureDevice,
+ std::string());
+}
+
+bool MediaCaptureDevicesDispatcher::IsOriginForCasting(const GURL& origin) {
+ // Whitelisted tab casting extensions.
+ return
+ // Media Router Dev
+ origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
+ // Media Router Stable
+ origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/";
+}
+
+void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!observers_.HasObserver(observer))
+ observers_.AddObserver(observer);
+}
+
+void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ observers_.RemoveObserver(observer);
+}
+
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
+ return test_audio_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
+}
+
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
+ return test_video_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
+}
+
+void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // Kill switch for getDisplayMedia() on browser side to prevent renderer from
+ // bypassing blink side checks.
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE &&
+ !base::FeatureList::IsEnabled(blink::features::kRTCGetDisplayMedia)) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
+ return;
+ }
+
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(web_contents, request.video_type,
+ extension) ||
+ handler->SupportsStreamType(web_contents, request.audio_type,
+ extension)) {
+ handler->HandleRequest(web_contents, request, std::move(callback),
+ extension);
+ return;
+ }
+ }
+ std::move(callback).Run(blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
+ nullptr);
+}
+
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return CheckMediaAccessPermission(render_frame_host, security_origin, type,
+ nullptr);
+}
+
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(
+ content::WebContents::FromRenderFrameHost(render_frame_host), type,
+ extension)) {
+ return handler->CheckMediaAccessPermission(
+ render_frame_host, security_origin, type, extension);
+ }
+ }
+ return false;
+}
+
+void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
+ Profile* profile,
+ bool audio,
+ bool video,
+ blink::MediaStreamDevices* devices) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(audio || video);
+
+ PrefService* prefs = profile->GetPrefs();
+ std::string default_device;
+ if (audio) {
+ default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+ const blink::MediaStreamDevice* device =
+ GetRequestedAudioDevice(default_device);
+ if (!device)
+ device = GetFirstAvailableAudioDevice();
+ if (device)
+ devices->push_back(*device);
+ }
+
+ if (video) {
+ default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+ const blink::MediaStreamDevice* device =
+ GetRequestedVideoDevice(default_device);
+ if (!device)
+ device = GetFirstAvailableVideoDevice();
+ if (device)
+ devices->push_back(*device);
+ }
+}
+
+std::string MediaCaptureDevicesDispatcher::GetDefaultDeviceIDForProfile(
+ Profile* profile,
+ blink::mojom::MediaStreamType type) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ PrefService* prefs = profile->GetPrefs();
+ if (type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE)
+ return prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+ else if (type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE)
+ return prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+ else
+ return std::string();
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
+ const std::string& requested_audio_device_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+ const blink::MediaStreamDevice* const device =
+ FindDeviceWithId(audio_devices, requested_audio_device_id);
+ return device;
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+ if (audio_devices.empty())
+ return NULL;
+ return &(*audio_devices.begin());
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
+ const std::string& requested_video_device_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+ const blink::MediaStreamDevice* const device =
+ FindDeviceWithId(video_devices, requested_video_device_id);
+ return device;
+}
+
+const blink::MediaStreamDevice*
+MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+ if (video_devices.empty())
+ return NULL;
+ return &(*video_devices.begin());
+}
+
+void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
+ is_device_enumeration_disabled_ = true;
+}
+
+scoped_refptr<MediaStreamCaptureIndicator>
+MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
+ return media_stream_capture_indicator_;
+}
+
+void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
+ base::Unretained(this)));
+}
+
+void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
+ base::Unretained(this)));
+}
+
+void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, security_origin, stream_type, state));
+}
+
+void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) {
+ // TODO(https://crbug.com/837606): Figure out how to simplify threading here.
+ // Currently, this will either always be called on the UI thread, or always
+ // on the IO thread, depending on how far along the work to migrate to the
+ // audio service has progressed. The rest of the methods of the
+ // content::MediaObserver are always called on the IO thread.
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ OnCreatingAudioStreamOnUIThread(render_process_id, render_frame_id);
+ return;
+ }
+
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
+ base::Unretained(this), render_process_id, render_frame_id));
+}
+
+void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetAudioCaptureDevices();
+ for (auto& observer : observers_)
+ observer.OnUpdateAudioDevices(devices);
+}
+
+void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetVideoCaptureDevices();
+ for (auto& observer : observers_)
+ observer.OnUpdateVideoDevices(devices);
+}
+
+void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->SupportsStreamType(
+ WebContentsFromIds(render_process_id, render_frame_id), stream_type,
+ nullptr)) {
+ handler->UpdateMediaRequestState(render_process_id, render_frame_id,
+ page_request_id, stream_type, state);
+ break;
+ }
+ }
+
+#if defined(OS_CHROMEOS)
+ if (IsOriginForCasting(security_origin) &&
+ blink::IsVideoInputMediaType(stream_type)) {
+ // Notify ash that casting state has changed.
+ if (state == content::MEDIA_REQUEST_STATE_DONE) {
+ ash::Shell::Get()->OnCastingSessionStartedOrStopped(true);
+ } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ ash::Shell::Get()->OnCastingSessionStartedOrStopped(false);
+ }
+ }
+#endif
+
+ for (auto& observer : observers_) {
+ observer.OnRequestUpdate(render_process_id, render_frame_id, stream_type,
+ state);
+ }
+}
+
+void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
+ int render_process_id,
+ int render_frame_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (auto& observer : observers_)
+ observer.OnCreatingAudioStream(render_process_id, render_frame_id);
+}
+
+bool MediaCaptureDevicesDispatcher::IsInsecureCapturingInProgress(
+ int render_process_id,
+ int render_frame_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ for (const auto& handler : media_access_handlers_) {
+ if (handler->IsInsecureCapturingInProgress(render_process_id,
+ render_frame_id))
+ return true;
+ }
+ return false;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_audio_devices_ = devices;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_video_devices_ = devices;
+}
+
+void MediaCaptureDevicesDispatcher::OnSetCapturingLinkSecured(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ if (!blink::IsVideoScreenCaptureMediaType(stream_type))
+ return;
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &MediaCaptureDevicesDispatcher::UpdateVideoScreenCaptureStatus,
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, stream_type, is_secure));
+}
+
+void MediaCaptureDevicesDispatcher::UpdateVideoScreenCaptureStatus(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(blink::IsVideoScreenCaptureMediaType(stream_type));
+
+ for (const auto& handler : media_access_handlers_) {
+ handler->UpdateVideoScreenCaptureStatus(render_process_id, render_frame_id,
+ page_request_id, is_secure);
+ break;
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
new file mode 100644
index 00000000000..51f9d9db2b3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
@@ -0,0 +1,209 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "content/public/browser/media_observer.h"
+#include "content/public/browser/media_stream_request.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+class MediaAccessHandler;
+class MediaStreamCaptureIndicator;
+class Profile;
+
+namespace extensions {
+class Extension;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// This singleton is used to receive updates about media events from the content
+// layer.
+class MediaCaptureDevicesDispatcher : public content::MediaObserver {
+ public:
+ class Observer {
+ public:
+ // Handle an information update consisting of a up-to-date audio capture
+ // device lists. This happens when a microphone is plugged in or unplugged.
+ virtual void OnUpdateAudioDevices(
+ const blink::MediaStreamDevices& devices) {}
+
+ // Handle an information update consisting of a up-to-date video capture
+ // device lists. This happens when a camera is plugged in or unplugged.
+ virtual void OnUpdateVideoDevices(
+ const blink::MediaStreamDevices& devices) {}
+
+ // Handle an information update related to a media stream request.
+ virtual void OnRequestUpdate(int render_process_id,
+ int render_frame_id,
+ blink::mojom::MediaStreamType stream_type,
+ const content::MediaRequestState state) {}
+
+ // Handle an information update that a new stream is being created.
+ virtual void OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) {}
+
+ virtual ~Observer() {}
+ };
+
+ static MediaCaptureDevicesDispatcher* GetInstance();
+
+ // Registers the preferences related to Media Stream default devices.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Returns true if the security origin is associated with casting.
+ static bool IsOriginForCasting(const GURL& origin);
+
+ // Methods for observers. Called on UI thread.
+ // Observers should add themselves on construction and remove themselves
+ // on destruction.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+ const blink::MediaStreamDevices& GetAudioCaptureDevices();
+ const blink::MediaStreamDevices& GetVideoCaptureDevices();
+
+ // Method called from WebCapturerDelegate implementations to process access
+ // requests. |extension| is set to NULL if request was made from a drive-by
+ // page.
+ void ProcessMediaAccessRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension);
+
+ // Method called from WebCapturerDelegate implementations to check media
+ // access permission. Note that this does not query the user.
+ bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type);
+
+ // Same as above but for an |extension|, which may not be NULL.
+ bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension);
+
+ // Helper to get the default devices which can be used by the media request.
+ // Uses the first available devices if the default devices are not available.
+ // If the return list is empty, it means there is no available device on the
+ // OS.
+ // Called on the UI thread.
+ void GetDefaultDevicesForProfile(Profile* profile,
+ bool audio,
+ bool video,
+ blink::MediaStreamDevices* devices);
+
+ // Helper to get default device IDs. If the returned value is an empty string,
+ // it means that there is no default device for the given device |type|. The
+ // only supported |type| values are
+ // blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE and
+ // blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE.
+ // Must be called on the UI thread.
+ std::string GetDefaultDeviceIDForProfile(Profile* profile,
+ blink::mojom::MediaStreamType type);
+
+ // Helpers for picking particular requested devices, identified by raw id.
+ // If the device requested is not available it will return NULL.
+ const blink::MediaStreamDevice* GetRequestedAudioDevice(
+ const std::string& requested_audio_device_id);
+ const blink::MediaStreamDevice* GetRequestedVideoDevice(
+ const std::string& requested_video_device_id);
+
+ // Returns the first available audio or video device, or NULL if no devices
+ // are available.
+ const blink::MediaStreamDevice* GetFirstAvailableAudioDevice();
+ const blink::MediaStreamDevice* GetFirstAvailableVideoDevice();
+
+ // Unittests that do not require actual device enumeration should call this
+ // API on the singleton. It is safe to call this multiple times on the
+ // signleton.
+ void DisableDeviceEnumerationForTesting();
+
+ // Overridden from content::MediaObserver:
+ void OnAudioCaptureDevicesChanged() override;
+ void OnVideoCaptureDevicesChanged() override;
+ void OnMediaRequestStateChanged(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+ void OnCreatingAudioStream(int render_process_id,
+ int render_frame_id) override;
+ void OnSetCapturingLinkSecured(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure) override;
+
+ scoped_refptr<MediaStreamCaptureIndicator> GetMediaStreamCaptureIndicator();
+
+ // Return true if there is any ongoing insecured capturing. The capturing is
+ // deemed secure if all connected video sinks are reported secure and the
+ // extension is trusted.
+ bool IsInsecureCapturingInProgress(int render_process_id,
+ int render_frame_id);
+
+ // Only for testing.
+ void SetTestAudioCaptureDevices(const blink::MediaStreamDevices& devices);
+ void SetTestVideoCaptureDevices(const blink::MediaStreamDevices& devices);
+
+ private:
+ friend struct base::DefaultSingletonTraits<MediaCaptureDevicesDispatcher>;
+
+ MediaCaptureDevicesDispatcher();
+ ~MediaCaptureDevicesDispatcher() override;
+
+ // Called by the MediaObserver() functions, executed on UI thread.
+ void NotifyAudioDevicesChangedOnUIThread();
+ void NotifyVideoDevicesChangedOnUIThread();
+ void UpdateMediaRequestStateOnUIThread(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state);
+ void OnCreatingAudioStreamOnUIThread(int render_process_id,
+ int render_frame_id);
+ void UpdateVideoScreenCaptureStatus(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ bool is_secure);
+
+ // Only for testing, a list of cached audio capture devices.
+ blink::MediaStreamDevices test_audio_devices_;
+
+ // Only for testing, a list of cached video capture devices.
+ blink::MediaStreamDevices test_video_devices_;
+
+ // A list of observers for the device update notifications.
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ // Flag used by unittests to disable device enumeration.
+ bool is_device_enumeration_disabled_;
+
+ scoped_refptr<MediaStreamCaptureIndicator> media_stream_capture_indicator_;
+
+ // Handlers for processing media access requests.
+ std::vector<std::unique_ptr<MediaAccessHandler>> media_access_handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaCaptureDevicesDispatcher);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
new file mode 100644
index 00000000000..8cda295cb72
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
@@ -0,0 +1,470 @@
+// 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 "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
+#include "chrome/browser/status_icons/status_icon.h"
+#include "chrome/browser/status_icons/status_tray.h"
+#include "chrome/browser/tab_contents/tab_util.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ui/gfx/image/image_skia.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/grit/chromium_strings.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#endif
+
+using content::BrowserThread;
+using content::WebContents;
+
+namespace {
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+const extensions::Extension* GetExtension(WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!web_contents)
+ return NULL;
+
+ extensions::ExtensionRegistry* registry =
+ extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext());
+ return registry->enabled_extensions().GetExtensionOrAppByURL(
+ web_contents->GetURL());
+}
+
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+
+base::string16 GetTitle(WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!web_contents)
+ return base::string16();
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ const extensions::Extension* const extension = GetExtension(web_contents);
+ if (extension)
+ return base::UTF8ToUTF16(extension->name());
+#endif
+
+ return url_formatter::FormatUrlForSecurityDisplay(web_contents->GetURL());
+}
+
+} // namespace
+
+// Stores usage counts for all the capture devices associated with a single
+// WebContents instance. Instances of this class are owned by
+// MediaStreamCaptureIndicator. They also observe for the destruction of their
+// corresponding WebContents and trigger their own deletion from their
+// MediaStreamCaptureIndicator.
+class MediaStreamCaptureIndicator::WebContentsDeviceUsage
+ : public content::WebContentsObserver {
+ public:
+ WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,
+ WebContents* web_contents)
+ : WebContentsObserver(web_contents), indicator_(std::move(indicator)) {}
+
+ bool IsCapturingAudio() const { return audio_stream_count_ > 0; }
+ bool IsCapturingVideo() const { return video_stream_count_ > 0; }
+ bool IsMirroring() const { return mirroring_stream_count_ > 0; }
+ bool IsCapturingDesktop() const { return desktop_stream_count_ > 0; }
+
+ std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui);
+
+ // Increment ref-counts up based on the type of each device provided.
+ void AddDevices(const blink::MediaStreamDevices& devices,
+ base::OnceClosure stop_callback);
+
+ // Decrement ref-counts up based on the type of each device provided.
+ void RemoveDevices(const blink::MediaStreamDevices& devices);
+
+ // Helper to call |stop_callback_|.
+ void NotifyStopped();
+
+ private:
+ int& GetStreamCount(blink::mojom::MediaStreamType type);
+
+ // content::WebContentsObserver overrides.
+ void WebContentsDestroyed() override {
+ indicator_->UnregisterWebContents(web_contents());
+ }
+
+ scoped_refptr<MediaStreamCaptureIndicator> indicator_;
+ int audio_stream_count_ = 0;
+ int video_stream_count_ = 0;
+ int mirroring_stream_count_ = 0;
+ int desktop_stream_count_ = 0;
+
+ base::OnceClosure stop_callback_;
+ base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
+};
+
+// Implements MediaStreamUI interface. Instances of this class are created for
+// each MediaStream and their ownership is passed to MediaStream implementation
+// in the content layer. Each UIDelegate keeps a weak pointer to the
+// corresponding WebContentsDeviceUsage object to deliver updates about state of
+// the stream.
+class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI {
+ public:
+ UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<::MediaStreamUI> ui)
+ : device_usage_(device_usage), devices_(devices), ui_(std::move(ui)) {
+ DCHECK(!devices_.empty());
+ }
+
+ ~UIDelegate() override {
+ if (started_ && device_usage_)
+ device_usage_->RemoveDevices(devices_);
+ }
+
+ private:
+ // content::MediaStreamUI interface.
+ gfx::NativeViewId OnStarted(
+ base::OnceClosure stop_callback,
+ content::MediaStreamUI::SourceCallback source_callback) override {
+ DCHECK(!started_);
+ started_ = true;
+
+ if (device_usage_) {
+ // |device_usage_| handles |stop_callback| when |ui_| is unspecified.
+ device_usage_->AddDevices(
+ devices_, ui_ ? base::OnceClosure() : std::move(stop_callback));
+ }
+
+ // If a custom |ui_| is specified, notify it that the stream started and let
+ // it handle the |stop_callback| and |source_callback|.
+ if (ui_)
+ return ui_->OnStarted(std::move(stop_callback),
+ std::move(source_callback));
+
+ return 0;
+ }
+
+ base::WeakPtr<WebContentsDeviceUsage> device_usage_;
+ const blink::MediaStreamDevices devices_;
+ const std::unique_ptr<::MediaStreamUI> ui_;
+ bool started_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(UIDelegate);
+};
+
+std::unique_ptr<content::MediaStreamUI>
+MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui) {
+ return std::make_unique<UIDelegate>(weak_factory_.GetWeakPtr(), devices,
+ std::move(ui));
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
+ const blink::MediaStreamDevices& devices,
+ base::OnceClosure stop_callback) {
+ for (const auto& device : devices)
+ ++GetStreamCount(device.type);
+
+ if (web_contents()) {
+ stop_callback_ = std::move(stop_callback);
+ web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+ }
+
+ indicator_->UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
+ const blink::MediaStreamDevices& devices) {
+ for (const auto& device : devices) {
+ int& stream_count = GetStreamCount(device.type);
+ --stream_count;
+ DCHECK_GE(stream_count, 0);
+ }
+
+ if (web_contents()) {
+ web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents());
+ }
+
+ indicator_->UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::WebContentsDeviceUsage::NotifyStopped() {
+ if (stop_callback_)
+ std::move(stop_callback_).Run();
+}
+
+int& MediaStreamCaptureIndicator::WebContentsDeviceUsage::GetStreamCount(
+ blink::mojom::MediaStreamType type) {
+ switch (type) {
+ case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
+ return audio_stream_count_;
+
+ case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
+ return video_stream_count_;
+
+ case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
+ case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
+ return mirroring_stream_count_;
+
+ case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
+ case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
+ case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
+ case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
+ return desktop_stream_count_;
+
+ case blink::mojom::MediaStreamType::NO_SERVICE:
+ case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
+ NOTREACHED();
+ return video_stream_count_;
+ }
+}
+
+MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() {}
+
+MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
+ // The user is responsible for cleaning up by reporting the closure of any
+ // opened devices. However, there exists a race condition at shutdown: The UI
+ // thread may be stopped before CaptureDevicesClosed() posts the task to
+ // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
+ // empty like it should.
+ DCHECK(usage_map_.empty() ||
+ !BrowserThread::IsThreadInitialized(BrowserThread::UI));
+}
+
+std::unique_ptr<content::MediaStreamUI>
+MediaStreamCaptureIndicator::RegisterMediaStream(
+ content::WebContents* web_contents,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui) {
+ auto& usage = usage_map_[web_contents];
+ if (!usage)
+ usage = std::make_unique<WebContentsDeviceUsage>(this, web_contents);
+
+ return usage->RegisterMediaStream(devices, std::move(ui));
+}
+
+void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
+ int event_flags) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const int index =
+ command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
+ DCHECK_LE(0, index);
+ DCHECK_GT(static_cast<int>(command_targets_.size()), index);
+ WebContents* web_contents = command_targets_[index];
+ if (base::Contains(usage_map_, web_contents))
+ web_contents->GetDelegate()->ActivateContents(web_contents);
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() &&
+ (it->second->IsCapturingAudio() || it->second->IsCapturingVideo());
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingVideo(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingVideo();
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingAudio(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingAudio();
+}
+
+bool MediaStreamCaptureIndicator::IsBeingMirrored(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsMirroring();
+}
+
+bool MediaStreamCaptureIndicator::IsCapturingDesktop(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ return it != usage_map_.end() && it->second->IsCapturingDesktop();
+}
+
+void MediaStreamCaptureIndicator::NotifyStopped(
+ content::WebContents* web_contents) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = usage_map_.find(web_contents);
+ DCHECK(it != usage_map_.end());
+ it->second->NotifyStopped();
+}
+
+void MediaStreamCaptureIndicator::UnregisterWebContents(
+ WebContents* web_contents) {
+ usage_map_.erase(web_contents);
+ UpdateNotificationUserInterface();
+}
+
+void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
+ bool video) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (status_icon_)
+ return;
+
+ // If there is no browser process, we should not create the status tray.
+ if (!g_browser_process)
+ return;
+
+ StatusTray* status_tray = g_browser_process->status_tray();
+ if (!status_tray)
+ return;
+
+ gfx::ImageSkia image;
+ base::string16 tool_tip;
+ GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
+ DCHECK(!image.isNull());
+ DCHECK(!tool_tip.empty());
+
+ status_icon_ = status_tray->CreateStatusIcon(
+ StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
+}
+
+void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!status_icon_)
+ return;
+
+ // If there is no browser process, we should not do anything.
+ if (!g_browser_process)
+ return;
+
+ StatusTray* status_tray = g_browser_process->status_tray();
+ if (status_tray != NULL) {
+ status_tray->RemoveStatusIcon(status_icon_);
+ status_icon_ = NULL;
+ }
+}
+
+void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
+ bool audio = false;
+ bool video = false;
+ int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
+ command_targets_.clear();
+
+ for (const auto& it : usage_map_) {
+ // Check if any audio and video devices have been used.
+ const WebContentsDeviceUsage& usage = *it.second;
+ if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
+ continue;
+
+ WebContents* const web_contents = it.first;
+
+ // The audio/video icon is shown only for non-whitelisted extensions or on
+ // Android. For regular tabs on desktop, we show an indicator in the tab
+ // icon.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ const extensions::Extension* extension = GetExtension(web_contents);
+ if (!extension)
+ continue;
+#endif
+
+ audio = audio || usage.IsCapturingAudio();
+ video = video || usage.IsCapturingVideo();
+
+ command_targets_.push_back(web_contents);
+ menu->AddItem(command_id, GetTitle(web_contents));
+
+ // If the menu item is not a label, enable it.
+ menu->SetCommandIdEnabled(command_id, command_id != IDC_MinimumLabelValue);
+
+ // If reaching the maximum number, no more item will be added to the menu.
+ if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
+ break;
+ ++command_id;
+ }
+
+ if (command_targets_.empty()) {
+ MaybeDestroyStatusTrayIcon();
+ return;
+ }
+
+ // The icon will take the ownership of the passed context menu.
+ MaybeCreateStatusTrayIcon(audio, video);
+ if (status_icon_) {
+ status_icon_->SetContextMenu(std::move(menu));
+ }
+}
+
+void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
+ bool audio,
+ bool video,
+ gfx::ImageSkia* image,
+ base::string16* tool_tip) {
+#if defined(OS_ANDROID)
+ NOTREACHED();
+#else // !defined(OS_ANDROID)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(audio || video);
+ DCHECK(image);
+ DCHECK(tool_tip);
+
+ int message_id = 0;
+ const gfx::VectorIcon* icon = nullptr;
+ if (audio && video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
+ icon = &vector_icons::kVideocamIcon;
+ } else if (audio && !video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
+ icon = &vector_icons::kMicIcon;
+ } else if (!audio && video) {
+ message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
+ icon = &vector_icons::kVideocamIcon;
+ }
+
+ *tool_tip = l10n_util::GetStringUTF16(message_id);
+ *image = gfx::CreateVectorIcon(*icon, 16, gfx::kChromeIconGrey);
+#endif // !defined(OS_ANDROID)
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h
new file mode 100644
index 00000000000..29591788429
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_capture_indicator.h
@@ -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.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/status_icons/status_icon_menu_model.h"
+#include "content/public/browser/media_stream_request.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace gfx {
+class ImageSkia;
+} // namespace gfx
+
+class StatusIcon;
+
+// Interface to display custom UI during stream capture.
+class MediaStreamUI {
+ public:
+ // Called when stream capture is stopped.
+ virtual ~MediaStreamUI() = default;
+
+ // Called when stream capture starts.
+ // |stop_callback| is a callback to stop the stream.
+ // |source_callback| is a callback to change the desktop capture source.
+ // Returns the platform-dependent window ID for the UI, or 0 if not
+ // applicable.
+ virtual gfx::NativeViewId OnStarted(
+ base::OnceClosure stop_callback,
+ content::MediaStreamUI::SourceCallback source_callback) = 0;
+};
+
+// Keeps track of which WebContents are capturing media streams. Used to display
+// indicators (e.g. in the tab strip, via notifications) and to make resource
+// allocation decisions (e.g. WebContents capturing streams are not discarded).
+//
+// Owned by MediaCaptureDevicesDispatcher, which is a singleton.
+class MediaStreamCaptureIndicator
+ : public base::RefCountedThreadSafe<MediaStreamCaptureIndicator>,
+ public StatusIconMenuModel::Delegate {
+ public:
+ MediaStreamCaptureIndicator();
+
+ // Registers a new media stream for |web_contents| and returns an object used
+ // by the content layer to notify about the state of the stream. Optionally,
+ // |ui| is used to display custom UI while the stream is captured.
+ std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
+ content::WebContents* web_contents,
+ const blink::MediaStreamDevices& devices,
+ std::unique_ptr<MediaStreamUI> ui = nullptr);
+
+ // Overrides from StatusIconMenuModel::Delegate implementation.
+ void ExecuteCommand(int command_id, int event_flags) override;
+
+ // Returns true if |web_contents| is capturing user media (e.g., webcam or
+ // microphone input).
+ bool IsCapturingUserMedia(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing video (e.g., webcam).
+ bool IsCapturingVideo(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing audio (e.g., microphone).
+ bool IsCapturingAudio(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| itself is being mirrored (e.g., a source of
+ // media for remote broadcast).
+ bool IsBeingMirrored(content::WebContents* web_contents) const;
+
+ // Returns true if |web_contents| is capturing the desktop (screen, window,
+ // audio).
+ bool IsCapturingDesktop(content::WebContents* web_contents) const;
+
+ // Called when STOP button in media capture notification is clicked.
+ void NotifyStopped(content::WebContents* web_contents) const;
+
+ private:
+ class UIDelegate;
+ class WebContentsDeviceUsage;
+ friend class WebContentsDeviceUsage;
+
+ friend class base::RefCountedThreadSafe<MediaStreamCaptureIndicator>;
+ ~MediaStreamCaptureIndicator() override;
+
+ // Following functions/variables are executed/accessed only on UI thread.
+
+ // Called by WebContentsDeviceUsage when it's about to destroy itself, i.e.
+ // when WebContents is being destroyed.
+ void UnregisterWebContents(content::WebContents* web_contents);
+
+ // Updates the status tray menu. Called by WebContentsDeviceUsage.
+ void UpdateNotificationUserInterface();
+
+ // Helpers to create and destroy status tray icon. Called from
+ // UpdateNotificationUserInterface().
+ void EnsureStatusTrayIconResources();
+ void MaybeCreateStatusTrayIcon(bool audio, bool video);
+ void MaybeDestroyStatusTrayIcon();
+
+ // Gets the status icon image and the string to use as the tooltip.
+ void GetStatusTrayIconInfo(bool audio,
+ bool video,
+ gfx::ImageSkia* image,
+ base::string16* tool_tip);
+
+ // Reference to our status icon - owned by the StatusTray. If null,
+ // the platform doesn't support status icons.
+ StatusIcon* status_icon_ = nullptr;
+
+ // A map that contains the usage counts of the opened capture devices for each
+ // WebContents instance.
+ std::unordered_map<content::WebContents*,
+ std::unique_ptr<WebContentsDeviceUsage>>
+ usage_map_;
+
+ // A vector which maps command IDs to their associated WebContents
+ // instance. This is rebuilt each time the status tray icon context menu is
+ // updated.
+ typedef std::vector<content::WebContents*> CommandTargets;
+ CommandTargets command_targets_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamCaptureIndicator);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_CAPTURE_INDICATOR_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
new file mode 100644
index 00000000000..b64eea23ca6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
@@ -0,0 +1,101 @@
+// Copyright 2015 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 "chrome/browser/media/webrtc/media_stream_device_permission_context.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+
+namespace {
+
+blink::mojom::FeaturePolicyFeature GetFeaturePolicyFeature(
+ ContentSettingsType type) {
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ return blink::mojom::FeaturePolicyFeature::kMicrophone;
+
+ DCHECK_EQ(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, type);
+ return blink::mojom::FeaturePolicyFeature::kCamera;
+}
+
+} // namespace
+
+MediaStreamDevicePermissionContext::MediaStreamDevicePermissionContext(
+ Profile* profile,
+ const ContentSettingsType content_settings_type)
+ : PermissionContextBase(profile,
+ content_settings_type,
+ GetFeaturePolicyFeature(content_settings_type)),
+ content_settings_type_(content_settings_type) {
+ DCHECK(content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
+ content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
+
+MediaStreamDevicePermissionContext::~MediaStreamDevicePermissionContext() {}
+
+void MediaStreamDevicePermissionContext::DecidePermission(
+ content::WebContents* web_contents,
+ const PermissionRequestID& id,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin,
+ bool user_gesture,
+ BrowserPermissionCallback callback) {
+ PermissionContextBase::DecidePermission(web_contents, id, requesting_origin,
+ embedding_origin, user_gesture,
+ std::move(callback));
+}
+
+ContentSetting MediaStreamDevicePermissionContext::GetPermissionStatusInternal(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) const {
+ // TODO(raymes): Merge this policy check into content settings
+ // crbug.com/244389.
+ const char* policy_name = nullptr;
+ const char* urls_policy_name = nullptr;
+ if (content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
+ policy_name = prefs::kAudioCaptureAllowed;
+ urls_policy_name = prefs::kAudioCaptureAllowedUrls;
+ } else {
+ DCHECK(content_settings_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ policy_name = prefs::kVideoCaptureAllowed;
+ urls_policy_name = prefs::kVideoCaptureAllowedUrls;
+ }
+
+ MediaStreamDevicePolicy policy = GetDevicePolicy(
+ profile(), requesting_origin, policy_name, urls_policy_name);
+
+ switch (policy) {
+ case ALWAYS_DENY:
+ return CONTENT_SETTING_BLOCK;
+ case ALWAYS_ALLOW:
+ return CONTENT_SETTING_ALLOW;
+ default:
+ DCHECK_EQ(POLICY_NOT_SET, policy);
+ }
+
+ // Check the content setting. TODO(raymes): currently mic/camera permission
+ // doesn't consider the embedder.
+ ContentSetting setting = PermissionContextBase::GetPermissionStatusInternal(
+ render_frame_host, requesting_origin, requesting_origin);
+
+ if (setting == CONTENT_SETTING_DEFAULT)
+ setting = CONTENT_SETTING_ASK;
+
+ return setting;
+}
+
+void MediaStreamDevicePermissionContext::ResetPermission(
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) {
+ NOTREACHED() << "ResetPermission is not implemented";
+}
+
+bool MediaStreamDevicePermissionContext::IsRestrictedToSecureOrigins() const {
+ return true;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h
new file mode 100644
index 00000000000..9a1983462d9
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context.h
@@ -0,0 +1,46 @@
+// Copyright 2015 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 CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
+
+#include "base/macros.h"
+#include "chrome/browser/permissions/permission_context_base.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+
+// Common class which handles the mic and camera permissions.
+class MediaStreamDevicePermissionContext : public PermissionContextBase {
+ public:
+ MediaStreamDevicePermissionContext(Profile* profile,
+ ContentSettingsType content_settings_type);
+ ~MediaStreamDevicePermissionContext() override;
+
+ // PermissionContextBase:
+ void DecidePermission(content::WebContents* web_contents,
+ const PermissionRequestID& id,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin,
+ bool user_gesture,
+ BrowserPermissionCallback callback) override;
+
+ // TODO(xhwang): GURL.GetOrigin() shouldn't be used as the origin. Need to
+ // refactor to use url::Origin. crbug.com/527149 is filed for this.
+ ContentSetting GetPermissionStatusInternal(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& requesting_origin,
+ const GURL& embedding_origin) const override;
+
+ void ResetPermission(const GURL& requesting_origin,
+ const GURL& embedding_origin) override;
+
+ private:
+ // PermissionContextBase:
+ bool IsRestrictedToSecureOrigins() const override;
+
+ ContentSettingsType content_settings_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicePermissionContext);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSION_CONTEXT_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
new file mode 100644
index 00000000000..7dbaaf09cbb
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2015 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 "chrome/browser/media/webrtc/media_stream_device_permission_context.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/permissions/permission_request_id.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/permissions/permission_request_manager.h"
+#endif
+
+namespace {
+class TestPermissionContext : public MediaStreamDevicePermissionContext {
+ public:
+ TestPermissionContext(Profile* profile,
+ const ContentSettingsType content_settings_type)
+ : MediaStreamDevicePermissionContext(profile, content_settings_type) {}
+
+ ~TestPermissionContext() override {}
+};
+
+} // anonymous namespace
+
+// TODO(raymes): many tests in MediaStreamDevicesControllerTest should be
+// converted to tests in this file.
+class MediaStreamDevicePermissionContextTests
+ : public ChromeRenderViewHostTestHarness {
+ protected:
+ MediaStreamDevicePermissionContextTests() = default;
+
+ void TestInsecureQueryingUrl(ContentSettingsType content_settings_type) {
+ TestPermissionContext permission_context(profile(), content_settings_type);
+ GURL insecure_url("http://www.example.com");
+ GURL secure_url("https://www.example.com");
+
+ // Check that there is no saved content settings.
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(insecure_url.GetOrigin(),
+ insecure_url.GetOrigin(),
+ content_settings_type, std::string()));
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(secure_url.GetOrigin(),
+ insecure_url.GetOrigin(),
+ content_settings_type, std::string()));
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(insecure_url.GetOrigin(),
+ secure_url.GetOrigin(),
+ content_settings_type, std::string()));
+
+ EXPECT_EQ(CONTENT_SETTING_BLOCK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ insecure_url, insecure_url)
+ .content_setting);
+
+ EXPECT_EQ(CONTENT_SETTING_BLOCK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ insecure_url, secure_url)
+ .content_setting);
+ }
+
+ void TestSecureQueryingUrl(ContentSettingsType content_settings_type) {
+ TestPermissionContext permission_context(profile(), content_settings_type);
+ GURL secure_url("https://www.example.com");
+
+ // Check that there is no saved content settings.
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ HostContentSettingsMapFactory::GetForProfile(profile())
+ ->GetContentSetting(secure_url.GetOrigin(),
+ secure_url.GetOrigin(),
+ content_settings_type,
+ std::string()));
+
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ permission_context
+ .GetPermissionStatus(nullptr /* render_frame_host */,
+ secure_url, secure_url)
+ .content_setting);
+ }
+
+ private:
+ // ChromeRenderViewHostTestHarness:
+ void SetUp() override {
+ ChromeRenderViewHostTestHarness::SetUp();
+#if defined(OS_ANDROID)
+ InfoBarService::CreateForWebContents(web_contents());
+#else
+ PermissionRequestManager::CreateForWebContents(web_contents());
+#endif
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicePermissionContextTests);
+};
+
+// MEDIASTREAM_MIC permission status should be ask for insecure origin to
+// accommodate the usage case of Flash.
+TEST_F(MediaStreamDevicePermissionContextTests, TestMicInsecureQueryingUrl) {
+ TestInsecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+}
+
+// MEDIASTREAM_CAMERA permission status should be ask for insecure origin to
+// accommodate the usage case of Flash.
+TEST_F(MediaStreamDevicePermissionContextTests, TestCameraInsecureQueryingUrl) {
+ TestInsecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
+
+// MEDIASTREAM_MIC permission status should be ask for Secure origin.
+TEST_F(MediaStreamDevicePermissionContextTests, TestMicSecureQueryingUrl) {
+ TestSecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+}
+
+// MEDIASTREAM_CAMERA permission status should be ask for Secure origin.
+TEST_F(MediaStreamDevicePermissionContextTests, TestCameraSecureQueryingUrl) {
+ TestSecureQueryingUrl(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc
new file mode 100644
index 00000000000..a1aaed06c81
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+
+#include <stddef.h>
+
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/common/constants.h"
+#include "url/gurl.h"
+
+MediaStreamDevicePolicy GetDevicePolicy(const Profile* profile,
+ const GURL& security_origin,
+ const char* policy_name,
+ const char* whitelist_policy_name) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // If the security origin policy matches a value in the whitelist, allow it.
+ // Otherwise, check the |policy_name| master switch for the default behavior.
+
+ const PrefService* prefs = profile->GetPrefs();
+
+ const base::ListValue* list = prefs->GetList(whitelist_policy_name);
+ std::string value;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (list->GetString(i, &value)) {
+ ContentSettingsPattern pattern =
+ ContentSettingsPattern::FromString(value);
+ if (pattern == ContentSettingsPattern::Wildcard()) {
+ DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value;
+ continue;
+ }
+ DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value;
+ if (pattern.IsValid() && pattern.Matches(security_origin))
+ return ALWAYS_ALLOW;
+ }
+ }
+
+ // If a match was not found, check if audio capture is otherwise disallowed
+ // or if the user should be prompted. Setting the policy value to "true"
+ // is equal to not setting it at all, so from hereon out, we will return
+ // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access).
+ if (!prefs->GetBoolean(policy_name))
+ return ALWAYS_DENY;
+
+ return POLICY_NOT_SET;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h
new file mode 100644
index 00000000000..5bfbb9e26a5
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_device_permissions.h
@@ -0,0 +1,26 @@
+// Copyright 2014 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 CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
+
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+
+class GURL;
+class Profile;
+
+enum MediaStreamDevicePolicy {
+ POLICY_NOT_SET,
+ ALWAYS_DENY,
+ ALWAYS_ALLOW,
+};
+
+// Get the device policy for |security_origin| and |profile|.
+MediaStreamDevicePolicy GetDevicePolicy(const Profile* profile,
+ const GURL& security_origin,
+ const char* policy_name,
+ const char* whitelist_policy_name);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICE_PERMISSIONS_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc
new file mode 100644
index 00000000000..cddfd5b1b4d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.cc
@@ -0,0 +1,605 @@
+// 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 "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/permissions/permission_result.h"
+#include "chrome/browser/permissions/permission_uma_util.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/origin_util.h"
+#include "extensions/common/constants.h"
+#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/android/preferences/pref_service_bridge.h"
+#include "chrome/browser/permissions/permission_dialog_delegate.h"
+#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
+#include "ui/android/window_android.h"
+#else // !defined(OS_ANDROID)
+#include "components/vector_icons/vector_icons.h"
+#endif
+
+using content::BrowserThread;
+
+namespace {
+
+// Returns true if the given ContentSettingsType is being requested in
+// |request|.
+bool ContentTypeIsRequested(ContentSettingsType type,
+ const content::MediaStreamRequest& request) {
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ return request.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+
+ if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)
+ return request.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
+
+ return false;
+}
+
+bool HasAvailableDevices(ContentSettingsType content_type,
+ const std::string& device_id) {
+ const blink::MediaStreamDevices* devices = nullptr;
+ if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
+ devices =
+ &MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
+ } else if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
+ devices =
+ &MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
+ } else {
+ NOTREACHED();
+ }
+
+ // TODO(tommi): It's kind of strange to have this here since if we fail this
+ // test, there'll be a UI shown that indicates to the user that access to
+ // non-existing audio/video devices has been denied. The user won't have
+ // any way to change that but there will be a UI shown which indicates that
+ // access is blocked.
+ if (devices->empty())
+ return false;
+
+ // Note: we check device_id before dereferencing devices. If the requested
+ // device id is non-empty, then the corresponding device list must not be
+ // NULL.
+ if (!device_id.empty()) {
+ auto it = std::find_if(devices->begin(), devices->end(),
+ [device_id](const blink::MediaStreamDevice& device) {
+ return device.id == device_id;
+ });
+ if (it == devices->end())
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+// static
+void MediaStreamDevicesController::RequestPermissions(
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ request.render_process_id, request.render_frame_id);
+ // The RFH may have been destroyed by the time the request is processed.
+ if (!rfh) {
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ std::unique_ptr<content::MediaStreamUI>());
+ return;
+ }
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(rfh);
+ std::unique_ptr<MediaStreamDevicesController> controller(
+ new MediaStreamDevicesController(web_contents, request,
+ std::move(callback)));
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ std::vector<ContentSettingsType> content_settings_types;
+
+ PermissionManager* permission_manager = PermissionManager::Get(profile);
+ bool will_prompt_for_audio = false;
+ bool will_prompt_for_video = false;
+
+ if (controller->ShouldRequestAudio()) {
+ PermissionResult permission_status =
+ permission_manager->GetPermissionStatusForFrame(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, rfh,
+ request.security_origin);
+ if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+ controller->denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ controller->RunCallback(permission_status.source ==
+ PermissionStatusSource::FEATURE_POLICY);
+ return;
+ }
+
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ will_prompt_for_audio =
+ permission_status.content_setting == CONTENT_SETTING_ASK;
+ }
+ if (controller->ShouldRequestVideo()) {
+ PermissionResult permission_status =
+ permission_manager->GetPermissionStatusForFrame(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, rfh,
+ request.security_origin);
+ if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+ controller->denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ controller->RunCallback(permission_status.source ==
+ PermissionStatusSource::FEATURE_POLICY);
+ return;
+ }
+
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ will_prompt_for_video =
+ permission_status.content_setting == CONTENT_SETTING_ASK;
+ }
+
+ permission_manager->RequestPermissions(
+ content_settings_types, rfh, request.security_origin,
+ request.user_gesture,
+ base::Bind(
+ &MediaStreamDevicesController::RequestAndroidPermissionsIfNeeded,
+ web_contents, base::Passed(&controller), will_prompt_for_audio,
+ will_prompt_for_video));
+}
+
+void MediaStreamDevicesController::RequestAndroidPermissionsIfNeeded(
+ content::WebContents* web_contents,
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ bool did_prompt_for_audio,
+ bool did_prompt_for_video,
+ const std::vector<ContentSetting>& responses) {
+#if defined(OS_ANDROID)
+ // If either audio or video was previously allowed and Chrome no longer has
+ // the necessary permissions, show a infobar to attempt to address this
+ // mismatch.
+ std::vector<ContentSettingsType> content_settings_types;
+ // The audio setting will always be the first one in the vector, if it was
+ // requested.
+ // If the user was already prompted for mic (|did_prompt_for_audio| flag), we
+ // would have requested Android permission at that point.
+ if (!did_prompt_for_audio &&
+ controller->ShouldRequestAudio() &&
+ responses.front() == CONTENT_SETTING_ALLOW) {
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ }
+
+ // If the user was already prompted for camera (|did_prompt_for_video| flag),
+ // we would have requested Android permission at that point.
+ if (!did_prompt_for_video &&
+ controller->ShouldRequestVideo() &&
+ responses.back() == CONTENT_SETTING_ALLOW) {
+ content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ }
+ if (content_settings_types.empty()) {
+ controller->PromptAnsweredGroupedRequest(responses);
+ return;
+ }
+
+ ShowPermissionInfoBarState show_permission_infobar_state =
+ PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
+ web_contents, content_settings_types);
+ switch (show_permission_infobar_state) {
+ case ShowPermissionInfoBarState::NO_NEED_TO_SHOW_PERMISSION_INFOBAR:
+ controller->PromptAnsweredGroupedRequest(responses);
+ return;
+ case ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR:
+ PermissionUpdateInfoBarDelegate::Create(
+ web_contents, content_settings_types,
+ base::BindOnce(&MediaStreamDevicesController::AndroidOSPromptAnswered,
+ base::Passed(&controller), responses));
+ return;
+ case ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR: {
+ std::vector<ContentSetting> blocked_responses(responses.size(),
+ CONTENT_SETTING_BLOCK);
+ controller->PromptAnsweredGroupedRequest(blocked_responses);
+ return;
+ }
+ }
+
+ NOTREACHED() << "Unknown show permission infobar state.";
+#else
+ controller->PromptAnsweredGroupedRequest(responses);
+#endif
+}
+
+#if defined(OS_ANDROID)
+// static
+void MediaStreamDevicesController::AndroidOSPromptAnswered(
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ std::vector<ContentSetting> responses,
+ bool android_prompt_granted) {
+ if (!android_prompt_granted) {
+ // Only permissions that were previously ALLOW for a site will have had
+ // their android permissions requested. It's only in that case that we need
+ // to change the setting to BLOCK to reflect that it wasn't allowed.
+ for (size_t i = 0; i < responses.size(); ++i) {
+ if (responses[i] == CONTENT_SETTING_ALLOW)
+ responses[i] = CONTENT_SETTING_BLOCK;
+ }
+ }
+
+ controller->PromptAnsweredGroupedRequest(responses);
+}
+#endif // defined(OS_ANDROID)
+
+// static
+void MediaStreamDevicesController::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* prefs) {
+ prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
+ prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
+ prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
+ prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
+}
+
+MediaStreamDevicesController::~MediaStreamDevicesController() {
+ if (!callback_.is_null()) {
+ std::move(callback_).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ std::unique_ptr<content::MediaStreamUI>());
+ }
+}
+
+void MediaStreamDevicesController::PromptAnsweredGroupedRequest(
+ const std::vector<ContentSetting>& responses) {
+ // The audio setting will always be the first one in the vector, if it was
+ // requested.
+ bool blocked_by_feature_policy = ShouldRequestAudio() || ShouldRequestVideo();
+ if (ShouldRequestAudio()) {
+ audio_setting_ = responses.front();
+ blocked_by_feature_policy &=
+ audio_setting_ == CONTENT_SETTING_BLOCK &&
+ PermissionIsBlockedForReason(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ PermissionStatusSource::FEATURE_POLICY);
+ }
+
+ if (ShouldRequestVideo()) {
+ video_setting_ = responses.back();
+ blocked_by_feature_policy &=
+ video_setting_ == CONTENT_SETTING_BLOCK &&
+ PermissionIsBlockedForReason(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ PermissionStatusSource::FEATURE_POLICY);
+ }
+
+ for (ContentSetting response : responses) {
+ if (response == CONTENT_SETTING_BLOCK)
+ denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ else if (response == CONTENT_SETTING_ASK)
+ denial_reason_ =
+ blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED;
+ }
+
+ RunCallback(blocked_by_feature_policy);
+}
+
+MediaStreamDevicesController::MediaStreamDevicesController(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : web_contents_(web_contents),
+ request_(request),
+ callback_(std::move(callback)) {
+ DCHECK(content::IsOriginSecure(request_.security_origin) ||
+ request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+
+ profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
+
+ denial_reason_ = blink::mojom::MediaStreamRequestResult::OK;
+ audio_setting_ = GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ request, &denial_reason_);
+ video_setting_ = GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ request, &denial_reason_);
+}
+
+bool MediaStreamDevicesController::ShouldRequestAudio() const {
+ return audio_setting_ == CONTENT_SETTING_ASK;
+}
+
+bool MediaStreamDevicesController::ShouldRequestVideo() const {
+ return video_setting_ == CONTENT_SETTING_ASK;
+}
+
+blink::MediaStreamDevices MediaStreamDevicesController::GetDevices(
+ ContentSetting audio_setting,
+ ContentSetting video_setting) {
+ bool audio_allowed = audio_setting == CONTENT_SETTING_ALLOW;
+ bool video_allowed = video_setting == CONTENT_SETTING_ALLOW;
+
+ if (!audio_allowed && !video_allowed)
+ return blink::MediaStreamDevices();
+
+ blink::MediaStreamDevices devices;
+ switch (request_.request_type) {
+ case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: {
+ const blink::MediaStreamDevice* device = NULL;
+ // For open device request, when requested device_id is empty, pick
+ // the first available of the given type. If requested device_id is
+ // not empty, return the desired device if it's available. Otherwise,
+ // return no device.
+ if (audio_allowed &&
+ request_.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::NO_SERVICE,
+ request_.video_type);
+ if (!request_.requested_audio_device_id.empty()) {
+ device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+ } else {
+ device = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetFirstAvailableAudioDevice();
+ }
+ } else if (video_allowed &&
+ request_.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::NO_SERVICE,
+ request_.audio_type);
+ // Pepper API opens only one device at a time.
+ if (!request_.requested_video_device_id.empty()) {
+ device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedVideoDevice(request_.requested_video_device_id);
+ } else {
+ device = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetFirstAvailableVideoDevice();
+ }
+ }
+ if (device)
+ devices.push_back(*device);
+ break;
+ }
+ case blink::MEDIA_GENERATE_STREAM: {
+ bool get_default_audio_device = audio_allowed;
+ bool get_default_video_device = video_allowed;
+
+ // Get the exact audio or video device if an id is specified.
+ if (audio_allowed && !request_.requested_audio_device_id.empty()) {
+ const blink::MediaStreamDevice* audio_device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+ if (audio_device) {
+ devices.push_back(*audio_device);
+ get_default_audio_device = false;
+ }
+ }
+ if (video_allowed && !request_.requested_video_device_id.empty()) {
+ const blink::MediaStreamDevice* video_device =
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetRequestedVideoDevice(request_.requested_video_device_id);
+ if (video_device) {
+ devices.push_back(*video_device);
+ get_default_video_device = false;
+ }
+ }
+
+ // If either or both audio and video devices were requested but not
+ // specified by id, get the default devices.
+ if (get_default_audio_device || get_default_video_device) {
+ MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetDefaultDevicesForProfile(profile_, get_default_audio_device,
+ get_default_video_device, &devices);
+ }
+ break;
+ }
+ case blink::MEDIA_DEVICE_ACCESS: {
+ // Get the default devices for the request.
+ MediaCaptureDevicesDispatcher::GetInstance()->GetDefaultDevicesForProfile(
+ profile_, audio_allowed, video_allowed, &devices);
+ break;
+ }
+ case blink::MEDIA_DEVICE_UPDATE: {
+ NOTREACHED();
+ break;
+ }
+ } // switch
+
+ return devices;
+}
+
+void MediaStreamDevicesController::RunCallback(bool blocked_by_feature_policy) {
+ CHECK(!callback_.is_null());
+
+ // If the kill switch is, or the request was blocked because of feature
+ // policy we don't update the tab context.
+ if (denial_reason_ !=
+ blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
+ !blocked_by_feature_policy) {
+ UpdateTabSpecificContentSettings(audio_setting_, video_setting_);
+ }
+
+ blink::MediaStreamDevices devices;
+
+ // If all requested permissions are allowed then the callback should report
+ // success, otherwise we report |denial_reason_|.
+ blink::mojom::MediaStreamRequestResult request_result =
+ blink::mojom::MediaStreamRequestResult::OK;
+ if ((audio_setting_ == CONTENT_SETTING_ALLOW ||
+ audio_setting_ == CONTENT_SETTING_DEFAULT) &&
+ (video_setting_ == CONTENT_SETTING_ALLOW ||
+ video_setting_ == CONTENT_SETTING_DEFAULT)) {
+ devices = GetDevices(audio_setting_, video_setting_);
+ if (devices.empty()) {
+ // Even if all requested permissions are allowed, if there are no devices
+ // at this point we still report a failure.
+ request_result = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
+ }
+ } else {
+ DCHECK_NE(blink::mojom::MediaStreamRequestResult::OK, denial_reason_);
+ request_result = denial_reason_;
+ }
+
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (!devices.empty()) {
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents_, devices);
+ }
+ std::move(callback_).Run(devices, request_result, std::move(ui));
+}
+
+void MediaStreamDevicesController::UpdateTabSpecificContentSettings(
+ ContentSetting audio_setting,
+ ContentSetting video_setting) const {
+ if (!content_settings_)
+ return;
+
+ TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+ TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
+ std::string selected_audio_device;
+ std::string selected_video_device;
+ std::string requested_audio_device = request_.requested_audio_device_id;
+ std::string requested_video_device = request_.requested_video_device_id;
+
+ // TODO(raymes): Why do we use the defaults here for the selected devices?
+ // Shouldn't we just use the devices that were actually selected?
+ PrefService* prefs = Profile::FromBrowserContext(
+ web_contents_->GetBrowserContext())->GetPrefs();
+ if (audio_setting != CONTENT_SETTING_DEFAULT) {
+ selected_audio_device =
+ requested_audio_device.empty()
+ ? prefs->GetString(prefs::kDefaultAudioCaptureDevice)
+ : requested_audio_device;
+ microphone_camera_state |=
+ TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ (audio_setting == CONTENT_SETTING_ALLOW
+ ? 0
+ : TabSpecificContentSettings::MICROPHONE_BLOCKED);
+ }
+
+ if (video_setting != CONTENT_SETTING_DEFAULT) {
+ selected_video_device =
+ requested_video_device.empty()
+ ? prefs->GetString(prefs::kDefaultVideoCaptureDevice)
+ : requested_video_device;
+ microphone_camera_state |=
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ (video_setting == CONTENT_SETTING_ALLOW
+ ? 0
+ : TabSpecificContentSettings::CAMERA_BLOCKED);
+ }
+
+ content_settings_->OnMediaStreamPermissionSet(
+ PermissionManager::Get(profile_)->GetCanonicalOrigin(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, request_.security_origin,
+ web_contents_->GetLastCommittedURL()),
+ microphone_camera_state, selected_audio_device, selected_video_device,
+ requested_audio_device, requested_video_device);
+}
+
+ContentSetting MediaStreamDevicesController::GetContentSetting(
+ ContentSettingsType content_type,
+ const content::MediaStreamRequest& request,
+ blink::mojom::MediaStreamRequestResult* denial_reason) const {
+ DCHECK(content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
+ content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+ DCHECK(!request_.security_origin.is_empty());
+ DCHECK(content::IsOriginSecure(request_.security_origin) ||
+ request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+ if (!ContentTypeIsRequested(content_type, request)) {
+ // No denial reason set as it will have been previously set.
+ return CONTENT_SETTING_DEFAULT;
+ }
+
+ std::string device_id;
+ if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
+ device_id = request.requested_audio_device_id;
+ else
+ device_id = request.requested_video_device_id;
+ if (!HasAvailableDevices(content_type, device_id)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ if (!IsUserAcceptAllowed(content_type)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ // Don't request if the kill switch is on.
+ if (PermissionIsBlockedForReason(content_type,
+ PermissionStatusSource::KILL_SWITCH)) {
+ *denial_reason = blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON;
+ return CONTENT_SETTING_BLOCK;
+ }
+
+ return CONTENT_SETTING_ASK;
+}
+
+bool MediaStreamDevicesController::IsUserAcceptAllowed(
+ ContentSettingsType content_type) const {
+#if defined(OS_ANDROID)
+ ui::WindowAndroid* window_android =
+ web_contents_->GetNativeView()->GetWindowAndroid();
+ if (!window_android)
+ return false;
+
+ std::vector<std::string> android_permissions;
+ PrefServiceBridge::GetAndroidPermissionsForContentSetting(
+ content_type, &android_permissions);
+ for (const auto& android_permission : android_permissions) {
+ if (!window_android->HasPermission(android_permission) &&
+ !window_android->CanRequestPermission(android_permission)) {
+ return false;
+ }
+ }
+
+ // Don't approve device requests if the tab was hidden.
+ // TODO(qinmin): Add a test for this. http://crbug.com/396869.
+ // TODO(raymes): Shouldn't this apply to all permissions not just audio/video?
+ return web_contents_->GetRenderWidgetHostView()->IsShowing();
+#endif
+ return true;
+}
+
+bool MediaStreamDevicesController::PermissionIsBlockedForReason(
+ ContentSettingsType content_type,
+ PermissionStatusSource reason) const {
+ // TODO(raymes): This function wouldn't be needed if
+ // PermissionManager::RequestPermissions returned a denial reason.
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ request_.render_process_id, request_.render_frame_id);
+ PermissionResult result =
+ PermissionManager::Get(profile_)->GetPermissionStatusForFrame(
+ content_type, rfh, request_.security_origin);
+ if (result.source == reason) {
+ DCHECK_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
+ return true;
+ }
+ return false;
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h
new file mode 100644
index 00000000000..4b5920a2c9b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller.h
@@ -0,0 +1,138 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "content/public/browser/media_stream_request.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+class MediaStreamDevicesController;
+class Profile;
+class TabSpecificContentSettings;
+enum class PermissionStatusSource;
+
+namespace content {
+class WebContents;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace policy {
+class MediaStreamDevicesControllerBrowserTest;
+}
+
+namespace test {
+class MediaStreamDevicesControllerTestApi;
+}
+
+class MediaStreamDevicesController {
+ public:
+ static void RequestPermissions(const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ static void RequestAndroidPermissionsIfNeeded(
+ content::WebContents* web_contents,
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ bool did_prompt_for_audio,
+ bool did_prompt_for_video,
+ const std::vector<ContentSetting>& responses);
+
+#if defined(OS_ANDROID)
+ // Called when the Android OS-level prompt is answered.
+ static void AndroidOSPromptAnswered(
+ std::unique_ptr<MediaStreamDevicesController> controller,
+ std::vector<ContentSetting> responses,
+ bool android_prompt_granted);
+#endif // defined(OS_ANDROID)
+
+ // Registers the prefs backing the audio and video policies.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ ~MediaStreamDevicesController();
+
+ // Called when a permission prompt is answered through the PermissionManager.
+ void PromptAnsweredGroupedRequest(
+ const std::vector<ContentSetting>& responses);
+
+ private:
+ friend class MediaStreamDevicesControllerTest;
+ friend class test::MediaStreamDevicesControllerTestApi;
+ friend class policy::MediaStreamDevicesControllerBrowserTest;
+
+ MediaStreamDevicesController(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ // Returns true if audio/video should be requested through the
+ // PermissionManager. We won't try to request permission if the request is
+ // already blocked for some other reason, e.g. there are no devices available.
+ bool ShouldRequestAudio() const;
+ bool ShouldRequestVideo() const;
+
+ // Returns a list of devices available for the request for the given
+ // audio/video permission settings.
+ blink::MediaStreamDevices GetDevices(ContentSetting audio_setting,
+ ContentSetting video_setting);
+
+ // Runs |callback_| with the current audio/video permission settings.
+ void RunCallback(bool blocked_by_feature_policy);
+
+ // Called when the permission has been set to update the
+ // TabSpecificContentSettings.
+ void UpdateTabSpecificContentSettings(ContentSetting audio_setting,
+ ContentSetting video_setting) const;
+
+ // Returns the content settings for the given content type and request.
+ ContentSetting GetContentSetting(
+ ContentSettingsType content_type,
+ const content::MediaStreamRequest& request,
+ blink::mojom::MediaStreamRequestResult* denial_reason) const;
+
+ // Returns true if clicking allow on the dialog should give access to the
+ // requested devices.
+ bool IsUserAcceptAllowed(ContentSettingsType content_type) const;
+
+ bool PermissionIsBlockedForReason(ContentSettingsType content_type,
+ PermissionStatusSource reason) const;
+
+ // The current state of the audio/video content settings which may be updated
+ // through the lifetime of the request.
+ ContentSetting audio_setting_;
+ ContentSetting video_setting_;
+ blink::mojom::MediaStreamRequestResult denial_reason_;
+
+ content::WebContents* web_contents_;
+
+ // The owner of this class needs to make sure it does not outlive the profile.
+ Profile* profile_;
+
+ // Weak pointer to the tab specific content settings of the tab for which the
+ // MediaStreamDevicesController was created. The tab specific content
+ // settings are associated with a the web contents of the tab. The
+ // MediaStreamDeviceController must not outlive the web contents for which it
+ // was created.
+ TabSpecificContentSettings* content_settings_;
+
+ // The original request for access to devices.
+ const content::MediaStreamRequest request_;
+
+ // The callback that needs to be Run to notify WebRTC of whether access to
+ // audio/video devices was granted or not.
+ content::MediaResponseCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicesController);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
new file mode 100644
index 00000000000..ed67e419527
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
@@ -0,0 +1,963 @@
+// Copyright 2014 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 <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/field_trial.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/permissions/permission_context_base.h"
+#include "chrome/browser/permissions/permission_request.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "extensions/common/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+
+class MediaStreamDevicesControllerTest : public WebRtcTestBase {
+ public:
+ MediaStreamDevicesControllerTest()
+ : example_audio_id_("fake_audio_dev"),
+ example_video_id_("fake_video_dev"),
+ media_stream_result_(
+ blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS) {
+ }
+
+ // Dummy callback for when we deny the current request directly.
+ void OnMediaStreamResponse(const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ media_stream_devices_ = devices;
+ media_stream_result_ = result;
+ quit_closure_.Run();
+ quit_closure_ = base::Closure();
+ }
+
+ protected:
+ enum DeviceType { DEVICE_TYPE_AUDIO, DEVICE_TYPE_VIDEO };
+ enum Access { ACCESS_ALLOWED, ACCESS_DENIED };
+
+ const GURL& example_url() const { return example_url_; }
+
+ TabSpecificContentSettings* GetContentSettings() {
+ return TabSpecificContentSettings::FromWebContents(GetWebContents());
+ }
+
+ const std::string& example_audio_id() const { return example_audio_id_; }
+ const std::string& example_video_id() const { return example_video_id_; }
+
+ blink::mojom::MediaStreamRequestResult media_stream_result() const {
+ return media_stream_result_;
+ }
+
+ void RequestPermissions(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ base::RunLoop run_loop;
+ ASSERT_TRUE(quit_closure_.is_null());
+ quit_closure_ = run_loop.QuitClosure();
+ MediaStreamDevicesController::RequestPermissions(request,
+ std::move(callback));
+ run_loop.Run();
+ }
+
+ // Sets the device policy-controlled |access| for |example_url_| to be for the
+ // selected |device_type|.
+ void SetDevicePolicy(DeviceType device_type, Access access) {
+ PrefService* prefs = Profile::FromBrowserContext(
+ GetWebContents()->GetBrowserContext())->GetPrefs();
+ const char* policy_name = NULL;
+ switch (device_type) {
+ case DEVICE_TYPE_AUDIO:
+ policy_name = prefs::kAudioCaptureAllowed;
+ break;
+ case DEVICE_TYPE_VIDEO:
+ policy_name = prefs::kVideoCaptureAllowed;
+ break;
+ }
+ prefs->SetBoolean(policy_name, access == ACCESS_ALLOWED);
+ }
+
+ // Set the content settings for mic/cam.
+ void SetContentSettings(ContentSetting mic_setting,
+ ContentSetting cam_setting) {
+ HostContentSettingsMap* content_settings =
+ HostContentSettingsMapFactory::GetForProfile(
+ Profile::FromBrowserContext(GetWebContents()->GetBrowserContext()));
+ content_settings->SetContentSettingDefaultScope(
+ example_url_, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+ std::string(), mic_setting);
+ content_settings->SetContentSettingDefaultScope(
+ example_url_, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ std::string(), cam_setting);
+ }
+
+ // Checks whether the devices returned in OnMediaStreamResponse contains a
+ // microphone and/or camera device.
+ bool CheckDevicesListContains(blink::mojom::MediaStreamType type) {
+ for (const auto& device : media_stream_devices_) {
+ if (device.type == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ content::WebContents* GetWebContents() {
+ return browser()->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Creates a MediaStreamRequest, asking for those media types, which have a
+ // non-empty id string.
+ content::MediaStreamRequest CreateRequestWithType(
+ const std::string& audio_id,
+ const std::string& video_id,
+ blink::MediaStreamRequestType request_type) {
+ blink::mojom::MediaStreamType audio_type =
+ audio_id.empty() ? blink::mojom::MediaStreamType::NO_SERVICE
+ : blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+ blink::mojom::MediaStreamType video_type =
+ video_id.empty() ? blink::mojom::MediaStreamType::NO_SERVICE
+ : blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
+ EXPECT_EQ(example_url(),
+ GetWebContents()->GetMainFrame()->GetLastCommittedURL());
+ int render_process_id =
+ GetWebContents()->GetMainFrame()->GetProcess()->GetID();
+ int render_frame_id = GetWebContents()->GetMainFrame()->GetRoutingID();
+ return content::MediaStreamRequest(
+ render_process_id, render_frame_id, 0, example_url(), false,
+ request_type, audio_id, video_id, audio_type, video_type, false);
+ }
+
+ content::MediaStreamRequest CreateRequest(const std::string& audio_id,
+ const std::string& video_id) {
+ return CreateRequestWithType(audio_id, video_id,
+ blink::MEDIA_DEVICE_ACCESS);
+ }
+
+ void InitWithUrl(const GURL& url) {
+ DCHECK(example_url_.is_empty());
+ example_url_ = url;
+ ui_test_utils::NavigateToURL(browser(), example_url_);
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ }
+
+ MockPermissionPromptFactory* prompt_factory() {
+ return prompt_factory_.get();
+ }
+
+ private:
+ void SetUpOnMainThread() override {
+ WebRtcTestBase::SetUpOnMainThread();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ PermissionRequestManager* manager =
+ PermissionRequestManager::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents());
+ prompt_factory_.reset(new MockPermissionPromptFactory(manager));
+
+ // Cleanup.
+ media_stream_devices_.clear();
+ media_stream_result_ =
+ blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS;
+
+ blink::MediaStreamDevices audio_devices;
+ blink::MediaStreamDevice fake_audio_device(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE, example_audio_id_,
+ "Fake Audio Device");
+ audio_devices.push_back(fake_audio_device);
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices(
+ audio_devices);
+
+ blink::MediaStreamDevices video_devices;
+ blink::MediaStreamDevice fake_video_device(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, example_video_id_,
+ "Fake Video Device");
+ video_devices.push_back(fake_video_device);
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestVideoCaptureDevices(
+ video_devices);
+ }
+
+ void TearDownOnMainThread() override {
+ prompt_factory_.reset();
+
+ WebRtcTestBase::TearDownOnMainThread();
+ }
+
+ GURL example_url_;
+ const std::string example_audio_id_;
+ const std::string example_video_id_;
+
+ blink::MediaStreamDevices media_stream_devices_;
+ blink::mojom::MediaStreamRequestResult media_stream_result_;
+
+ base::Closure quit_closure_;
+
+ std::unique_ptr<MockPermissionPromptFactory> prompt_factory_;
+};
+
+// Request and allow microphone access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndAllowMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and allow camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndAllowCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block microphone access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndBlockMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, RequestAndBlockCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(std::string(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and allow microphone and camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndAllowMicCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request and block microphone and camera access.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndBlockMicCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone and camera access. Camera is denied, thus everything
+// must be denied.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestMicCamBlockCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone and camera access. Microphone is denied, thus everything
+// must be denied.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestMicCamBlockMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED |
+ TabSpecificContentSettings::CAMERA_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+}
+
+// Request microphone access. Requesting camera should not change microphone
+// state.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamDoesNotChangeMic) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ // Request mic and deny.
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+
+ // Request cam and allow
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+
+ // Mic state should not have changed.
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+}
+
+// Denying mic access after camera access should still show the camera as state.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ DenyMicDoesNotChangeCam) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ // Request cam and allow
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+ EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+
+ // Simulate that an a video stream is now being captured.
+ blink::MediaStreamDevices video_devices(1);
+ video_devices[0] = blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, example_video_id(),
+ example_video_id());
+ MediaCaptureDevicesDispatcher* dispatcher =
+ MediaCaptureDevicesDispatcher::GetInstance();
+ dispatcher->SetTestVideoCaptureDevices(video_devices);
+ std::unique_ptr<content::MediaStreamUI> video_stream_ui =
+ dispatcher->GetMediaStreamCaptureIndicator()->RegisterMediaStream(
+ GetWebContents(), video_devices);
+ video_stream_ui->OnStarted(base::OnceClosure(),
+ content::MediaStreamUI::SourceCallback());
+
+ // Request mic and deny.
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_DENIED);
+ // Ensure the prompt is accepted if necessary such that tab specific content
+ // settings are updated.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), std::string()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC));
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_requested_audio_device());
+ EXPECT_EQ(example_audio_id(),
+ GetContentSettings()->media_stream_selected_audio_device());
+
+ // Cam should still be included in the state.
+ EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA));
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_requested_video_device());
+ EXPECT_EQ(example_video_id(),
+ GetContentSettings()->media_stream_selected_video_device());
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED |
+ TabSpecificContentSettings::CAMERA_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+
+ // After ending the camera capture, the camera permission is no longer
+ // relevant, so it should no be included in the mic/cam state.
+ video_stream_ui.reset();
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
+ TabSpecificContentSettings::MICROPHONE_BLOCKED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+// Stores the ContentSettings inputs for a particular test and has functions
+// which return the expected outputs for that test.
+struct ContentSettingsTestData {
+ // The initial value of the mic/cam content settings.
+ ContentSetting mic;
+ ContentSetting cam;
+ // Whether the infobar should be accepted if it's shown.
+ bool accept_infobar;
+
+ // Whether the infobar should be displayed to request mic/cam for the given
+ // content settings inputs.
+ bool ExpectMicInfobar() const {
+ return mic == CONTENT_SETTING_ASK && cam != CONTENT_SETTING_BLOCK;
+ }
+ bool ExpectCamInfobar() const {
+ return cam == CONTENT_SETTING_ASK && mic != CONTENT_SETTING_BLOCK;
+ }
+
+ // Whether or not the mic/cam should be allowed after clicking accept/deny for
+ // the given inputs.
+ bool ExpectMicAllowed() const {
+ return mic == CONTENT_SETTING_ALLOW ||
+ (mic == CONTENT_SETTING_ASK && accept_infobar);
+ }
+ bool ExpectCamAllowed() const {
+ return cam == CONTENT_SETTING_ALLOW ||
+ (cam == CONTENT_SETTING_ASK && accept_infobar);
+ }
+
+ // The expected media stream result after clicking accept/deny for the given
+ // inputs.
+ blink::mojom::MediaStreamRequestResult ExpectedMediaStreamResult() const {
+ if (ExpectMicAllowed() && ExpectCamAllowed())
+ return blink::mojom::MediaStreamRequestResult::OK;
+ return blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+ }
+};
+
+// Test all combinations of cam/mic content settings. Then tests the result of
+// clicking both accept/deny on the infobar. Both cam/mic are requested.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, ContentSettings) {
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ static const ContentSettingsTestData tests[] = {
+ // Settings that won't result in an infobar.
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, false},
+ {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK, false},
+
+ // Settings that will result in an infobar. Test both accept and deny.
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, true},
+
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ASK, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ASK, true},
+
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, false},
+ {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, true},
+ };
+
+ for (auto& test : tests) {
+ SetContentSettings(test.mic, test.cam);
+
+ prompt_factory()->ResetCounts();
+
+ // Accept or deny the infobar if it's showing.
+ if (test.ExpectMicInfobar() || test.ExpectCamInfobar()) {
+ if (test.accept_infobar) {
+ prompt_factory()->set_response_type(
+ PermissionRequestManager::ACCEPT_ALL);
+ } else {
+ prompt_factory()->set_response_type(PermissionRequestManager::DENY_ALL);
+ }
+ } else {
+ prompt_factory()->set_response_type(PermissionRequestManager::NONE);
+ }
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_LE(prompt_factory()->TotalRequestCount(), 2);
+ ASSERT_EQ(test.ExpectMicInfobar(),
+ prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_MIC));
+ ASSERT_EQ(test.ExpectCamInfobar(),
+ prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA));
+
+ // Check the media stream result is expected and the devices returned are
+ // expected;
+ ASSERT_EQ(test.ExpectedMediaStreamResult(), media_stream_result());
+ ASSERT_EQ(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE),
+ test.ExpectMicAllowed() && test.ExpectCamAllowed());
+ ASSERT_EQ(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE),
+ test.ExpectMicAllowed() && test.ExpectCamAllowed());
+ }
+}
+
+// Request and allow camera access on WebUI pages without prompting.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ WebUIRequestAndAllowCam) {
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(), CreateRequest(std::string(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ ExtensionRequestMicCam) {
+ std::string pdf_extension_page = std::string(extensions::kExtensionScheme) +
+ "://" + extension_misc::kPdfExtensionId +
+ "/index.html";
+ InitWithUrl(GURL(pdf_extension_page));
+ // Test that a prompt is required.
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(2, prompt_factory()->TotalRequestCount());
+ ASSERT_TRUE(prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA));
+ ASSERT_TRUE(prompt_factory()->RequestTypeSeen(
+ PermissionRequestType::PERMISSION_MEDIASTREAM_MIC));
+
+ // Accept the prompt.
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+
+ // Check that re-requesting allows without prompting.
+ prompt_factory()->ResetCounts();
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperRequestInsecure) {
+ InitWithUrl(GURL("http://www.example.com"));
+
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(example_audio_id(), example_video_id(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest, WebContentsDestroyed) {
+ InitWithUrl(GURL("http://www.example.com"));
+
+ prompt_factory()->set_response_type(PermissionRequestManager::ACCEPT_ALL);
+
+ content::MediaStreamRequest request =
+ CreateRequest(example_audio_id(), example_video_id());
+ // Simulate a destroyed RenderFrameHost.
+ request.render_frame_id = 0;
+ request.render_process_id = 0;
+
+ RequestPermissions(
+ nullptr, request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+// Request and block microphone and camera access with kill switch.
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestAndKillSwitchMicCam) {
+ std::map<std::string, std::string> params;
+ params[PermissionUtil::GetPermissionString(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)] =
+ PermissionContextBase::kPermissionsKillSwitchBlockedValue;
+ params[PermissionUtil::GetPermissionString(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)] =
+ PermissionContextBase::kPermissionsKillSwitchBlockedValue;
+ variations::AssociateVariationParams(
+ PermissionContextBase::kPermissionsKillSwitchFieldStudy,
+ "TestGroup", params);
+ base::FieldTrialList::CreateFieldTrial(
+ PermissionContextBase::kPermissionsKillSwitchFieldStudy,
+ "TestGroup");
+ InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
+ SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
+ SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
+ RequestPermissions(
+ GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamAndMicBlockedByFeaturePolicy) {
+ InitWithUrl(embedded_test_server()->GetURL("/iframe_blank.html"));
+
+ // Create a cross-origin request by using localhost as the iframe origin.
+ GURL::Replacements replace_host;
+ replace_host.SetHostStr("localhost");
+ GURL cross_origin_url = embedded_test_server()
+ ->GetURL("/simple.html")
+ .ReplaceComponents(replace_host);
+ content::NavigateIframeToURL(GetWebContents(), "test",
+ GURL(cross_origin_url));
+ content::RenderFrameHost* child_frame =
+ ChildFrameAt(GetWebContents()->GetMainFrame(), 0);
+
+ content::MediaStreamRequest request =
+ CreateRequest(example_audio_id(), example_video_id());
+ // Make the child frame the source of the request.
+ request.render_process_id = child_frame->GetProcess()->GetID();
+ request.render_frame_id = child_frame->GetRoutingID();
+ RequestPermissions(
+ GetWebContents(), request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ RequestCamBlockedByFeaturePolicy) {
+ InitWithUrl(embedded_test_server()->GetURL("/iframe_blank.html"));
+
+ // Create a cross-origin request by using localhost as the iframe origin.
+ GURL::Replacements replace_host;
+ replace_host.SetHostStr("localhost");
+ GURL cross_origin_url = embedded_test_server()
+ ->GetURL("/simple.html")
+ .ReplaceComponents(replace_host);
+ content::NavigateIframeToURL(GetWebContents(), "test",
+ GURL(cross_origin_url));
+ content::RenderFrameHost* child_frame =
+ ChildFrameAt(GetWebContents()->GetMainFrame(), 0);
+
+ content::MediaStreamRequest request =
+ CreateRequest(std::string(), example_video_id());
+ // Make the child frame the source of the request.
+ request.render_process_id = child_frame->GetProcess()->GetID();
+ request.render_frame_id = child_frame->GetRoutingID();
+ RequestPermissions(
+ GetWebContents(), request,
+ base::Bind(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
+
+ ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
+ media_stream_result());
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ ASSERT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+ EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+ GetContentSettings()->GetMicrophoneCameraState());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperAudioRequestNoCamera) {
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestVideoCaptureDevices({});
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(example_audio_id(), std::string(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ EXPECT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ EXPECT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
+ PepperVideoRequestNoMic) {
+ MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices({});
+ InitWithUrl(GURL(chrome::kChromeUIVersionURL));
+ RequestPermissions(
+ GetWebContents(),
+ CreateRequestWithType(std::string(), example_video_id(),
+ blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
+ base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+ base::Unretained(this)));
+
+ EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
+ EXPECT_FALSE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+ EXPECT_TRUE(CheckDevicesListContains(
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
diff --git a/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc b/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
new file mode 100644
index 00000000000..651c8025892
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
@@ -0,0 +1,168 @@
+// 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/command_line.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/test_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+// MediaStreamPermissionTest ---------------------------------------------------
+
+class MediaStreamPermissionTest : public WebRtcTestBase {
+ public:
+ MediaStreamPermissionTest() {}
+ ~MediaStreamPermissionTest() override {}
+
+ // InProcessBrowserTest:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // This test expects to run with fake devices but real UI.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream))
+ << "Since this test tests the UI we want the real UI!";
+ }
+
+ protected:
+ content::WebContents* LoadTestPageInTab() {
+ return LoadTestPageInBrowser(browser());
+ }
+
+ content::WebContents* LoadTestPageInIncognitoTab() {
+ return LoadTestPageInBrowser(CreateIncognitoBrowser());
+ }
+
+ // Returns the URL of the main test page.
+ GURL test_page_url() const {
+ const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+ return embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage);
+ }
+
+ private:
+ content::WebContents* LoadTestPageInBrowser(Browser* browser) {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ // Uses the default server.
+ GURL url = test_page_url();
+
+ EXPECT_TRUE(content::IsOriginSecure(url));
+
+ ui_test_utils::NavigateToURL(browser, url);
+ return browser->tab_strip_model()->GetActiveWebContents();
+ }
+
+ // Dummy callback for when we deny the current request directly.
+ static void OnMediaStreamResponse(
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {}
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamPermissionTest);
+};
+
+// Actual tests ---------------------------------------------------------------
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestAllowingUserMedia) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(GetUserMediaAndAccept(tab_contents));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDenyingUserMedia) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDismissingRequest) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ GetUserMediaAndDismiss(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestDenyingUserMediaIncognito) {
+ content::WebContents* tab_contents = LoadTestPageInIncognitoTab();
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestSecureOriginDenyIsSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(content::IsOriginSecure(tab_contents->GetLastCommittedURL()));
+
+ GetUserMediaAndDeny(tab_contents);
+ GetUserMediaAndExpectAutoDenyWithoutPrompt(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestSecureOriginAcceptIsSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+ EXPECT_TRUE(content::IsOriginSecure(tab_contents->GetLastCommittedURL()));
+
+ EXPECT_TRUE(GetUserMediaAndAccept(tab_contents));
+ GetUserMediaAndExpectAutoAcceptWithoutPrompt(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest, TestDismissIsNotSticky) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaAndDismiss(tab_contents);
+ GetUserMediaAndDismiss(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ TestDenyingThenClearingStickyException) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaAndDeny(tab_contents);
+ GetUserMediaAndExpectAutoDenyWithoutPrompt(tab_contents);
+
+ HostContentSettingsMap* settings_map =
+ HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+
+ settings_map->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
+ settings_map->ClearSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+
+ GetUserMediaAndDeny(tab_contents);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ DenyingMicDoesNotCauseStickyDenyForCameras) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kAudioOnlyCallConstraints);
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kVideoOnlyCallConstraints));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+ DenyingCameraDoesNotCauseStickyDenyForMics) {
+ content::WebContents* tab_contents = LoadTestPageInTab();
+
+ GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kVideoOnlyCallConstraints);
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kAudioOnlyCallConstraints));
+}
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc
new file mode 100644
index 00000000000..2b109bd1dea
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.cc
@@ -0,0 +1,407 @@
+// 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 "chrome/browser/media/webrtc/native_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/hash/hash.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/base/video_util.h"
+#include "third_party/libyuv/include/libyuv/scale_argb.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/snapshot/snapshot.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/snapshot/snapshot_aura.h"
+#endif
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+namespace {
+
+// Update the list every second.
+const int kDefaultNativeDesktopMediaListUpdatePeriod = 1000;
+
+// Returns a hash of a DesktopFrame content to detect when image for a desktop
+// media source has changed.
+uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
+ int data_size = frame->stride() * frame->size().height();
+ return base::Hash(frame->data(), data_size);
+}
+
+gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
+ gfx::Size size) {
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(0, 0, size.width(), size.height()),
+ gfx::Size(frame->size().width(), frame->size().height()));
+
+ SkBitmap result;
+ result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+ libyuv::ARGBScale(frame->data(), frame->stride(), frame->size().width(),
+ frame->size().height(), pixels_data, result.rowBytes(),
+ scaled_rect.width(), scaled_rect.height(),
+ libyuv::kFilterBilinear);
+
+ // Set alpha channel values to 255 for all pixels.
+ // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
+ // remove this code. Currently screen/window capturers (at least some
+ // implementations) only capture R, G and B channels and set Alpha to 0.
+ // crbug.com/264424
+ for (int y = 0; y < result.height(); ++y) {
+ for (int x = 0; x < result.width(); ++x) {
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
+ 0xff;
+ }
+ }
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
+
+} // namespace
+
+class NativeDesktopMediaList::Worker
+ : public webrtc::DesktopCapturer::Callback {
+ public:
+ Worker(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<NativeDesktopMediaList> media_list,
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer);
+ ~Worker() override;
+
+ void Start();
+ void Refresh(const DesktopMediaID::Id& view_dialog_id, bool update_thumnails);
+
+ void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids,
+ const gfx::Size& thumbnail_size);
+
+ private:
+ typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap;
+
+ // webrtc::DesktopCapturer::Callback interface.
+ void OnCaptureResult(webrtc::DesktopCapturer::Result result,
+ std::unique_ptr<webrtc::DesktopFrame> frame) override;
+
+ // Task runner used for capturing operations.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ base::WeakPtr<NativeDesktopMediaList> media_list_;
+
+ DesktopMediaID::Type type_;
+ std::unique_ptr<webrtc::DesktopCapturer> capturer_;
+
+ std::unique_ptr<webrtc::DesktopFrame> current_frame_;
+
+ ImageHashesMap image_hashes_;
+
+ DISALLOW_COPY_AND_ASSIGN(Worker);
+};
+
+NativeDesktopMediaList::Worker::Worker(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<NativeDesktopMediaList> media_list,
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer)
+ : task_runner_(task_runner),
+ media_list_(media_list),
+ type_(type),
+ capturer_(std::move(capturer)) {}
+
+NativeDesktopMediaList::Worker::~Worker() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+}
+
+void NativeDesktopMediaList::Worker::Start() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ capturer_->Start(this);
+}
+
+void NativeDesktopMediaList::Worker::Refresh(
+ const DesktopMediaID::Id& view_dialog_id,
+ bool update_thumnails) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ std::vector<SourceDescription> result;
+
+ webrtc::DesktopCapturer::SourceList sources;
+ if (!capturer_->GetSourceList(&sources)) {
+ // Will pass empty results list to RefreshForAuraWindows().
+ sources.clear();
+ }
+
+ bool mutiple_sources = sources.size() > 1;
+ base::string16 title;
+ for (size_t i = 0; i < sources.size(); ++i) {
+ switch (type_) {
+ case DesktopMediaID::TYPE_SCREEN:
+ // Just in case 'Screen' is inflected depending on the screen number,
+ // use plural formatter.
+ title = mutiple_sources
+ ? l10n_util::GetPluralStringFUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
+ static_cast<int>(i + 1))
+ : l10n_util::GetStringUTF16(
+ IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
+ break;
+
+ case DesktopMediaID::TYPE_WINDOW:
+ // Skip the picker dialog window.
+ if (sources[i].id == view_dialog_id)
+ continue;
+ title = base::UTF8ToUTF16(sources[i].title);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ result.push_back(
+ SourceDescription(DesktopMediaID(type_, sources[i].id), title));
+ }
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::RefreshForAuraWindows,
+ media_list_, result, update_thumnails));
+}
+
+void NativeDesktopMediaList::Worker::RefreshThumbnails(
+ const std::vector<DesktopMediaID>& native_ids,
+ const gfx::Size& thumbnail_size) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ ImageHashesMap new_image_hashes;
+
+ // Get a thumbnail for each native source.
+ for (const auto& id : native_ids) {
+ if (!capturer_->SelectSource(id.id))
+ continue;
+ capturer_->CaptureFrame();
+
+ // Expect that DesktopCapturer to always captures frames synchronously.
+ // |current_frame_| may be NULL if capture failed (e.g. because window has
+ // been closed).
+ if (current_frame_) {
+ uint32_t frame_hash = GetFrameHash(current_frame_.get());
+ new_image_hashes[id] = frame_hash;
+
+ // Scale the image only if it has changed.
+ auto it = image_hashes_.find(id);
+ if (it == image_hashes_.end() || it->second != frame_hash) {
+ gfx::ImageSkia thumbnail =
+ ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
+ media_list_, id, thumbnail));
+ }
+ }
+ }
+
+ image_hashes_.swap(new_image_hashes);
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
+ media_list_));
+}
+
+void NativeDesktopMediaList::Worker::OnCaptureResult(
+ webrtc::DesktopCapturer::Result result,
+ std::unique_ptr<webrtc::DesktopFrame> frame) {
+ current_frame_ = std::move(frame);
+}
+
+NativeDesktopMediaList::NativeDesktopMediaList(
+ DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer)
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultNativeDesktopMediaListUpdatePeriod)),
+ thread_("DesktopMediaListCaptureThread") {
+ type_ = type;
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ // On Windows/OSX the thread must be a UI thread.
+ base::MessagePumpType thread_type = base::MessagePumpType::UI;
+#else
+ base::MessagePumpType thread_type = base::MessagePumpType::DEFAULT;
+#endif
+ thread_.StartWithOptions(base::Thread::Options(thread_type, 0));
+
+ worker_.reset(new Worker(thread_.task_runner(), weak_factory_.GetWeakPtr(),
+ type, std::move(capturer)));
+
+ thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Worker::Start, base::Unretained(worker_.get())));
+}
+
+NativeDesktopMediaList::~NativeDesktopMediaList() {
+ // This thread should mostly be an idle observer. Stopping it should be fast.
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
+ thread_.task_runner()->DeleteSoon(FROM_HERE, worker_.release());
+ thread_.Stop();
+}
+
+void NativeDesktopMediaList::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+
+#if defined(USE_AURA)
+ DCHECK_EQ(pending_aura_capture_requests_, 0);
+ DCHECK(!pending_native_thumbnail_capture_);
+ new_aura_thumbnail_hashes_.clear();
+#endif
+
+ thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Worker::Refresh, base::Unretained(worker_.get()),
+ view_dialog_id_.id, update_thumnails));
+}
+
+void NativeDesktopMediaList::RefreshForAuraWindows(
+ std::vector<SourceDescription> sources,
+ bool update_thumnails) {
+ DCHECK(can_refresh());
+
+#if defined(USE_AURA)
+ // Associate aura id with native id.
+ for (auto& source : sources) {
+ if (source.id.type != DesktopMediaID::TYPE_WINDOW)
+ continue;
+
+ aura::WindowTreeHost* const host =
+ aura::WindowTreeHost::GetForAcceleratedWidget(
+ *reinterpret_cast<gfx::AcceleratedWidget*>(&source.id.id));
+ aura::Window* const aura_window = host ? host->window() : nullptr;
+ if (aura_window) {
+ DesktopMediaID aura_id = DesktopMediaID::RegisterNativeWindow(
+ DesktopMediaID::TYPE_WINDOW, aura_window);
+ source.id.window_id = aura_id.window_id;
+ }
+ }
+#endif // defined(USE_AURA)
+
+ UpdateSourcesList(sources);
+
+ if (!update_thumnails) {
+ OnRefreshComplete();
+ return;
+ }
+
+ if (thumbnail_size_.IsEmpty()) {
+#if defined(USE_AURA)
+ pending_native_thumbnail_capture_ = true;
+#endif
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ // OnAuraThumbnailCaptured() and UpdateNativeThumbnailsFinished() are
+ // guaranteed to be executed after RefreshForAuraWindows() and
+ // CaptureAuraWindowThumbnail() in the browser UI thread.
+ // Therefore pending_aura_capture_requests_ will be set the number of aura
+ // windows to be captured and pending_native_thumbnail_capture_ will be set
+ // true if native thumbnail capture is needed before OnAuraThumbnailCaptured()
+ // or UpdateNativeThumbnailsFinished() are called.
+ std::vector<DesktopMediaID> native_ids;
+ for (const auto& source : sources) {
+#if defined(USE_AURA)
+ if (source.id.window_id > DesktopMediaID::kNullId) {
+ CaptureAuraWindowThumbnail(source.id);
+ continue;
+ }
+#endif // defined(USE_AURA)
+ native_ids.push_back(source.id);
+ }
+
+ if (!native_ids.empty()) {
+#if defined(USE_AURA)
+ pending_native_thumbnail_capture_ = true;
+#endif
+ thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
+ base::Unretained(worker_.get()), native_ids,
+ thumbnail_size_));
+ }
+}
+
+void NativeDesktopMediaList::UpdateNativeThumbnailsFinished() {
+#if defined(USE_AURA)
+ DCHECK(pending_native_thumbnail_capture_);
+ pending_native_thumbnail_capture_ = false;
+ // If native thumbnail captures finished after aura thumbnail captures,
+ // execute |done_callback| to let the caller know the update process is
+ // finished. If necessary, this will schedule the next refresh.
+ if (pending_aura_capture_requests_ == 0)
+ OnRefreshComplete();
+#else
+ OnRefreshComplete();
+#endif // defined(USE_AURA)
+}
+
+#if defined(USE_AURA)
+
+void NativeDesktopMediaList::CaptureAuraWindowThumbnail(
+ const DesktopMediaID& id) {
+ DCHECK(can_refresh());
+
+ gfx::NativeWindow window = DesktopMediaID::GetNativeWindowById(id);
+ if (!window)
+ return;
+
+ gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
+ gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
+ gfx::Rect(thumbnail_size_), window_rect.size());
+
+ pending_aura_capture_requests_++;
+ ui::GrabWindowSnapshotAndScaleAsyncAura(
+ window, window_rect, scaled_rect.size(),
+ base::Bind(&NativeDesktopMediaList::OnAuraThumbnailCaptured,
+ weak_factory_.GetWeakPtr(), id));
+}
+
+void NativeDesktopMediaList::OnAuraThumbnailCaptured(const DesktopMediaID& id,
+ gfx::Image image) {
+ DCHECK(can_refresh());
+
+ if (!image.IsEmpty()) {
+ // Only new or changed thumbnail need update.
+ new_aura_thumbnail_hashes_[id] = GetImageHash(image);
+ if (!previous_aura_thumbnail_hashes_.count(id) ||
+ previous_aura_thumbnail_hashes_[id] != new_aura_thumbnail_hashes_[id]) {
+ UpdateSourceThumbnail(id, image.AsImageSkia());
+ }
+ }
+
+ // After all aura windows are processed, schedule next refresh;
+ pending_aura_capture_requests_--;
+ DCHECK_GE(pending_aura_capture_requests_, 0);
+ if (pending_aura_capture_requests_ == 0) {
+ previous_aura_thumbnail_hashes_ = std::move(new_aura_thumbnail_hashes_);
+ // Schedule next refresh if aura thumbnail captures finished after native
+ // thumbnail captures.
+ if (!pending_native_thumbnail_capture_)
+ OnRefreshComplete();
+ }
+}
+
+#endif // defined(USE_AURA)
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h
new file mode 100644
index 00000000000..cfd8378fe93
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list.h
@@ -0,0 +1,67 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/gfx/image/image.h"
+
+namespace webrtc {
+class DesktopCapturer;
+}
+
+// Implementation of DesktopMediaList that shows native screens and
+// native windows.
+class NativeDesktopMediaList : public DesktopMediaListBase {
+ public:
+ NativeDesktopMediaList(content::DesktopMediaID::Type type,
+ std::unique_ptr<webrtc::DesktopCapturer> capturer);
+ ~NativeDesktopMediaList() override;
+
+ private:
+ typedef std::map<content::DesktopMediaID, uint32_t> ImageHashesMap;
+
+ class Worker;
+ friend class Worker;
+
+ // Refresh() posts a task for the |worker_| to update list of windows, get
+ // thumbnails and schedules next refresh.
+ void Refresh(bool update_thumnails) override;
+
+ void RefreshForAuraWindows(std::vector<SourceDescription> sources,
+ bool update_thumnails);
+ void UpdateNativeThumbnailsFinished();
+
+#if defined(USE_AURA)
+ void CaptureAuraWindowThumbnail(const content::DesktopMediaID& id);
+ void OnAuraThumbnailCaptured(const content::DesktopMediaID& id,
+ gfx::Image image);
+#endif
+
+ base::Thread thread_;
+ std::unique_ptr<Worker> worker_;
+
+#if defined(USE_AURA)
+ // previous_aura_thumbnail_hashes_ holds thumbanil hash values of aura windows
+ // in the previous refresh. While new_aura_thumbnail_hashes_ has hash values
+ // of the ongoing refresh. Those two maps are used to detect new thumbnails
+ // and changed thumbnails from the previous refresh.
+ ImageHashesMap previous_aura_thumbnail_hashes_;
+ ImageHashesMap new_aura_thumbnail_hashes_;
+
+ int pending_aura_capture_requests_ = 0;
+ bool pending_native_thumbnail_capture_ = false;
+#endif
+ base::WeakPtrFactory<NativeDesktopMediaList> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_NATIVE_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc b/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
new file mode 100644
index 00000000000..fe430e586e3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
@@ -0,0 +1,554 @@
+// 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 "chrome/browser/media/webrtc/native_desktop_media_list.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
+#endif
+
+using content::DesktopMediaID;
+using testing::_;
+using testing::DoAll;
+
+namespace {
+
+// Aura window capture unit tests are not stable. crbug.com/602494 and
+// crbug.com/603823.
+// #define ENABLE_AURA_WINDOW_TESTS
+
+static const int kDefaultWindowCount = 2;
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+static const int kDefaultAuraCount = 1;
+#else
+static const int kDefaultAuraCount = 0;
+#endif
+
+class MockObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+ MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
+};
+
+class FakeScreenCapturer : public webrtc::DesktopCapturer {
+ public:
+ FakeScreenCapturer() {}
+ ~FakeScreenCapturer() override {}
+
+ // webrtc::ScreenCapturer implementation.
+ void Start(Callback* callback) override { callback_ = callback; }
+
+ void CaptureFrame() override {
+ DCHECK(callback_);
+ std::unique_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
+ callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
+ std::move(frame));
+ }
+
+ bool GetSourceList(SourceList* screens) override {
+ screens->push_back({0});
+ return true;
+ }
+
+ bool SelectSource(SourceId id) override {
+ EXPECT_EQ(0, id);
+ return true;
+ }
+
+ protected:
+ Callback* callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeScreenCapturer);
+};
+
+class FakeWindowCapturer : public webrtc::DesktopCapturer {
+ public:
+ FakeWindowCapturer() : callback_(nullptr) {}
+ ~FakeWindowCapturer() override {}
+
+ void SetWindowList(const SourceList& list) {
+ base::AutoLock lock(window_list_lock_);
+ window_list_ = list;
+ }
+
+ // Sets |value| thats going to be used to memset() content of the frames
+ // generated for |window_id|. By default generated frames are set to zeros.
+ void SetNextFrameValue(SourceId window_id, int8_t value) {
+ base::AutoLock lock(frame_values_lock_);
+ frame_values_[window_id] = value;
+ }
+
+ // webrtc::WindowCapturer implementation.
+ void Start(Callback* callback) override { callback_ = callback; }
+
+ void CaptureFrame() override {
+ DCHECK(callback_);
+
+ base::AutoLock lock(frame_values_lock_);
+
+ auto it = frame_values_.find(selected_window_id_);
+ int8_t value = (it != frame_values_.end()) ? it->second : 0;
+ std::unique_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
+ memset(frame->data(), value, frame->stride() * frame->size().height());
+ callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
+ std::move(frame));
+ }
+
+ bool GetSourceList(SourceList* windows) override {
+ base::AutoLock lock(window_list_lock_);
+ *windows = window_list_;
+ return true;
+ }
+
+ bool SelectSource(SourceId id) override {
+ selected_window_id_ = id;
+ return true;
+ }
+
+ bool FocusOnSelectedSource() override { return true; }
+
+ private:
+ Callback* callback_;
+ SourceList window_list_;
+ base::Lock window_list_lock_;
+
+ SourceId selected_window_id_;
+
+ // Frames to be captured per window.
+ std::map<SourceId, int8_t> frame_values_;
+ base::Lock frame_values_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeWindowCapturer);
+};
+
+} // namespace
+
+ACTION_P2(CheckListSize, model, expected_list_size) {
+ EXPECT_EQ(expected_list_size, model->GetSourceCount());
+}
+
+ACTION_P2(QuitRunLoop, task_runner, run_loop) {
+ task_runner->PostTask(FROM_HERE, run_loop->QuitWhenIdleClosure());
+}
+
+class NativeDesktopMediaListTest : public ChromeViewsTestBase {
+ public:
+ NativeDesktopMediaListTest() = default;
+
+ void TearDown() override {
+ for (size_t i = 0; i < desktop_widgets_.size(); i++)
+ desktop_widgets_[i].reset();
+
+ ChromeViewsTestBase::TearDown();
+ }
+
+ void AddNativeWindow(int id) {
+ webrtc::DesktopCapturer::Source window;
+ window.id = id;
+ window.title = "Test window";
+ window_list_.push_back(window);
+ }
+
+#if defined(USE_AURA)
+ std::unique_ptr<views::Widget> CreateDesktopWidget() {
+ std::unique_ptr<views::Widget> widget(new views::Widget);
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.accept_events = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.native_widget = new views::DesktopNativeWidgetAura(widget.get());
+ params.bounds = gfx::Rect(0, 0, 20, 20);
+ widget->Init(std::move(params));
+ widget->Show();
+ return widget;
+ }
+
+ void AddAuraWindow() {
+ webrtc::DesktopCapturer::Source window;
+ window.title = "Test window";
+
+ // Create a aura native widow through a widget.
+ desktop_widgets_.push_back(CreateDesktopWidget());
+ aura::WindowTreeHost* const host =
+ desktop_widgets_.back()->GetNativeWindow()->GetHost();
+ aura::Window* const aura_window = host->window();
+
+ // Get the native window's id.
+ gfx::AcceleratedWidget widget = host->GetAcceleratedWidget();
+#if defined(OS_WIN)
+ window.id = reinterpret_cast<DesktopMediaID::Id>(widget);
+#else
+ window.id = widget;
+#endif
+
+ // Get the aura window's id.
+ DesktopMediaID aura_id = DesktopMediaID::RegisterNativeWindow(
+ DesktopMediaID::TYPE_WINDOW, aura_window);
+ native_aura_id_map_[window.id] = aura_id.window_id;
+
+ window_list_.push_back(window);
+ }
+
+ void RemoveAuraWindow(int index) {
+ DCHECK_LT(index, static_cast<int>(desktop_widgets_.size()));
+
+ // Get the native window's id.
+ aura::Window* aura_window = desktop_widgets_[index]->GetNativeWindow();
+ gfx::AcceleratedWidget widget =
+ aura_window->GetHost()->GetAcceleratedWidget();
+#if defined(OS_WIN)
+ int native_id = reinterpret_cast<DesktopMediaID::Id>(widget);
+#else
+ int native_id = widget;
+#endif
+ // Remove the widget and associated aura window.
+ desktop_widgets_.erase(desktop_widgets_.begin() + index);
+ // Remove the aura window from the window list.
+ size_t i;
+ for (i = 0; i < window_list_.size(); i++) {
+ if (window_list_[i].id == native_id)
+ break;
+ }
+ DCHECK_LT(i, window_list_.size());
+ window_list_.erase(window_list_.begin() + i);
+ native_aura_id_map_.erase(native_id);
+ }
+
+#endif // defined(USE_AURA)
+
+ void AddWindowsAndVerify(bool has_view_dialog) {
+ window_capturer_ = new FakeWindowCapturer();
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ // Set up widows.
+ size_t aura_window_first_index = kDefaultWindowCount - kDefaultAuraCount;
+ for (size_t i = 0; i < kDefaultWindowCount; ++i) {
+ if (i < aura_window_first_index) {
+ AddNativeWindow(i);
+ } else {
+#if defined(USE_AURA)
+ AddAuraWindow();
+#endif
+ }
+ }
+
+ if (window_capturer_)
+ window_capturer_->SetWindowList(window_list_);
+
+ size_t window_count = kDefaultWindowCount;
+
+ // Set view dialog window ID as the first window id.
+ if (has_view_dialog) {
+ DesktopMediaID dialog_window_id(DesktopMediaID::TYPE_WINDOW,
+ window_list_[0].id);
+ model_->SetViewDialogWindowId(dialog_window_id);
+ window_count--;
+ aura_window_first_index--;
+ }
+
+ base::RunLoop run_loop;
+
+ {
+ testing::InSequence dummy;
+ for (size_t i = 0; i < window_count; ++i) {
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), i))
+ .WillOnce(CheckListSize(model_.get(), static_cast<int>(i + 1)));
+ }
+ for (size_t i = 0; i < window_count - 1; ++i) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i));
+ }
+ EXPECT_CALL(observer_,
+ OnSourceThumbnailChanged(model_.get(), window_count - 1))
+ .WillOnce(
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+ }
+ model_->StartUpdating(&observer_);
+ run_loop.Run();
+
+ for (size_t i = 0; i < window_count; ++i) {
+ EXPECT_EQ(model_->GetSource(i).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(i).name, base::UTF8ToUTF16("Test window"));
+ int index = has_view_dialog ? i + 1 : i;
+ int native_id = window_list_[index].id;
+ EXPECT_EQ(model_->GetSource(i).id.id, native_id);
+#if defined(USE_AURA)
+ if (i >= aura_window_first_index)
+ EXPECT_EQ(model_->GetSource(i).id.window_id,
+ native_aura_id_map_[native_id]);
+#endif
+ }
+ testing::Mock::VerifyAndClearExpectations(&observer_);
+ }
+
+ protected:
+ // Must be listed before |model_|, so it's destroyed last.
+ MockObserver observer_;
+
+ // Owned by |model_|;
+ FakeWindowCapturer* window_capturer_;
+
+ webrtc::DesktopCapturer::SourceList window_list_;
+ std::vector<std::unique_ptr<views::Widget>> desktop_widgets_;
+ std::map<DesktopMediaID::Id, DesktopMediaID::Id> native_aura_id_map_;
+ std::unique_ptr<NativeDesktopMediaList> model_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaListTest);
+};
+
+TEST_F(NativeDesktopMediaListTest, Windows) {
+ AddWindowsAndVerify(false);
+}
+
+TEST_F(NativeDesktopMediaListTest, ScreenOnly) {
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_SCREEN, std::make_unique<FakeScreenCapturer>());
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ base::RunLoop run_loop;
+
+ {
+ testing::InSequence dummy;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
+ .WillOnce(CheckListSize(model_.get(), 1));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+ }
+ model_->StartUpdating(&observer_);
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_SCREEN);
+ EXPECT_EQ(model_->GetSource(0).id.id, 0);
+}
+
+// Verifies that the window specified with SetViewDialogWindowId() is filtered
+// from the results.
+TEST_F(NativeDesktopMediaListTest, WindowFiltering) {
+ AddWindowsAndVerify(true);
+}
+
+TEST_F(NativeDesktopMediaListTest, AddNativeWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ const int index = kDefaultWindowCount;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ AddNativeWindow(index);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(index).id.id, index);
+}
+
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+TEST_F(NativeDesktopMediaListTest, AddAuraWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ const int index = kDefaultWindowCount;
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ AddAuraWindow();
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ int native_id = window_list_.back().id;
+ EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(index).id.id, native_id);
+ EXPECT_EQ(model_->GetSource(index).id.window_id,
+ native_aura_id_map_[native_id]);
+}
+#endif // defined(ENABLE_AURA_WINDOW_TESTS)
+
+TEST_F(NativeDesktopMediaListTest, RemoveNativeWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ window_list_.erase(window_list_.begin());
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+#if defined(ENABLE_AURA_WINDOW_TESTS)
+TEST_F(NativeDesktopMediaListTest, RemoveAuraWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ int aura_window_start_index = kDefaultWindowCount - kDefaultAuraCount;
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), aura_window_start_index))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ RemoveAuraWindow(0);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+#endif // defined(ENABLE_AURA_WINDOW_TESTS)
+
+TEST_F(NativeDesktopMediaListTest, RemoveAllWindows) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ testing::InSequence seq;
+ for (int i = 0; i < kDefaultWindowCount - 1; i++) {
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(CheckListSize(model_.get(), kDefaultWindowCount - i - 1));
+ }
+ EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), 0),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ window_list_.clear();
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+TEST_F(NativeDesktopMediaListTest, UpdateTitle) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceNameChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+
+ const std::string kTestTitle = "New Title";
+ window_list_[0].title = kTestTitle;
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).name, base::UTF8ToUTF16(kTestTitle));
+}
+
+TEST_F(NativeDesktopMediaListTest, UpdateThumbnail) {
+ AddWindowsAndVerify(false);
+
+ // Aura windows' thumbnails may unpredictably change over time.
+ for (size_t i = kDefaultWindowCount - kDefaultAuraCount;
+ i < kDefaultWindowCount; ++i) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i))
+ .Times(testing::AnyNumber());
+ }
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
+ .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
+
+ // Update frame for the window and verify that we get notification about it.
+ window_capturer_->SetNextFrameValue(0, 10);
+
+ run_loop.Run();
+}
+
+TEST_F(NativeDesktopMediaListTest, MoveWindow) {
+ AddWindowsAndVerify(false);
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceMoved(model_.get(), 1, 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+
+ std::swap(window_list_[0], window_list_[1]);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+}
+
+// This test verifies that webrtc::DesktopCapturer::CaptureFrame() is not
+// called when the thumbnail size is empty.
+TEST_F(NativeDesktopMediaListTest, EmptyThumbnail) {
+ window_capturer_ = new FakeWindowCapturer();
+ model_ = std::make_unique<NativeDesktopMediaList>(
+ DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
+ model_->SetThumbnailSize(gfx::Size());
+
+ // Set update period to reduce the time it takes to run tests.
+ model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
+
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
+ .WillOnce(
+ DoAll(CheckListSize(model_.get(), 1),
+ QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
+ // Called upon webrtc::DesktopCapturer::CaptureFrame() call.
+ ON_CALL(observer_, OnSourceThumbnailChanged(_, _))
+ .WillByDefault(testing::InvokeWithoutArgs([]() { NOTREACHED(); }));
+
+ model_->StartUpdating(&observer_);
+
+ AddNativeWindow(0);
+ window_capturer_->SetWindowList(window_list_);
+
+ run_loop.Run();
+
+ EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_WINDOW);
+ EXPECT_EQ(model_->GetSource(0).id.id, 0);
+ EXPECT_EQ(model_->GetSource(0).thumbnail.size(), gfx::Size());
+}
diff --git a/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
new file mode 100644
index 00000000000..c6303424cf2
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -0,0 +1,324 @@
+// Copyright 2015 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 "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/metrics/field_trial.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
+#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/permissions/permission_result.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+
+#if defined(OS_ANDROID)
+#include <vector>
+
+#include "chrome/browser/android/chrome_feature_list.h"
+#include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
+#include "chrome/browser/permissions/permission_uma_util.h"
+#include "chrome/browser/permissions/permission_util.h"
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_MACOSX)
+#include "base/metrics/histogram_macros.h"
+#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
+#endif
+
+using content::BrowserThread;
+
+using RepeatingMediaResponseCallback =
+ base::RepeatingCallback<void(const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui)>;
+
+#if defined(OS_MACOSX)
+using system_media_permissions::SystemPermission;
+#endif
+
+struct PermissionBubbleMediaAccessHandler::PendingAccessRequest {
+ PendingAccessRequest(const content::MediaStreamRequest& request,
+ RepeatingMediaResponseCallback callback)
+ : request(request), callback(callback) {}
+ ~PendingAccessRequest() {}
+
+ // TODO(gbillock): make the MediaStreamDevicesController owned by
+ // this object when we're using bubbles.
+ content::MediaStreamRequest request;
+ RepeatingMediaResponseCallback callback;
+};
+
+PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() {
+ // PermissionBubbleMediaAccessHandler should be created on UI thread.
+ // Otherwise, it will not receive
+ // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
+ // possible use after free.
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ notifications_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::NotificationService::AllSources());
+}
+
+PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() {}
+
+bool PermissionBubbleMediaAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+#if defined(OS_ANDROID)
+ return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+#else
+ return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
+#endif
+}
+
+bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(render_frame_host);
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ ContentSettingsType content_settings_type =
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
+ ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
+ : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA;
+
+ DCHECK(!security_origin.is_empty());
+ GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
+ PermissionManager* permission_manager = PermissionManager::Get(profile);
+ return permission_manager
+ ->GetPermissionStatusForFrame(content_settings_type,
+ render_frame_host, security_origin)
+ .content_setting == CONTENT_SETTING_ALLOW;
+}
+
+void PermissionBubbleMediaAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if defined(OS_ANDROID)
+ if (blink::IsScreenCaptureMediaType(request.video_type) &&
+ !base::FeatureList::IsEnabled(
+ chrome::android::kUserMediaScreenCapturing)) {
+ // If screen capturing isn't enabled on Android, we'll use "invalid state"
+ // as result, same as on desktop.
+ std::move(callback).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
+ return;
+ }
+#endif // defined(OS_ANDROID)
+
+ RequestsMap& requests_map = pending_requests_[web_contents];
+ requests_map.emplace(
+ next_request_id_++,
+ PendingAccessRequest(
+ request, base::AdaptCallbackForRepeating(std::move(callback))));
+
+ // If this is the only request then show the infobar.
+ if (requests_map.size() == 1)
+ ProcessQueuedAccessRequest(web_contents);
+}
+
+void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest(
+ content::WebContents* web_contents) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto it = pending_requests_.find(web_contents);
+
+ if (it == pending_requests_.end() || it->second.empty()) {
+ // Don't do anything if the tab was closed.
+ return;
+ }
+
+ DCHECK(!it->second.empty());
+
+ const int request_id = it->second.begin()->first;
+ const content::MediaStreamRequest& request =
+ it->second.begin()->second.request;
+#if defined(OS_ANDROID)
+ if (blink::IsScreenCaptureMediaType(request.video_type)) {
+ ScreenCaptureInfoBarDelegateAndroid::Create(
+ web_contents, request,
+ base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ base::Unretained(this), web_contents, request_id));
+ return;
+ }
+#endif
+
+ MediaStreamDevicesController::RequestPermissions(
+ request,
+ base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ base::Unretained(this), web_contents, request_id));
+}
+
+void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState(
+ int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (state != content::MEDIA_REQUEST_STATE_CLOSING)
+ return;
+
+ bool found = false;
+ for (auto requests_it = pending_requests_.begin();
+ requests_it != pending_requests_.end(); ++requests_it) {
+ RequestsMap& requests_map = requests_it->second;
+ for (RequestsMap::iterator it = requests_map.begin();
+ it != requests_map.end(); ++it) {
+ if (it->second.request.render_process_id == render_process_id &&
+ it->second.request.render_frame_id == render_frame_id &&
+ it->second.request.page_request_id == page_request_id) {
+ requests_map.erase(it);
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+}
+
+void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse(
+ content::WebContents* web_contents,
+ int request_id,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ auto request_maps_it = pending_requests_.find(web_contents);
+ if (request_maps_it == pending_requests_.end()) {
+ // WebContents has been destroyed. Don't need to do anything.
+ return;
+ }
+
+ RequestsMap& requests_map(request_maps_it->second);
+ if (requests_map.empty())
+ return;
+
+ auto request_it = requests_map.find(request_id);
+ DCHECK(request_it != requests_map.end());
+ if (request_it == requests_map.end())
+ return;
+
+ blink::mojom::MediaStreamRequestResult final_result = result;
+
+#if defined(OS_MACOSX)
+ // If the request was approved, ask for system permissions if needed, and run
+ // this function again when done.
+ if (result == blink::mojom::MediaStreamRequestResult::OK) {
+ const content::MediaStreamRequest& request = request_it->second.request;
+ if (request.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
+ const SystemPermission system_audio_permission =
+ system_media_permissions::CheckSystemAudioCapturePermission();
+ UMA_HISTOGRAM_ENUMERATION(
+ "Media.Audio.Capture.Mac.MicSystemPermission.UserMedia",
+ system_audio_permission);
+ if (system_audio_permission == SystemPermission::kNotDetermined) {
+ // Using WeakPtr since callback can come at any time and we might be
+ // destroyed.
+ system_media_permissions::RequestSystemAudioCapturePermisson(
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
+ result, std::move(ui)),
+ {content::BrowserThread::UI});
+ return;
+ } else if (system_audio_permission == SystemPermission::kRestricted ||
+ system_audio_permission == SystemPermission::kDenied) {
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ final_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ system_media_permissions::SystemAudioCapturePermissionBlocked();
+ } else {
+ DCHECK_EQ(system_audio_permission, SystemPermission::kAllowed);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ }
+ }
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
+ const SystemPermission system_video_permission =
+ system_media_permissions::CheckSystemVideoCapturePermission();
+ UMA_HISTOGRAM_ENUMERATION(
+ "Media.Video.Capture.Mac.CameraSystemPermission.UserMedia",
+ system_video_permission);
+ if (system_video_permission == SystemPermission::kNotDetermined) {
+ // Using WeakPtr since callback can come at any time and we might be
+ // destroyed.
+ system_media_permissions::RequestSystemVideoCapturePermisson(
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
+ weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
+ result, std::move(ui)),
+ {content::BrowserThread::UI});
+ return;
+ } else if (system_video_permission == SystemPermission::kRestricted ||
+ system_video_permission == SystemPermission::kDenied) {
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ final_result =
+ blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
+ system_media_permissions::SystemVideoCapturePermissionBlocked();
+ } else {
+ DCHECK_EQ(system_video_permission, SystemPermission::kAllowed);
+ content_settings::UpdateLocationBarUiForWebContents(web_contents);
+ }
+ }
+ }
+#endif // defined(OS_MACOSX)
+
+ RepeatingMediaResponseCallback callback =
+ std::move(request_it->second.callback);
+ requests_map.erase(request_it);
+
+ if (!requests_map.empty()) {
+ // Post a task to process next queued request. It has to be done
+ // asynchronously to make sure that calling infobar is not destroyed until
+ // after this function returns.
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest,
+ base::Unretained(this), web_contents));
+ }
+
+ std::move(callback).Run(devices, final_result, std::move(ui));
+}
+
+void PermissionBubbleMediaAccessHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+ pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
diff --git a/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
new file mode 100644
index 00000000000..53da4bb7a94
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
@@ -0,0 +1,67 @@
+// Copyright 2015 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 CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+// MediaAccessHandler for permission bubble requests.
+class PermissionBubbleMediaAccessHandler
+ : public MediaAccessHandler,
+ public content::NotificationObserver {
+ public:
+ PermissionBubbleMediaAccessHandler();
+ ~PermissionBubbleMediaAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+ void UpdateMediaRequestState(int render_process_id,
+ int render_frame_id,
+ int page_request_id,
+ blink::mojom::MediaStreamType stream_type,
+ content::MediaRequestState state) override;
+
+ private:
+ struct PendingAccessRequest;
+ using RequestsMap = std::map<int, PendingAccessRequest>;
+ using RequestsMaps = std::map<content::WebContents*, RequestsMap>;
+
+ void ProcessQueuedAccessRequest(content::WebContents* web_contents);
+ void OnAccessRequestResponse(content::WebContents* web_contents,
+ int request_id,
+ const blink::MediaStreamDevices& devices,
+ blink::mojom::MediaStreamRequestResult result,
+ std::unique_ptr<content::MediaStreamUI> ui);
+
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ int next_request_id_ = 0;
+ RequestsMaps pending_requests_;
+ content::NotificationRegistrar notifications_registrar_;
+
+ base::WeakPtrFactory<PermissionBubbleMediaAccessHandler> weak_factory_{this};
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_PERMISSION_BUBBLE_MEDIA_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/rtp_dump_type.h b/chromium/chrome/browser/media/webrtc/rtp_dump_type.h
new file mode 100644
index 00000000000..e4fc33dc8fd
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/rtp_dump_type.h
@@ -0,0 +1,12 @@
+// Copyright 2014 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 CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
+
+// The types of RTP header dumps: incoming packets only, outgoing packets only,
+// and both incoming and outgoing packets.
+enum RtpDumpType { RTP_DUMP_INCOMING, RTP_DUMP_OUTGOING, RTP_DUMP_BOTH };
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_RTP_DUMP_TYPE_H_
diff --git a/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
new file mode 100644
index 00000000000..d891933c061
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
@@ -0,0 +1,109 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
+
+#include "base/callback_helpers.h"
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/infobars/core/infobar.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "ui/base/l10n/l10n_util.h"
+
+// static
+void ScreenCaptureInfoBarDelegateAndroid::Create(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback) {
+ InfoBarService* infobar_service =
+ InfoBarService::FromWebContents(web_contents);
+
+ infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate>(
+ new ScreenCaptureInfoBarDelegateAndroid(web_contents, request,
+ std::move(callback)))));
+}
+
+ScreenCaptureInfoBarDelegateAndroid::ScreenCaptureInfoBarDelegateAndroid(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback)
+ : web_contents_(web_contents),
+ request_(request),
+ callback_(std::move(callback)) {
+ DCHECK_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ request.video_type);
+}
+
+ScreenCaptureInfoBarDelegateAndroid::~ScreenCaptureInfoBarDelegateAndroid() {
+ if (!callback_.is_null()) {
+ std::move(callback_).Run(
+ blink::MediaStreamDevices(),
+ blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+ nullptr);
+ }
+}
+
+infobars::InfoBarDelegate::InfoBarIdentifier
+ScreenCaptureInfoBarDelegateAndroid::GetIdentifier() const {
+ return SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID;
+}
+
+base::string16 ScreenCaptureInfoBarDelegateAndroid::GetMessageText() const {
+ return l10n_util::GetStringFUTF16(
+ IDS_MEDIA_CAPTURE_SCREEN_INFOBAR_TEXT,
+ url_formatter::FormatUrlForSecurityDisplay(request_.security_origin));
+}
+
+int ScreenCaptureInfoBarDelegateAndroid::GetIconId() const {
+ return IDR_ANDROID_INFOBAR_MEDIA_STREAM_SCREEN;
+}
+
+base::string16 ScreenCaptureInfoBarDelegateAndroid::GetButtonLabel(
+ InfoBarButton button) const {
+ return l10n_util::GetStringUTF16((button == BUTTON_OK) ? IDS_PERMISSION_ALLOW
+ : IDS_PERMISSION_DENY);
+}
+
+bool ScreenCaptureInfoBarDelegateAndroid::Accept() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::OK);
+ return true;
+}
+
+bool ScreenCaptureInfoBarDelegateAndroid::Cancel() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED);
+ return true;
+}
+
+void ScreenCaptureInfoBarDelegateAndroid::InfoBarDismissed() {
+ RunCallback(blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED);
+}
+
+void ScreenCaptureInfoBarDelegateAndroid::RunCallback(
+ blink::mojom::MediaStreamRequestResult result) {
+ DCHECK(!callback_.is_null());
+
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+ if (result == blink::mojom::MediaStreamRequestResult::OK) {
+ content::DesktopMediaID screen_id = content::DesktopMediaID(
+ content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
+ screen_id.ToString(), "Screen"));
+
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents_, devices);
+ }
+
+ std::move(callback_).Run(devices, result, std::move(ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h
new file mode 100644
index 00000000000..829212ce022
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h
@@ -0,0 +1,52 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
+
+#include "chrome/browser/media/media_access_handler.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+namespace content {
+class WebContents;
+}
+
+// An infobar that allows the user to share their screen with the current page.
+class ScreenCaptureInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
+ public:
+ // Creates a screen capture infobar and delegate and adds the infobar to the
+ // InfoBarService associated with |web_contents|.
+ static void Create(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+
+ private:
+ ScreenCaptureInfoBarDelegateAndroid(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback);
+ ~ScreenCaptureInfoBarDelegateAndroid() override;
+
+ // ConfirmInfoBarDelegate:
+ infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
+ base::string16 GetMessageText() const override;
+ int GetIconId() const override;
+ base::string16 GetButtonLabel(InfoBarButton button) const override;
+ bool Accept() override;
+ bool Cancel() override;
+ void InfoBarDismissed() override;
+
+ // Runs |callback_|, passing it the |result|, and (if permission was granted)
+ // the appropriate stream device and UI object for video capture.
+ void RunCallback(blink::mojom::MediaStreamRequestResult result);
+
+ content::WebContents* web_contents_;
+ const content::MediaStreamRequest request_;
+ content::MediaResponseCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureInfoBarDelegateAndroid);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SCREEN_CAPTURE_INFOBAR_DELEGATE_ANDROID_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
new file mode 100644
index 00000000000..73b04535322
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
@@ -0,0 +1,57 @@
+// Copyright 2019 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 CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+
+#include "base/callback_forward.h"
+
+namespace base {
+class TaskTraits;
+}
+
+namespace system_media_permissions {
+
+class MediaAuthorizationWrapper;
+
+// System permission state. These are also used in stats - do not remove or
+// re-arrange the values.
+enum class SystemPermission {
+ kNotDetermined = 0,
+ kRestricted = 1,
+ kDenied = 2,
+ kAllowed = 3,
+ kMaxValue = kAllowed
+};
+
+// On 10.14 and above: returns the system permission.
+// On 10.13 and below: returns |SystemPermission::kAllowed|, since there are no
+// system media capture permissions.
+SystemPermission CheckSystemAudioCapturePermission();
+SystemPermission CheckSystemVideoCapturePermission();
+
+// On 10.15 and above: returns the system permission.
+// On 10.14 and below: returns |SystemPermission::kAllowed|, since there are no
+// system screen capture permissions.
+SystemPermission CheckSystemScreenCapturePermission();
+
+// On 10.14 and above: requests system permission and returns. When requesting
+// permission, the OS will show a user dialog and respond asynchronously. At the
+// response, |callback| is posted with |traits|.
+// On 10.13 and below: posts |callback| with |traits|, since there are no system
+// media capture permissions.
+// Note: these functions should really never be called for pre-10.14 since one
+// would normally check the permission first, and only call this if it's not
+// determined.
+void RequestSystemAudioCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits);
+void RequestSystemVideoCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits);
+
+// Sets the wrapper object for OS calls. For test mocking purposes.
+void SetMediaAuthorizationWrapperForTesting(MediaAuthorizationWrapper* wrapper);
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
new file mode 100644
index 00000000000..66db2533c01
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
@@ -0,0 +1,246 @@
+// Copyright 2019 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.
+//
+// Authorization functions and types are available on 10.14+.
+// To avoid availability compile errors, use performSelector invocation of
+// functions, NSInteger instead of AVAuthorizationStatus, and NSString* instead
+// of AVMediaType.
+// The AVAuthorizationStatus enum is defined as follows (10.14 SDK):
+// AVAuthorizationStatusNotDetermined = 0,
+// AVAuthorizationStatusRestricted = 1,
+// AVAuthorizationStatusDenied = 2,
+// AVAuthorizationStatusAuthorized = 3,
+// TODO(grunell): Call functions directly and use AVAuthorizationStatus once
+// we use the 10.14 SDK.
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/media/webrtc/media_authorization_wrapper_mac.h"
+#include "chrome/common/chrome_features.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/base/media_switches.h"
+
+namespace system_media_permissions {
+
+namespace {
+
+bool UsingFakeMediaDevices() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream);
+}
+
+// Pointer to OS call wrapper that tests can set.
+MediaAuthorizationWrapper* g_media_authorization_wrapper_for_tests = nullptr;
+
+// Implementation of OS call wrapper that does the actual OS calls.
+class MediaAuthorizationWrapperImpl : public MediaAuthorizationWrapper {
+ public:
+ MediaAuthorizationWrapperImpl() = default;
+ ~MediaAuthorizationWrapperImpl() final = default;
+
+ NSInteger AuthorizationStatusForMediaType(NSString* media_type) final {
+ if (@available(macOS 10.14, *)) {
+ AVCaptureDevice* target = [AVCaptureDevice class];
+ SEL selector = @selector(authorizationStatusForMediaType:);
+ NSInteger auth_status = 0;
+ if ([target respondsToSelector:selector]) {
+ auth_status =
+ (NSInteger)[target performSelector:selector withObject:media_type];
+ } else {
+ DLOG(WARNING)
+ << "authorizationStatusForMediaType could not be executed";
+ }
+ return auth_status;
+ }
+
+ NOTREACHED();
+ return 0;
+ }
+
+ void RequestAccessForMediaType(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) final {
+ if (@available(macOS 10.14, *)) {
+ AVCaptureDevice* target = [AVCaptureDevice class];
+ SEL selector = @selector(requestAccessForMediaType:completionHandler:);
+ if ([target respondsToSelector:selector]) {
+ [target performSelector:selector
+ withObject:media_type
+ withObject:^(BOOL granted) {
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }];
+ } else {
+ DLOG(WARNING) << "requestAccessForMediaType could not be executed";
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+ } else {
+ NOTREACHED();
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaAuthorizationWrapperImpl);
+};
+
+MediaAuthorizationWrapper& GetMediaAuthorizationWrapper() {
+ if (g_media_authorization_wrapper_for_tests)
+ return *g_media_authorization_wrapper_for_tests;
+
+ static base::NoDestructor<MediaAuthorizationWrapperImpl>
+ media_authorization_wrapper;
+ return *media_authorization_wrapper;
+}
+
+NSInteger MediaAuthorizationStatus(NSString* media_type) {
+ if (@available(macOS 10.14, *)) {
+ return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType(
+ media_type);
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+SystemPermission CheckSystemMediaCapturePermission(NSString* media_type) {
+ if (UsingFakeMediaDevices())
+ return SystemPermission::kAllowed;
+
+ if (@available(macOS 10.14, *)) {
+ NSInteger auth_status = MediaAuthorizationStatus(media_type);
+ switch (auth_status) {
+ case 0:
+ return SystemPermission::kNotDetermined;
+ case 1:
+ return SystemPermission::kRestricted;
+ case 2:
+ return SystemPermission::kDenied;
+ case 3:
+ return SystemPermission::kAllowed;
+ default:
+ NOTREACHED();
+ return SystemPermission::kAllowed;
+ }
+ }
+
+ // On pre-10.14, there are no system permissions, so we return allowed.
+ return SystemPermission::kAllowed;
+}
+
+// Use RepeatingCallback since it must be copyable for use in the block. It's
+// only called once though.
+void RequestSystemMediaCapturePermission(NSString* media_type,
+ base::RepeatingClosure callback,
+ const base::TaskTraits& traits) {
+ if (UsingFakeMediaDevices()) {
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ return;
+ }
+
+ if (@available(macOS 10.14, *)) {
+ GetMediaAuthorizationWrapper().RequestAccessForMediaType(
+ media_type, std::move(callback), traits);
+ } else {
+ NOTREACHED();
+ // Should never happen since for pre-10.14 system permissions don't exist
+ // and checking them in CheckSystemAudioCapturePermission() will always
+ // return allowed, and this function should not be called.
+ base::PostTask(FROM_HERE, traits, std::move(callback));
+ }
+}
+
+// Heuristic to check screen capture permission on macOS 10.15.
+// Screen Capture is considered allowed if the name of at least one normal
+// or dock window running on another process is visible.
+// See https://crbug.com/993692.
+bool IsScreenCaptureAllowed() {
+ if (@available(macOS 10.15, *)) {
+ if (!base::FeatureList::IsEnabled(
+ features::kMacSystemScreenCapturePermissionCheck)) {
+ return true;
+ }
+
+ base::ScopedCFTypeRef<CFArrayRef> window_list(
+ CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID));
+ int current_pid = [[NSProcessInfo processInfo] processIdentifier];
+ for (NSDictionary* window in base::mac::CFToNSCast(window_list.get())) {
+ NSNumber* window_pid =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowOwnerPID)];
+ if (!window_pid || [window_pid integerValue] == current_pid)
+ continue;
+
+ NSString* window_name =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowName)];
+ if (!window_name)
+ continue;
+
+ NSNumber* layer =
+ [window objectForKey:base::mac::CFToNSCast(kCGWindowLayer)];
+ if (!layer)
+ continue;
+
+ NSInteger layer_integer = [layer integerValue];
+ if (layer_integer == CGWindowLevelForKey(kCGNormalWindowLevelKey) ||
+ layer_integer == CGWindowLevelForKey(kCGDockWindowLevelKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Screen capture is always allowed in older macOS versions.
+ return true;
+}
+
+} // namespace
+
+SystemPermission CheckSystemAudioCapturePermission() {
+ return CheckSystemMediaCapturePermission(AVMediaTypeAudio);
+}
+
+SystemPermission CheckSystemVideoCapturePermission() {
+ return CheckSystemMediaCapturePermission(AVMediaTypeVideo);
+}
+
+SystemPermission CheckSystemScreenCapturePermission() {
+ return IsScreenCaptureAllowed() ? SystemPermission::kAllowed
+ : SystemPermission::kDenied;
+}
+
+void RequestSystemAudioCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits) {
+ RequestSystemMediaCapturePermission(
+ AVMediaTypeAudio, base::AdaptCallbackForRepeating(std::move(callback)),
+ traits);
+}
+
+void RequestSystemVideoCapturePermisson(base::OnceClosure callback,
+ const base::TaskTraits& traits) {
+ RequestSystemMediaCapturePermission(
+ AVMediaTypeVideo, base::AdaptCallbackForRepeating(std::move(callback)),
+ traits);
+}
+
+void SetMediaAuthorizationWrapperForTesting(
+ MediaAuthorizationWrapper* wrapper) {
+ CHECK(!g_media_authorization_wrapper_for_tests);
+ g_media_authorization_wrapper_for_tests = wrapper;
+}
+
+} // namespace system_media_permissions
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h
new file mode 100644
index 00000000000..9e1269beb47
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h
@@ -0,0 +1,36 @@
+// Copyright 2019 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.
+//
+// Functions for handling stats for system media permissions (camera,
+// microphone).
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+
+class PrefRegistrySimple;
+
+namespace system_media_permissions {
+
+// Registers preferences used for system media permissions stats.
+void RegisterSystemMediaPermissionStatesPrefs(PrefRegistrySimple* registry);
+
+// Logs stats for system media permissions. Called once per browser session, at
+// browser start.
+void LogSystemMediaPermissionsStartupStats();
+
+// Called when a system permission goes from "not determined" to another state.
+// The new permission is logged as startup state.
+void SystemAudioCapturePermissionDetermined(SystemPermission permission);
+void SystemVideoCapturePermissionDetermined(SystemPermission permission);
+
+// Called when a system permission was requested but was blocked. Information
+// stored is later used when logging stats at startup.
+void SystemAudioCapturePermissionBlocked();
+void SystemVideoCapturePermissionBlocked();
+
+} // namespace system_media_permissions
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_STATS_MAC_H_
diff --git a/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm
new file mode 100644
index 00000000000..8c85e68cb36
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.mm
@@ -0,0 +1,196 @@
+// Copyright 2019 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 "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace system_media_permissions {
+
+namespace {
+
+const char kSystemPermissionMicFirstBlockedTimePref[] =
+ "system_permission.mic.first_blocked_time";
+const char kSystemPermissionMicLastBlockedTimePref[] =
+ "system_permission.mic.last_blocked_time";
+const char kSystemPermissionCameraFirstBlockedTimePref[] =
+ "system_permission.camera.first_blocked_time";
+const char kSystemPermissionCameraLastBlockedTimePref[] =
+ "system_permission.camera.last_blocked_time";
+
+void LogStartupMicSystemPermission(SystemPermission permission) {
+ base::UmaHistogramEnumeration(
+ "Media.Audio.Capture.Mac.MicSystemPermission.Startup", permission);
+}
+
+void LogStartupCameraSystemPermission(SystemPermission permission) {
+ base::UmaHistogramEnumeration(
+ "Media.Video.Capture.Mac.CameraSystemPermission.Startup", permission);
+}
+
+void MaybeLogAdditionalMicSystemPermissionStats(SystemPermission permission) {
+ PrefService* prefs = g_browser_process->local_state();
+
+ if (!prefs->HasPrefPath(kSystemPermissionMicFirstBlockedTimePref)) {
+ DCHECK(!prefs->HasPrefPath(kSystemPermissionMicLastBlockedTimePref));
+ return;
+ }
+
+ // A pref exists, so there was a failure accessing the mic due to blocked
+ // system permission before the last restart. Log additional stats.
+
+ DCHECK(prefs->HasPrefPath(kSystemPermissionMicLastBlockedTimePref));
+ base::UmaHistogramEnumeration("Media.Audio.Capture.Mac.MicSystemPermission."
+ "StartupAfterFailure",
+ permission);
+
+ // If the state has changed to allowed, log the time it took since first
+ // and last failure before restart. Check for positive time delta, since
+ // the system clock may change at any time.
+ if (permission == SystemPermission::kAllowed) {
+ base::Time stored_time =
+ prefs->GetTime(kSystemPermissionMicFirstBlockedTimePref);
+ base::TimeDelta time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Audio.Capture.Mac.MicSystemPermission."
+ "FixedTime.SinceFirstFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+
+ stored_time = prefs->GetTime(kSystemPermissionMicLastBlockedTimePref);
+ time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Audio.Capture.Mac.MicSystemPermission."
+ "FixedTime.SinceLastFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+ }
+
+ prefs->ClearPref(kSystemPermissionMicFirstBlockedTimePref);
+ prefs->ClearPref(kSystemPermissionMicLastBlockedTimePref);
+}
+
+void MaybeLogAdditionalCameraSystemPermissionStats(
+ SystemPermission permission) {
+ PrefService* prefs = g_browser_process->local_state();
+
+ if (!prefs->HasPrefPath(kSystemPermissionCameraFirstBlockedTimePref)) {
+ DCHECK(!prefs->HasPrefPath(kSystemPermissionCameraLastBlockedTimePref));
+ return;
+ }
+
+ // A pref exists, so there was a failure accessing the camera due to blocked
+ // system permission before the last restart. Log additional stats.
+
+ DCHECK(prefs->HasPrefPath(kSystemPermissionCameraLastBlockedTimePref));
+ base::UmaHistogramEnumeration(
+ "Media.Video.Capture.Mac.CameraSystemPermission."
+ "StartupAfterFailure",
+ permission);
+
+ // If the state has changed to allowed, log the time it took since first
+ // and last failure before restart. Check for positive time delta, since
+ // the system clock may change at any time.
+ if (permission == SystemPermission::kAllowed) {
+ base::Time stored_time =
+ prefs->GetTime(kSystemPermissionCameraFirstBlockedTimePref);
+ base::TimeDelta time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Video.Capture.Mac.CameraSystemPermission.FixedTime."
+ "SinceFirstFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+
+ stored_time = prefs->GetTime(kSystemPermissionCameraLastBlockedTimePref);
+ time_delta = base::Time::Now() - stored_time;
+ if (time_delta > base::TimeDelta()) {
+ base::UmaHistogramCustomTimes(
+ "Media.Video.Capture.Mac.CameraSystemPermission.FixedTime."
+ "SinceLastFailure",
+ time_delta, base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(1), 50);
+ }
+ }
+
+ prefs->ClearPref(kSystemPermissionCameraFirstBlockedTimePref);
+ prefs->ClearPref(kSystemPermissionCameraLastBlockedTimePref);
+}
+
+} // namespace
+
+void RegisterSystemMediaPermissionStatesPrefs(PrefRegistrySimple* registry) {
+ if (@available(macOS 10.14, *)) {
+ registry->RegisterTimePref(kSystemPermissionMicFirstBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionMicLastBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionCameraFirstBlockedTimePref,
+ base::Time());
+ registry->RegisterTimePref(kSystemPermissionCameraLastBlockedTimePref,
+ base::Time());
+ }
+}
+
+void LogSystemMediaPermissionsStartupStats() {
+ if (@available(macOS 10.14, *)) {
+ const SystemPermission audio_permission =
+ CheckSystemAudioCapturePermission();
+ LogStartupMicSystemPermission(audio_permission);
+ MaybeLogAdditionalMicSystemPermissionStats(audio_permission);
+
+ const SystemPermission video_permission =
+ CheckSystemVideoCapturePermission();
+ LogStartupCameraSystemPermission(video_permission);
+ MaybeLogAdditionalCameraSystemPermissionStats(video_permission);
+ } // (@available(macOS 10.14, *))
+}
+
+void SystemAudioCapturePermissionDetermined(SystemPermission permission) {
+ if (@available(macOS 10.14, *)) {
+ DCHECK_NE(permission, SystemPermission::kNotDetermined);
+ LogStartupMicSystemPermission(permission);
+ }
+}
+
+void SystemVideoCapturePermissionDetermined(SystemPermission permission) {
+ if (@available(macOS 10.14, *)) {
+ DCHECK_NE(permission, SystemPermission::kNotDetermined);
+ LogStartupCameraSystemPermission(permission);
+ }
+}
+
+void SystemAudioCapturePermissionBlocked() {
+ if (@available(macOS 10.14, *)) {
+ PrefService* prefs = g_browser_process->local_state();
+ if (!prefs->HasPrefPath(kSystemPermissionMicFirstBlockedTimePref)) {
+ prefs->SetTime(kSystemPermissionMicFirstBlockedTimePref,
+ base::Time::Now());
+ }
+ prefs->SetTime(kSystemPermissionMicLastBlockedTimePref, base::Time::Now());
+ }
+}
+
+void SystemVideoCapturePermissionBlocked() {
+ if (@available(macOS 10.14, *)) {
+ PrefService* prefs = g_browser_process->local_state();
+ if (!prefs->HasPrefPath(kSystemPermissionCameraFirstBlockedTimePref)) {
+ prefs->SetTime(kSystemPermissionCameraFirstBlockedTimePref,
+ base::Time::Now());
+ }
+ prefs->SetTime(kSystemPermissionCameraLastBlockedTimePref,
+ base::Time::Now());
+ }
+}
+
+} // namespace system_media_permissions
diff --git a/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc
new file mode 100644
index 00000000000..8a3fe7e1918
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.cc
@@ -0,0 +1,92 @@
+// Copyright 2015 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 "chrome/browser/media/webrtc/tab_capture_access_handler.h"
+
+#include <utility>
+
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+TabCaptureAccessHandler::TabCaptureAccessHandler() {
+}
+
+TabCaptureAccessHandler::~TabCaptureAccessHandler() {
+}
+
+bool TabCaptureAccessHandler::SupportsStreamType(
+ content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return type == blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
+ type == blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
+}
+
+bool TabCaptureAccessHandler::CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) {
+ return false;
+}
+
+void TabCaptureAccessHandler::HandleRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) {
+ blink::MediaStreamDevices devices;
+ std::unique_ptr<content::MediaStreamUI> ui;
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ extensions::TabCaptureRegistry* tab_capture_registry =
+ extensions::TabCaptureRegistry::Get(profile);
+ if (!tab_capture_registry) {
+ NOTREACHED();
+ std::move(callback).Run(
+ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
+ std::move(ui));
+ return;
+ }
+ // |extension| may be null if the tabCapture starts with
+ // tabCapture.getMediaStreamId().
+ // TODO(crbug.com/831722): Deprecate tabCaptureRegistry soon.
+ const std::string extension_id = extension ? extension->id() : "";
+ const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
+ request.render_process_id, request.render_frame_id, extension_id);
+
+ if (request.audio_type ==
+ blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE &&
+ tab_capture_allowed) {
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE, std::string(),
+ std::string()));
+ }
+
+ if (request.video_type ==
+ blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE &&
+ tab_capture_allowed) {
+ devices.push_back(blink::MediaStreamDevice(
+ blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE, std::string(),
+ std::string()));
+ }
+
+ if (!devices.empty()) {
+ ui = MediaCaptureDevicesDispatcher::GetInstance()
+ ->GetMediaStreamCaptureIndicator()
+ ->RegisterMediaStream(web_contents, devices);
+ }
+ UpdateExtensionTrusted(request, extension);
+ std::move(callback).Run(
+ devices,
+ devices.empty() ? blink::mojom::MediaStreamRequestResult::INVALID_STATE
+ : blink::mojom::MediaStreamRequestResult::OK,
+ std::move(ui));
+}
diff --git a/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h
new file mode 100644
index 00000000000..26fd89ad892
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_capture_access_handler.h
@@ -0,0 +1,31 @@
+// Copyright 2015 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 CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
+
+#include "chrome/browser/media/capture_access_handler_base.h"
+
+// MediaAccessHandler for TabCapture API.
+class TabCaptureAccessHandler : public CaptureAccessHandlerBase {
+ public:
+ TabCaptureAccessHandler();
+ ~TabCaptureAccessHandler() override;
+
+ // MediaAccessHandler implementation.
+ bool SupportsStreamType(content::WebContents* web_contents,
+ const blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ bool CheckMediaAccessPermission(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& security_origin,
+ blink::mojom::MediaStreamType type,
+ const extensions::Extension* extension) override;
+ void HandleRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ content::MediaResponseCallback callback,
+ const extensions::Extension* extension) override;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc
new file mode 100644
index 00000000000..00df7fa7d40
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.cc
@@ -0,0 +1,163 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/hash/hash.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "components/favicon/content/content_favicon_driver.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "media/base/video_util.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/gfx/image/image.h"
+
+using content::BrowserThread;
+using content::DesktopMediaID;
+
+namespace {
+
+gfx::ImageSkia CreateEnclosedFaviconImage(gfx::Size size,
+ const gfx::ImageSkia& favicon) {
+ DCHECK_GE(size.width(), gfx::kFaviconSize);
+ DCHECK_GE(size.height(), gfx::kFaviconSize);
+
+ // Create a bitmap.
+ SkBitmap result;
+ result.allocN32Pixels(size.width(), size.height(), false);
+ SkCanvas canvas(result);
+ canvas.clear(SK_ColorTRANSPARENT);
+
+ // Draw the favicon image into the center of result image. If the favicon is
+ // too big, scale it down.
+ gfx::Size fill_size = favicon.size();
+ if (result.width() < favicon.width() || result.height() < favicon.height())
+ fill_size = media::ScaleSizeToFitWithinTarget(favicon.size(), size);
+
+ gfx::Rect center_rect(result.width(), result.height());
+ center_rect.ClampToCenteredSize(fill_size);
+ SkRect dest_rect =
+ SkRect::MakeLTRB(center_rect.x(), center_rect.y(), center_rect.right(),
+ center_rect.bottom());
+ canvas.drawBitmapRect(*favicon.bitmap(), dest_rect, nullptr);
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
+
+// Update the list once per second.
+const int kDefaultTabDesktopMediaListUpdatePeriod = 1000;
+
+} // namespace
+
+TabDesktopMediaList::TabDesktopMediaList()
+ : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
+ kDefaultTabDesktopMediaListUpdatePeriod)) {
+ type_ = DesktopMediaID::TYPE_WEB_CONTENTS;
+ thumbnail_task_runner_ = base::CreateSequencedTaskRunner(
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE});
+}
+
+TabDesktopMediaList::~TabDesktopMediaList() {}
+
+void TabDesktopMediaList::Refresh(bool update_thumnails) {
+ DCHECK(can_refresh());
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
+ if (!profile) {
+ OnRefreshComplete();
+ return;
+ }
+
+ std::vector<Browser*> browsers;
+ for (auto* browser : *BrowserList::GetInstance()) {
+ if (browser->profile()->GetOriginalProfile() ==
+ profile->GetOriginalProfile()) {
+ browsers.push_back(browser);
+ }
+ }
+
+ ImageHashesMap new_favicon_hashes;
+ std::vector<SourceDescription> sources;
+ std::map<base::TimeTicks, SourceDescription> tab_map;
+ std::vector<std::pair<DesktopMediaID, gfx::ImageSkia>> favicon_pairs;
+
+ // Enumerate all tabs with their titles and favicons for a user profile.
+ for (auto* browser : browsers) {
+ const TabStripModel* tab_strip_model = browser->tab_strip_model();
+ DCHECK(tab_strip_model);
+
+ for (int i = 0; i < tab_strip_model->count(); i++) {
+ // Create id for tab.
+ content::WebContents* contents = tab_strip_model->GetWebContentsAt(i);
+ DCHECK(contents);
+ content::RenderFrameHost* main_frame = contents->GetMainFrame();
+ DCHECK(main_frame);
+ DesktopMediaID media_id(
+ DesktopMediaID::TYPE_WEB_CONTENTS, DesktopMediaID::kNullId,
+ content::WebContentsMediaCaptureId(main_frame->GetProcess()->GetID(),
+ main_frame->GetRoutingID()));
+
+ // Get tab's last active time stamp.
+ const base::TimeTicks t = contents->GetLastActiveTime();
+ tab_map.insert(
+ std::make_pair(t, SourceDescription(media_id, contents->GetTitle())));
+
+ // Get favicon for tab.
+ favicon::FaviconDriver* favicon_driver =
+ favicon::ContentFaviconDriver::FromWebContents(contents);
+ if (!favicon_driver)
+ continue;
+
+ gfx::Image favicon = favicon_driver->GetFavicon();
+ if (favicon.IsEmpty())
+ continue;
+
+ // Only new or changed favicon need update.
+ new_favicon_hashes[media_id] = GetImageHash(favicon);
+ if (!favicon_hashes_.count(media_id) ||
+ (favicon_hashes_[media_id] != new_favicon_hashes[media_id])) {
+ gfx::ImageSkia image = favicon.AsImageSkia();
+ image.MakeThreadSafe();
+ favicon_pairs.push_back(std::make_pair(media_id, image));
+ }
+ }
+ }
+ favicon_hashes_ = new_favicon_hashes;
+
+ // Sort tab sources by time. Most recent one first. Then update sources list.
+ for (auto it = tab_map.rbegin(); it != tab_map.rend(); ++it)
+ sources.push_back(it->second);
+
+ UpdateSourcesList(sources);
+
+ for (const auto& it : favicon_pairs) {
+ // Create a thumbail in a different thread and update the thumbnail in
+ // current thread.
+ base::PostTaskAndReplyWithResult(
+ thumbnail_task_runner_.get(), FROM_HERE,
+ base::Bind(&CreateEnclosedFaviconImage, thumbnail_size_, it.second),
+ base::Bind(&TabDesktopMediaList::UpdateSourceThumbnail,
+ weak_factory_.GetWeakPtr(), it.first));
+ }
+
+ // OnRefreshComplete() needs to be called after all calls for
+ // UpdateSourceThumbnail() have done. Therefore, a DoNothing task is posted to
+ // the same sequenced task runner that CreateEnlargedFaviconImag() is posted.
+ thumbnail_task_runner_.get()->PostTaskAndReply(
+ FROM_HERE, base::DoNothing(),
+ base::BindOnce(&TabDesktopMediaList::OnRefreshComplete,
+ weak_factory_.GetWeakPtr()));
+}
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h
new file mode 100644
index 00000000000..7454d5c364a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list.h
@@ -0,0 +1,31 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
+
+#include "chrome/browser/media/webrtc/desktop_media_list_base.h"
+
+// Implementation of DesktopMediaList that shows tab/WebContents.
+class TabDesktopMediaList : public DesktopMediaListBase {
+ public:
+ TabDesktopMediaList();
+ ~TabDesktopMediaList() override;
+
+ private:
+ typedef std::map<content::DesktopMediaID, uint32_t> ImageHashesMap;
+
+ void Refresh(bool update_thumnails) override;
+
+ ImageHashesMap favicon_hashes_;
+
+ // Task runner used for the |worker_|.
+ scoped_refptr<base::SequencedTaskRunner> thumbnail_task_runner_;
+
+ base::WeakPtrFactory<TabDesktopMediaList> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaList);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TAB_DESKTOP_MEDIA_LIST_H_
diff --git a/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc
new file mode 100644
index 00000000000..b2d850b6c42
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/tab_desktop_media_list_unittest.cc
@@ -0,0 +1,372 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
+#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#endif // defined(OS_CHROMEOS)
+
+using content::WebContents;
+using content::WebContentsTester;
+
+namespace {
+
+constexpr int kDefaultSourceCount = 2;
+constexpr int kThumbnailSize = 50;
+
+class UnittestProfileManager : public ::ProfileManagerWithoutInit {
+ public:
+ explicit UnittestProfileManager(const base::FilePath& user_data_dir)
+ : ::ProfileManagerWithoutInit(user_data_dir) {}
+
+ protected:
+ std::unique_ptr<Profile> CreateProfileHelper(
+ const base::FilePath& path) override {
+ if (!base::PathExists(path) && !base::CreateDirectory(path))
+ return nullptr;
+ return std::make_unique<TestingProfile>(path);
+ }
+};
+
+// Create a greyscale image with certain size and grayscale value.
+gfx::Image CreateGrayscaleImage(gfx::Size size, uint8_t greyscale_value) {
+ SkBitmap result;
+ result.allocN32Pixels(size.width(), size.height(), true);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+
+ // Set greyscale value for all pixels.
+ for (int y = 0; y < result.height(); ++y) {
+ for (int x = 0; x < result.width(); ++x) {
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel()] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 1] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 2] =
+ greyscale_value;
+ pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
+ 0xff;
+ }
+ }
+
+ return gfx::Image::CreateFrom1xBitmap(result);
+}
+
+} // namespace
+
+class MockObserver : public DesktopMediaListObserver {
+ public:
+ MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
+ MOCK_METHOD3(OnSourceMoved,
+ void(DesktopMediaList* list, int old_index, int new_index));
+ MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
+ MOCK_METHOD2(OnSourceThumbnailChanged,
+ void(DesktopMediaList* list, int index));
+ MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
+
+ void VerifyAndClearExpectations() {
+ testing::Mock::VerifyAndClearExpectations(this);
+ }
+};
+
+ACTION_P2(CheckListSize, list, expected_list_size) {
+ EXPECT_EQ(expected_list_size, list->GetSourceCount());
+}
+
+ACTION(QuitMessageLoop) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
+}
+
+class TabDesktopMediaListTest : public testing::Test {
+ protected:
+ TabDesktopMediaListTest()
+ : local_state_(TestingBrowserProcess::GetGlobal()) {}
+
+ void AddWebcontents(int favicon_greyscale) {
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ std::unique_ptr<WebContents> contents(
+ content::WebContentsTester::CreateTestWebContents(
+ profile_, content::SiteInstance::Create(profile_)));
+ ASSERT_TRUE(contents);
+
+ WebContentsTester::For(contents.get())
+ ->SetLastActiveTime(base::TimeTicks::Now());
+
+ // Get or create the transient NavigationEntry and add a title and a
+ // favicon to it.
+ content::NavigationEntry* entry =
+ contents->GetController().GetTransientEntry();
+ if (!entry) {
+ std::unique_ptr<content::NavigationEntry> entry_new =
+ content::NavigationController::CreateNavigationEntry(
+ GURL("chrome://blank"), content::Referrer(), base::nullopt,
+ ui::PAGE_TRANSITION_LINK, false, std::string(), profile_,
+ nullptr /* blob_url_loader_factory */);
+
+ contents->GetController().SetTransientEntry(std::move(entry_new));
+ entry = contents->GetController().GetTransientEntry();
+ }
+
+ contents->UpdateTitleForEntry(entry, base::ASCIIToUTF16("Test tab"));
+
+ content::FaviconStatus favicon_info;
+ favicon_info.image =
+ CreateGrayscaleImage(gfx::Size(10, 10), favicon_greyscale);
+ entry->GetFavicon() = favicon_info;
+
+ manually_added_web_contents_.push_back(contents.get());
+ tab_strip_model->AppendWebContents(std::move(contents), true);
+ }
+
+ void SetUp() override {
+ manually_added_web_contents_.clear();
+ rvh_test_enabler_.reset(new content::RenderViewHostTestEnabler());
+ // Create a new temporary directory, and store the path.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ TestingBrowserProcess::GetGlobal()->SetProfileManager(
+ new UnittestProfileManager(temp_dir_.GetPath()));
+
+#if defined(OS_CHROMEOS)
+ base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+ cl->AppendSwitch(switches::kTestType);
+#endif
+
+ // Create profile.
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ ASSERT_TRUE(profile_manager);
+
+ profile_ = profile_manager->GetLastUsedProfileAllowedByPolicy();
+ ASSERT_TRUE(profile_);
+
+ // Create browser.
+ Browser::CreateParams profile_params(profile_, true);
+ browser_ = CreateBrowserWithTestWindowForParams(&profile_params);
+ ASSERT_TRUE(browser_);
+ for (int i = 0; i < kDefaultSourceCount; i++) {
+ AddWebcontents(i + 1);
+ }
+ }
+
+ void TearDown() override {
+ // TODO(erikchen): Tearing down the TabStripModel should just delete all its
+ // owned WebContents. Then |manually_added_web_contents_| won't be
+ // necessary. https://crbug.com/832879.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ for (WebContents* contents : manually_added_web_contents_) {
+ tab_strip_model->DetachWebContentsAt(
+ tab_strip_model->GetIndexOfWebContents(contents));
+ }
+ manually_added_web_contents_.clear();
+
+ browser_.reset();
+ TestingBrowserProcess::GetGlobal()->SetProfileManager(NULL);
+ base::RunLoop().RunUntilIdle();
+ rvh_test_enabler_.reset();
+ }
+
+ void CreateDefaultList() {
+ list_.reset(new TabDesktopMediaList());
+ list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
+
+ // Set update period to reduce the time it takes to run tests.
+ // >0 to avoid unit test failure.
+ list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
+ }
+
+ void InitializeAndVerify() {
+ CreateDefaultList();
+
+ // The tabs in media source list are sorted in decreasing time order. The
+ // latest one is listed first. However, tabs are added to TabStripModel in
+ // increasing time order, the oldest one is added first.
+ {
+ testing::InSequence dummy;
+
+ for (int i = 0; i < kDefaultSourceCount; i++) {
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), i))
+ .WillOnce(CheckListSize(list_.get(), i + 1));
+ }
+
+ for (int i = 0; i < kDefaultSourceCount - 1; i++) {
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(
+ list_.get(), kDefaultSourceCount - 1 - i));
+ }
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+ }
+
+ list_->StartUpdating(&observer_);
+ base::RunLoop().Run();
+
+ for (int i = 0; i < kDefaultSourceCount; ++i) {
+ EXPECT_EQ(list_->GetSource(i).id.type,
+ content::DesktopMediaID::TYPE_WEB_CONTENTS);
+ }
+
+ observer_.VerifyAndClearExpectations();
+ }
+
+ // The path to temporary directory used to contain the test operations.
+ base::ScopedTempDir temp_dir_;
+ ScopedTestingLocalState local_state_;
+
+ std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
+ Profile* profile_;
+ std::unique_ptr<Browser> browser_;
+
+ // Must be listed before |list_|, so it's destroyed last.
+ MockObserver observer_;
+ std::unique_ptr<TabDesktopMediaList> list_;
+ std::vector<WebContents*> manually_added_web_contents_;
+
+ content::BrowserTaskEnvironment task_environment_;
+
+#if defined(OS_CHROMEOS)
+ chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
+ chromeos::ScopedTestUserManager test_user_manager_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaListTest);
+};
+
+TEST_F(TabDesktopMediaListTest, AddTab) {
+ InitializeAndVerify();
+
+ AddWebcontents(10);
+
+ EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0))
+ .WillOnce(CheckListSize(list_.get(), kDefaultSourceCount + 1));
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, RemoveTab) {
+ InitializeAndVerify();
+
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ std::unique_ptr<WebContents> released_web_contents =
+ tab_strip_model->DetachWebContentsAt(kDefaultSourceCount - 1);
+ for (auto it = manually_added_web_contents_.begin();
+ it != manually_added_web_contents_.end(); ++it) {
+ if (*it == released_web_contents.get()) {
+ manually_added_web_contents_.erase(it);
+ break;
+ }
+ }
+
+ EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
+ .WillOnce(
+ testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount - 1),
+ QuitMessageLoop()));
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, MoveTab) {
+ InitializeAndVerify();
+
+ // Swap the two media sources by swap their time stamps.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+
+ WebContents* contents0 = tab_strip_model->GetWebContentsAt(0);
+ ASSERT_TRUE(contents0);
+ base::TimeTicks t0 = contents0->GetLastActiveTime();
+ WebContents* contents1 = tab_strip_model->GetWebContentsAt(1);
+ ASSERT_TRUE(contents1);
+ base::TimeTicks t1 = contents1->GetLastActiveTime();
+
+ WebContentsTester::For(contents0)->SetLastActiveTime(t1);
+ WebContentsTester::For(contents1)->SetLastActiveTime(t0);
+
+ EXPECT_CALL(observer_, OnSourceMoved(list_.get(), 1, 0))
+ .WillOnce(testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount),
+ QuitMessageLoop()));
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, UpdateTitle) {
+ InitializeAndVerify();
+
+ // Change tab's title.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ WebContents* contents =
+ tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
+ ASSERT_TRUE(contents);
+ content::NavigationController& controller = contents->GetController();
+ contents->UpdateTitleForEntry(controller.GetTransientEntry(),
+ base::ASCIIToUTF16("New test tab"));
+
+ EXPECT_CALL(observer_, OnSourceNameChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ EXPECT_EQ(list_->GetSource(0).name, base::UTF8ToUTF16("New test tab"));
+
+ list_.reset();
+}
+
+TEST_F(TabDesktopMediaListTest, UpdateThumbnail) {
+ InitializeAndVerify();
+
+ // Change tab's favicon.
+ TabStripModel* tab_strip_model = browser_->tab_strip_model();
+ ASSERT_TRUE(tab_strip_model);
+ WebContents* contents =
+ tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
+ ASSERT_TRUE(contents);
+
+ content::FaviconStatus favicon_info;
+ favicon_info.image = CreateGrayscaleImage(gfx::Size(10, 10), 100);
+ contents->GetController().GetTransientEntry()->GetFavicon() = favicon_info;
+
+ EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
+ .WillOnce(QuitMessageLoop());
+
+ base::RunLoop().Run();
+
+ list_.reset();
+}
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc
new file mode 100644
index 00000000000..a02c385a068
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.cc
@@ -0,0 +1,216 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/test_stats_dictionary.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+
+namespace content {
+
+TestStatsReportDictionary::TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue> report)
+ : report_(std::move(report)) {
+ CHECK(report_);
+}
+
+TestStatsReportDictionary::~TestStatsReportDictionary() {
+}
+
+void TestStatsReportDictionary::ForEach(
+ std::function<void(const TestStatsDictionary&)> iteration) {
+ for (base::DictionaryValue::Iterator it(*report_); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* it_value;
+ CHECK(it.value().GetAsDictionary(&it_value));
+ iteration(TestStatsDictionary(this, it_value));
+ }
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::Filter(
+ std::function<bool(const TestStatsDictionary&)> filter) {
+ std::vector<TestStatsDictionary> result;
+ ForEach([&result, &filter](const TestStatsDictionary& stats) {
+ if (filter(stats))
+ result.push_back(stats);
+ });
+ return result;
+}
+
+std::unique_ptr<TestStatsDictionary> TestStatsReportDictionary::Get(
+ const std::string& id) {
+ const base::DictionaryValue* dictionary;
+ if (!report_->GetDictionary(id, &dictionary))
+ return nullptr;
+ return std::unique_ptr<TestStatsDictionary>(
+ new TestStatsDictionary(this, dictionary));
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::GetAll() {
+ return Filter([](const TestStatsDictionary&) { return true; });
+}
+
+std::vector<TestStatsDictionary> TestStatsReportDictionary::GetByType(
+ const std::string& type) {
+ return Filter([&type](const TestStatsDictionary& stats) {
+ return stats.GetString("type") == type;
+ });
+}
+
+TestStatsDictionary::TestStatsDictionary(
+ TestStatsReportDictionary* report, const base::DictionaryValue* stats)
+ : report_(report), stats_(stats) {
+ CHECK(report_);
+ CHECK(stats_);
+}
+
+TestStatsDictionary::TestStatsDictionary(
+ const TestStatsDictionary& other) = default;
+
+TestStatsDictionary::~TestStatsDictionary() {
+}
+
+bool TestStatsDictionary::IsBoolean(const std::string& key) const {
+ bool value;
+ return GetBoolean(key, &value);
+}
+
+bool TestStatsDictionary::GetBoolean(const std::string& key) const {
+ bool value;
+ CHECK(GetBoolean(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsNumber(const std::string& key) const {
+ double value;
+ return GetNumber(key, &value);
+}
+
+double TestStatsDictionary::GetNumber(const std::string& key) const {
+ double value;
+ CHECK(GetNumber(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsString(const std::string& key) const {
+ std::string value;
+ return GetString(key, &value);
+}
+
+std::string TestStatsDictionary::GetString(const std::string& key) const {
+ std::string value;
+ CHECK(GetString(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceBoolean(const std::string& key) const {
+ std::vector<bool> value;
+ return GetSequenceBoolean(key, &value);
+}
+
+std::vector<bool> TestStatsDictionary::GetSequenceBoolean(
+ const std::string& key) const {
+ std::vector<bool> value;
+ CHECK(GetSequenceBoolean(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceNumber(const std::string& key) const {
+ std::vector<double> value;
+ return GetSequenceNumber(key, &value);
+}
+
+std::vector<double> TestStatsDictionary::GetSequenceNumber(
+ const std::string& key) const {
+ std::vector<double> value;
+ CHECK(GetSequenceNumber(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::IsSequenceString(const std::string& key) const {
+ std::vector<std::string> value;
+ return GetSequenceString(key, &value);
+}
+
+std::vector<std::string> TestStatsDictionary::GetSequenceString(
+ const std::string& key) const {
+ std::vector<std::string> value;
+ CHECK(GetSequenceString(key, &value));
+ return value;
+}
+
+bool TestStatsDictionary::GetBoolean(
+ const std::string& key, bool* out) const {
+ return stats_->GetBoolean(key, out);
+}
+
+bool TestStatsDictionary::GetNumber(
+ const std::string& key, double* out) const {
+ return stats_->GetDouble(key, out);
+}
+
+bool TestStatsDictionary::GetString(
+ const std::string& key, std::string* out) const {
+ return stats_->GetString(key, out);
+}
+
+bool TestStatsDictionary::GetSequenceBoolean(
+ const std::string& key,
+ std::vector<bool>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<bool> sequence;
+ bool element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetBoolean(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+bool TestStatsDictionary::GetSequenceNumber(
+ const std::string& key,
+ std::vector<double>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<double> sequence;
+ double element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetDouble(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+bool TestStatsDictionary::GetSequenceString(
+ const std::string& key,
+ std::vector<std::string>* out) const {
+ const base::ListValue* list;
+ if (!stats_->GetList(key, &list))
+ return false;
+ std::vector<std::string> sequence;
+ std::string element;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ if (!list->GetString(i, &element))
+ return false;
+ sequence.push_back(element);
+ }
+ *out = std::move(sequence);
+ return true;
+}
+
+std::string TestStatsDictionary::ToString() const {
+ std::string str;
+ CHECK(base::JSONWriter::WriteWithOptions(
+ *stats_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &str));
+ return str;
+}
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h
new file mode 100644
index 00000000000..13fd7f78c6a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary.h
@@ -0,0 +1,85 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+
+namespace content {
+
+class TestStatsDictionary;
+
+class TestStatsReportDictionary
+ : public base::RefCounted<TestStatsReportDictionary> {
+ public:
+ explicit TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue> report);
+
+ void ForEach(std::function<void(const TestStatsDictionary&)> iteration);
+ std::vector<TestStatsDictionary> Filter(
+ std::function<bool(const TestStatsDictionary&)> filter);
+
+ std::unique_ptr<TestStatsDictionary> Get(const std::string& id);
+ std::vector<TestStatsDictionary> GetAll();
+ std::vector<TestStatsDictionary> GetByType(const std::string& type);
+
+ private:
+ friend class base::RefCounted<TestStatsReportDictionary>;
+ ~TestStatsReportDictionary();
+
+ std::unique_ptr<base::DictionaryValue> report_;
+};
+
+class TestStatsDictionary {
+ public:
+ TestStatsDictionary(TestStatsReportDictionary* report,
+ const base::DictionaryValue* stats);
+ TestStatsDictionary(const TestStatsDictionary& other);
+ ~TestStatsDictionary();
+
+ bool IsBoolean(const std::string& key) const;
+ bool GetBoolean(const std::string& key) const;
+
+ bool IsNumber(const std::string& key) const;
+ double GetNumber(const std::string& key) const;
+
+ bool IsString(const std::string& key) const;
+ std::string GetString(const std::string& key) const;
+
+ bool IsSequenceBoolean(const std::string& key) const;
+ std::vector<bool> GetSequenceBoolean(const std::string& key) const;
+
+ bool IsSequenceNumber(const std::string& key) const;
+ std::vector<double> GetSequenceNumber(const std::string& key) const;
+
+ bool IsSequenceString(const std::string& key) const;
+ std::vector<std::string> GetSequenceString(const std::string& key) const;
+
+ std::string ToString() const;
+
+ private:
+ bool GetBoolean(const std::string& key, bool* out) const;
+ bool GetNumber(const std::string& key, double* out) const;
+ bool GetString(const std::string& key, std::string* out) const;
+ bool GetSequenceBoolean(
+ const std::string& key, std::vector<bool>* out) const;
+ bool GetSequenceNumber(
+ const std::string& key, std::vector<double>* out) const;
+ bool GetSequenceString(
+ const std::string& key, std::vector<std::string>* out) const;
+
+ // The reference keeps the report alive which indirectly owns |stats_|.
+ scoped_refptr<TestStatsReportDictionary> report_;
+ const base::DictionaryValue* stats_;
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_TEST_STATS_DICTIONARY_H_
diff --git a/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc b/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc
new file mode 100644
index 00000000000..51bfa1630bc
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/test_stats_dictionary_unittest.cc
@@ -0,0 +1,167 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/test_stats_dictionary.h"
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+const char kTestStatsReportJson[] =
+ R"({
+ "GarbageA": {
+ "id": "GarbageA",
+ "timestamp": 0.0,
+ "type": "garbage"
+ },
+ "RTCTestStatsID": {
+ "id": "RTCTestStatsID",
+ "timestamp": 13.37,
+ "type": "test",
+ "boolean": true,
+ "number": 42,
+ "string": "text",
+ "sequenceBoolean": [ true ],
+ "sequenceNumber": [ 42 ],
+ "sequenceString": [ "text" ]
+ },
+ "GarbageB": {
+ "id": "GarbageB",
+ "timestamp": 0.0,
+ "type": "garbage"
+ }
+})";
+
+class TestStatsDictionaryTest : public testing::Test {
+ public:
+ TestStatsDictionaryTest() {
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadDeprecated(kTestStatsReportJson);
+ CHECK(value);
+ base::DictionaryValue* dictionary;
+ CHECK(value->GetAsDictionary(&dictionary));
+ ignore_result(value.release());
+ report_ = new TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue>(dictionary));
+ }
+
+ protected:
+ scoped_refptr<TestStatsReportDictionary> report_;
+};
+
+TEST_F(TestStatsDictionaryTest, ReportGetStats) {
+ EXPECT_FALSE(report_->Get("InvalidID"));
+ EXPECT_TRUE(report_->Get("GarbageA"));
+ EXPECT_TRUE(report_->Get("RTCTestStatsID"));
+ EXPECT_TRUE(report_->Get("GarbageB"));
+}
+
+TEST_F(TestStatsDictionaryTest, ReportForEach) {
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("RTCTestStatsID");
+ remaining.insert("GarbageB");
+ report_->ForEach([&remaining](const TestStatsDictionary& stats) {
+ remaining.erase(stats.GetString("id"));
+ });
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, ReportFilterStats) {
+ std::vector<TestStatsDictionary> filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return false;
+ });
+ EXPECT_EQ(filtered_stats.size(), 0u);
+
+ filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return true;
+ });
+ EXPECT_EQ(filtered_stats.size(), 3u);
+
+ filtered_stats = report_->Filter(
+ [](const TestStatsDictionary& stats) -> bool {
+ return stats.GetString("id") == "RTCTestStatsID";
+ });
+ EXPECT_EQ(filtered_stats.size(), 1u);
+}
+
+TEST_F(TestStatsDictionaryTest, ReportGetAll) {
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("RTCTestStatsID");
+ remaining.insert("GarbageB");
+ for (const TestStatsDictionary& stats : report_->GetAll()) {
+ remaining.erase(stats.GetString("id"));
+ }
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, ReportGetByType) {
+ std::vector<TestStatsDictionary> stats = report_->GetByType("garbage");
+ EXPECT_EQ(stats.size(), 2u);
+ std::set<std::string> remaining;
+ remaining.insert("GarbageA");
+ remaining.insert("GarbageB");
+ report_->ForEach([&remaining](const TestStatsDictionary& stats) {
+ remaining.erase(stats.GetString("id"));
+ });
+ EXPECT_TRUE(remaining.empty());
+}
+
+TEST_F(TestStatsDictionaryTest, StatsVerifyMembers) {
+ std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
+ EXPECT_TRUE(stats);
+
+ EXPECT_FALSE(stats->IsBoolean("nonexistentMember"));
+ EXPECT_FALSE(stats->IsNumber("nonexistentMember"));
+ EXPECT_FALSE(stats->IsString("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceBoolean("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceNumber("nonexistentMember"));
+ EXPECT_FALSE(stats->IsSequenceString("nonexistentMember"));
+
+ ASSERT_TRUE(stats->IsBoolean("boolean"));
+ EXPECT_EQ(stats->GetBoolean("boolean"), true);
+
+ ASSERT_TRUE(stats->IsNumber("number"));
+ EXPECT_EQ(stats->GetNumber("number"), 42.0);
+
+ ASSERT_TRUE(stats->IsString("string"));
+ EXPECT_EQ(stats->GetString("string"), "text");
+
+ ASSERT_TRUE(stats->IsSequenceBoolean("sequenceBoolean"));
+ EXPECT_EQ(stats->GetSequenceBoolean("sequenceBoolean"),
+ std::vector<bool> { true });
+
+ ASSERT_TRUE(stats->IsSequenceNumber("sequenceNumber"));
+ EXPECT_EQ(stats->GetSequenceNumber("sequenceNumber"),
+ std::vector<double> { 42.0 });
+
+ ASSERT_TRUE(stats->IsSequenceString("sequenceString"));
+ EXPECT_EQ(stats->GetSequenceString("sequenceString"),
+ std::vector<std::string> { "text" });
+}
+
+TEST_F(TestStatsDictionaryTest, TestStatsDictionaryShouldKeepReportAlive) {
+ std::unique_ptr<TestStatsDictionary> stats = report_->Get("RTCTestStatsID");
+ EXPECT_TRUE(stats);
+ report_ = nullptr;
+ EXPECT_EQ(stats->GetString("string"), "text");
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc
new file mode 100644
index 00000000000..e46f6188f28
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_apprtc_browsertest.cc
@@ -0,0 +1,259 @@
+// 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/command_line.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/rand_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/infobars/infobar_responder.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/python_utils.h"
+#include "ui/gl/gl_switches.h"
+
+const char kTitlePageOfAppEngineAdminPage[] = "Instances";
+
+const char kIsApprtcCallUpJavascript[] =
+ "var remoteVideo = document.querySelector('#remote-video');"
+ "var remoteVideoActive ="
+ " remoteVideo != null &&"
+ " remoteVideo.classList.contains('active');"
+ "window.domAutomationController.send(remoteVideoActive.toString());";
+
+// WebRTC-AppRTC integration test. Requires a real webcam and microphone
+// on the running system. This test is not meant to run in the main browser
+// test suite since normal tester machines do not have webcams.
+//
+// This test will bring up a AppRTC instance on localhost and verify that the
+// call gets up when connecting to the same room from two tabs in a browser.
+class WebRtcApprtcBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcApprtcBrowserTest() {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ // This test fails on some Mac bots if no default devices are specified on
+ // the command line.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ "audio-input-default-id=default,video-input-default-id=default");
+ }
+
+ void TearDown() override {
+ // Kill any processes we may have brought up. Note: this isn't perfect,
+ // especially if the test hangs or if we're on Windows.
+ LOG(INFO) << "Entering TearDown";
+ if (dev_appserver_.IsValid())
+ dev_appserver_.Terminate(0, false);
+ if (collider_server_.IsValid())
+ collider_server_.Terminate(0, false);
+ LOG(INFO) << "Exiting TearDown";
+ }
+
+ protected:
+ bool LaunchApprtcInstanceOnLocalhost(const std::string& port) {
+ base::FilePath appengine_dev_appserver = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/browsertest/"
+ "apprtc/temp/google-cloud-sdk/bin/dev_appserver.py"));
+ if (!base::PathExists(appengine_dev_appserver)) {
+ LOG(ERROR) << "Missing appengine sdk at " <<
+ appengine_dev_appserver.value() << ".\n" <<
+ test::kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::FilePath apprtc_dir = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/apprtc/out/app_engine"));
+ if (!base::PathExists(apprtc_dir)) {
+ LOG(ERROR) << "Missing AppRTC AppEngine app at " <<
+ apprtc_dir.value() << ".\n" << test::kAdviseOnGclientSolution;
+ return false;
+ }
+ if (!base::PathExists(apprtc_dir.Append(FILE_PATH_LITERAL("app.yaml")))) {
+ LOG(ERROR) << "The AppRTC AppEngine app at " << apprtc_dir.value()
+ << " appears to have not been built."
+ << "This should have been done by webrtc.DEPS scripts.";
+ return false;
+ }
+
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ EXPECT_TRUE(GetPythonCommand(&command_line));
+
+ command_line.AppendArgPath(appengine_dev_appserver);
+ command_line.AppendArgPath(apprtc_dir);
+ command_line.AppendArg("--port=" + port);
+ command_line.AppendArg("--admin_port=9998");
+ command_line.AppendArg("--skip_sdk_update_check");
+ command_line.AppendArg("--clear_datastore=yes");
+
+ DVLOG(1) << "Running " << command_line.GetCommandLineString();
+ dev_appserver_ = base::LaunchProcess(command_line, base::LaunchOptions());
+ return dev_appserver_.IsValid();
+ }
+
+ bool LaunchColliderOnLocalHost(const std::string& apprtc_url,
+ const std::string& collider_port) {
+ // The go workspace should be created, and collidermain built, at the
+ // runhooks stage when webrtc.DEPS/build_apprtc_collider.py runs.
+#if defined(OS_WIN)
+ base::FilePath collider_server = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/collider/collidermain.exe"));
+#else
+ base::FilePath collider_server = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/testing/"
+ "browsertest/collider/collidermain"));
+#endif
+ if (!base::PathExists(collider_server)) {
+ LOG(ERROR) << "Missing Collider server binary at " <<
+ collider_server.value() << ".\n" << test::kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::CommandLine command_line(collider_server);
+
+ command_line.AppendArg("-tls=false");
+ command_line.AppendArg("-port=" + collider_port);
+ command_line.AppendArg("-room-server=" + apprtc_url);
+
+ DVLOG(1) << "Running " << command_line.GetCommandLineString();
+ collider_server_ = base::LaunchProcess(command_line, base::LaunchOptions());
+ return collider_server_.IsValid();
+ }
+
+ bool LocalApprtcInstanceIsUp() {
+ // Load the admin page and see if we manage to load it right.
+ ui_test_utils::NavigateToURL(browser(), GURL("localhost:9998"));
+ content::WebContents* tab_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::string javascript =
+ "window.domAutomationController.send(document.title)";
+ std::string result;
+ if (!content::ExecuteScriptAndExtractString(tab_contents, javascript,
+ &result))
+ return false;
+
+ return result == kTitlePageOfAppEngineAdminPage;
+ }
+
+ bool WaitForCallToComeUp(content::WebContents* tab_contents) {
+ return test::PollingWaitUntil(kIsApprtcCallUpJavascript, "true",
+ tab_contents);
+ }
+
+ bool EvalInJavascriptFile(content::WebContents* tab_contents,
+ const base::FilePath& path) {
+ std::string javascript;
+ if (!ReadFileToString(path, &javascript)) {
+ LOG(ERROR) << "Missing javascript code at " << path.value() << ".";
+ return false;
+ }
+
+ if (!content::ExecuteScript(tab_contents, javascript)) {
+ LOG(ERROR) << "Failed to execute the following javascript: " <<
+ javascript;
+ return false;
+ }
+ return true;
+ }
+
+ bool DetectLocalVideoPlaying(content::WebContents* tab_contents) {
+ // The remote video tag is called "local-video" in the AppRTC code.
+ return DetectVideoPlaying(tab_contents, "local-video");
+ }
+
+ bool DetectRemoteVideoPlaying(content::WebContents* tab_contents) {
+ // The remote video tag is called "remote-video" in the AppRTC code.
+ return DetectVideoPlaying(tab_contents, "remote-video");
+ }
+
+ bool DetectVideoPlaying(content::WebContents* tab_contents,
+ const std::string& video_tag) {
+ if (!EvalInJavascriptFile(tab_contents, GetSourceDir().Append(
+ FILE_PATH_LITERAL("chrome/test/data/webrtc/test_functions.js"))))
+ return false;
+ if (!EvalInJavascriptFile(tab_contents, GetSourceDir().Append(
+ FILE_PATH_LITERAL("chrome/test/data/webrtc/video_detector.js"))))
+ return false;
+
+ StartDetectingVideo(tab_contents, video_tag);
+ WaitForVideoToPlay(tab_contents);
+ return true;
+ }
+
+ base::FilePath GetSourceDir() {
+ base::FilePath source_dir;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir);
+ return source_dir;
+ }
+
+ private:
+ base::Process dev_appserver_;
+ base::Process collider_server_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebRtcApprtcBrowserTest, MANUAL_WorksOnApprtc) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ DetectErrorsInJavaScript();
+ ASSERT_TRUE(LaunchApprtcInstanceOnLocalhost("9999"));
+ ASSERT_TRUE(LaunchColliderOnLocalHost("http://localhost:9999", "8089"));
+ while (!LocalApprtcInstanceIsUp())
+ DVLOG(1) << "Waiting for AppRTC to come up...";
+
+ GURL room_url = GURL("http://localhost:9999/r/some_room"
+ "?wshpp=localhost:8089&wstls=false");
+
+ // Set up the left tab.
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ content::WebContents* left_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ PermissionRequestManager::FromWebContents(left_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ InfoBarResponder left_infobar_responder(
+ InfoBarService::FromWebContents(left_tab), InfoBarResponder::ACCEPT);
+ ui_test_utils::NavigateToURL(browser(), room_url);
+
+ // Wait for the local video to start playing. This is needed, because opening
+ // a new tab too quickly, by sending the current tab to the background, can
+ // lead to the request for starting the video capture in the current tab to
+ // not get sent before it comes back to the foreground (which in this test
+ // case is never).
+ ASSERT_TRUE(DetectLocalVideoPlaying(left_tab));
+
+ // Set up the right tab.
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ content::WebContents* right_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ PermissionRequestManager::FromWebContents(right_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ InfoBarResponder right_infobar_responder(
+ InfoBarService::FromWebContents(right_tab), InfoBarResponder::ACCEPT);
+ ui_test_utils::NavigateToURL(browser(), room_url);
+
+ ASSERT_TRUE(WaitForCallToComeUp(left_tab));
+ ASSERT_TRUE(WaitForCallToComeUp(right_tab));
+
+ ASSERT_TRUE(DetectRemoteVideoPlaying(left_tab));
+ ASSERT_TRUE(DetectRemoteVideoPlaying(right_tab));
+
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc
new file mode 100644
index 00000000000..3afb1d4d9c8
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest.cc
@@ -0,0 +1,344 @@
+// 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/command_line.h"
+#include "base/deferred_sequenced_task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/nqe/network_quality_estimator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/network_service.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_service_test.mojom.h"
+#include "third_party/blink/public/common/features.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+static const char kKeygenAlgorithmRsa[] =
+ "{ name: \"RSASSA-PKCS1-v1_5\", modulusLength: 2048, publicExponent: "
+ "new Uint8Array([1, 0, 1]), hash: \"SHA-256\" }";
+static const char kKeygenAlgorithmEcdsa[] =
+ "{ name: \"ECDSA\", namedCurve: \"P-256\" }";
+
+// Top-level integration test for WebRTC. It always uses fake devices; see
+// WebRtcWebcamBrowserTest for a test that acquires any real webcam on the
+// system.
+class WebRtcBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcBrowserTest() : left_tab_(nullptr), right_tab_(nullptr) {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Flag used by TestWebAudioMediaStream to force garbage collection.
+ command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+ }
+
+ void RunsAudioVideoWebRTCCallInTwoTabs(
+ const std::string& video_codec = WebRtcTestBase::kUseDefaultVideoCodec,
+ bool prefer_hw_video_codec = false,
+ const std::string& offer_cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen,
+ const std::string& answer_cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen) {
+ StartServerAndOpenTabs();
+
+ SetupPeerconnectionWithLocalStream(left_tab_, offer_cert_keygen_alg);
+ SetupPeerconnectionWithLocalStream(right_tab_, answer_cert_keygen_alg);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab_, video_codec, prefer_hw_video_codec);
+ SetDefaultVideoCodec(right_tab_, video_codec, prefer_hw_video_codec);
+ }
+ NegotiateCall(left_tab_, right_tab_);
+
+ DetectVideoAndHangUp();
+ }
+
+ void RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(
+ const std::string& cert_keygen_alg =
+ WebRtcTestBase::kUseDefaultCertKeygen) {
+ StartServerAndOpenTabs();
+
+ // Generate and clone a certificate, resulting in JavaScript variable
+ // |gCertificateClone| being set to the resulting clone.
+ DeleteDatabase(left_tab_);
+ OpenDatabase(left_tab_);
+ GenerateAndCloneCertificate(left_tab_, cert_keygen_alg);
+ CloseDatabase(left_tab_);
+ DeleteDatabase(left_tab_);
+
+ SetupPeerconnectionWithCertificateAndLocalStream(
+ left_tab_, "gCertificateClone");
+ SetupPeerconnectionWithLocalStream(right_tab_, cert_keygen_alg);
+
+ NegotiateCall(left_tab_, right_tab_);
+ VerifyLocalDescriptionContainsCertificate(left_tab_, "gCertificate");
+
+ DetectVideoAndHangUp();
+ }
+
+ uint32_t GetPeerToPeerConnectionsCountChangeFromNetworkService() {
+ uint32_t connection_count = 0u;
+ if (content::IsInProcessNetworkService()) {
+ base::RunLoop run_loop;
+ content::GetNetworkTaskRunner()->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&connection_count, &run_loop] {
+ connection_count =
+ network::NetworkService::GetNetworkServiceForTesting()
+ ->network_quality_estimator()
+ ->GetPeerToPeerConnectionsCountChange();
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ return connection_count;
+ }
+
+ mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+ content::GetNetworkService()->BindTestInterface(
+ network_service_test.BindNewPipeAndPassReceiver());
+ // TODO(crbug.com/901026): Make sure the network process is started to avoid
+ // a deadlock on Android.
+ network_service_test.FlushForTesting();
+
+ mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+
+ bool available = network_service_test->GetPeerToPeerConnectionsCountChange(
+ &connection_count);
+ EXPECT_TRUE(available);
+
+ return connection_count;
+ }
+
+ protected:
+ void StartServerAndOpenTabs() {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ left_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ }
+
+ void DetectVideoAndHangUp() {
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+#if !defined(OS_MACOSX)
+ // Video is choppy on Mac OS X. http://crbug.com/443542.
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+#endif
+ HangUp(left_tab_);
+ HangUp(right_tab_);
+ }
+
+ content::WebContents* left_tab_;
+ content::WebContents* right_tab_;
+};
+
+// TODO(898546): many of these tests are failing on ASan builds.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_WebRtcBrowserTest DISABLED_WebRtcBrowserTest
+class DISABLED_WebRtcBrowserTest : public WebRtcBrowserTest {};
+#else
+#define MAYBE_WebRtcBrowserTest WebRtcBrowserTest
+#endif
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsVP8) {
+ RunsAudioVideoWebRTCCallInTwoTabs("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsVP9) {
+ RunsAudioVideoWebRTCCallInTwoTabs("VP9");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsH264) {
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcBrowserTest.RunsAudioVideoWebRTCCallInTwoTabsH264 "
+ "(test \"OK\")";
+ return;
+ }
+
+#if defined(OS_MACOSX)
+ // TODO(jam): this test only on 10.12.
+ if (base::mac::IsOS10_12())
+ return;
+#endif
+
+ RunsAudioVideoWebRTCCallInTwoTabs("H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(WebRtcBrowserTest, TestWebAudioMediaStream) {
+ // This tests against crash regressions for the WebAudio-MediaStream
+ // integration.
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/webrtc/webaudio_crash.html"));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // A sleep is necessary to be able to detect the crash.
+ test::SleepInJavascript(tab, 1000);
+
+ ASSERT_FALSE(tab->IsCrashed());
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferRsaAnswerRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmRsa, kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferEcdsaAnswerEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(
+ WebRtcTestBase::kUseDefaultVideoCodec, false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmEcdsa, kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificateRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificateEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabsWithClonedCertificate(kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferRsaAnswerEcdsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmRsa, kKeygenAlgorithmEcdsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsOfferEcdsaAnswerRsa) {
+ RunsAudioVideoWebRTCCallInTwoTabs(WebRtcTestBase::kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */,
+ kKeygenAlgorithmEcdsa, kKeygenAlgorithmRsa);
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsGetStatsCallback) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ VerifyStatsGeneratedCallback(left_tab_);
+
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ GetPeerToPeerConnectionsCountChangeFromNetworkService) {
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ VerifyStatsGeneratedCallback(left_tab_);
+ EXPECT_EQ(2u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ DetectVideoAndHangUp();
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+}
+
+IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsGetStatsPromise) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ CreateDataChannel(left_tab_, "data");
+ CreateDataChannel(right_tab_, "data");
+ NegotiateCall(left_tab_, right_tab_);
+
+ std::set<std::string> missing_expected_stats;
+ for (const std::string& type : GetMandatoryStatsTypes(left_tab_)) {
+ missing_expected_stats.insert(type);
+ }
+ for (const std::string& type : VerifyStatsGeneratedPromise(left_tab_)) {
+ missing_expected_stats.erase(type);
+ }
+ for (const std::string& type : missing_expected_stats) {
+ EXPECT_TRUE(false) << "Expected stats dictionary is missing: " << type;
+ }
+
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsEmitsGatheringStateChange) {
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+
+ std::string ice_gatheringstate =
+ ExecuteJavascript("getLastGatheringState()", left_tab_);
+
+ EXPECT_EQ("complete", ice_gatheringstate);
+ DetectVideoAndHangUp();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ MAYBE_WebRtcBrowserTest,
+ RunsAudioVideoWebRTCCallInTwoTabsEmitsGatheringStateChange_ConnectionCount) {
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+ StartServerAndOpenTabs();
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+ EXPECT_EQ(2u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+
+ std::string ice_gatheringstate =
+ ExecuteJavascript("getLastGatheringState()", left_tab_);
+
+ EXPECT_EQ("complete", ice_gatheringstate);
+ DetectVideoAndHangUp();
+ EXPECT_EQ(0u, GetPeerToPeerConnectionsCountChangeFromNetworkService());
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
new file mode 100644
index 00000000000..3affb26a72b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
@@ -0,0 +1,651 @@
+// 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 "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+
+#include <stddef.h>
+
+#include <limits>
+
+#include "base/json/json_reader.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if defined(OS_WIN)
+// For fine-grained suppression.
+#include "base/win/windows_version.h"
+#endif
+
+const char WebRtcTestBase::kAudioVideoCallConstraints[] =
+ "{audio: true, video: true}";
+const char WebRtcTestBase::kVideoCallConstraintsQVGA[] =
+ "{video: {mandatory: {minWidth: 320, maxWidth: 320, "
+ " minHeight: 240, maxHeight: 240}}}";
+const char WebRtcTestBase::kVideoCallConstraints360p[] =
+ "{video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 360, maxHeight: 360}}}";
+const char WebRtcTestBase::kVideoCallConstraintsVGA[] =
+ "{video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 480, maxHeight: 480}}}";
+const char WebRtcTestBase::kVideoCallConstraints720p[] =
+ "{video: {mandatory: {minWidth: 1280, maxWidth: 1280, "
+ " minHeight: 720, maxHeight: 720}}}";
+const char WebRtcTestBase::kVideoCallConstraints1080p[] =
+ "{video: {mandatory: {minWidth: 1920, maxWidth: 1920, "
+ " minHeight: 1080, maxHeight: 1080}}}";
+const char WebRtcTestBase::kAudioOnlyCallConstraints[] = "{audio: true}";
+const char WebRtcTestBase::kVideoOnlyCallConstraints[] = "{video: true}";
+const char WebRtcTestBase::kOkGotStream[] = "ok-got-stream";
+const char WebRtcTestBase::kFailedWithNotAllowedError[] =
+ "failed-with-error-NotAllowedError";
+const char WebRtcTestBase::kAudioVideoCallConstraints360p[] =
+ "{audio: true, video: {mandatory: {minWidth: 640, maxWidth: 640, "
+ " minHeight: 360, maxHeight: 360}}}";
+const char WebRtcTestBase::kAudioVideoCallConstraints720p[] =
+ "{audio: true, video: {mandatory: {minWidth: 1280, maxWidth: 1280, "
+ " minHeight: 720, maxHeight: 720}}}";
+const char WebRtcTestBase::kUseDefaultCertKeygen[] = "null";
+const char WebRtcTestBase::kUseDefaultAudioCodec[] = "";
+const char WebRtcTestBase::kUseDefaultVideoCodec[] = "";
+const char WebRtcTestBase::kVP9Profile0Specifier[] = "profile-id=0";
+const char WebRtcTestBase::kVP9Profile2Specifier[] = "profile-id=2";
+const char WebRtcTestBase::kUndefined[] = "undefined";
+
+namespace {
+
+base::LazyInstance<bool>::DestructorAtExit hit_javascript_errors_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Intercepts all log messages. We always attach this handler but only look at
+// the results if the test requests so. Note that this will only work if the
+// WebrtcTestBase-inheriting test cases do not run in parallel (if they did they
+// would race to look at the log, which is global to all tests).
+bool JavascriptErrorDetectingLogHandler(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str) {
+ if (file == NULL || std::string("CONSOLE") != file)
+ return false;
+
+ // TODO(crbug.com/918871): Fix AppRTC and stop ignoring this error.
+ if (str.find("Synchronous XHR in page dismissal") != std::string::npos)
+ return false;
+
+ bool contains_uncaught = str.find("\"Uncaught ") != std::string::npos;
+ if (severity == logging::LOG_ERROR ||
+ (severity == logging::LOG_INFO && contains_uncaught)) {
+ hit_javascript_errors_.Get() = true;
+ }
+
+ return false;
+}
+
+// PermissionRequestObserver ---------------------------------------------------
+
+// Used to observe the creation of permission prompt without responding.
+class PermissionRequestObserver : public PermissionRequestManager::Observer {
+ public:
+ explicit PermissionRequestObserver(content::WebContents* web_contents)
+ : request_manager_(
+ PermissionRequestManager::FromWebContents(web_contents)),
+ request_shown_(false),
+ message_loop_runner_(new content::MessageLoopRunner) {
+ request_manager_->AddObserver(this);
+ }
+ ~PermissionRequestObserver() override {
+ // Safe to remove twice if it happens.
+ request_manager_->RemoveObserver(this);
+ }
+
+ void Wait() { message_loop_runner_->Run(); }
+
+ bool request_shown() const { return request_shown_; }
+
+ private:
+ // PermissionRequestManager::Observer
+ void OnBubbleAdded() override {
+ request_shown_ = true;
+ request_manager_->RemoveObserver(this);
+ message_loop_runner_->Quit();
+ }
+
+ PermissionRequestManager* request_manager_;
+ bool request_shown_;
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermissionRequestObserver);
+};
+
+std::vector<std::string> JsonArrayToVectorOfStrings(
+ const std::string& json_array) {
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadDeprecated(json_array);
+ EXPECT_TRUE(value);
+ EXPECT_TRUE(value->is_list());
+ std::unique_ptr<base::ListValue> list =
+ base::ListValue::From(std::move(value));
+ std::vector<std::string> vector;
+ vector.reserve(list->GetSize());
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ base::Value* item;
+ EXPECT_TRUE(list->Get(i, &item));
+ EXPECT_TRUE(item->is_string());
+ std::string item_str;
+ EXPECT_TRUE(item->GetAsString(&item_str));
+ vector.push_back(std::move(item_str));
+ }
+ return vector;
+}
+
+} // namespace
+
+WebRtcTestBase::WebRtcTestBase(): detect_errors_in_javascript_(false) {
+ // The handler gets set for each test method, but that's fine since this
+ // set operation is idempotent.
+ logging::SetLogMessageHandler(&JavascriptErrorDetectingLogHandler);
+ hit_javascript_errors_.Get() = false;
+
+ EnablePixelOutput();
+}
+
+WebRtcTestBase::~WebRtcTestBase() {
+ if (detect_errors_in_javascript_) {
+ EXPECT_FALSE(hit_javascript_errors_.Get())
+ << "Encountered javascript errors during test execution (Search "
+ << "for Uncaught or ERROR:CONSOLE in the test output).";
+ }
+}
+
+bool WebRtcTestBase::GetUserMediaAndAccept(
+ content::WebContents* tab_contents) const {
+ return GetUserMediaWithSpecificConstraintsAndAccept(
+ tab_contents, kAudioVideoCallConstraints);
+}
+
+bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAccept(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ return kOkGotStream == result;
+}
+
+bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ return kOkGotStream == result;
+}
+
+void WebRtcTestBase::GetUserMediaAndDeny(content::WebContents* tab_contents) {
+ return GetUserMediaWithSpecificConstraintsAndDeny(tab_contents,
+ kAudioVideoCallConstraints);
+}
+
+void WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndDeny(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DENY_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, constraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndDismiss(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DISMISS);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_TRUE(permissionRequestObserver.request_shown());
+ // A dismiss should be treated like a deny.
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndExpectAutoAcceptWithoutPrompt(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ // We issue a GetUserMedia() request. We expect that the origin already has a
+ // sticky "accept" permission (e.g. because the caller previously called
+ // GetUserMediaAndAccept()), and therefore the GetUserMedia() request
+ // automatically succeeds without a prompt.
+ // If the caller made a mistake, a prompt may show up instead. For this case,
+ // we set an auto-response to avoid leaving the prompt hanging. The choice of
+ // DENY_ALL makes sure that the response to the prompt doesn't accidentally
+ // result in a newly granted media stream permission.
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::DENY_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_FALSE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kOkGotStream, result);
+}
+
+void WebRtcTestBase::GetUserMediaAndExpectAutoDenyWithoutPrompt(
+ content::WebContents* tab_contents) const {
+ std::string result;
+ // We issue a GetUserMedia() request. We expect that the origin already has a
+ // sticky "deny" permission (e.g. because the caller previously called
+ // GetUserMediaAndDeny()), and therefore the GetUserMedia() request
+ // automatically succeeds without a prompt.
+ // If the caller made a mistake, a prompt may show up instead. For this case,
+ // we set an auto-response to avoid leaving the prompt hanging. The choice of
+ // ACCEPT_ALL makes sure that the response to the prompt doesn't accidentally
+ // result in a newly granted media stream permission.
+ PermissionRequestManager::FromWebContents(tab_contents)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ PermissionRequestObserver permissionRequestObserver(tab_contents);
+ GetUserMedia(tab_contents, kAudioVideoCallConstraints);
+ EXPECT_FALSE(permissionRequestObserver.request_shown());
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kFailedWithNotAllowedError, result);
+}
+
+void WebRtcTestBase::GetUserMedia(content::WebContents* tab_contents,
+ const std::string& constraints) const {
+ // Request user media: this will launch the media stream info bar or bubble.
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents, "doGetUserMedia(" + constraints + ");", &result));
+ EXPECT_TRUE(result == "request-callback-denied" ||
+ result == "request-callback-granted");
+}
+
+content::WebContents* WebRtcTestBase::OpenPageAndGetUserMediaInNewTab(
+ const GURL& url) const {
+ return OpenPageAndGetUserMediaInNewTabWithConstraints(
+ url, kAudioVideoCallConstraints);
+}
+
+content::WebContents*
+WebRtcTestBase::OpenPageAndGetUserMediaInNewTabWithConstraints(
+ const GURL& url,
+ const std::string& constraints) const {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* new_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ // Accept if necessary, but don't expect a prompt (because auto-accept is also
+ // okay).
+ PermissionRequestManager::FromWebContents(new_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ GetUserMedia(new_tab, constraints);
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ new_tab->GetMainFrame(), "obtainGetUserMediaResult();", &result));
+ EXPECT_EQ(kOkGotStream, result);
+ return new_tab;
+}
+
+content::WebContents* WebRtcTestBase::OpenTestPageAndGetUserMediaInNewTab(
+ const std::string& test_page) const {
+ return OpenPageAndGetUserMediaInNewTab(
+ embedded_test_server()->GetURL(test_page));
+}
+
+content::WebContents* WebRtcTestBase::OpenTestPageInNewTab(
+ const std::string& test_page) const {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ GURL url = embedded_test_server()->GetURL(test_page);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* new_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ // Accept if necessary, but don't expect a prompt (because auto-accept is also
+ // okay).
+ PermissionRequestManager::FromWebContents(new_tab)
+ ->set_auto_response_for_test(PermissionRequestManager::ACCEPT_ALL);
+ return new_tab;
+}
+
+void WebRtcTestBase::CloseLastLocalStream(
+ content::WebContents* tab_contents) const {
+ EXPECT_EQ("ok-stopped",
+ ExecuteJavascript("stopLocalStream();", tab_contents));
+}
+
+// Convenience method which executes the provided javascript in the context
+// of the provided web contents and returns what it evaluated to.
+std::string WebRtcTestBase::ExecuteJavascript(
+ const std::string& javascript,
+ content::WebContents* tab_contents) const {
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab_contents, javascript, &result));
+ return result;
+}
+
+void WebRtcTestBase::ChangeToLegacyGetStats(content::WebContents* tab) const {
+ content::ExecuteScriptAsync(tab, "changeToLegacyGetStats()");
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm) const {
+ SetupPeerconnectionWithoutLocalStream(tab, certificate_keygen_algorithm);
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnection(%s)", certificate_keygen_algorithm.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithCertificateAndLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ SetupPeerconnectionWithCertificateWithoutLocalStream(tab, certificate);
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithCertificateWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnectionWithCertificate(%s)", certificate.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::SetupPeerconnectionWithConstraintsAndLocalStream(
+ content::WebContents* tab,
+ const std::string& constraints,
+ const std::string& certificate_keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "preparePeerConnection(%s, %s)", certificate_keygen_algorithm.c_str(),
+ constraints.c_str());
+ EXPECT_EQ("ok-peerconnection-created", ExecuteJavascript(javascript, tab));
+ EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", tab));
+}
+
+std::string WebRtcTestBase::CreateLocalOffer(
+ content::WebContents* from_tab) const {
+ std::string response = ExecuteJavascript("createLocalOffer({})", from_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3)) << "Failed to create local offer: "
+ << response;
+
+ std::string local_offer = response.substr(3);
+ return local_offer;
+}
+
+std::string WebRtcTestBase::CreateAnswer(std::string local_offer,
+ content::WebContents* to_tab) const {
+ std::string javascript =
+ base::StringPrintf("receiveOfferFromPeer('%s', {})", local_offer.c_str());
+ std::string response = ExecuteJavascript(javascript, to_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3))
+ << "Receiving peer failed to receive offer and create answer: "
+ << response;
+
+ std::string answer = response.substr(3);
+ response = ExecuteJavascript(
+ base::StringPrintf("verifyDefaultCodecs('%s')", answer.c_str()),
+ to_tab);
+ EXPECT_EQ("ok-", response.substr(0, 3))
+ << "Receiving peer failed to verify default codec: " << response;
+ return answer;
+}
+
+void WebRtcTestBase::ReceiveAnswer(const std::string& answer,
+ content::WebContents* from_tab) const {
+ ASSERT_EQ(
+ "ok-accepted-answer",
+ ExecuteJavascript(
+ base::StringPrintf("receiveAnswerFromPeer('%s')", answer.c_str()),
+ from_tab));
+}
+
+void WebRtcTestBase::GatherAndSendIceCandidates(
+ content::WebContents* from_tab,
+ content::WebContents* to_tab) const {
+ std::string ice_candidates =
+ ExecuteJavascript("getAllIceCandidates()", from_tab);
+
+ EXPECT_EQ("ok-received-candidates", ExecuteJavascript(
+ base::StringPrintf("receiveIceCandidates('%s')", ice_candidates.c_str()),
+ to_tab));
+}
+
+void WebRtcTestBase::CreateDataChannel(content::WebContents* tab,
+ const std::string& label) {
+ EXPECT_EQ("ok-created",
+ ExecuteJavascript("createDataChannel('" + label + "')", tab));
+}
+
+void WebRtcTestBase::NegotiateCall(content::WebContents* from_tab,
+ content::WebContents* to_tab) const {
+ std::string local_offer = CreateLocalOffer(from_tab);
+ std::string answer = CreateAnswer(local_offer, to_tab);
+ ReceiveAnswer(answer, from_tab);
+
+ // Send all ICE candidates (wait for gathering to finish if necessary).
+ GatherAndSendIceCandidates(to_tab, from_tab);
+ GatherAndSendIceCandidates(from_tab, to_tab);
+}
+
+void WebRtcTestBase::VerifyLocalDescriptionContainsCertificate(
+ content::WebContents* tab,
+ const std::string& certificate) const {
+ std::string javascript = base::StringPrintf(
+ "verifyLocalDescriptionContainsCertificate(%s)", certificate.c_str());
+ EXPECT_EQ("ok-verified", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::HangUp(content::WebContents* from_tab) const {
+ EXPECT_EQ("ok-call-hung-up", ExecuteJavascript("hangUp()", from_tab));
+}
+
+void WebRtcTestBase::DetectErrorsInJavaScript() {
+ detect_errors_in_javascript_ = true;
+}
+
+void WebRtcTestBase::StartDetectingVideo(
+ content::WebContents* tab_contents,
+ const std::string& video_element) const {
+ std::string javascript = base::StringPrintf(
+ "startDetection('%s', 320, 240)", video_element.c_str());
+ EXPECT_EQ("ok-started", ExecuteJavascript(javascript, tab_contents));
+}
+
+bool WebRtcTestBase::WaitForVideoToPlay(
+ content::WebContents* tab_contents) const {
+ bool is_video_playing = test::PollingWaitUntil(
+ "isVideoPlaying()", "video-playing", tab_contents);
+ EXPECT_TRUE(is_video_playing);
+ return is_video_playing;
+}
+
+std::string WebRtcTestBase::GetStreamSize(
+ content::WebContents* tab_contents,
+ const std::string& video_element) const {
+ std::string javascript =
+ base::StringPrintf("getStreamSize('%s')", video_element.c_str());
+ std::string result = ExecuteJavascript(javascript, tab_contents);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ return result.substr(3);
+}
+
+bool WebRtcTestBase::OnWin8OrHigher() const {
+#if defined(OS_WIN)
+ return base::win::GetVersion() >= base::win::Version::WIN8;
+#else
+ return false;
+#endif
+}
+
+void WebRtcTestBase::OpenDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-opened", ExecuteJavascript("openDatabase()", tab));
+}
+
+void WebRtcTestBase::CloseDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-closed", ExecuteJavascript("closeDatabase()", tab));
+}
+
+void WebRtcTestBase::DeleteDatabase(content::WebContents* tab) const {
+ EXPECT_EQ("ok-database-deleted", ExecuteJavascript("deleteDatabase()", tab));
+}
+
+void WebRtcTestBase::GenerateAndCloneCertificate(
+ content::WebContents* tab, const std::string& keygen_algorithm) const {
+ std::string javascript = base::StringPrintf(
+ "generateAndCloneCertificate(%s)", keygen_algorithm.c_str());
+ EXPECT_EQ("ok-generated-and-cloned", ExecuteJavascript(javascript, tab));
+}
+
+void WebRtcTestBase::VerifyStatsGeneratedCallback(
+ content::WebContents* tab) const {
+ EXPECT_EQ("ok-got-stats",
+ ExecuteJavascript("verifyLegacyStatsGenerated()", tab));
+}
+
+std::vector<std::string> WebRtcTestBase::VerifyStatsGeneratedPromise(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("verifyStatsGeneratedPromise()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ return JsonArrayToVectorOfStrings(result.substr(3));
+}
+
+double WebRtcTestBase::MeasureGetStatsCallbackPerformance(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript(
+ "measureGetStatsCallbackPerformance()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ double ms;
+ if (!base::StringToDouble(result.substr(3), &ms))
+ return std::numeric_limits<double>::infinity();
+ return ms;
+}
+
+scoped_refptr<content::TestStatsReportDictionary>
+WebRtcTestBase::GetStatsReportDictionary(content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("getStatsReportDictionary()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(result.substr(3));
+ base::DictionaryValue* dictionary;
+ CHECK(parsed_json);
+ CHECK(parsed_json->GetAsDictionary(&dictionary));
+ ignore_result(parsed_json.release());
+ return scoped_refptr<content::TestStatsReportDictionary>(
+ new content::TestStatsReportDictionary(
+ std::unique_ptr<base::DictionaryValue>(dictionary)));
+}
+
+double WebRtcTestBase::MeasureGetStatsPerformance(
+ content::WebContents* tab) const {
+ std::string result = ExecuteJavascript("measureGetStatsPerformance()", tab);
+ EXPECT_TRUE(base::StartsWith(result, "ok-", base::CompareCase::SENSITIVE));
+ double ms;
+ if (!base::StringToDouble(result.substr(3), &ms))
+ return std::numeric_limits<double>::infinity();
+ return ms;
+}
+
+std::vector<std::string> WebRtcTestBase::GetMandatoryStatsTypes(
+ content::WebContents* tab) const {
+ return JsonArrayToVectorOfStrings(
+ ExecuteJavascript("getMandatoryStatsTypes()", tab));
+}
+
+void WebRtcTestBase::SetDefaultAudioCodec(
+ content::WebContents* tab,
+ const std::string& audio_codec) const {
+ EXPECT_EQ("ok", ExecuteJavascript(
+ "setDefaultAudioCodec('" + audio_codec + "')", tab));
+}
+
+void WebRtcTestBase::SetDefaultVideoCodec(content::WebContents* tab,
+ const std::string& video_codec,
+ bool prefer_hw_codec,
+ const std::string& profile) const {
+ std::string codec_profile = profile;
+ // When no |profile| is given, we default VP9 to Profile 0.
+ if (video_codec.compare("VP9") == 0 && codec_profile.empty())
+ codec_profile = kVP9Profile0Specifier;
+
+ EXPECT_EQ("ok", ExecuteJavascript(
+ "setDefaultVideoCodec('" + video_codec + "'," +
+ (prefer_hw_codec ? "true" : "false") + "," +
+ (codec_profile.empty() ? "null"
+ : "'" + codec_profile + "'") +
+ ")",
+ tab));
+}
+
+void WebRtcTestBase::EnableOpusDtx(content::WebContents* tab) const {
+ EXPECT_EQ("ok-forced", ExecuteJavascript("forceOpusDtx()", tab));
+}
+
+std::string WebRtcTestBase::GetDesktopMediaStream(content::WebContents* tab) {
+ DCHECK(static_cast<bool>(LoadDesktopCaptureExtension()));
+
+ // Post a task to the extension, opening a desktop media stream.
+ return ExecuteJavascript("openDesktopMediaStream()", tab);
+}
+
+base::Optional<std::string> WebRtcTestBase::LoadDesktopCaptureExtension() {
+ base::Optional<std::string> extension_id;
+ if (!desktop_capture_extension_.get()) {
+ extensions::ChromeTestExtensionLoader loader(browser()->profile());
+ base::FilePath extension_path;
+ EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &extension_path));
+ extension_path = extension_path.AppendASCII("extensions/desktop_capture");
+ desktop_capture_extension_ = loader.LoadExtension(extension_path);
+ LOG(INFO) << "Loaded desktop capture extension, id = "
+ << desktop_capture_extension_->id();
+
+ extensions::ExtensionRegistry* registry =
+ extensions::ExtensionRegistry::Get(browser()->profile());
+
+ EXPECT_TRUE(registry->enabled_extensions().GetByID(
+ desktop_capture_extension_->id()));
+ }
+ if (desktop_capture_extension_)
+ extension_id.emplace(desktop_capture_extension_->id());
+ return extension_id;
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h
new file mode 100644
index 00000000000..e85f0026928
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_base.h
@@ -0,0 +1,257 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+namespace infobars {
+class InfoBar;
+}
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+class Extension;
+}
+
+// Base class for WebRTC browser tests with useful primitives for interacting
+// getUserMedia. We use inheritance here because it makes the test code look
+// as clean as it can be.
+class WebRtcTestBase : public InProcessBrowserTest {
+ public:
+ // Typical constraints.
+ static const char kAudioVideoCallConstraints[];
+ static const char kAudioOnlyCallConstraints[];
+ static const char kVideoOnlyCallConstraints[];
+ static const char kVideoCallConstraintsQVGA[];
+ static const char kVideoCallConstraints360p[];
+ static const char kVideoCallConstraintsVGA[];
+ static const char kVideoCallConstraints720p[];
+ static const char kVideoCallConstraints1080p[];
+ static const char kAudioVideoCallConstraints360p[];
+ static const char kAudioVideoCallConstraints720p[];
+
+ static const char kOkGotStream[];
+ static const char kFailedWithNotAllowedError[];
+
+ static const char kUseDefaultCertKeygen[];
+ static const char kUseDefaultAudioCodec[];
+ static const char kUseDefaultVideoCodec[];
+
+ static const char kVP9Profile0Specifier[];
+ static const char kVP9Profile2Specifier[];
+
+ static const char kUndefined[];
+
+ enum class StreamArgumentType {
+ NO_STREAM,
+ SHARED_STREAM,
+ INDIVIDUAL_STREAMS
+ };
+
+ protected:
+ WebRtcTestBase();
+ ~WebRtcTestBase() override;
+
+ // These all require that the loaded page fulfills the public interface in
+ // chrome/test/data/webrtc/getusermedia.js.
+ // If an error is reported back from the getUserMedia call, these functions
+ // will return false.
+ // The ...AndAccept()/...AndDeny()/...AndDismiss() functions expect that a
+ // prompt will be shown (i.e. the current origin in the tab_contents doesn't
+ // have a saved permission).
+ bool GetUserMediaAndAccept(content::WebContents* tab_contents) const;
+ bool GetUserMediaWithSpecificConstraintsAndAccept(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ bool GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ void GetUserMediaAndDeny(content::WebContents* tab_contents);
+ void GetUserMediaWithSpecificConstraintsAndDeny(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+ void GetUserMediaAndDismiss(content::WebContents* tab_contents) const;
+ void GetUserMediaAndExpectAutoAcceptWithoutPrompt(
+ content::WebContents* tab_contents) const;
+ void GetUserMediaAndExpectAutoDenyWithoutPrompt(
+ content::WebContents* tab_contents) const;
+ void GetUserMedia(content::WebContents* tab_contents,
+ const std::string& constraints) const;
+
+ // Convenience method which opens the page at url, calls GetUserMediaAndAccept
+ // and returns the new tab.
+ content::WebContents* OpenPageAndGetUserMediaInNewTab(const GURL& url) const;
+
+ // Convenience method which opens the page at url, calls
+ // GetUserMediaAndAcceptWithSpecificConstraints and returns the new tab.
+ content::WebContents* OpenPageAndGetUserMediaInNewTabWithConstraints(
+ const GURL& url, const std::string& constraints) const;
+
+ // Convenience method which gets the URL for |test_page| and calls
+ // OpenPageAndGetUserMediaInNewTab().
+ content::WebContents* OpenTestPageAndGetUserMediaInNewTab(
+ const std::string& test_page) const;
+
+ // Convenience method which gets the URL for |test_page|, but without calling
+ // GetUserMedia.
+ content::WebContents* OpenTestPageInNewTab(
+ const std::string& test_page) const;
+
+ // Closes the last local stream acquired by the GetUserMedia* methods.
+ void CloseLastLocalStream(content::WebContents* tab_contents) const;
+
+ std::string ExecuteJavascript(const std::string& javascript,
+ content::WebContents* tab_contents) const;
+
+ // TODO(https://crbug.com/1004239): Remove this function as soon as browser
+ // tests stop relying on the legacy getStats() API.
+ void ChangeToLegacyGetStats(content::WebContents* tab) const;
+
+ // Sets up a peer connection in the tab and adds the current local stream
+ // (which you can prepare by calling one of the GetUserMedia* methods above).
+ // Optionally, |certificate_keygen_algorithm| is JavaScript for an
+ // |AlgorithmIdentifier| to be used as parameter to
+ // |RTCPeerConnection.generateCertificate|. The resulting certificate will be
+ // used by the peer connection. Or use |kUseDefaultCertKeygen| to use a
+ // certificate.
+ void SetupPeerconnectionWithLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+ // Same as above but does not add the local stream.
+ void SetupPeerconnectionWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+ // Same as |SetupPeerconnectionWithLocalStream| except a certificate is
+ // specified, which is a reference to an |RTCCertificate| object.
+ void SetupPeerconnectionWithCertificateAndLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+ // Same as above but does not add the local stream.
+ void SetupPeerconnectionWithCertificateWithoutLocalStream(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+ // Same as |SetupPeerconnectionWithLocalStream| except RTCPeerConnection
+ // constraints are specified.
+ void SetupPeerconnectionWithConstraintsAndLocalStream(
+ content::WebContents* tab,
+ const std::string& constraints,
+ const std::string& certificate_keygen_algorithm =
+ kUseDefaultCertKeygen) const;
+
+ void CreateDataChannel(content::WebContents* tab, const std::string& label);
+
+ // Exchanges offers and answers between the peer connections in the
+ // respective tabs. Before calling this, you must have prepared peer
+ // connections in both tabs and configured them as you like (for instance by
+ // calling SetupPeerconnectionWithLocalStream).
+ // If |video_codec| is not |kUseDefaultVideoCodec|, the SDP offer is modified
+ // (and SDP answer verified) so that the specified video codec (case-sensitive
+ // name) is used during the call instead of the default one.
+ void NegotiateCall(content::WebContents* from_tab,
+ content::WebContents* to_tab) const;
+
+ void VerifyLocalDescriptionContainsCertificate(
+ content::WebContents* tab,
+ const std::string& certificate) const;
+
+ // Hangs up a negotiated call.
+ void HangUp(content::WebContents* from_tab) const;
+
+ // Call this to enable monitoring of javascript errors for this test method.
+ // This will only work if the tests are run sequentially by the test runner
+ // (i.e. with --test-launcher-developer-mode or --test-launcher-jobs=1).
+ void DetectErrorsInJavaScript();
+
+ // Methods for detecting if video is playing (the loaded page must have
+ // chrome/test/data/webrtc/video_detector.js and its dependencies loaded to
+ // make that work). Looks at a 320x240 area of the target video tag.
+ void StartDetectingVideo(content::WebContents* tab_contents,
+ const std::string& video_element) const;
+ bool WaitForVideoToPlay(content::WebContents* tab_contents) const;
+
+ // Returns the stream size as a string on the format <width>x<height>.
+ std::string GetStreamSize(content::WebContents* tab_contents,
+ const std::string& video_element) const;
+
+ // Returns true if we're on Windows 8 or higher.
+ bool OnWin8OrHigher() const;
+
+ void OpenDatabase(content::WebContents* tab) const;
+ void CloseDatabase(content::WebContents* tab) const;
+ void DeleteDatabase(content::WebContents* tab) const;
+
+ void GenerateAndCloneCertificate(content::WebContents* tab,
+ const std::string& keygen_algorithm) const;
+
+ void VerifyStatsGeneratedCallback(content::WebContents* tab) const;
+ double MeasureGetStatsCallbackPerformance(content::WebContents* tab) const;
+ std::vector<std::string> VerifyStatsGeneratedPromise(
+ content::WebContents* tab) const;
+ scoped_refptr<content::TestStatsReportDictionary> GetStatsReportDictionary(
+ content::WebContents* tab) const;
+ double MeasureGetStatsPerformance(content::WebContents* tab) const;
+ std::vector<std::string> GetMandatoryStatsTypes(
+ content::WebContents* tab) const;
+
+ // Change the default audio/video codec in the offer SDP.
+ void SetDefaultAudioCodec(content::WebContents* tab,
+ const std::string& audio_codec) const;
+ // |prefer_hw_codec| controls what codec with name |video_codec| (and with
+ // profile |profile| if given)should be selected. This parameter only matters
+ // if there are multiple codecs with the same name, which can be the case for
+ // H264.
+ void SetDefaultVideoCodec(content::WebContents* tab,
+ const std::string& video_codec,
+ bool prefer_hw_codec = false,
+ const std::string& profile = std::string()) const;
+
+ // Add 'usedtx=1' to the offer SDP.
+ void EnableOpusDtx(content::WebContents* tab) const;
+
+ // Try to open a dekstop media stream, and return the stream id.
+ // On failure, will return empty string.
+ std::string GetDesktopMediaStream(content::WebContents* tab);
+ base::Optional<std::string> LoadDesktopCaptureExtension();
+
+ private:
+ void CloseInfoBarInTab(content::WebContents* tab_contents,
+ infobars::InfoBar* infobar) const;
+
+ std::string CreateLocalOffer(content::WebContents* from_tab) const;
+ std::string CreateAnswer(std::string local_offer,
+ content::WebContents* to_tab) const;
+ void ReceiveAnswer(const std::string& answer,
+ content::WebContents* from_tab) const;
+ void GatherAndSendIceCandidates(content::WebContents* from_tab,
+ content::WebContents* to_tab) const;
+ bool HasStreamWithTrack(content::WebContents* tab,
+ const char* function_name,
+ std::string stream_id,
+ std::string track_id) const;
+
+ infobars::InfoBar* GetUserMediaAndWaitForInfoBar(
+ content::WebContents* tab_contents,
+ const std::string& constraints) const;
+
+ bool detect_errors_in_javascript_;
+ scoped_refptr<const extensions::Extension> desktop_capture_extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcTestBase);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_BASE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc
new file mode 100644
index 00000000000..3bb61917d8c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.cc
@@ -0,0 +1,180 @@
+// 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 "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/common/chrome_paths.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace test {
+
+// Relative to the chrome/test/data directory.
+const base::FilePath::CharType kReferenceFilesDirName[] =
+ FILE_PATH_LITERAL("webrtc/resources");
+const base::FilePath::CharType kReferenceFileName360p[] =
+ FILE_PATH_LITERAL("reference_video_640x360_30fps");
+const base::FilePath::CharType kReferenceFileName720p[] =
+ FILE_PATH_LITERAL("reference_video_1280x720_30fps");
+const base::FilePath::CharType kYuvFileExtension[] = FILE_PATH_LITERAL("yuv");
+const base::FilePath::CharType kY4mFileExtension[] = FILE_PATH_LITERAL("y4m");
+
+// This message describes how to modify your .gclient to get the reference
+// video files downloaded for you.
+const char kAdviseOnGclientSolution[] =
+ "To run this test, you must run download_from_google_storage --config\n"
+ "and follow the instructions (use 'browser' for project id)\n"
+ "You also need to add this solution to your .gclient:\n"
+ "{\n"
+ " \"name\" : \"webrtc.DEPS\",\n"
+ " \"url\" : \"https://chromium.googlesource.com/chromium/deps/"
+ "webrtc/webrtc.DEPS\",\n"
+ "}\n"
+ "and run gclient sync. This will download the required ref files.";
+
+#if defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER) || \
+ defined(ADDRESS_SANITIZER)
+#if defined(OS_CHROMEOS)
+const int kDefaultPollIntervalMsec = 2000;
+#else
+const int kDefaultPollIntervalMsec = 1000;
+#endif // OS_CHROMEOS
+#else
+#if defined(OS_CHROMEOS)
+const int kDefaultPollIntervalMsec = 500;
+#else
+const int kDefaultPollIntervalMsec = 250;
+#endif // OS_CHROMEOS
+#endif
+
+bool IsErrorResult(const std::string& result) {
+ return base::StartsWith(result, "failed-",
+ base::CompareCase::INSENSITIVE_ASCII);
+}
+
+base::FilePath GetReferenceFilesDir() {
+ base::FilePath test_data_dir;
+ base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
+
+ return test_data_dir.Append(kReferenceFilesDirName);
+}
+
+base::FilePath GetToolForPlatform(const std::string& tool_name) {
+ base::FilePath tools_dir =
+ GetReferenceFilesDir().Append(FILE_PATH_LITERAL("tools"));
+#if defined(OS_WIN)
+ return tools_dir
+ .Append(FILE_PATH_LITERAL("win"))
+ .AppendASCII(tool_name)
+ .AddExtension(FILE_PATH_LITERAL("exe"));
+#elif defined(OS_MACOSX)
+ return tools_dir.Append(FILE_PATH_LITERAL("mac")).AppendASCII(tool_name);
+#elif defined(OS_LINUX)
+ return tools_dir.Append(FILE_PATH_LITERAL("linux")).AppendASCII(tool_name);
+#else
+ CHECK(false) << "Can't retrieve tool " << tool_name << " on this platform.";
+ return base::FilePath();
+#endif
+}
+
+bool HasReferenceFilesInCheckout() {
+ if (!base::PathExists(GetReferenceFilesDir())) {
+ LOG(ERROR)
+ << "Cannot find the working directory for the reference video "
+ << "files, expected at " << GetReferenceFilesDir().value() << ". " <<
+ kAdviseOnGclientSolution;
+ return false;
+ }
+ return HasYuvAndY4mFile(test::kReferenceFileName360p) &&
+ HasYuvAndY4mFile(test::kReferenceFileName720p);
+}
+
+bool HasYuvAndY4mFile(const base::FilePath::CharType* reference_file) {
+ base::FilePath webrtc_reference_video_yuv = GetReferenceFilesDir()
+ .Append(reference_file).AddExtension(kYuvFileExtension);
+ if (!base::PathExists(webrtc_reference_video_yuv)) {
+ LOG(ERROR)
+ << "Missing YUV reference video to be used for quality"
+ << " comparison, expected at " << webrtc_reference_video_yuv.value()
+ << ". " << kAdviseOnGclientSolution;
+ return false;
+ }
+
+ base::FilePath webrtc_reference_video_y4m = GetReferenceFilesDir()
+ .Append(reference_file).AddExtension(kY4mFileExtension);
+ if (!base::PathExists(webrtc_reference_video_y4m)) {
+ LOG(ERROR)
+ << "Missing Y4M reference video to be used for quality"
+ << " comparison, expected at "<< webrtc_reference_video_y4m.value()
+ << ". " << kAdviseOnGclientSolution;
+ return false;
+ }
+ return true;
+}
+
+bool SleepInJavascript(content::WebContents* tab_contents, int timeout_msec) {
+ const std::string javascript = base::StringPrintf(
+ "setTimeout(function() {"
+ " window.domAutomationController.send('sleep-ok');"
+ "}, %d)", timeout_msec);
+
+ std::string result;
+ bool ok = content::ExecuteScriptAndExtractString(
+ tab_contents, javascript, &result);
+ return ok && result == "sleep-ok";
+}
+
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents) {
+ return PollingWaitUntil(javascript, evaluates_to, tab_contents,
+ kDefaultPollIntervalMsec);
+}
+
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents,
+ int poll_interval_msec) {
+ base::Time start_time = base::Time::Now();
+ base::TimeDelta timeout = TestTimeouts::action_max_timeout();
+ std::string result;
+
+ while (base::Time::Now() - start_time < timeout) {
+ std::string result;
+ if (!content::ExecuteScriptAndExtractString(tab_contents, javascript,
+ &result)) {
+ LOG(ERROR) << "Failed to execute javascript " << javascript;
+ return false;
+ }
+
+ if (evaluates_to == result) {
+ return true;
+ } else if (IsErrorResult(result)) {
+ LOG(ERROR) << "|" << javascript << "| returned an error: " << result;
+ return false;
+ }
+
+ // Sleep a bit here to keep this loop from spinlocking too badly.
+ if (!SleepInJavascript(tab_contents, poll_interval_msec)) {
+ // TODO(phoglund): Figure out why this fails every now and then.
+ // It's not a huge deal if it does though.
+ LOG(ERROR) << "Failed to sleep.";
+ }
+ }
+ LOG(ERROR)
+ << "Timed out while waiting for " << javascript
+ << " to evaluate to " << evaluates_to << "; last result was '" << result
+ << "'";
+ return false;
+}
+
+} // namespace test
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h
new file mode 100644
index 00000000000..cd3ecef1f4c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_common.h
@@ -0,0 +1,67 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/process/process_handle.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace test {
+
+// Reference file locations.
+
+// Checks if the user has the reference files directory, returns true if so.
+// If the user's checkout don't have these dirs, they need to configure their
+// .gclient as described in chrome/test/data/webrtc/resources/README. The reason
+// for this is that we don't want to burden regular chrome devs with downloading
+// these sizable reference files by default.
+bool HasReferenceFilesInCheckout();
+
+// Verifies both the YUV and Y4M version of the reference file exists.
+bool HasYuvAndY4mFile(const base::FilePath::CharType* reference_file);
+
+// Retrieves the reference files dir, to which file names can be appended.
+base::FilePath GetReferenceFilesDir();
+
+// Retrieves a tool binary path from chrome/test/data/webrtc/resources/tools,
+// according to platform. If we're running on Linux, requesting pesq will yield
+// chrome/test/data/webrtc/resources/tools/linux/pesq, whereas the same call on
+// Windows will yield chrome/test/data/webrtc/resources/tools/win/pesq.exe.
+// This function does not check the binary actually exists.
+base::FilePath GetToolForPlatform(const std::string& tool_name);
+
+extern const base::FilePath::CharType kReferenceFileName360p[];
+extern const base::FilePath::CharType kReferenceFileName720p[];
+extern const base::FilePath::CharType kYuvFileExtension[];
+extern const base::FilePath::CharType kY4mFileExtension[];
+extern const char kAdviseOnGclientSolution[];
+
+// Executes javascript code which will sleep for |timeout_msec| milliseconds.
+// Returns true on success.
+bool SleepInJavascript(content::WebContents* tab_contents, int timeout_msec);
+
+// This function will execute the provided |javascript| until it causes a call
+// to window.domAutomationController.send() with |evaluates_to| as the message.
+// That is, we are NOT checking what the javascript evaluates to. Returns false
+// if we exceed the TestTimeouts::action_max_timeout().
+// TODO(phoglund): Consider a better interaction method with the javascript
+// than polling javascript methods.
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents);
+bool PollingWaitUntil(const std::string& javascript,
+ const std::string& evaluates_to,
+ content::WebContents* tab_contents,
+ int poll_interval_msec);
+
+} // namespace test
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_COMMON_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc
new file mode 100644
index 00000000000..9a61637d902
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.cc
@@ -0,0 +1,338 @@
+// 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 "chrome/browser/media/webrtc/webrtc_browsertest_perf.h"
+
+#include <stddef.h>
+
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "testing/perf/perf_result_reporter.h"
+
+namespace {
+
+constexpr char kMetricPrefixAudioReceive[] = "WebRtcAudioReceive.";
+constexpr char kMetricPrefixAudioSend[] = "WebRtcAudioSend.";
+constexpr char kMetricPrefixVideoSend[] = "WebRtcVideoSend.";
+constexpr char kMetricPrefixVideoRecieve[] = "WebRtcVideoReceive.";
+constexpr char kMetricPrefixBwe[] = "WebRtcBwe.";
+constexpr char kMetricPacketsLostFrames[] = "packets_lost";
+constexpr char kMetricGoogJitterRecvMs[] = "goog_jitter_recv";
+constexpr char kMetricGoogExpandRatePercent[] = "goog_expand_rate";
+constexpr char kMetricGoogSpeechExpandRatePercent[] = "goog_speech_expand_rate";
+constexpr char kMetricGoogSecondaryDecodeRatePercent[] =
+ "goog_secondary_decode_rate";
+constexpr char kMetricGoogRttMs[] = "goog_rtt";
+constexpr char kMetricPacketsPerSecondPackets[] = "packets_per_second";
+constexpr char kMetricGoogFpsSentFps[] = "goog_frame_rate_sent";
+constexpr char kMetricGoogFpsInputFps[] = "goog_frame_rate_input";
+constexpr char kMetricGoogFirsReceivedUnitless[] = "goog_firs_recv";
+constexpr char kMetricGoogNacksReceivedUnitless[] = "goog_nacks_recv";
+constexpr char kMetricGoogFrameWidthCount[] = "goog_frame_width";
+constexpr char kMetricGoogFrameHeightCount[] = "goog_frame_height";
+constexpr char kMetricGoogAvgEncodeMs[] = "goog_avg_encode";
+constexpr char kMetricGoogEncodeCpuUsagePercent[] = "goog_encode_cpu_usage";
+constexpr char kMetricGoogFpsRecvFps[] = "goog_frame_rate_recv";
+constexpr char kMetricGoogFpsOutputFps[] = "goog_frame_rate_output";
+constexpr char kMetricGoogActualDelayMs[] = "goog_actual_delay";
+constexpr char kMetricGoogTargetDelayMs[] = "goog_target_delay";
+constexpr char kMetricGoogDecodeTimeMs[] = "goog_decode_time";
+constexpr char kMetricGoogMaxDecodeTimeMs[] = "goog_max_decode_time";
+constexpr char kMetricGoogJitterBufferMs[] = "goog_jitter_buffer";
+constexpr char kMetricGoogRenderDelayMs[] = "goog_render_delay";
+constexpr char kMetricAvailableSendBandwidthBitsPerS[] = "available_send_bw";
+constexpr char kMetricAvailableRecvBandwidthBitsPerS[] = "available_recv_bw";
+constexpr char kMetricTargetEncodeBitrateBitsPerS[] = "target_encode_bitrate";
+constexpr char kMetricActualEncodeBitrateBitsPerS[] = "actual_encode_bitrate";
+constexpr char kMetricTransmitBitrateBitsPerS[] = "transmit_bitrate";
+
+perf_test::PerfResultReporter SetUpAudioReceiveReporter(
+ const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioReceive, story);
+ reporter.RegisterFyiMetric(kMetricPacketsLostFrames, "frames");
+ reporter.RegisterFyiMetric(kMetricGoogJitterRecvMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogExpandRatePercent, "%");
+ reporter.RegisterFyiMetric(kMetricGoogSpeechExpandRatePercent, "%");
+ reporter.RegisterFyiMetric(kMetricGoogSecondaryDecodeRatePercent, "%");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpAudioSendReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioSend, story);
+ reporter.RegisterFyiMetric(kMetricGoogJitterRecvMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRttMs, "ms");
+ reporter.RegisterFyiMetric(kMetricPacketsPerSecondPackets, "packets");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoSendReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoSend, story);
+ reporter.RegisterFyiMetric(kMetricGoogFpsSentFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFpsInputFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFirsReceivedUnitless, "unitless");
+ reporter.RegisterFyiMetric(kMetricGoogNacksReceivedUnitless, "unitless");
+ reporter.RegisterFyiMetric(kMetricGoogFrameWidthCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogFrameHeightCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogAvgEncodeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRttMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogEncodeCpuUsagePercent, "%");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoReceiveReporter(
+ const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoRecieve, story);
+ reporter.RegisterFyiMetric(kMetricGoogFpsRecvFps, "fps");
+ reporter.RegisterFyiMetric(kMetricGoogFpsOutputFps, "fps");
+ reporter.RegisterFyiMetric(kMetricPacketsLostFrames, "frames");
+ reporter.RegisterFyiMetric(kMetricGoogFrameWidthCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogFrameHeightCount, "count");
+ reporter.RegisterFyiMetric(kMetricGoogActualDelayMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogTargetDelayMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogDecodeTimeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogMaxDecodeTimeMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogJitterBufferMs, "ms");
+ reporter.RegisterFyiMetric(kMetricGoogRenderDelayMs, "ms");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpBweReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixBwe, story);
+ reporter.RegisterFyiMetric(kMetricAvailableSendBandwidthBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricAvailableRecvBandwidthBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricTargetEncodeBitrateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricActualEncodeBitrateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricTransmitBitrateBitsPerS, "bits/s");
+ return reporter;
+}
+
+} // namespace
+
+static std::string Statistic(const std::string& statistic,
+ const std::string& bucket) {
+ // A ssrc stats key will be on the form stats.<bucket>-<key>.values.
+ // This will give a json "path" which will dig into the time series for the
+ // specified statistic. Buckets can be for instance ssrc_1212344, bweforvideo,
+ // and will each contain a bunch of statistics relevant to their nature.
+ // Each peer connection has a number of such buckets.
+ return base::StringPrintf("stats.%s-%s.values", bucket.c_str(),
+ statistic.c_str());
+}
+
+static void MaybePrintResultsForAudioReceive(
+ const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("audioOutputLevel", ssrc), &value)) {
+ // Not an audio receive stream.
+ return;
+ }
+
+ auto reporter = SetUpAudioReceiveReporter(story);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
+ reporter.AddResult(kMetricPacketsLostFrames, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterRecvMs, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googExpandRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogExpandRatePercent, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googSpeechExpandRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogSpeechExpandRatePercent, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googSecondaryDecodedRate", ssrc), &value));
+ reporter.AddResult(kMetricGoogSecondaryDecodeRatePercent, value);
+}
+
+static void MaybePrintResultsForAudioSend(const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("audioInputLevel", ssrc), &value)) {
+ // Not an audio send stream.
+ return;
+ }
+
+ auto reporter = SetUpAudioSendReporter(story);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterRecvMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
+ reporter.AddResult(kMetricGoogRttMs, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("packetsSentPerSecond", ssrc), &value));
+ reporter.AddResult(kMetricPacketsPerSecondPackets, value);
+}
+
+static void MaybePrintResultsForVideoSend(const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("googFrameRateSent", ssrc), &value)) {
+ // Not a video send stream.
+ return;
+ }
+
+ // Graph these by unit: the dashboard expects all stats in one graph to have
+ // the same unit (e.g. ms, fps, etc). Most graphs, like video_fps, will also
+ // be populated by the counterparts on the video receiving side.
+ auto reporter = SetUpVideoSendReporter(story);
+ reporter.AddResult(kMetricGoogFpsSentFps, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameRateInput", ssrc), &value));
+ reporter.AddResult(kMetricGoogFpsInputFps, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFirsReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFirsReceivedUnitless, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googNacksReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogNacksReceivedUnitless, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameWidthSent", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameWidthCount, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameHeightSent", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameHeightCount, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googAvgEncodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogAvgEncodeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
+ reporter.AddResult(kMetricGoogRttMs, value);
+
+ EXPECT_TRUE(pc_dict.GetString(
+ Statistic("googEncodeUsagePercent", ssrc), &value));
+ reporter.AddResult(kMetricGoogEncodeCpuUsagePercent, value);
+}
+
+static void MaybePrintResultsForVideoReceive(
+ const std::string& ssrc,
+ const base::DictionaryValue& pc_dict,
+ const std::string& story) {
+ std::string value;
+ if (!pc_dict.GetString(Statistic("googFrameRateReceived", ssrc), &value)) {
+ // Not a video receive stream.
+ return;
+ }
+
+ auto reporter = SetUpVideoReceiveReporter(story);
+ reporter.AddResult(kMetricGoogFpsRecvFps, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameRateOutput", ssrc), &value));
+ reporter.AddResult(kMetricGoogFpsOutputFps, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
+ reporter.AddResult(kMetricPacketsLostFrames, value);
+
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameWidthReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameWidthCount, value);
+ EXPECT_TRUE(
+ pc_dict.GetString(Statistic("googFrameHeightReceived", ssrc), &value));
+ reporter.AddResult(kMetricGoogFrameHeightCount, value);
+
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googCurrentDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogActualDelayMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googTargetDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogTargetDelayMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googDecodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogDecodeTimeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googMaxDecodeMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogMaxDecodeTimeMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterBufferMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogJitterBufferMs, value);
+ EXPECT_TRUE(pc_dict.GetString(Statistic("googRenderDelayMs", ssrc), &value));
+ reporter.AddResult(kMetricGoogRenderDelayMs, value);
+}
+
+static std::string ExtractSsrcIdentifier(const std::string& key) {
+ // Example key: ssrc_1234-someStatName. Grab the part before the dash.
+ size_t key_start_pos = 0;
+ size_t key_end_pos = key.find("-");
+ CHECK(key_end_pos != std::string::npos) << "Could not parse key " << key;
+ return key.substr(key_start_pos, key_end_pos - key_start_pos);
+}
+
+// Returns the set of unique ssrc identifiers in the call (e.g. ssrc_1234,
+// ssrc_12356, etc). |stats_dict| is the .stats dict from one peer connection.
+static std::set<std::string> FindAllSsrcIdentifiers(
+ const base::DictionaryValue& stats_dict) {
+ std::set<std::string> result;
+ base::DictionaryValue::Iterator stats_iterator(stats_dict);
+
+ while (!stats_iterator.IsAtEnd()) {
+ if (stats_iterator.key().find("ssrc_") != std::string::npos)
+ result.insert(ExtractSsrcIdentifier(stats_iterator.key()));
+ stats_iterator.Advance();
+ }
+ return result;
+}
+
+namespace test {
+
+void PrintBweForVideoMetrics(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const std::string kBweStatsKey = "bweforvideo";
+ std::string value;
+ auto reporter = SetUpBweReporter(story);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googAvailableSendBandwidth", kBweStatsKey), &value));
+ reporter.AddResult(kMetricAvailableSendBandwidthBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googAvailableReceiveBandwidth", kBweStatsKey), &value));
+ reporter.AddResult(kMetricAvailableRecvBandwidthBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googTargetEncBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricTargetEncodeBitrateBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googActualEncBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricActualEncodeBitrateBitsPerS, value);
+ ASSERT_TRUE(pc_dict.GetString(
+ Statistic("googTransmitBitrate", kBweStatsKey), &value));
+ reporter.AddResult(kMetricTransmitBitrateBitsPerS, value);
+}
+
+void PrintMetricsForAllStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ PrintMetricsForSendStreams(pc_dict, modifier, video_codec);
+ PrintMetricsForRecvStreams(pc_dict, modifier, video_codec);
+}
+
+void PrintMetricsForSendStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const base::DictionaryValue* stats_dict;
+ ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
+ std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
+
+ auto ssrc_iterator = ssrc_identifiers.begin();
+ for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
+ const std::string& ssrc = *ssrc_iterator;
+ MaybePrintResultsForAudioSend(ssrc, pc_dict, story);
+ MaybePrintResultsForVideoSend(ssrc, pc_dict, story);
+ }
+}
+
+void PrintMetricsForRecvStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec) {
+ std::string story = video_codec.empty() ? "baseline_story" : video_codec;
+ story += modifier;
+ const base::DictionaryValue* stats_dict;
+ ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
+ std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
+
+ auto ssrc_iterator = ssrc_identifiers.begin();
+ for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
+ const std::string& ssrc = *ssrc_iterator;
+ MaybePrintResultsForAudioReceive(ssrc, pc_dict, story);
+ MaybePrintResultsForVideoReceive(ssrc, pc_dict, story);
+ }
+}
+
+} // namespace test
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h
new file mode 100644
index 00000000000..9394f235b64
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_browsertest_perf.h
@@ -0,0 +1,44 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
+
+#include <string>
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace test {
+
+// These functions takes parsed data (on one peer connection) from the
+// peerConnectionDataStore object that is backing webrtc-internals and prints
+// metrics they consider interesting using testing/perf/perf_test.h primitives.
+// The idea is to put as many webrtc-related metrics as possible into the
+// dashboard and thereby track it for regressions.
+//
+// These functions expect to run under googletest and will use EXPECT_ and
+// ASSERT_ macros to signal failure. They take as argument one peer connection's
+// stats data and a |modifier| to append to each result bucket. For instance, if
+// the modifier is '_oneway', the rtt stat will be logged as goog_rtt in
+// the video_tx_oneway bucket.
+// If |video_codec| is a non-empty string, the codec name is appended last for
+// video metrics, e.g. 'video_tx_oneway_VP9'.
+void PrintBweForVideoMetrics(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForAllStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForSendStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+void PrintMetricsForRecvStreams(const base::DictionaryValue& pc_dict,
+ const std::string& modifier,
+ const std::string& video_codec);
+
+} // namespace test
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_BROWSERTEST_PERF_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
new file mode 100644
index 00000000000..1b757d9f528
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
@@ -0,0 +1,96 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+} // namespace
+
+// Top-level integration test for WebRTC. Uses an actual desktop capture
+// extension to capture whole screen.
+class WebRtcDesktopCaptureBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcDesktopCaptureBrowserTest() : left_tab_(nullptr), right_tab_(nullptr) {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Flags use to automatically select the right dekstop source and get
+ // around security restrictions.
+ command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
+ "Entire screen");
+ command_line->AppendSwitch(switches::kEnableUserMediaScreenCapturing);
+ }
+
+ protected:
+ void DetectVideoAndHangUp() {
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+#if !defined(OS_MACOSX)
+ // Video is choppy on Mac OS X. http://crbug.com/443542.
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+#endif
+ HangUp(left_tab_);
+ HangUp(right_tab_);
+ }
+
+ content::WebContents* left_tab_;
+ content::WebContents* right_tab_;
+};
+
+// TODO(crbug.com/796889): Enable on Mac when thread check crash is fixed.
+// TODO(sprang): Figure out why test times out on Win 10 and ChromeOS.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#define MAYBE_RunsScreenshareFromOneTabToAnother \
+ RunsScreenshareFromOneTabToAnother
+#else
+#define MAYBE_RunsScreenshareFromOneTabToAnother \
+ DISABLED_RunsScreenshareFromOneTabToAnother
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest,
+ MAYBE_RunsScreenshareFromOneTabToAnother) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ LoadDesktopCaptureExtension();
+ left_tab_ = OpenTestPageInNewTab(kMainWebrtcTestHtmlPage);
+ std::string stream_id = GetDesktopMediaStream(left_tab_);
+ EXPECT_NE(stream_id, "");
+
+ LOG(INFO) << "Opened desktop media stream, got id " << stream_id;
+
+ const std::string constraints =
+ "{audio: false, video: {mandatory: {chromeMediaSource: 'desktop',"
+ "chromeMediaSourceId: '" +
+ stream_id + "'}}}";
+ EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+ left_tab_, constraints));
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ NegotiateCall(left_tab_, right_tab_);
+ VerifyStatsGeneratedCallback(right_tab_);
+ DetectVideoAndHangUp();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
new file mode 100644
index 00000000000..c8897585016
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
@@ -0,0 +1,99 @@
+// Copyright 2014 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/macros.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/common/channel_info.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+// This tests the --disable-webrtc-encryption command line flag. Disabling
+// encryption should only be possible on certain channels.
+
+// NOTE: The test case for each channel will only be exercised when the browser
+// is actually built for that channel. This is not ideal. One can test manually
+// by e.g. faking the channel returned in chrome::GetChannel(). It's likely good
+// to have the test anyway, even though a failure might not be detected until a
+// branch has been promoted to another channel. The unit test for
+// ChromeContentBrowserClient::MaybeCopyDisableWebRtcEncryptionSwitch tests for
+// all channels however.
+// TODO(grunell): Test the different channel cases for any build.
+class WebRtcDisableEncryptionFlagBrowserTest : public WebRtcTestBase {
+ public:
+ WebRtcDisableEncryptionFlagBrowserTest() {}
+ ~WebRtcDisableEncryptionFlagBrowserTest() override {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // This test should run with fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // Disable encryption with the command line flag.
+ command_line->AppendSwitch(switches::kDisableWebRtcEncryption);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRtcDisableEncryptionFlagBrowserTest);
+};
+
+// Makes a call and checks that there's encryption or not in the SDP offer.
+// TODO(crbug.com/910216): De-flake this for ChromeOs.
+// TODO(crbug.com/984879): De-flake this for MSAN Linux.
+#if defined(OS_CHROMEOS) || (defined(OS_LINUX) && defined(MEMORY_SANITIZER))
+#define MAYBE_VerifyEncryption DISABLED_VerifyEncryption
+#else
+#define MAYBE_VerifyEncryption VerifyEncryption
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcDisableEncryptionFlagBrowserTest,
+ MAYBE_VerifyEncryption) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(left_tab, "remote-view");
+ StartDetectingVideo(right_tab, "remote-view");
+
+ WaitForVideoToPlay(left_tab);
+ WaitForVideoToPlay(right_tab);
+
+ bool should_detect_encryption = true;
+ version_info::Channel channel = chrome::GetChannel();
+ if (channel == version_info::Channel::UNKNOWN ||
+ channel == version_info::Channel::CANARY ||
+ channel == version_info::Channel::DEV) {
+ should_detect_encryption = false;
+ }
+#if defined(OS_ANDROID)
+ if (channel == version_info::Channel::BETA)
+ should_detect_encryption = false;
+#endif
+
+ std::string expected_string = should_detect_encryption ?
+ "crypto-seen" : "no-crypto-seen";
+
+ ASSERT_EQ(expected_string,
+ ExecuteJavascript("hasSeenCryptoInSdp()", left_tab));
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc
new file mode 100644
index 00000000000..59f14e44920
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.cc
@@ -0,0 +1,423 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+const size_t kWebRtcEventLogMaxUploadIdBytes = 100;
+
+namespace {
+// Compactness is not important for these few and small files; we therefore
+// go with a human-readable format.
+const char kCaptureTimeLinePrefix[] =
+ "Capture time (seconds since UNIX epoch): ";
+const char kUploadTimeLinePrefix[] = "Upload time (seconds since UNIX epoch): ";
+const char kUploadIdLinePrefix[] = "Upload ID: ";
+
+// No need to use \r\n for Windows; better have a consistent file format
+// between platforms.
+const char kEOL[] = "\n";
+static_assert(base::size(kEOL) == 1 + 1 /* +1 for the implicit \0. */,
+ "SplitString relies on this being a single character.");
+
+// |time| must *not* be earlier than UNIX epoch start. If it is, the empty
+// string is returned.
+std::string DeltaFromEpochSeconds(base::Time time) {
+ if (time.is_null() || time.is_min() || time.is_max()) {
+ LOG(ERROR) << "Not a valid time (" << time << ").";
+ return std::string();
+ }
+
+ const base::Time epoch = base::Time::UnixEpoch();
+ if (time < epoch) {
+ LOG(WARNING) << "Time to go back to the future.";
+ return std::string();
+ }
+
+ return base::NumberToString((time - epoch).InSeconds());
+}
+
+// Helper for ParseTime; see its documentation for details.
+base::Time StringToTime(const std::string& time) {
+ int64_t seconds_from_epoch;
+ if (!base::StringToInt64(time, &seconds_from_epoch) ||
+ seconds_from_epoch < 0) {
+ LOG(WARNING) << "Error encountered while reading time.";
+ return base::Time();
+ }
+
+ return base::Time::UnixEpoch() +
+ base::TimeDelta::FromSeconds(seconds_from_epoch);
+}
+
+// Convert a history file's timestamp, which is the number of seconds since
+// UNIX epoch, into a base::Time object.
+// This function errors on timestamps from UNIX epoch or before it.
+bool ParseTime(const std::string& line,
+ const std::string& prefix,
+ base::Time* out) {
+ DCHECK(line.find(prefix) == 0);
+ DCHECK(out);
+
+ if (!out->is_null()) {
+ LOG(WARNING) << "Repeated line.";
+ return false;
+ }
+
+ const base::Time time = StringToTime(line.substr(prefix.length()));
+ if (time.is_null()) {
+ LOG(WARNING) << "Null time.";
+ return false;
+ }
+
+ *out = time;
+
+ return true;
+}
+
+bool ParseString(const std::string& line,
+ const std::string& prefix,
+ std::string* out) {
+ DCHECK(line.find(prefix) == 0);
+ DCHECK(out);
+
+ if (!out->empty()) {
+ LOG(WARNING) << "Repeated line.";
+ return false;
+ }
+
+ *out = line.substr(prefix.length());
+
+ if (out->empty()) {
+ LOG(WARNING) << "Empty string.";
+ return false;
+ }
+
+ return true;
+}
+} // namespace
+
+std::unique_ptr<WebRtcEventLogHistoryFileWriter>
+WebRtcEventLogHistoryFileWriter::Create(const base::FilePath& path) {
+ auto history_file_writer =
+ base::WrapUnique(new WebRtcEventLogHistoryFileWriter(path));
+ if (!history_file_writer->Init()) {
+ LOG(WARNING) << "Initialization of history file writer failed.";
+ return nullptr;
+ }
+ return history_file_writer;
+}
+
+WebRtcEventLogHistoryFileWriter::WebRtcEventLogHistoryFileWriter(
+ const base::FilePath& path)
+ : path_(path), valid_(false) {}
+
+bool WebRtcEventLogHistoryFileWriter::Init() {
+ DCHECK(!valid_);
+
+ if (base::PathExists(path_)) {
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "History file already exists, and could not be deleted.";
+ return false;
+ } else {
+ LOG(WARNING) << "History file already existed; deleted.";
+ }
+ }
+
+ // Attempt to create the file.
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file_.Initialize(path_, file_flags);
+ if (!file_.IsValid() || !file_.created()) {
+ LOG(WARNING) << "Couldn't create history file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ return false;
+ }
+
+ valid_ = true;
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteCaptureTime(
+ base::Time capture_time) {
+ DCHECK(valid_);
+
+ if (capture_time.is_null()) {
+ valid_ = false;
+ return false;
+ }
+
+ const std::string delta_seconds = DeltaFromEpochSeconds(capture_time);
+ if (delta_seconds.empty()) {
+ valid_ = false;
+ return false;
+ }
+
+ const bool written = Write(kCaptureTimeLinePrefix + delta_seconds + kEOL);
+ if (!written) {
+ // Error logged by Write().
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteUploadTime(base::Time upload_time) {
+ DCHECK(valid_);
+
+ if (upload_time.is_null()) {
+ valid_ = false;
+ return false;
+ }
+
+ const std::string delta_seconds = DeltaFromEpochSeconds(upload_time);
+ if (delta_seconds.empty()) {
+ valid_ = false;
+ return false;
+ }
+
+ const bool written = Write(kUploadTimeLinePrefix + delta_seconds + kEOL);
+ if (!written) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcEventLogHistoryFileWriter::WriteUploadId(
+ const std::string& upload_id) {
+ DCHECK(valid_);
+ DCHECK(!upload_id.empty());
+ DCHECK_LE(upload_id.length(), kWebRtcEventLogMaxUploadIdBytes);
+
+ const bool written = Write(kUploadIdLinePrefix + upload_id + kEOL);
+ if (!written) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+void WebRtcEventLogHistoryFileWriter::Delete() {
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "History file could not be deleted.";
+ }
+
+ valid_ = false; // Like was already false.
+}
+
+base::FilePath WebRtcEventLogHistoryFileWriter::path() const {
+ DCHECK(valid_); // Can be performed on invalid objects, but likely shouldn't.
+ return path_;
+}
+
+bool WebRtcEventLogHistoryFileWriter::Write(const std::string& str) {
+ DCHECK(valid_);
+ DCHECK(!str.empty());
+ DCHECK_LE(str.length(), static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ const int written = file_.WriteAtCurrentPos(str.c_str(), str.length());
+ if (written != static_cast<int>(str.length())) {
+ LOG(WARNING) << "Writing to history file failed.";
+ valid_ = false;
+ return false;
+ }
+
+ // Writes to the history file are infrequent, and happen on a |task_runner_|
+ // dedicated to event logs. We can therefore afford to Flush() after every
+ // write, giving us greater confidence that information would not get lost if,
+ // e.g., Chrome crashes.
+ file_.Flush();
+
+ return true;
+}
+
+std::unique_ptr<WebRtcEventLogHistoryFileReader>
+WebRtcEventLogHistoryFileReader::Create(const base::FilePath& path) {
+ auto history_file_reader =
+ base::WrapUnique(new WebRtcEventLogHistoryFileReader(path));
+ if (!history_file_reader->Init()) {
+ LOG(WARNING) << "Initialization of history file reader failed.";
+ return nullptr;
+ }
+ return history_file_reader;
+}
+
+WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
+ const base::FilePath& path)
+ : path_(path),
+ local_id_(ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(path_)),
+ valid_(false) {}
+
+WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
+ WebRtcEventLogHistoryFileReader&& other)
+ : path_(other.path_),
+ local_id_(other.local_id_),
+ capture_time_(other.capture_time_),
+ upload_time_(other.upload_time_),
+ upload_id_(other.upload_id_),
+ valid_(other.valid_) {
+ other.valid_ = false;
+}
+
+bool WebRtcEventLogHistoryFileReader::Init() {
+ DCHECK(!valid_);
+
+ if (local_id_.empty()) {
+ LOG(WARNING) << "Unknown local ID.";
+ return false;
+ }
+
+ if (local_id_.length() > kWebRtcEventLogMaxUploadIdBytes) {
+ LOG(WARNING) << "Excessively long local ID.";
+ return false;
+ }
+
+ if (!base::PathExists(path_)) {
+ LOG(WARNING) << "File does not exist.";
+ return false;
+ }
+
+ constexpr int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ base::File file(path_, file_flags);
+ if (!file.IsValid()) {
+ LOG(WARNING) << "Couldn't read history file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ return false;
+ }
+
+ constexpr size_t kMaxHistoryFileSizeBytes = 1024;
+ static_assert(kWebRtcEventLogMaxUploadIdBytes < kMaxHistoryFileSizeBytes, "");
+
+ std::string file_contents;
+ file_contents.resize(kMaxHistoryFileSizeBytes);
+ const int read_bytes = file.Read(0, &file_contents[0], file_contents.size());
+ if (read_bytes < 0) {
+ LOG(WARNING) << "Couldn't read contents of history file.";
+ return false;
+ }
+ DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
+ file_contents.resize(static_cast<size_t>(read_bytes));
+ // Note: In excessively long files, the rest of the file will be ignored; the
+ // beginning of the file will encounter a parse error.
+
+ if (!Parse(file_contents)) {
+ LOG(WARNING) << "Parsing of history file failed.";
+ return false;
+ }
+
+ valid_ = true;
+ return true;
+}
+
+std::string WebRtcEventLogHistoryFileReader::LocalId() const {
+ DCHECK(valid_);
+ DCHECK(!local_id_.empty());
+ return local_id_;
+}
+
+base::Time WebRtcEventLogHistoryFileReader::CaptureTime() const {
+ DCHECK(valid_);
+ DCHECK(!capture_time_.is_null());
+ return capture_time_;
+}
+
+base::Time WebRtcEventLogHistoryFileReader::UploadTime() const {
+ DCHECK(valid_);
+ return upload_time_; // May be null (which indicates "unset").
+}
+
+std::string WebRtcEventLogHistoryFileReader::UploadId() const {
+ DCHECK(valid_);
+ return upload_id_;
+}
+
+base::FilePath WebRtcEventLogHistoryFileReader::path() const {
+ DCHECK(valid_);
+ return path_;
+}
+
+bool WebRtcEventLogHistoryFileReader::operator<(
+ const WebRtcEventLogHistoryFileReader& other) const {
+ DCHECK(valid_);
+ DCHECK(!capture_time_.is_null());
+ DCHECK(other.valid_);
+ DCHECK(!other.capture_time_.is_null());
+ if (capture_time_ == other.capture_time_) {
+ // Resolve ties arbitrarily, but consistently (Local IDs are unique).
+ return LocalId() < other.LocalId();
+ }
+ return (capture_time_ < other.capture_time_);
+}
+
+bool WebRtcEventLogHistoryFileReader::Parse(const std::string& file_contents) {
+ DCHECK(!valid_);
+ DCHECK(capture_time_.is_null());
+ DCHECK(upload_time_.is_null());
+ DCHECK(upload_id_.empty());
+
+ const std::vector<std::string> lines =
+ base::SplitString(file_contents, kEOL, base::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+
+ for (const std::string& line : lines) {
+ if (line.find(kCaptureTimeLinePrefix) == 0) {
+ if (!ParseTime(line, kCaptureTimeLinePrefix, &capture_time_)) {
+ return false;
+ }
+ } else if (line.find(kUploadTimeLinePrefix) == 0) {
+ if (!ParseTime(line, kUploadTimeLinePrefix, &upload_time_)) {
+ return false;
+ }
+ } else if (line.find(kUploadIdLinePrefix) == 0) {
+ if (!ParseString(line, kUploadIdLinePrefix, &upload_id_)) {
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Unrecognized line in history file.";
+ return false;
+ }
+ }
+
+ if (capture_time_.is_null()) {
+ LOG(WARNING) << "Incomplete history file; capture time unknown.";
+ return false;
+ }
+
+ if (!upload_id_.empty() && upload_time_.is_null()) {
+ LOG(WARNING) << "Incomplete history file; upload time known, "
+ << "but ID unknown.";
+ return false;
+ }
+
+ if (!upload_time_.is_null() && upload_time_ < capture_time_) {
+ LOG(WARNING) << "Defective history file; claims to have been uploaded "
+ << "before being captured.";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h
new file mode 100644
index 00000000000..96180ec1ede
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_history.h
@@ -0,0 +1,121 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace webrtc_event_logging {
+
+// Writes a small history file to disk, which allows us to remember what logs
+// were captured and uploaded, after they are uploaded (whether successfully or
+// not), or after they ware pruned (if they expire before an upload opportunity
+// presents itself).
+class WebRtcEventLogHistoryFileWriter final {
+ public:
+ // Creates and initializes a WebRtcEventLogHistoryFileWriter object.
+ // Overwrites existing files on disk, if any.
+ // If initialization fails (e.g. couldn't create the file), an empty
+ // unique_ptr is returned.
+ static std::unique_ptr<WebRtcEventLogHistoryFileWriter> Create(
+ const base::FilePath& path);
+
+ // The capture time must be later than UNIX epoch start.
+ bool WriteCaptureTime(base::Time capture_time);
+
+ // The upload time must be later than UNIX epoch start.
+ // Writing an upload time earlier than the capture time is not prevented,
+ // but an invalid history file will be produced.
+ bool WriteUploadTime(base::Time upload_time);
+
+ // If |upload_id| is empty, it means the upload was not successful. In that
+ // case, the |upload_time| still denotes the time when the upload started.
+ // |upload_id|'s length must not exceed kWebRtcEventLogMaxUploadIdBytes.
+ bool WriteUploadId(const std::string& upload_id);
+
+ // Deletes the file being written to, and invalidates this object.
+ void Delete();
+
+ // May only be called on a valid object.
+ base::FilePath path() const;
+
+ private:
+ explicit WebRtcEventLogHistoryFileWriter(const base::FilePath& path);
+
+ // Returns true if initialization was successful; false otherwise.
+ // Overwrites existing files on disk, if any.
+ bool Init();
+
+ // Returns true if and only if the entire string was written to the file.
+ bool Write(const std::string& str);
+
+ const base::FilePath path_;
+ base::File file_;
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogHistoryFileWriter);
+};
+
+// Reads from disk a small history file and recovers the data from it.
+class WebRtcEventLogHistoryFileReader final {
+ public:
+ // Creates and initializes a WebRtcEventLogHistoryFileReader object.
+ // If initialization fails (e.g. couldn't parse the file), an empty
+ // unique_ptr is returned.
+ static std::unique_ptr<WebRtcEventLogHistoryFileReader> Create(
+ const base::FilePath& path);
+
+ WebRtcEventLogHistoryFileReader(WebRtcEventLogHistoryFileReader&& other);
+
+ // Mandatory fields.
+ std::string LocalId() const; // Must return a non-empty ID.
+ base::Time CaptureTime() const; // Must return a non-null base::Time.
+
+ // Optional fields.
+ base::Time UploadTime() const; // Non-null only if upload was attempted.
+ std::string UploadId() const; // Non-null only if upload was successful.
+
+ // May only be performed on a valid object.
+ base::FilePath path() const;
+
+ // Compares by capture time.
+ bool operator<(const WebRtcEventLogHistoryFileReader& other) const;
+
+ private:
+ explicit WebRtcEventLogHistoryFileReader(const base::FilePath& path);
+
+ // Returns true if initialization was successful; false otherwise.
+ // If true is returned, |this| is now valid, and will remain so until
+ // the object is destroyed or std::move()-ed away from.
+ bool Init();
+
+ // Returns true if parsing succeeded; false otherwise.
+ bool Parse(const std::string& file_contents);
+
+ const base::FilePath path_;
+
+ const std::string local_id_;
+
+ // Mandatory field; must be non-null (and therefore also non-zero).
+ base::Time capture_time_;
+
+ // Optional fields; may appear 0 or 1 times in the file.
+ base::Time upload_time_; // Nullness/zero-ness indicates "unset".
+ std::string upload_id_; // Empty string indicates "unset".
+
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogHistoryFileReader);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_HISTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
new file mode 100644
index 00000000000..577b56b007a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
@@ -0,0 +1,1086 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/render_process_host.h"
+
+#if defined OS_CHROMEOS
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#endif
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#endif
+
+namespace webrtc_event_logging {
+
+namespace {
+
+using BrowserContext = content::BrowserContext;
+using BrowserThread = content::BrowserThread;
+using RenderProcessHost = content::RenderProcessHost;
+
+using BrowserContextId = WebRtcEventLogManager::BrowserContextId;
+
+class PeerConnectionTrackerProxyImpl
+ : public WebRtcEventLogManager::PeerConnectionTrackerProxy {
+ public:
+ ~PeerConnectionTrackerProxyImpl() override = default;
+
+ void EnableWebRtcEventLogging(const WebRtcEventLogPeerConnectionKey& key,
+ int output_period_ms) override {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PeerConnectionTrackerProxyImpl::EnableWebRtcEventLoggingInternal,
+ key, output_period_ms));
+ }
+
+ void DisableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key) override {
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ &PeerConnectionTrackerProxyImpl::DisableWebRtcEventLoggingInternal,
+ key));
+ }
+
+ private:
+ static void EnableWebRtcEventLoggingInternal(
+ WebRtcEventLogPeerConnectionKey key,
+ int output_period_ms) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* host = RenderProcessHost::FromID(key.render_process_id);
+ if (!host) {
+ return; // The host has been asynchronously removed; not a problem.
+ }
+ host->EnableWebRtcEventLogOutput(key.lid, output_period_ms);
+ }
+
+ static void DisableWebRtcEventLoggingInternal(
+ WebRtcEventLogPeerConnectionKey key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* host = RenderProcessHost::FromID(key.render_process_id);
+ if (!host) {
+ return; // The host has been asynchronously removed; not a problem.
+ }
+ host->DisableWebRtcEventLogOutput(key.lid);
+ }
+};
+
+// Check whether remote-bound logging is generally allowed, although not
+// necessarily for any given user profile.
+// 1. Certain platforms (mobile) are blocked from remote-bound logging.
+// 2. There is a Finch-controlled kill-switch for the feature.
+bool IsRemoteLoggingFeatureEnabled() {
+#if defined(OS_ANDROID)
+ bool enabled = false;
+#else
+ bool enabled = base::FeatureList::IsEnabled(features::kWebRtcRemoteEventLog);
+#endif
+
+ VLOG(1) << "WebRTC remote-bound event logging "
+ << (enabled ? "enabled" : "disabled") << ".";
+
+ return enabled;
+}
+
+// Checks whether the Profile is considered managed. Used to
+// determine the default value for the policy controlling event logging.
+bool IsBrowserManagedForProfile(const Profile* profile) {
+// For Chrome OS, exclude the signin profile and ephemeral profiles.
+#if defined(OS_CHROMEOS)
+ if (chromeos::ProfileHelper::IsSigninProfile(profile) ||
+ chromeos::ProfileHelper::IsEphemeralUserProfile(profile)) {
+ return false;
+ }
+#endif
+
+ // Child accounts should not have a logging default of true so
+ // we do not consider them as being managed here.
+ if (profile->IsChild()) {
+ return false;
+ }
+
+ if (profile->GetProfilePolicyConnector()
+ ->policy_service()
+ ->IsInitializationComplete(policy::POLICY_DOMAIN_CHROME) &&
+ profile->GetProfilePolicyConnector()->IsManaged()) {
+ return true;
+ }
+
+ // For desktop, machine level policies (Windows, Linux, Mac OS) can affect
+ // user profiles, so we consider these profiles managed.
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ return g_browser_process->browser_policy_connector()
+ ->HasMachineLevelPolicies();
+#else
+ return false;
+#endif
+}
+
+BrowserContext* GetBrowserContext(int render_process_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHost* const host = RenderProcessHost::FromID(render_process_id);
+ return host ? host->GetBrowserContext() : nullptr;
+}
+
+// Post reply back if non-empty.
+template <typename... Args>
+inline void MaybeReply(const base::Location& location,
+ base::OnceCallback<void(Args...)> reply,
+ Args... args) {
+ if (reply) {
+ base::PostTask(location, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), args...));
+ }
+}
+
+} // namespace
+
+WebRtcEventLogManager* WebRtcEventLogManager::g_webrtc_event_log_manager =
+ nullptr;
+
+std::unique_ptr<WebRtcEventLogManager>
+WebRtcEventLogManager::CreateSingletonInstance() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = new WebRtcEventLogManager;
+ return base::WrapUnique<WebRtcEventLogManager>(g_webrtc_event_log_manager);
+}
+
+WebRtcEventLogManager* WebRtcEventLogManager::GetInstance() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return g_webrtc_event_log_manager;
+}
+
+base::FilePath WebRtcEventLogManager::GetRemoteBoundWebRtcEventLogsDir(
+ content::BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+ // Incognito BrowserContext will return their parent profile's directory.
+ return webrtc_event_logging::GetRemoteBoundWebRtcEventLogsDir(
+ browser_context->GetPath());
+}
+
+WebRtcEventLogManager::WebRtcEventLogManager()
+ : task_runner_(base::CreateSequencedTaskRunner(
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+ remote_logging_feature_enabled_(IsRemoteLoggingFeatureEnabled()),
+ local_logs_observer_(nullptr),
+ remote_logs_observer_(nullptr),
+ local_logs_manager_(this),
+ remote_logs_manager_(this, task_runner_),
+ pc_tracker_proxy_(new PeerConnectionTrackerProxyImpl),
+ first_browser_context_initializations_done_(false) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = this;
+}
+
+WebRtcEventLogManager::~WebRtcEventLogManager() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ for (RenderProcessHost* host : observed_render_process_hosts_) {
+ host->RemoveObserver(this);
+ }
+
+ DCHECK(g_webrtc_event_log_manager);
+ g_webrtc_event_log_manager = nullptr;
+}
+
+void WebRtcEventLogManager::EnableForBrowserContext(
+ BrowserContext* browser_context,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+ CHECK(!browser_context->IsOffTheRecord());
+
+ if (!first_browser_context_initializations_done_) {
+ OnFirstBrowserContextLoaded();
+ first_browser_context_initializations_done_ = true;
+ }
+
+ StartListeningForPrefChangeForBrowserContext(browser_context);
+
+ if (!IsRemoteLoggingAllowedForBrowserContext(browser_context)) {
+ // If remote-bound logging was enabled during a previous Chrome session,
+ // it might have produced some pending log files, which we will now
+ // wish to remove.
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::
+ RemovePendingRemoteBoundLogsForNotEnabledBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), std::move(reply)));
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), std::move(reply)));
+}
+
+void WebRtcEventLogManager::DisableForBrowserContext(
+ content::BrowserContext* browser_context,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+
+ StopListeningForPrefChangeForBrowserContext(browser_context);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionAdded(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ RenderProcessHost* rph = RenderProcessHost::FromID(render_process_id);
+ if (!rph) {
+ // RPH died before processing of this notification.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ auto it = observed_render_process_hosts_.find(rph);
+ if (it == observed_render_process_hosts_.end()) {
+ // This is the first PeerConnection which we see that's associated
+ // with this RPH.
+ rph->AddObserver(this);
+ observed_render_process_hosts_.insert(rph);
+ }
+
+ const auto browser_context_id = GetBrowserContextId(rph->GetBrowserContext());
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionAddedInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionRemoved(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(render_process_id);
+ if (browser_context_id == kNullBrowserContextId) {
+ // RPH died before processing of this notification. This is handled by
+ // RenderProcessExited() / RenderProcessHostDestroyed.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionRemovedInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::PeerConnectionStopped(
+ int render_process_id,
+ int lid,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return PeerConnectionRemoved(render_process_id, lid, std::move(reply));
+}
+
+void WebRtcEventLogManager::PeerConnectionSessionIdSet(
+ int render_process_id,
+ int lid,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(render_process_id);
+ if (browser_context_id == kNullBrowserContextId) {
+ // RPH died before processing of this notification. This is handled by
+ // RenderProcessExited() / RenderProcessHostDestroyed.
+ MaybeReply(FROM_HERE, std::move(reply), false);
+ return;
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::PeerConnectionSessionIdSetInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ session_id, std::move(reply)));
+}
+
+void WebRtcEventLogManager::EnableLocalLogging(
+ const base::FilePath& base_path,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EnableLocalLogging(base_path, kDefaultMaxLocalLogFileSizeBytes,
+ std::move(reply));
+}
+
+void WebRtcEventLogManager::EnableLocalLogging(
+ const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!base_path.empty());
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::EnableLocalLoggingInternal,
+ base::Unretained(this), base_path, max_file_size_bytes,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::DisableLocalLogging(
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::DisableLocalLoggingInternal,
+ base::Unretained(this), std::move(reply)));
+}
+
+void WebRtcEventLogManager::OnWebRtcEventLogWrite(
+ int render_process_id,
+ int lid,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const BrowserContext* browser_context = GetBrowserContext(render_process_id);
+ if (!browser_context) {
+ // RPH died before processing of this notification.
+ MaybeReply(FROM_HERE, std::move(reply), std::make_pair(false, false));
+ return;
+ }
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::OnWebRtcEventLogWriteInternal,
+ base::Unretained(this),
+ PeerConnectionKey(render_process_id, lid, browser_context_id),
+ message, std::move(reply)));
+}
+
+void WebRtcEventLogManager::StartRemoteLogging(
+ int render_process_id,
+ const std::string& session_id,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ BrowserContext* browser_context = GetBrowserContext(render_process_id);
+ const char* error = nullptr;
+
+ if (!browser_context) {
+ // RPH died before processing of this notification.
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kDeadRph);
+ error = kStartRemoteLoggingFailureDeadRenderProcessHost;
+ } else if (!IsRemoteLoggingAllowedForBrowserContext(browser_context)) {
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kFeatureDisabled);
+ error = kStartRemoteLoggingFailureFeatureDisabled;
+ } else if (browser_context->IsOffTheRecord()) {
+ // Feature disable in incognito. Since the feature can be disabled for
+ // non-incognito sessions, this should not expose incognito mode.
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIncognito);
+ error = kStartRemoteLoggingFailureFeatureDisabled;
+ }
+
+ if (error) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), false, std::string(),
+ std::string(error)));
+ return;
+ }
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::StartRemoteLoggingInternal,
+ base::Unretained(this), render_process_id,
+ browser_context_id, session_id, browser_context->GetPath(),
+ max_file_size_bytes, output_period_ms, web_app_id,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::ClearCacheForBrowserContext(
+ const BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::ClearCacheForBrowserContextInternal,
+ base::Unretained(this), browser_context_id, delete_begin, delete_end),
+ std::move(reply));
+}
+
+void WebRtcEventLogManager::GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcEventLogManager::GetHistoryInternal,
+ base::Unretained(this), browser_context_id,
+ std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetLocalLogsObserver(
+ WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::SetLocalLogsObserverInternal,
+ base::Unretained(this), observer, std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetRemoteLogsObserver(
+ WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::SetRemoteLogsObserverInternal,
+ base::Unretained(this), observer, std::move(reply)));
+}
+
+bool WebRtcEventLogManager::IsRemoteLoggingAllowedForBrowserContext(
+ BrowserContext* browser_context) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(browser_context);
+
+ if (!remote_logging_feature_enabled_) {
+ return false;
+ }
+
+ const Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+
+ const PrefService::Preference* webrtc_event_log_collection_allowed_pref =
+ profile->GetPrefs()->FindPreference(
+ prefs::kWebRtcEventLogCollectionAllowed);
+ DCHECK(webrtc_event_log_collection_allowed_pref);
+
+ if (webrtc_event_log_collection_allowed_pref->IsDefaultValue()) {
+ // The pref has not been set. GetBoolean would only return the default
+ // value. However, there is no single default value,
+ // because it depends on whether Chrome is managed,
+ // so we check whether Chrome is managed.
+ // TODO(https://crbug.com/980132): use generalized policy default
+ // mechanism when it is available.
+ const bool managed = IsBrowserManagedForProfile(profile);
+ constexpr bool kCollectionAllowedDefaultManaged = true;
+ constexpr bool kCollectionAllowedDefaultUnManaged = false;
+ return managed ? kCollectionAllowedDefaultManaged
+ : kCollectionAllowedDefaultUnManaged;
+ }
+
+ // There is a non-default value set, so this value is authoritative.
+ return profile->GetPrefs()->GetBoolean(
+ prefs::kWebRtcEventLogCollectionAllowed);
+}
+
+std::unique_ptr<LogFileWriter::Factory>
+WebRtcEventLogManager::CreateRemoteLogFileWriterFactory() {
+ if (remote_log_file_writer_factory_for_testing_) {
+ return std::move(remote_log_file_writer_factory_for_testing_);
+#if !defined(OS_ANDROID)
+ } else if (base::FeatureList::IsEnabled(
+ features::kWebRtcRemoteEventLogGzipped)) {
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<DefaultGzippedSizeEstimator::Factory>()));
+#endif
+ } else {
+ return std::make_unique<BaseLogFileWriterFactory>();
+ }
+}
+
+void WebRtcEventLogManager::RenderProcessExited(
+ RenderProcessHost* host,
+ const content::ChildProcessTerminationInfo& info) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHostExitedDestroyed(host);
+}
+
+void WebRtcEventLogManager::RenderProcessHostDestroyed(
+ RenderProcessHost* host) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderProcessHostExitedDestroyed(host);
+}
+
+void WebRtcEventLogManager::RenderProcessHostExitedDestroyed(
+ RenderProcessHost* host) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(host);
+
+ auto it = observed_render_process_hosts_.find(host);
+ if (it == observed_render_process_hosts_.end()) {
+ return; // We've never seen PeerConnections associated with this RPH.
+ }
+ host->RemoveObserver(this);
+ observed_render_process_hosts_.erase(host);
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcEventLogManager::RenderProcessExitedInternal,
+ base::Unretained(this), host->GetID()));
+}
+
+void WebRtcEventLogManager::OnLocalLogStarted(PeerConnectionKey peer_connection,
+ const base::FilePath& file_path) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ constexpr int kLogOutputPeriodMsForLocalLogging = 0; // No batching.
+ OnLoggingTargetStarted(LoggingTarget::kLocalLogging, peer_connection,
+ kLogOutputPeriodMsForLocalLogging);
+
+ if (local_logs_observer_) {
+ local_logs_observer_->OnLocalLogStarted(peer_connection, file_path);
+ }
+}
+
+void WebRtcEventLogManager::OnLocalLogStopped(
+ PeerConnectionKey peer_connection) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ OnLoggingTargetStopped(LoggingTarget::kLocalLogging, peer_connection);
+
+ if (local_logs_observer_) {
+ local_logs_observer_->OnLocalLogStopped(peer_connection);
+ }
+}
+
+void WebRtcEventLogManager::OnRemoteLogStarted(PeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ OnLoggingTargetStarted(LoggingTarget::kRemoteLogging, key, output_period_ms);
+ if (remote_logs_observer_) {
+ remote_logs_observer_->OnRemoteLogStarted(key, file_path, output_period_ms);
+ }
+}
+
+void WebRtcEventLogManager::OnRemoteLogStopped(
+ WebRtcEventLogPeerConnectionKey key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ OnLoggingTargetStopped(LoggingTarget::kRemoteLogging, key);
+ if (remote_logs_observer_) {
+ remote_logs_observer_->OnRemoteLogStopped(key);
+ }
+}
+
+void WebRtcEventLogManager::OnLoggingTargetStarted(LoggingTarget target,
+ PeerConnectionKey key,
+ int output_period_ms) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ auto it = peer_connections_with_event_logging_enabled_in_webrtc_.find(key);
+ if (it != peer_connections_with_event_logging_enabled_in_webrtc_.end()) {
+ DCHECK_EQ((it->second & target), 0u);
+ it->second |= target;
+ } else {
+ // This is the first client for WebRTC event logging - let WebRTC know
+ // that it should start informing us of events.
+ peer_connections_with_event_logging_enabled_in_webrtc_.emplace(key, target);
+ pc_tracker_proxy_->EnableWebRtcEventLogging(key, output_period_ms);
+ }
+}
+
+void WebRtcEventLogManager::OnLoggingTargetStopped(LoggingTarget target,
+ PeerConnectionKey key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Record that we're no longer performing this type of logging for this PC.
+ auto it = peer_connections_with_event_logging_enabled_in_webrtc_.find(key);
+ CHECK(it != peer_connections_with_event_logging_enabled_in_webrtc_.end());
+ DCHECK_NE(it->second, 0u);
+ it->second &= ~target;
+
+ // If we're not doing any other type of logging for this peer connection,
+ // it's time to stop receiving notifications for it from WebRTC.
+ if (it->second == 0u) {
+ peer_connections_with_event_logging_enabled_in_webrtc_.erase(it);
+ pc_tracker_proxy_->DisableWebRtcEventLogging(key);
+ }
+}
+
+void WebRtcEventLogManager::StartListeningForPrefChangeForBrowserContext(
+ BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(first_browser_context_initializations_done_);
+ CHECK(!browser_context->IsOffTheRecord());
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ auto it = pref_change_registrars_.emplace(std::piecewise_construct,
+ std::make_tuple(browser_context_id),
+ std::make_tuple());
+ DCHECK(it.second) << "Already listening.";
+ PrefChangeRegistrar& registrar = it.first->second;
+
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+ registrar.Init(profile->GetPrefs());
+
+ // * |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ // * base::Unretained(browser_context) is safe, because |browser_context|
+ // stays alive until Chrome shut-down, at which point we'll stop listening
+ // as part of its (BrowserContext's) tear-down process.
+ registrar.Add(prefs::kWebRtcEventLogCollectionAllowed,
+ base::BindRepeating(&WebRtcEventLogManager::OnPrefChange,
+ base::Unretained(this),
+ base::Unretained(browser_context)));
+}
+
+void WebRtcEventLogManager::StopListeningForPrefChangeForBrowserContext(
+ BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+
+ size_t erased_count = pref_change_registrars_.erase(browser_context_id);
+ DCHECK_EQ(erased_count, 1u);
+}
+
+void WebRtcEventLogManager::OnPrefChange(BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(first_browser_context_initializations_done_);
+
+ const Profile* profile = Profile::FromBrowserContext(browser_context);
+ DCHECK(profile);
+
+ const bool enabled = IsRemoteLoggingAllowedForBrowserContext(browser_context);
+
+ if (!enabled) {
+ // Dynamic refresh of the policy to DISABLED; stop ongoing logs, remove
+ // pending log files and stop any active uploads.
+ ClearCacheForBrowserContext(browser_context, base::Time::Min(),
+ base::Time::Max(), base::DoNothing());
+ }
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ base::OnceClosure task;
+ if (enabled) {
+ task = base::BindOnce(
+ &WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ browser_context->GetPath(), base::OnceClosure());
+ } else {
+ task = base::BindOnce(
+ &WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext,
+ base::Unretained(this), GetBrowserContextId(browser_context),
+ base::OnceClosure());
+ }
+
+ task_runner_->PostTask(FROM_HERE, std::move(task));
+}
+
+void WebRtcEventLogManager::OnFirstBrowserContextLoaded() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ network::NetworkConnectionTracker* network_connection_tracker =
+ content::GetNetworkConnectionTracker();
+ DCHECK(network_connection_tracker);
+
+ auto log_file_writer_factory = CreateRemoteLogFileWriterFactory();
+ DCHECK(log_file_writer_factory);
+
+ // |network_connection_tracker| is owned by BrowserProcessImpl, which owns
+ // the IOThread. The internal task runner on which |this| uses
+ // |network_connection_tracker|, stops before IOThread dies, so we can trust
+ // that |network_connection_tracker| will not be used after destruction.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal,
+ base::Unretained(this), base::Unretained(network_connection_tracker),
+ std::move(log_file_writer_factory)));
+}
+
+void WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal(
+ network::NetworkConnectionTracker* network_connection_tracker,
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker);
+ DCHECK(log_file_writer_factory);
+ remote_logs_manager_.SetNetworkConnectionTracker(network_connection_tracker);
+ remote_logs_manager_.SetLogFileWriterFactory(
+ std::move(log_file_writer_factory));
+}
+
+void WebRtcEventLogManager::EnableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(browser_context_id, kNullBrowserContextId);
+
+ remote_logs_manager_.EnableForBrowserContext(browser_context_id,
+ browser_context_dir);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::DisableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Note that the BrowserContext might never have been enabled in the
+ // remote-bound manager; that's not a problem.
+ remote_logs_manager_.DisableForBrowserContext(browser_context_id);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::
+ RemovePendingRemoteBoundLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ remote_logs_manager_.RemovePendingLogsForNotEnabledBrowserContext(
+ browser_context_id, browser_context_dir);
+
+ MaybeReply(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::PeerConnectionAddedInternal(
+ PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.PeerConnectionAdded(key);
+ const bool remote_result = remote_logs_manager_.PeerConnectionAdded(key);
+ DCHECK_EQ(local_result, remote_result);
+
+ MaybeReply(FROM_HERE, std::move(reply), local_result);
+}
+
+void WebRtcEventLogManager::PeerConnectionRemovedInternal(
+ PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.PeerConnectionRemoved(key);
+ const bool remote_result = remote_logs_manager_.PeerConnectionRemoved(key);
+ DCHECK_EQ(local_result, remote_result);
+
+ MaybeReply(FROM_HERE, std::move(reply), local_result);
+}
+
+void WebRtcEventLogManager::PeerConnectionSessionIdSetInternal(
+ PeerConnectionKey key,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ const bool result =
+ remote_logs_manager_.PeerConnectionSessionIdSet(key, session_id);
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::EnableLocalLoggingInternal(
+ const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool result =
+ local_logs_manager_.EnableLogging(base_path, max_file_size_bytes);
+
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::DisableLocalLoggingInternal(
+ base::OnceCallback<void(bool)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool result = local_logs_manager_.DisableLogging();
+
+ MaybeReply(FROM_HERE, std::move(reply), result);
+}
+
+void WebRtcEventLogManager::OnWebRtcEventLogWriteInternal(
+ PeerConnectionKey key,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const bool local_result = local_logs_manager_.EventLogWrite(key, message);
+ const bool remote_result = remote_logs_manager_.EventLogWrite(key, message);
+
+ MaybeReply(FROM_HERE, std::move(reply),
+ std::make_pair(local_result, remote_result));
+}
+
+void WebRtcEventLogManager::StartRemoteLoggingInternal(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::string log_id;
+ std::string error_message;
+ const bool result = remote_logs_manager_.StartRemoteLogging(
+ render_process_id, browser_context_id, session_id, browser_context_dir,
+ max_file_size_bytes, output_period_ms, web_app_id, &log_id,
+ &error_message);
+
+ // |log_id| set only if successful; |error_message| set only if unsuccessful.
+ DCHECK_EQ(result, !log_id.empty());
+ DCHECK_EQ(!result, !error_message.empty());
+
+ base::PostTask(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(std::move(reply), result, log_id, error_message));
+}
+
+void WebRtcEventLogManager::ClearCacheForBrowserContextInternal(
+ BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ remote_logs_manager_.ClearCacheForBrowserContext(browser_context_id,
+ delete_begin, delete_end);
+}
+
+void WebRtcEventLogManager::GetHistoryInternal(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(reply);
+ remote_logs_manager_.GetHistory(browser_context_id, std::move(reply));
+}
+
+void WebRtcEventLogManager::RenderProcessExitedInternal(int render_process_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ local_logs_manager_.RenderProcessHostExitedDestroyed(render_process_id);
+ remote_logs_manager_.RenderProcessHostExitedDestroyed(render_process_id);
+}
+
+void WebRtcEventLogManager::SetLocalLogsObserverInternal(
+ WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ local_logs_observer_ = observer;
+
+ if (reply) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ }
+}
+
+void WebRtcEventLogManager::SetRemoteLogsObserverInternal(
+ WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ remote_logs_observer_ = observer;
+
+ if (reply) {
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ }
+}
+
+void WebRtcEventLogManager::SetClockForTesting(base::Clock* clock,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task = [](WebRtcEventLogManager* manager, base::Clock* clock,
+ base::OnceClosure reply) {
+ manager->local_logs_manager_.SetClockForTesting(clock);
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ clock, std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task = [](WebRtcEventLogManager* manager,
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply) {
+ manager->pc_tracker_proxy_ = std::move(pc_tracker_proxy);
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ std::move(pc_tracker_proxy), std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(reply);
+
+ auto task =
+ [](WebRtcEventLogManager* manager,
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply) {
+ auto& remote_logs_manager = manager->remote_logs_manager_;
+ remote_logs_manager.SetWebRtcEventLogUploaderFactoryForTesting(
+ std::move(uploader_factory));
+
+ base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(reply));
+ };
+
+ // |this| is destroyed by ~BrowserProcessImpl(), so base::Unretained(this)
+ // will not be dereferenced after destruction.
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(task, base::Unretained(this),
+ std::move(uploader_factory), std::move(reply)));
+}
+
+void WebRtcEventLogManager::SetRemoteLogFileWriterFactoryForTesting(
+ std::unique_ptr<LogFileWriter::Factory> factory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!first_browser_context_initializations_done_) << "Too late.";
+ DCHECK(!remote_log_file_writer_factory_for_testing_) << "Already called.";
+ remote_log_file_writer_factory_for_testing_ = std::move(factory);
+}
+
+void WebRtcEventLogManager::UploadConditionsHoldForTesting(
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Unit tests block until |callback| is sent back, so the use
+ // of base::Unretained(&remote_logs_manager_) is safe.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting,
+ base::Unretained(&remote_logs_manager_), std::move(callback)));
+}
+
+scoped_refptr<base::SequencedTaskRunner>&
+WebRtcEventLogManager::GetTaskRunnerForTesting() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return task_runner_;
+}
+
+void WebRtcEventLogManager::PostNullTaskForTesting(base::OnceClosure reply) {
+ task_runner_->PostTask(FROM_HERE, std::move(reply));
+}
+
+void WebRtcEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Unit tests block until |callback| is sent back, so the use
+ // of base::Unretained(&remote_logs_manager_) is safe.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::ShutDownForTesting,
+ base::Unretained(&remote_logs_manager_),
+ std::move(reply)));
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h
new file mode 100644
index 00000000000..6b4b815e6af
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager.h
@@ -0,0 +1,429 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_local.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/upload_list/upload_list.h"
+#include "content/public/browser/render_process_host_observer.h"
+#include "content/public/browser/webrtc_event_logger.h"
+
+class WebRTCInternalsIntegrationBrowserTest;
+
+namespace content {
+class BrowserContext;
+class NetworkConnectionTracker;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// This is a singleton class running in the browser UI thread (ownership of
+// the only instance lies in BrowserContext). It is in charge of writing WebRTC
+// event logs to temporary files, then uploading those files to remote servers,
+// as well as of writing the logs to files which were manually indicated by the
+// user from the WebRTCIntenals. (A log may simulatenously be written to both,
+// either, or none.)
+// The only instance of this class is owned by BrowserProcessImpl. It is
+// destroyed from ~BrowserProcessImpl(), at which point any tasks posted to the
+// internal SequencedTaskRunner, or coming from another thread, would no longer
+// execute.
+class WebRtcEventLogManager final : public content::RenderProcessHostObserver,
+ public content::WebRtcEventLogger,
+ public WebRtcLocalEventLogsObserver,
+ public WebRtcRemoteEventLogsObserver {
+ public:
+ using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+ // To turn WebRTC on and off, we go through PeerConnectionTrackerProxy. In
+ // order to make this toggling easily testable, PeerConnectionTrackerProxyImpl
+ // will send real messages to PeerConnectionTracker, whereas
+ // PeerConnectionTrackerProxyForTesting will be a mock that just makes sure
+ // the correct messages were attempted to be sent.
+ class PeerConnectionTrackerProxy {
+ public:
+ virtual ~PeerConnectionTrackerProxy() = default;
+
+ virtual void EnableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key,
+ int output_period_ms) = 0;
+
+ virtual void DisableWebRtcEventLogging(
+ const WebRtcEventLogPeerConnectionKey& key) = 0;
+ };
+
+ // Ensures that no previous instantiation of the class was performed, then
+ // instantiates the class and returns the object (ownership is transfered to
+ // the caller). Subsequent calls to GetInstance() will return this object,
+ // until it is destructed, at which pointer nullptr will be returned by
+ // subsequent calls.
+ static std::unique_ptr<WebRtcEventLogManager> CreateSingletonInstance();
+
+ // Returns the object previously constructed using CreateSingletonInstance(),
+ // if it was constructed and was not yet destroyed; nullptr otherwise.
+ static WebRtcEventLogManager* GetInstance();
+
+ // Given a BrowserContext, return the path to the directory where its
+ // remote-bound event logs are kept.
+ // Since incognito sessions don't have such a directory, an empty
+ // base::FilePath will be returned for them.
+ static base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ content::BrowserContext* browser_context);
+
+ ~WebRtcEventLogManager() override;
+
+ void EnableForBrowserContext(content::BrowserContext* browser_context,
+ base::OnceClosure reply);
+
+ void DisableForBrowserContext(content::BrowserContext* browser_context,
+ base::OnceClosure reply);
+
+ void PeerConnectionAdded(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ void PeerConnectionRemoved(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ // From the logger's perspective, we treat stopping a peer connection the
+ // same as we do its removal. Should a stopped peer connection be later
+ // removed, the removal callback will assume the value |false|.
+ void PeerConnectionStopped(int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ base::OnceCallback<void(bool)> reply) override;
+
+ void PeerConnectionSessionIdSet(
+ int render_process_id,
+ int lid,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply) override;
+
+ // The file's actual path is derived from |base_path| by adding a timestamp,
+ // the render process ID and the PeerConnection's local ID.
+ void EnableLocalLogging(const base::FilePath& base_path,
+ base::OnceCallback<void(bool)> reply) override;
+ void EnableLocalLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply);
+
+ void DisableLocalLogging(base::OnceCallback<void(bool)> reply) override;
+
+ void OnWebRtcEventLogWrite(
+ int render_process_id,
+ int lid, // Renderer-local PeerConnection ID.
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply) override;
+
+ // Start logging a peer connection's WebRTC events to a file, which will
+ // later be uploaded to a remote server. If a reply is provided, it will be
+ // posted back to BrowserThread::UI with the log-identifier (if successful)
+ // of the created log or (if unsuccessful) the error message.
+ // See the comment in WebRtcRemoteEventLogManager::StartRemoteLogging for
+ // more details.
+ void StartRemoteLogging(
+ int render_process_id,
+ const std::string& session_id,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply);
+
+ // Clear WebRTC event logs associated with a given browser context, in a given
+ // time range (|delete_begin| inclusive, |delete_end| exclusive), then
+ // post |reply| back to the thread from which the method was originally
+ // invoked (which can be any thread).
+ void ClearCacheForBrowserContext(
+ const content::BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::OnceClosure reply);
+
+ // Get the logging history (relevant only to remote-bound logs). This includes
+ // information such as when logs were captured, when they were uploaded,
+ // and what their ID in the remote server was.
+ // Must be called on the UI thread.
+ // The results to the query are posted using |reply| back to the UI thread.
+ // If |browser_context_id| is not the ID a profile for which remote-bound
+ // logging is enabled, an empty list is returned.
+ // The returned vector is sorted by capture time in ascending order.
+ void GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ // Set (or unset) an observer that will be informed whenever a local log file
+ // is started/stopped. The observer needs to be able to either run from
+ // anywhere. If you need the code to run on specific runners or queues, have
+ // the observer post them there.
+ // If a reply callback is given, it will be posted back to BrowserThread::UI
+ // after the observer has been set.
+ void SetLocalLogsObserver(WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ // Set (or unset) an observer that will be informed whenever a remote log file
+ // is started/stopped. Note that this refers to writing these files to disk,
+ // not for uploading them to the server.
+ // The observer needs to be able to either run from anywhere. If you need the
+ // code to run on specific runners or queues, have the observer post
+ // them there.
+ // If a reply callback is given, it will be posted back to BrowserThread::UI
+ // after the observer has been set.
+ void SetRemoteLogsObserver(WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ private:
+ friend class WebRtcEventLogManagerTestBase;
+ friend class ::WebRTCInternalsIntegrationBrowserTest;
+
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ // This bitmap allows us to track for which clients (local/remote logging)
+ // we have turned WebRTC event logging on for a given peer connection, so that
+ // we may turn it off only when the last client no longer needs it.
+ enum LoggingTarget : unsigned int {
+ kLocalLogging = 1 << 0,
+ kRemoteLogging = 1 << 1
+ };
+ using LoggingTargetBitmap = std::underlying_type<LoggingTarget>::type;
+
+ WebRtcEventLogManager();
+
+ bool IsRemoteLoggingAllowedForBrowserContext(
+ content::BrowserContext* browser_context) const;
+
+ // Determines the exact subclass of LogFileWriter::Factory to be used for
+ // producing remote-bound logs.
+ std::unique_ptr<LogFileWriter::Factory> CreateRemoteLogFileWriterFactory();
+
+ // RenderProcessHostObserver implementation.
+ void RenderProcessExited(
+ content::RenderProcessHost* host,
+ const content::ChildProcessTerminationInfo& info) override;
+ void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
+
+ // RenderProcessExited() and RenderProcessHostDestroyed() treated similarly
+ // by this function.
+ void RenderProcessHostExitedDestroyed(content::RenderProcessHost* host);
+
+ // WebRtcLocalEventLogsObserver implementation:
+ void OnLocalLogStarted(PeerConnectionKey peer_connection,
+ const base::FilePath& file_path) override;
+ void OnLocalLogStopped(PeerConnectionKey peer_connection) override;
+
+ // WebRtcRemoteEventLogsObserver implementation:
+ void OnRemoteLogStarted(PeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) override;
+ void OnRemoteLogStopped(PeerConnectionKey key) override;
+
+ void OnLoggingTargetStarted(LoggingTarget target,
+ PeerConnectionKey key,
+ int output_period_ms);
+ void OnLoggingTargetStopped(LoggingTarget target, PeerConnectionKey key);
+
+ void StartListeningForPrefChangeForBrowserContext(
+ content::BrowserContext* browser_context);
+ void StopListeningForPrefChangeForBrowserContext(
+ content::BrowserContext* browser_context);
+
+ void OnPrefChange(content::BrowserContext* browser_context);
+
+ // network_connection_tracker() is not available during instantiation;
+ // we get it when the first profile is loaded, which is also the earliest
+ // time when it could be needed.
+ // The LogFileWriter::Factory is similarly deferred, but for a different
+ // reason - it makes it easier to allow unit tests to inject their own.
+ // OnFirstBrowserContextLoaded() is on the UI thread.
+ // OnFirstBrowserContextLoadedInternal() is the task sent to |task_runner_|.
+ void OnFirstBrowserContextLoaded();
+ void OnFirstBrowserContextLoadedInternal(
+ network::NetworkConnectionTracker* network_connection_tracker,
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
+
+ void EnableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply);
+
+ void DisableRemoteBoundLoggingForBrowserContext(
+ BrowserContextId browser_context_id,
+ base::OnceClosure reply);
+
+ void RemovePendingRemoteBoundLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir,
+ base::OnceClosure reply);
+
+ void PeerConnectionAddedInternal(PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply);
+ void PeerConnectionRemovedInternal(PeerConnectionKey key,
+ base::OnceCallback<void(bool)> reply);
+
+ void PeerConnectionSessionIdSetInternal(PeerConnectionKey key,
+ const std::string& session_id,
+ base::OnceCallback<void(bool)> reply);
+
+ void EnableLocalLoggingInternal(const base::FilePath& base_path,
+ size_t max_file_size_bytes,
+ base::OnceCallback<void(bool)> reply);
+ void DisableLocalLoggingInternal(base::OnceCallback<void(bool)> reply);
+
+ void OnWebRtcEventLogWriteInternal(
+ PeerConnectionKey key,
+ const std::string& message,
+ base::OnceCallback<void(std::pair<bool, bool>)> reply);
+
+ void StartRemoteLoggingInternal(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ reply);
+
+ void ClearCacheForBrowserContextInternal(BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end);
+
+ void GetHistoryInternal(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ void RenderProcessExitedInternal(int render_process_id);
+
+ void SetLocalLogsObserverInternal(WebRtcLocalEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ void SetRemoteLogsObserverInternal(WebRtcRemoteEventLogsObserver* observer,
+ base::OnceClosure reply);
+
+ // Injects a fake clock, to be used by tests. For example, this could be
+ // used to inject a frozen clock, thereby allowing unit tests to know what a
+ // local log's filename would end up being.
+ void SetClockForTesting(base::Clock* clock, base::OnceClosure reply);
+
+ // Injects a PeerConnectionTrackerProxy for testing. The normal tracker proxy
+ // is used to communicate back to WebRTC whether event logging is desired for
+ // a given peer connection. Using this function, those indications can be
+ // intercepted by a unit test.
+ void SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy,
+ base::OnceClosure reply);
+
+ // Injects a fake uploader, to be used by unit tests.
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
+ base::OnceClosure reply);
+
+ // Sets a LogFileWriter factory for remote-bound files.
+ // Only usable in tests.
+ // Must be called before the first browser context is enabled.
+ // Effective immediately.
+ void SetRemoteLogFileWriterFactoryForTesting(
+ std::unique_ptr<LogFileWriter::Factory> factory);
+
+ // It is not always feasible to check in unit tests that uploads do not occur
+ // at a certain time, because that's (sometimes) racy with the event that
+ // suppresses the upload. We therefore allow unit tests to glimpse into the
+ // black box and verify that the box is aware that it should not upload.
+ void UploadConditionsHoldForTesting(base::OnceCallback<void(bool)> callback);
+
+ // This allows unit tests that do not wish to change the task runner to still
+ // check when certain operations are finished.
+ // TODO(crbug.com/775415): Remove this and use PostNullTaskForTesting instead.
+ scoped_refptr<base::SequencedTaskRunner>& GetTaskRunnerForTesting();
+
+ void PostNullTaskForTesting(base::OnceClosure reply);
+
+ // Documented in WebRtcRemoteEventLogManager.
+ void ShutDownForTesting(base::OnceClosure reply);
+
+ static WebRtcEventLogManager* g_webrtc_event_log_manager;
+
+ // The main logic will run sequentially on this runner, on which blocking
+ // tasks are allowed.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Indicates whether remote-bound logging is generally allowed, although
+ // possibly not for all profiles. This makes it possible for remote-bound to
+ // be disabled through Finch.
+ // TODO(crbug.com/775415): Remove this kill-switch.
+ const bool remote_logging_feature_enabled_;
+
+ // Observer which will be informed whenever a local log file is started or
+ // stopped. Its callbacks are called synchronously from |task_runner_|,
+ // so the observer needs to be able to either run from any (sequenced) runner.
+ WebRtcLocalEventLogsObserver* local_logs_observer_;
+
+ // Observer which will be informed whenever a remote log file is started or
+ // stopped. Its callbacks are called synchronously from |task_runner_|,
+ // so the observer needs to be able to either run from any (sequenced) runner.
+ WebRtcRemoteEventLogsObserver* remote_logs_observer_;
+
+ // Manages local-bound logs - logs stored on the local filesystem when
+ // logging has been explicitly enabled by the user.
+ WebRtcLocalEventLogManager local_logs_manager_;
+
+ // Manages remote-bound logs - logs which will be sent to a remote server.
+ // This is only possible when the appropriate Chrome policy is configured.
+ WebRtcRemoteEventLogManager remote_logs_manager_;
+
+ // Each loaded BrowserContext is mapped to a PrefChangeRegistrar, which keeps
+ // us informed about preference changes, thereby allowing as to support
+ // dynamic refresh.
+ std::map<BrowserContextId, PrefChangeRegistrar> pref_change_registrars_;
+
+ // This keeps track of which peer connections have event logging turned on
+ // in WebRTC, and for which client(s).
+ std::map<PeerConnectionKey, LoggingTargetBitmap>
+ peer_connections_with_event_logging_enabled_in_webrtc_;
+
+ // The set of RenderProcessHosts with which the manager is registered for
+ // observation. Allows us to register for each RPH only once, and get notified
+ // when it exits (cleanly or due to a crash).
+ // This object is only to be accessed on the UI thread.
+ base::flat_set<content::RenderProcessHost*> observed_render_process_hosts_;
+
+ // In production, this holds a small object that just tells WebRTC (via
+ // PeerConnectionTracker) to start/stop producing event logs for a specific
+ // peer connection. In (relevant) unit tests, a mock will be injected.
+ std::unique_ptr<PeerConnectionTrackerProxy> pc_tracker_proxy_;
+
+ // The globals network_connection_tracker() and system_request_context() are
+ // sent down to |remote_logs_manager_| with the first enabled browser context.
+ // This member must only be accessed on the UI thread.
+ bool first_browser_context_initializations_done_;
+
+ // May only be set for tests, in which case, it will be passed to
+ // |remote_logs_manager_| when (and if) produced.
+ std::unique_ptr<LogFileWriter::Factory>
+ remote_log_file_writer_factory_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
new file mode 100644
index 00000000000..59ad7a2b45e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
@@ -0,0 +1,1013 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+#include <cctype>
+#include <limits>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/unguessable_token.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "third_party/zlib/zlib.h"
+
+namespace webrtc_event_logging {
+
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
+
+const size_t kWebRtcEventLogIdLength = 32;
+
+// Be careful not to change these without updating the number of characters
+// reserved in the filename. See kWebAppIdLength.
+const size_t kMinWebRtcEventLogWebAppId = 1;
+const size_t kMaxWebRtcEventLogWebAppId = 99;
+
+// Sentinel value for an invalid web-app ID.
+const size_t kInvalidWebRtcEventLogWebAppId = 0;
+static_assert(kInvalidWebRtcEventLogWebAppId < kMinWebRtcEventLogWebAppId ||
+ kInvalidWebRtcEventLogWebAppId > kMaxWebRtcEventLogWebAppId,
+ "Sentinel value must be distinct from legal values.");
+
+const char kRemoteBoundWebRtcEventLogFileNamePrefix[] = "webrtc_event_log";
+
+// Important! These values may be relied on by web-apps. Do not change.
+const char kStartRemoteLoggingFailureAlreadyLogging[] = "Already logging.";
+const char kStartRemoteLoggingFailureDeadRenderProcessHost[] =
+ "RPH already dead.";
+const char kStartRemoteLoggingFailureFeatureDisabled[] = "Feature disabled.";
+const char kStartRemoteLoggingFailureFileCreationError[] =
+ "Could not create file.";
+const char kStartRemoteLoggingFailureFilePathUsedHistory[] =
+ "Used history file path.";
+const char kStartRemoteLoggingFailureFilePathUsedLog[] = "Used log file path.";
+const char kStartRemoteLoggingFailureIllegalWebAppId[] = "Illegal web-app ID.";
+const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[] =
+ "Disabled for browser context.";
+const char kStartRemoteLoggingFailureMaxSizeTooLarge[] =
+ "Excessively large max log size.";
+const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
+const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[] =
+ "No additional active logs allowed.";
+const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[] =
+ "Excessively large output period (ms).";
+const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[] =
+ "Unknown or inactive peer connection.";
+const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[] =
+ "Unlimited size disallowed.";
+
+const BrowserContextId kNullBrowserContextId =
+ reinterpret_cast<BrowserContextId>(nullptr);
+
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result) {
+ base::UmaHistogramEnumeration("WebRtcEventLogging.Api", result);
+}
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result) {
+ base::UmaHistogramEnumeration("WebRtcEventLogging.Upload", result);
+}
+
+void UmaRecordWebRtcEventLoggingNetErrorType(int net_error) {
+ base::UmaHistogramSparse("WebRtcEventLogging.NetError", net_error);
+}
+
+namespace {
+
+constexpr int kDefaultMemLevel = 8;
+
+constexpr size_t kGzipHeaderBytes = 15;
+constexpr size_t kGzipFooterBytes = 10;
+
+constexpr size_t kWebAppIdLength = 2;
+
+// Tracks budget over a resource (such as bytes allowed in a file, etc.).
+// Allows an unlimited budget.
+class Budget {
+ public:
+ // If !max.has_value(), the budget is unlimited.
+ explicit Budget(base::Optional<size_t> max) : max_(max), current_(0) {}
+
+ // Check whether the budget allows consuming an additional |consumed| of
+ // the resource.
+ bool ConsumeAllowed(size_t consumed) const {
+ if (!max_.has_value()) {
+ return true;
+ }
+
+ DCHECK_LE(current_, max_.value());
+
+ const size_t after_consumption = current_ + consumed;
+
+ if (after_consumption < current_) {
+ return false; // Wrap-around.
+ } else if (after_consumption > max_.value()) {
+ return false; // Budget exceeded.
+ } else {
+ return true;
+ }
+ }
+
+ // Checks whether the budget has been completely used up.
+ bool Exhausted() const { return !ConsumeAllowed(0); }
+
+ // Consume an additional |consumed| of the resource.
+ void Consume(size_t consumed) {
+ DCHECK(ConsumeAllowed(consumed));
+ current_ += consumed;
+ }
+
+ private:
+ const base::Optional<size_t> max_;
+ size_t current_;
+};
+
+// Writes a log to a file while observing a maximum size.
+class BaseLogFileWriter : public LogFileWriter {
+ public:
+ // If !max_file_size_bytes.has_value(), an unlimited writer is created.
+ // If it has a value, it must be at least MinFileSizeBytes().
+ BaseLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes);
+
+ ~BaseLogFileWriter() override;
+
+ bool Init() override;
+
+ const base::FilePath& path() const override;
+
+ bool MaxSizeReached() const override;
+
+ bool Write(const std::string& input) override;
+
+ bool Close() override;
+
+ void Delete() override;
+
+ protected:
+ // * Logs are created PRE_INIT.
+ // * If Init() is successful (potentially writing some header to the log),
+ // the log becomes ACTIVE.
+ // * Any error puts the log into an unrecoverable ERRORED state. When an
+ // errored file is Close()-ed, it is deleted.
+ // * If Write() is ever denied because of budget constraintss, the file
+ // becomes FULL. Only metadata is then allowed (subject to its own budget).
+ // * Closing an ACTIVE or FULL file puts it into CLOSED, at which point the
+ // file may be used. (Note that closing itself might also yield an error,
+ // which would put the file into ERRORED, then deleted.)
+ // * Closed files may be DELETED.
+ enum class State { PRE_INIT, ACTIVE, FULL, CLOSED, ERRORED, DELETED };
+
+ // Setter/getter for |state_|.
+ void SetState(State state);
+ State state() const { return state_; }
+
+ // Checks whether the budget allows writing an additional |bytes|.
+ bool WithinBudget(size_t bytes) const;
+
+ // Writes |input| to the file.
+ // May only be called on ACTIVE or FULL files (for FULL files, only metadata
+ // such as compression footers, etc., may be written; the budget must still
+ // be respected).
+ // It's up to the caller to respect the budget; this will DCHECK on it.
+ // Returns |true| if writing was successful. |false| indicates an
+ // unrecoverable error; the file must be discarded.
+ bool WriteInternal(const std::string& input, bool metadata);
+
+ // Finalizes the file (writes metadata such as compression footer, if any).
+ // Reports whether the file was successfully finalized. Those which weren't
+ // should be discarded.
+ virtual bool Finalize();
+
+ private:
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ const base::FilePath path_;
+ base::File file_; // Populated by Init().
+ State state_;
+ Budget budget_;
+};
+
+BaseLogFileWriter::BaseLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes)
+ : task_runner_(base::SequencedTaskRunnerHandle::Get()),
+ path_(path),
+ state_(State::PRE_INIT),
+ budget_(max_file_size_bytes) {}
+
+BaseLogFileWriter::~BaseLogFileWriter() {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ // Chrome shut-down. The original task_runner_ is no longer running, so
+ // no risk of concurrent access or races.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ task_runner_ = base::SequencedTaskRunnerHandle::Get();
+ }
+
+ if (state() != State::CLOSED && state() != State::DELETED) {
+ Close();
+ }
+}
+
+bool BaseLogFileWriter::Init() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::PRE_INIT);
+
+ // TODO(crbug.com/775415): Use a temporary filename which will indicate
+ // incompletion, and rename to something that is eligible for upload only
+ // on an orderly and successful Close().
+
+ // Attempt to create the file.
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file_.Initialize(path_, file_flags);
+ if (!file_.IsValid() || !file_.created()) {
+ LOG(WARNING) << "Couldn't create remote-bound WebRTC event log file.";
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ SetState(State::ACTIVE);
+
+ return true;
+}
+
+const base::FilePath& BaseLogFileWriter::path() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return path_;
+}
+
+bool BaseLogFileWriter::MaxSizeReached() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::ACTIVE);
+ return !WithinBudget(1);
+}
+
+bool BaseLogFileWriter::Write(const std::string& input) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(state(), State::ACTIVE);
+ DCHECK(!MaxSizeReached());
+
+ if (input.empty()) {
+ return true;
+ }
+
+ if (!WithinBudget(input.length())) {
+ SetState(State::FULL);
+ return false;
+ }
+
+ const bool did_write = WriteInternal(input, /*metadata=*/false);
+ if (!did_write) {
+ SetState(State::ERRORED);
+ }
+ return did_write;
+}
+
+bool BaseLogFileWriter::Close() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+
+ const bool result = ((state() != State::ERRORED) && Finalize());
+
+ if (result) {
+ file_.Flush();
+ file_.Close();
+ SetState(State::CLOSED);
+ } else {
+ Delete(); // Changes the state to DELETED.
+ }
+
+ return result;
+}
+
+void BaseLogFileWriter::Delete() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::DELETED);
+
+ // The file should be closed before deletion. However, we do not want to go
+ // through Finalize() and any potential production of a compression footer,
+ // etc., since we'll be discarding the file anyway.
+ if (state() != State::CLOSED) {
+ file_.Close();
+ }
+
+ if (!base::DeleteFile(path_, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path_ << ".";
+ }
+
+ SetState(State::DELETED);
+}
+
+void BaseLogFileWriter::SetState(State state) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ state_ = state;
+}
+
+bool BaseLogFileWriter::WithinBudget(size_t bytes) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return budget_.ConsumeAllowed(bytes);
+}
+
+bool BaseLogFileWriter::WriteInternal(const std::string& input, bool metadata) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(state() == State::ACTIVE || (state() == State::FULL && metadata));
+ DCHECK(WithinBudget(input.length()));
+
+ // base::File's interface does not allow writing more than
+ // numeric_limits<int>::max() bytes at a time.
+ DCHECK_LE(input.length(),
+ static_cast<size_t>(std::numeric_limits<int>::max()));
+ const int input_len = static_cast<int>(input.length());
+
+ int written = file_.WriteAtCurrentPos(input.c_str(), input_len);
+ if (written != input_len) {
+ LOG(WARNING) << "WebRTC event log couldn't be written to the "
+ "locally stored file in its entirety.";
+ return false;
+ }
+
+ budget_.Consume(static_cast<size_t>(written));
+
+ return true;
+}
+
+bool BaseLogFileWriter::Finalize() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+ DCHECK_NE(state(), State::ERRORED);
+ return true;
+}
+
+// Writes a GZIP-compressed log to a file while observing a maximum size.
+class GzippedLogFileWriter : public BaseLogFileWriter {
+ public:
+ GzippedLogFileWriter(const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes,
+ std::unique_ptr<LogCompressor> compressor);
+
+ ~GzippedLogFileWriter() override = default;
+
+ bool Init() override;
+
+ bool MaxSizeReached() const override;
+
+ bool Write(const std::string& input) override;
+
+ protected:
+ bool Finalize() override;
+
+ private:
+ std::unique_ptr<LogCompressor> compressor_;
+};
+
+GzippedLogFileWriter::GzippedLogFileWriter(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes,
+ std::unique_ptr<LogCompressor> compressor)
+ : BaseLogFileWriter(path, max_file_size_bytes),
+ compressor_(std::move(compressor)) {
+ // Factory validates size before instantiation.
+ DCHECK(!max_file_size_bytes.has_value() ||
+ max_file_size_bytes.value() >= kGzipOverheadBytes);
+}
+
+bool GzippedLogFileWriter::Init() {
+ if (!BaseLogFileWriter::Init()) {
+ // Super-class should SetState on its own.
+ return false;
+ }
+
+ std::string header;
+ compressor_->CreateHeader(&header);
+
+ const bool result = WriteInternal(header, /*metadata=*/true);
+ if (!result) {
+ SetState(State::ERRORED);
+ }
+
+ return result;
+}
+
+bool GzippedLogFileWriter::MaxSizeReached() const {
+ DCHECK_EQ(state(), State::ACTIVE);
+
+ // Note that the overhead used (footer only) assumes state() is State::ACTIVE,
+ // as DCHECKed above.
+ return !WithinBudget(1 + kGzipFooterBytes);
+}
+
+bool GzippedLogFileWriter::Write(const std::string& input) {
+ DCHECK_EQ(state(), State::ACTIVE);
+ DCHECK(!MaxSizeReached());
+
+ if (input.empty()) {
+ return true;
+ }
+
+ std::string compressed_input;
+ const auto result = compressor_->Compress(input, &compressed_input);
+
+ switch (result) {
+ case LogCompressor::Result::OK: {
+ // |compressor_| guarantees |compressed_input| is within-budget.
+ bool did_write = WriteInternal(compressed_input, /*metadata=*/false);
+ if (!did_write) {
+ SetState(State::ERRORED);
+ }
+ return did_write;
+ }
+ case LogCompressor::Result::DISALLOWED: {
+ SetState(State::FULL);
+ return false;
+ }
+ case LogCompressor::Result::ERROR_ENCOUNTERED: {
+ SetState(State::ERRORED);
+ return false;
+ }
+ }
+
+ NOTREACHED();
+ return false; // Appease compiler.
+}
+
+bool GzippedLogFileWriter::Finalize() {
+ DCHECK_NE(state(), State::CLOSED);
+ DCHECK_NE(state(), State::DELETED);
+ DCHECK_NE(state(), State::ERRORED);
+
+ std::string footer;
+ if (!compressor_->CreateFooter(&footer)) {
+ LOG(WARNING) << "Compression footer could not be produced.";
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ // |compressor_| guarantees |footer| is within-budget.
+ if (!WriteInternal(footer, /*metadata=*/true)) {
+ LOG(WARNING) << "Footer could not be written.";
+ SetState(State::ERRORED);
+ return false;
+ }
+
+ return true;
+}
+
+// Concrete implementation of LogCompressor using GZIP.
+class GzipLogCompressor : public LogCompressor {
+ public:
+ GzipLogCompressor(
+ base::Optional<size_t> max_size_bytes,
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator);
+
+ ~GzipLogCompressor() override;
+
+ void CreateHeader(std::string* output) override;
+
+ Result Compress(const std::string& input, std::string* output) override;
+
+ bool CreateFooter(std::string* output) override;
+
+ private:
+ // * A compressed log starts out empty (PRE_HEADER).
+ // * Once the header is produced, the stream is ACTIVE.
+ // * If it is ever detected that compressing the next input would exceed the
+ // budget, that input is NOT compressed, and the state becomes FULL, from
+ // which only writing the footer or discarding the file are allowed.
+ // * Writing the footer is allowed on an ACTIVE or FULL stream. Then, the
+ // stream is effectively closed.
+ // * Any error puts the stream into ERRORED. An errored stream can only
+ // be discarded.
+ enum class State { PRE_HEADER, ACTIVE, FULL, POST_FOOTER, ERRORED };
+
+ // Returns the budget left after reserving the GZIP overhead.
+ // Optionals without a value, both in the parameters as well as in the
+ // return value of the function, signal an unlimited amount.
+ static base::Optional<size_t> SizeAfterOverheadReservation(
+ base::Optional<size_t> max_size_bytes);
+
+ // Compresses |input| into |output|, while observing the budget (unless
+ // !budgeted). If |last|, also closes the stream.
+ Result CompressInternal(const std::string& input,
+ std::string* output,
+ bool budgeted,
+ bool last);
+
+ // Compresses the input data already in |stream_| into |output|.
+ bool Deflate(int flush, std::string* output);
+
+ State state_;
+ Budget budget_;
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator_;
+ z_stream stream_;
+};
+
+GzipLogCompressor::GzipLogCompressor(
+ base::Optional<size_t> max_size_bytes,
+ std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)
+ : state_(State::PRE_HEADER),
+ budget_(SizeAfterOverheadReservation(max_size_bytes)),
+ compressed_size_estimator_(std::move(compressed_size_estimator)) {
+ memset(&stream_, 0, sizeof(z_stream));
+ // Using (MAX_WBITS + 16) triggers the creation of a GZIP header.
+ const int result =
+ deflateInit2(&stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16,
+ kDefaultMemLevel, Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(result, Z_OK);
+}
+
+GzipLogCompressor::~GzipLogCompressor() {
+ const int result = deflateEnd(&stream_);
+ // Z_DATA_ERROR reports that the stream was not properly terminated,
+ // but nevertheless correctly released. That happens when we don't
+ // write the footer.
+ DCHECK(result == Z_OK ||
+ (result == Z_DATA_ERROR && state_ != State::POST_FOOTER));
+}
+
+void GzipLogCompressor::CreateHeader(std::string* output) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK_EQ(state_, State::PRE_HEADER);
+
+ const Result result = CompressInternal(std::string(), output,
+ /*budgeted=*/false, /*last=*/false);
+ DCHECK_EQ(result, Result::OK);
+ DCHECK_EQ(output->size(), kGzipHeaderBytes);
+
+ state_ = State::ACTIVE;
+}
+
+LogCompressor::Result GzipLogCompressor::Compress(const std::string& input,
+ std::string* output) {
+ DCHECK_EQ(state_, State::ACTIVE);
+
+ if (input.empty()) {
+ return Result::OK;
+ }
+
+ const auto result =
+ CompressInternal(input, output, /*budgeted=*/true, /*last=*/false);
+
+ switch (result) {
+ case Result::OK:
+ return result;
+ case Result::DISALLOWED:
+ state_ = State::FULL;
+ return result;
+ case Result::ERROR_ENCOUNTERED:
+ state_ = State::ERRORED;
+ return result;
+ }
+
+ NOTREACHED();
+ return Result::ERROR_ENCOUNTERED; // Appease compiler.
+}
+
+bool GzipLogCompressor::CreateFooter(std::string* output) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK(state_ == State::ACTIVE || state_ == State::FULL);
+
+ const Result result = CompressInternal(std::string(), output,
+ /*budgeted=*/false, /*last=*/true);
+ if (result != Result::OK) { // !budgeted -> Result::DISALLOWED impossible.
+ DCHECK_EQ(result, Result::ERROR_ENCOUNTERED);
+ // An error message was logged by CompressInternal().
+ state_ = State::ERRORED;
+ return false;
+ }
+
+ if (output->length() != kGzipFooterBytes) {
+ LOG(ERROR) << "Incorrect footer size (" << output->length() << ").";
+ state_ = State::ERRORED;
+ return false;
+ }
+
+ state_ = State::POST_FOOTER;
+
+ return true;
+}
+
+base::Optional<size_t> GzipLogCompressor::SizeAfterOverheadReservation(
+ base::Optional<size_t> max_size_bytes) {
+ if (!max_size_bytes.has_value()) {
+ return base::Optional<size_t>();
+ } else {
+ DCHECK_GE(max_size_bytes.value(), kGzipHeaderBytes + kGzipFooterBytes);
+ return max_size_bytes.value() - (kGzipHeaderBytes + kGzipFooterBytes);
+ }
+}
+
+LogCompressor::Result GzipLogCompressor::CompressInternal(
+ const std::string& input,
+ std::string* output,
+ bool budgeted,
+ bool last) {
+ DCHECK(output);
+ DCHECK(output->empty());
+ DCHECK(state_ == State::PRE_HEADER || state_ == State::ACTIVE ||
+ (!budgeted && state_ == State::FULL));
+
+ // Avoid writing to |output| unless the return value is OK.
+ std::string temp_output;
+
+ if (budgeted) {
+ const size_t estimated_compressed_size =
+ compressed_size_estimator_->EstimateCompressedSize(input);
+ if (!budget_.ConsumeAllowed(estimated_compressed_size)) {
+ return Result::DISALLOWED;
+ }
+ }
+
+ if (last) {
+ DCHECK(input.empty());
+ stream_.next_in = nullptr;
+ } else {
+ stream_.next_in = reinterpret_cast<z_const Bytef*>(input.c_str());
+ }
+
+ DCHECK_LE(input.length(),
+ static_cast<size_t>(std::numeric_limits<uInt>::max()));
+ stream_.avail_in = static_cast<uInt>(input.length());
+
+ const bool result = Deflate(last ? Z_FINISH : Z_SYNC_FLUSH, &temp_output);
+
+ stream_.next_in = nullptr; // Avoid dangling pointers.
+
+ if (!result) {
+ // An error message was logged by Deflate().
+ return Result::ERROR_ENCOUNTERED;
+ }
+
+ if (budgeted) {
+ if (!budget_.ConsumeAllowed(temp_output.length())) {
+ LOG(WARNING) << "Compressed size was above estimate and unexpectedly "
+ "exceeded the budget.";
+ return Result::ERROR_ENCOUNTERED;
+ }
+ budget_.Consume(temp_output.length());
+ }
+
+ std::swap(*output, temp_output);
+ return Result::OK;
+}
+
+bool GzipLogCompressor::Deflate(int flush, std::string* output) {
+ DCHECK((flush != Z_FINISH && stream_.next_in != nullptr) ||
+ (flush == Z_FINISH && stream_.next_in == nullptr));
+ DCHECK(output->empty());
+
+ bool success = true; // Result of this method.
+ int z_result; // Result of the zlib function.
+
+ size_t total_compressed_size = 0;
+
+ do {
+ // Allocate some additional buffer.
+ constexpr uInt kCompressionBuffer = 4 * 1024;
+ output->resize(total_compressed_size + kCompressionBuffer);
+
+ // This iteration should write directly beyond previous iterations' last
+ // written byte.
+ stream_.next_out =
+ reinterpret_cast<uint8_t*>(&((*output)[total_compressed_size]));
+ stream_.avail_out = kCompressionBuffer;
+
+ z_result = deflate(&stream_, flush);
+
+ DCHECK_GE(kCompressionBuffer, stream_.avail_out);
+ const size_t compressed_size = kCompressionBuffer - stream_.avail_out;
+
+ if (flush != Z_FINISH) {
+ if (z_result != Z_OK) {
+ LOG(ERROR) << "Compression failed (" << z_result << ").";
+ success = false;
+ break;
+ }
+ } else { // flush == Z_FINISH
+ // End of the stream; we expect the footer to be exactly the size which
+ // we've set aside for it.
+ if (z_result != Z_STREAM_END || compressed_size != kGzipFooterBytes) {
+ LOG(ERROR) << "Compression failed (" << z_result << ", "
+ << compressed_size << ").";
+ success = false;
+ break;
+ }
+ }
+
+ total_compressed_size += compressed_size;
+ } while (stream_.avail_out == 0 && z_result != Z_STREAM_END);
+
+ stream_.next_out = nullptr; // Avoid dangling pointers.
+
+ if (success) {
+ output->resize(total_compressed_size);
+ } else {
+ output->clear();
+ }
+
+ return success;
+}
+
+// Given a string with a textual representation of a web-app ID, return the
+// ID in integer form. If the textual representation does not name a valid
+// web-app ID, return kInvalidWebRtcEventLogWebAppId.
+size_t ExtractWebAppId(base::StringPiece str) {
+ DCHECK_EQ(str.length(), kWebAppIdLength);
+
+ // Avoid leading '+', etc.
+ for (size_t i = 0; i < str.length(); i++) {
+ if (!std::isdigit(str[i])) {
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+ }
+
+ size_t result;
+ if (!base::StringToSizeT(str, &result) ||
+ result < kMinWebRtcEventLogWebAppId ||
+ result > kMaxWebRtcEventLogWebAppId) {
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+ return result;
+}
+
+} // namespace
+
+const size_t kGzipOverheadBytes = kGzipHeaderBytes + kGzipFooterBytes;
+
+const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[] =
+ FILE_PATH_LITERAL("log");
+const base::FilePath::CharType kWebRtcEventLogGzippedExtension[] =
+ FILE_PATH_LITERAL("log.gz");
+const base::FilePath::CharType kWebRtcEventLogHistoryExtension[] =
+ FILE_PATH_LITERAL("hist");
+
+size_t BaseLogFileWriterFactory::MinFileSizeBytes() const {
+ // No overhead incurred; data written straight to the file without metadata.
+ return 0;
+}
+
+base::FilePath::StringPieceType BaseLogFileWriterFactory::Extension() const {
+ return kWebRtcEventLogUncompressedExtension;
+}
+
+std::unique_ptr<LogFileWriter> BaseLogFileWriterFactory::Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const {
+ if (max_file_size_bytes.has_value() &&
+ max_file_size_bytes.value() < MinFileSizeBytes()) {
+ LOG(WARNING) << "Max size (" << max_file_size_bytes.value()
+ << ") below minimum size (" << MinFileSizeBytes() << ").";
+ return nullptr;
+ }
+
+ auto result = std::make_unique<BaseLogFileWriter>(path, max_file_size_bytes);
+
+ if (!result->Init()) {
+ // Error logged by Init.
+ result.reset(); // Destructor deletes errored files.
+ }
+
+ return result;
+}
+
+std::unique_ptr<CompressedSizeEstimator>
+DefaultGzippedSizeEstimator::Factory::Create() const {
+ return std::make_unique<DefaultGzippedSizeEstimator>();
+}
+
+size_t DefaultGzippedSizeEstimator::EstimateCompressedSize(
+ const std::string& input) const {
+ // This estimation is not tight. Since we expect to produce logs of
+ // several MBs, overshooting the estimation by one KB should be
+ // very safe and still relatively efficient.
+ constexpr size_t kOverheadOverUncompressedSizeBytes = 1000;
+ return input.length() + kOverheadOverUncompressedSizeBytes;
+}
+
+GzipLogCompressorFactory::GzipLogCompressorFactory(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)
+ : estimator_factory_(std::move(estimator_factory)) {}
+
+GzipLogCompressorFactory::~GzipLogCompressorFactory() = default;
+
+size_t GzipLogCompressorFactory::MinSizeBytes() const {
+ return kGzipOverheadBytes;
+}
+
+std::unique_ptr<LogCompressor> GzipLogCompressorFactory::Create(
+ base::Optional<size_t> max_size_bytes) const {
+ if (max_size_bytes.has_value() && max_size_bytes.value() < MinSizeBytes()) {
+ LOG(WARNING) << "Max size (" << max_size_bytes.value()
+ << ") below minimum size (" << MinSizeBytes() << ").";
+ return nullptr;
+ }
+ return std::make_unique<GzipLogCompressor>(max_size_bytes,
+ estimator_factory_->Create());
+}
+
+GzippedLogFileWriterFactory::GzippedLogFileWriterFactory(
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)
+ : gzip_compressor_factory_(std::move(gzip_compressor_factory)) {}
+
+GzippedLogFileWriterFactory::~GzippedLogFileWriterFactory() = default;
+
+size_t GzippedLogFileWriterFactory::MinFileSizeBytes() const {
+ // Only the compression's own overhead is incurred.
+ return gzip_compressor_factory_->MinSizeBytes();
+}
+
+base::FilePath::StringPieceType GzippedLogFileWriterFactory::Extension() const {
+ return kWebRtcEventLogGzippedExtension;
+}
+
+std::unique_ptr<LogFileWriter> GzippedLogFileWriterFactory::Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const {
+ if (max_file_size_bytes.has_value() &&
+ max_file_size_bytes.value() < MinFileSizeBytes()) {
+ LOG(WARNING) << "Size below allowed minimum.";
+ return nullptr;
+ }
+
+ auto gzip_compressor = gzip_compressor_factory_->Create(max_file_size_bytes);
+ if (!gzip_compressor) {
+ // The factory itself will have logged an error.
+ return nullptr;
+ }
+
+ auto result = std::make_unique<GzippedLogFileWriter>(
+ path, max_file_size_bytes, std::move(gzip_compressor));
+
+ if (!result->Init()) {
+ // Error logged by Init.
+ result.reset(); // Destructor deletes errored files.
+ }
+
+ return result;
+}
+
+// Create a random identifier of 32 hexadecimal (uppercase) characters.
+std::string CreateWebRtcEventLogId() {
+ // UnguessableToken's interface makes no promisses over case. We therefore
+ // convert, even if the current implementation does not require it.
+ std::string log_id =
+ base::ToUpperASCII(base::UnguessableToken::Create().ToString());
+ DCHECK_EQ(log_id.size(), kWebRtcEventLogIdLength);
+ DCHECK_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
+ return log_id;
+}
+
+BrowserContextId GetBrowserContextId(
+ const content::BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ return reinterpret_cast<BrowserContextId>(browser_context);
+}
+
+BrowserContextId GetBrowserContextId(int render_process_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ content::RenderProcessHost* const host =
+ content::RenderProcessHost::FromID(render_process_id);
+
+ content::BrowserContext* const browser_context =
+ host ? host->GetBrowserContext() : nullptr;
+
+ return GetBrowserContextId(browser_context);
+}
+
+base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ const base::FilePath& browser_context_dir) {
+ const base::FilePath::CharType kRemoteBoundLogSubDirectory[] =
+ FILE_PATH_LITERAL("webrtc_event_logs");
+ return browser_context_dir.Append(kRemoteBoundLogSubDirectory);
+}
+
+base::FilePath WebRtcEventLogPath(
+ const base::FilePath& remote_logs_dir,
+ const std::string& log_id,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension) {
+ DCHECK_GE(web_app_id, kMinWebRtcEventLogWebAppId);
+ DCHECK_LE(web_app_id, kMaxWebRtcEventLogWebAppId);
+
+ static_assert(kWebAppIdLength == 2u, "Fix the code below.");
+ const std::string web_app_id_str = base::StringPrintf("%02zu", web_app_id);
+ DCHECK_EQ(web_app_id_str.length(), kWebAppIdLength);
+
+ const std::string filename =
+ std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
+ web_app_id_str + "_" + log_id;
+
+ return remote_logs_dir.AppendASCII(filename).AddExtension(extension);
+}
+
+bool IsValidRemoteBoundLogFilename(const std::string& filename) {
+ // The -1 is because of the implict \0.
+ const size_t kPrefixLength =
+ base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
+
+ // [prefix]_[web_app_id]_[log_id]
+ const size_t expected_length =
+ kPrefixLength + 1 + kWebAppIdLength + 1 + kWebRtcEventLogIdLength;
+ if (filename.length() != expected_length) {
+ return false;
+ }
+
+ size_t index = 0;
+
+ // Expect prefix.
+ if (filename.find(kRemoteBoundWebRtcEventLogFileNamePrefix) != index) {
+ return false;
+ }
+ index += kPrefixLength;
+
+ // Expect underscore between prefix and web-app ID.
+ if (filename[index] != '_') {
+ return false;
+ }
+ index += 1;
+
+ // Expect web-app-ID.
+ const size_t web_app_id =
+ ExtractWebAppId(base::StringPiece(&filename[index], kWebAppIdLength));
+ if (web_app_id == kInvalidWebRtcEventLogWebAppId) {
+ return false;
+ }
+ index += kWebAppIdLength;
+
+ // Expect underscore between web-app ID and log ID.
+ if (filename[index] != '_') {
+ return false;
+ }
+ index += 1;
+
+ // Expect log ID.
+ const std::string log_id = filename.substr(index);
+ DCHECK_EQ(log_id.length(), kWebRtcEventLogIdLength);
+ const char* const log_id_chars = "0123456789ABCDEF";
+ if (filename.find_first_not_of(log_id_chars, index) != std::string::npos) {
+ return false;
+ }
+
+ return true;
+}
+
+bool IsValidRemoteBoundLogFilePath(const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ return IsValidRemoteBoundLogFilename(filename);
+}
+
+base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path) {
+ // TODO(crbug.com/775415): Check for validity (after fixing unit tests).
+ return path.RemoveExtension().AddExtension(kWebRtcEventLogHistoryExtension);
+}
+
+std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
+ const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ if (!IsValidRemoteBoundLogFilename(filename)) {
+ LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
+ return std::string();
+ }
+
+ DCHECK_GE(filename.length(), kWebRtcEventLogIdLength);
+ return filename.substr(filename.length() - kWebRtcEventLogIdLength);
+}
+
+size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
+ const base::FilePath& path) {
+ const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
+ if (!IsValidRemoteBoundLogFilename(filename)) {
+ LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
+ return kInvalidWebRtcEventLogWebAppId;
+ }
+
+ // The -1 is because of the implict \0.
+ const size_t kPrefixLength =
+ base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
+
+ // The +1 is for the underscore between the prefix and the web-app ID.
+ // Length verified by above call to IsValidRemoteBoundLogFilename().
+ DCHECK_GE(filename.length(), kPrefixLength + 1 + kWebAppIdLength);
+ base::StringPiece id_str(&filename[kPrefixLength + 1], kWebAppIdLength);
+
+ return ExtractWebAppId(id_str);
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
new file mode 100644
index 00000000000..c6479729a94
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
@@ -0,0 +1,543 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// This file is intended for:
+// 1. Code shared between WebRtcEventLogManager, WebRtcLocalEventLogManager
+// and WebRtcRemoteEventLogManager.
+// 2. Code specific to either of the above classes, but which also needs
+// to be seen by unit tests (such as constants).
+
+extern const size_t kWebRtcEventLogManagerUnlimitedFileSize;
+
+extern const size_t kDefaultMaxLocalLogFileSizeBytes;
+extern const size_t kMaxNumberLocalWebRtcEventLogFiles;
+
+extern const size_t kMaxRemoteLogFileSizeBytes;
+
+extern const int kMaxOutputPeriodMs;
+
+// Maximum size for a response from Crash, which is the upload ID.
+extern const size_t kWebRtcEventLogMaxUploadIdBytes;
+
+// The number of digits required to encode a remote-bound log ID.
+extern const size_t kWebRtcEventLogIdLength;
+
+// Min/max legal web-app IDs.
+extern const size_t kMinWebRtcEventLogWebAppId;
+extern const size_t kMaxWebRtcEventLogWebAppId;
+
+// Sentinel value, guaranteed not to fall inside the range of min-max valid IDs.
+extern const size_t kInvalidWebRtcEventLogWebAppId;
+
+// Limit over the number of concurrently active (currently being written to
+// disk) remote-bound log files. This limits IO operations, and so it is
+// applied globally (all browser contexts are limited together).
+extern const size_t kMaxActiveRemoteBoundWebRtcEventLogs;
+
+// Limit over the number of pending logs (logs stored on disk and awaiting to
+// be uploaded to a remote server). This limit avoids excessive storage. If a
+// user chooses to have multiple profiles (and hence browser contexts) on a
+// system, it is assumed that the user has enough storage to accommodate
+// the increased storage consumption that comes with it. Therefore, this
+// limit is applied per browser context.
+extern const size_t kMaxPendingRemoteBoundWebRtcEventLogs;
+
+// Max number of history files that may be kept; after this number is exceeded,
+// the oldest logs should be pruned.
+extern const size_t kMaxWebRtcEventLogHistoryFiles;
+
+// Overhead incurred by GZIP due to its header and footer.
+extern const size_t kGzipOverheadBytes;
+
+// Remote-bound log files' names will be of the format:
+// [prefix]_[web_app_id]_[log_id].[ext]
+// Where:
+// * |prefix| is equal to kRemoteBoundWebRtcEventLogFileNamePrefix.
+// * |web_app_id| is a number between kMinWebRtcEventLogWebAppId and
+// kMaxWebRtcEventLogWebAppId, with zero padding.
+// * |log_id| is composed of 32 random characters from '0'-'9' and 'A'-'F'.
+// * |ext| is the extension determined by the used LogCompressor::Factory,
+// which will be either kWebRtcEventLogUncompressedExtension or
+// kWebRtcEventLogGzippedExtension.
+extern const char kRemoteBoundWebRtcEventLogFileNamePrefix[];
+extern const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[];
+extern const base::FilePath::CharType kWebRtcEventLogGzippedExtension[];
+
+// Logs themselves are kept on disk for kRemoteBoundWebRtcEventLogsMaxRetention,
+// or until uploaded. Smaller history files are kept for a longer time, allowing
+// Chrome to display on chrome://webrtc-logs/ that these files were captured
+// and later uploaded.
+extern const base::FilePath::CharType kWebRtcEventLogHistoryExtension[];
+
+// Remote-bound event logs will not be uploaded if the time since their last
+// modification (meaning the time when they were completed) exceeds this value.
+// Such expired files will be purged from disk when examined.
+extern const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention;
+
+// These are made globally visible so that unit tests may check for them.
+extern const char kStartRemoteLoggingFailureAlreadyLogging[];
+extern const char kStartRemoteLoggingFailureDeadRenderProcessHost[];
+extern const char kStartRemoteLoggingFailureFeatureDisabled[];
+extern const char kStartRemoteLoggingFailureFileCreationError[];
+extern const char kStartRemoteLoggingFailureFilePathUsedHistory[];
+extern const char kStartRemoteLoggingFailureFilePathUsedLog[];
+extern const char kStartRemoteLoggingFailureIllegalWebAppId[];
+extern const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[];
+extern const char kStartRemoteLoggingFailureMaxSizeTooLarge[];
+extern const char kStartRemoteLoggingFailureMaxSizeTooSmall[];
+extern const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[];
+extern const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[];
+extern const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[];
+extern const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[];
+
+// Values for the histogram for the result of the API call to collect
+// a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingApiEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingApiUma {
+ kSuccess = 0, // Log successfully collected.
+ kDeadRph = 1, // Log not collected.
+ kFeatureDisabled = 2, // Log not collected.
+ kIncognito = 3, // Log not collected.
+ kInvalidArguments = 4, // Log not collected.
+ kIllegalSessionId = 5, // Log not collected.
+ kDisabledBrowserContext = 6, // Log not collected.
+ kUnknownOrInvalidPeerConnection = 7, // Log not collected.
+ kAlreadyLogging = 8, // Log not collected.
+ kNoAdditionalLogsAllowed = 9, // Log not collected.
+ kLogPathNotAvailable = 10, // Log not collected.
+ kHistoryPathNotAvailable = 11, // Log not collected.
+ kFileCreationError = 12, // Log not collected.
+ kMaxValue = kFileCreationError
+};
+
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result);
+
+// Values for the histogram for the result of the upload of a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingUploadEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingUploadUma {
+ kSuccess = 0, // Uploaded successfully.
+ kLogFileWriteError = 1, // Will not be uploaded.
+ kActiveLogCancelledDueToCacheClear = 2, // Will not be uploaded.
+ kPendingLogDeletedDueToCacheClear = 3, // Will not be uploaded.
+ kHistoryFileCreationError = 4, // Will not be uploaded.
+ kHistoryFileWriteError = 5, // Will not be uploaded.
+ kLogFileReadError = 6, // Will not be uploaded.
+ kLogFileNameError = 7, // Will not be uploaded.
+ kUploadCancelled = 8, // Upload started then cancelled.
+ kUploadFailure = 9, // Upload attempted and failed.
+ kIncompletePastUpload = 10, // Upload attempted and failed.
+ kExpiredLogFileAtChromeStart = 11, // Expired before upload opportunity.
+ kExpiredLogFileDuringSession = 12, // Expired before upload opportunity.
+ kMaxValue = kExpiredLogFileDuringSession
+};
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result);
+
+// Success is signalled by 0.
+// All negative values signal errors.
+// Positive values are not used.
+void UmaRecordWebRtcEventLoggingNetErrorType(int net_error);
+
+// For a given Chrome session, this is a unique key for PeerConnections.
+// It's not, however, unique between sessions (after Chrome is restarted).
+struct WebRtcEventLogPeerConnectionKey {
+ using BrowserContextId = uintptr_t;
+
+ constexpr WebRtcEventLogPeerConnectionKey()
+ : WebRtcEventLogPeerConnectionKey(
+ /* render_process_id = */ 0,
+ /* lid = */ 0,
+ reinterpret_cast<BrowserContextId>(nullptr)) {}
+
+ constexpr WebRtcEventLogPeerConnectionKey(int render_process_id,
+ int lid,
+ BrowserContextId browser_context_id)
+ : render_process_id(render_process_id),
+ lid(lid),
+ browser_context_id(browser_context_id) {}
+
+ bool operator==(const WebRtcEventLogPeerConnectionKey& other) const {
+ // Each RPH is associated with exactly one BrowserContext.
+ DCHECK(render_process_id != other.render_process_id ||
+ browser_context_id == other.browser_context_id);
+
+ const bool equal = std::tie(render_process_id, lid) ==
+ std::tie(other.render_process_id, other.lid);
+ return equal;
+ }
+
+ bool operator<(const WebRtcEventLogPeerConnectionKey& other) const {
+ // Each RPH is associated with exactly one BrowserContext.
+ DCHECK(render_process_id != other.render_process_id ||
+ browser_context_id == other.browser_context_id);
+
+ return std::tie(render_process_id, lid) <
+ std::tie(other.render_process_id, other.lid);
+ }
+
+ // These two fields are the actual key; any peer connection is uniquely
+ // identifiable by the renderer process in which it lives, and its ID within
+ // that process.
+ int render_process_id;
+ int lid; // Renderer-local PeerConnection ID.
+
+ // The BrowserContext is not actually part of the key, but each PeerConnection
+ // is associated with a BrowserContext, and that BrowserContext is almost
+ // always necessary, so it makes sense to remember it along with the key.
+ BrowserContextId browser_context_id;
+};
+
+// Sentinel value for an unknown BrowserContext.
+extern const WebRtcEventLogPeerConnectionKey::BrowserContextId
+ kNullBrowserContextId;
+
+// Holds housekeeping information about log files.
+struct WebRtcLogFileInfo {
+ WebRtcLogFileInfo(
+ WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified)
+ : browser_context_id(browser_context_id),
+ path(path),
+ last_modified(last_modified) {}
+
+ WebRtcLogFileInfo(const WebRtcLogFileInfo& other)
+ : browser_context_id(other.browser_context_id),
+ path(other.path),
+ last_modified(other.last_modified) {}
+
+ bool operator<(const WebRtcLogFileInfo& other) const {
+ if (last_modified != other.last_modified) {
+ return last_modified < other.last_modified;
+ }
+ return path < other.path; // Break ties arbitrarily, but consistently.
+ }
+
+ // The BrowserContext which produced this file.
+ const WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id;
+
+ // The path to the log file itself.
+ const base::FilePath path;
+
+ // |last_modified| recorded at BrowserContext initialization. Chrome will
+ // not modify it afterwards, and neither should the user.
+ const base::Time last_modified;
+};
+
+// An observer for notifications of local log files being started/stopped, and
+// the paths which will be used for these logs.
+class WebRtcLocalEventLogsObserver {
+ public:
+ virtual void OnLocalLogStarted(WebRtcEventLogPeerConnectionKey key,
+ const base::FilePath& file_path) = 0;
+ virtual void OnLocalLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
+
+ protected:
+ virtual ~WebRtcLocalEventLogsObserver() = default;
+};
+
+// An observer for notifications of remote-bound log files being
+// started/stopped. The start event would likely only interest unit tests
+// (because it exposes the randomized filename to them). The stop event is of
+// general interest, because it would often mean that WebRTC can stop sending
+// us event logs for this peer connection.
+// Some cases where OnRemoteLogStopped would be called include:
+// 1. The PeerConnection has become inactive.
+// 2. The file's maximum size has been reached.
+// 3. Any type of error while writing to the file.
+class WebRtcRemoteEventLogsObserver {
+ public:
+ virtual void OnRemoteLogStarted(WebRtcEventLogPeerConnectionKey key,
+ const base::FilePath& file_path,
+ int output_period_ms) = 0;
+ virtual void OnRemoteLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
+
+ protected:
+ virtual ~WebRtcRemoteEventLogsObserver() = default;
+};
+
+// Writes a log to a file while observing a maximum size.
+class LogFileWriter {
+ public:
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // The smallest size a log file of this type may assume.
+ virtual size_t MinFileSizeBytes() const = 0;
+
+ // The extension type associated with this type of log files.
+ virtual base::FilePath::StringPieceType Extension() const = 0;
+
+ // Instantiate and initialize a LogFileWriter.
+ // If creation or initialization fail, an empty unique_ptr will be returned,
+ // and it will be guaranteed that the file itself is not created. (If |path|
+ // had pointed to an existing file, that file will be deleted.)
+ // If !max_file_size_bytes.has_value(), the LogFileWriter is unlimited.
+ virtual std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const = 0;
+ };
+
+ virtual ~LogFileWriter() = default;
+
+ // Init() must be called on each LogFileWriter exactly once, before it's used.
+ // If initialization fails, no further actions may be performed on the object
+ // other than Close() and Delete().
+ virtual bool Init() = 0;
+
+ // Getter for the path of the file |this| wraps.
+ virtual const base::FilePath& path() const = 0;
+
+ // Whether the maximum file size was reached.
+ virtual bool MaxSizeReached() const = 0;
+
+ // Writes to the log file while respecting the file's size limit.
+ // True is returned if and only if the message was written to the file in
+ // it entirety. That is, |false| is returned either if a genuine error
+ // occurs, or when the budget does not allow the next write.
+ // If |false| is ever returned, only Close() and Delete() may subsequently
+ // be called.
+ // The function does *not* close the file.
+ // The function may not be called if MaxSizeReached().
+ virtual bool Write(const std::string& input) = 0;
+
+ // If the file was successfully closed, true is returned, and the file may
+ // now be used. Otherwise, the file is deleted, and false is returned.
+ virtual bool Close() = 0;
+
+ // Delete the file from disk.
+ virtual void Delete() = 0;
+};
+
+// Produces LogFileWriter instances that perform no compression.
+class BaseLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+ ~BaseLogFileWriterFactory() override = default;
+
+ size_t MinFileSizeBytes() const override;
+
+ base::FilePath::StringPieceType Extension() const override;
+
+ std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const override;
+};
+
+// Interface for a class that provides compression of a stream, while attempting
+// to observe a limit on the size.
+//
+// One should note that:
+// * For compressors that use a footer, to guarantee proper decompression,
+// the footer must be written to the file.
+// * In such a case, usually, nothing can be omitted from the file, or the
+// footer's CRC (if used) would be wrong.
+// * Determining a string's size pre-compression, without performing the actual
+// compression, is heuristic in nature.
+//
+// Therefore, compression might terminate (FULL) earlier than it
+// must, or even in theory (which we attempt to avoid in practice) exceed the
+// size allowed it, in which case the file will be discarded (ERROR).
+class LogCompressor {
+ public:
+ // By subclassing this factory, concrete implementations of LogCompressor can
+ // be produced by unit tests, while keeping their definition in the .cc file.
+ // (Only the factory needs to be declared in the header.)
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // The smallest size a log file of this type may assume.
+ virtual size_t MinSizeBytes() const = 0;
+
+ // Returns a LogCompressor if the parameters are valid and all
+ // initializations are successful; en empty unique_ptr otherwise.
+ // If !max_size_bytes.has_value(), an unlimited compressor is created.
+ virtual std::unique_ptr<LogCompressor> Create(
+ base::Optional<size_t> max_size_bytes) const = 0;
+ };
+
+ // Result of a call to Compress().
+ // * OK and ERROR_ENCOUNTERED are self-explanatory.
+ // * DISALLOWED means that, due to budget constraints, the input could
+ // not be compressed. The stream is still in a legal state, but only
+ // a call to CreateFooter() is now allowed.
+ enum class Result { OK, DISALLOWED, ERROR_ENCOUNTERED };
+
+ virtual ~LogCompressor() = default;
+
+ // Produces a compression header and writes it to |output|.
+ // The size does not count towards the max size limit.
+ // Guaranteed not to fail (nothing can realistically go wrong).
+ virtual void CreateHeader(std::string* output) = 0;
+
+ // Compresses |input| into |output|.
+ // * If compression succeeded, and the budget was observed, OK is returned.
+ // * If the compressor thinks the string, once compressed, will exceed the
+ // maximum size (when combined with previously compressed strings),
+ // compression will not be done, and DISALLOWED will be returned.
+ // This allows producing a valid footer without exceeding the size limit.
+ // * Unexpected errors in the underlying compressor (e.g. zlib, etc.),
+ // or unexpectedly getting a compressed string which exceeds the budget,
+ // will return ERROR_ENCOUNTERED.
+ // This function may not be called again if DISALLOWED or ERROR_ENCOUNTERED
+ // were ever returned before, or after CreateFooter() was called.
+ virtual Result Compress(const std::string& input, std::string* output) = 0;
+
+ // Produces a compression footer and writes it to |output|.
+ // The footer does not count towards the max size limit.
+ // May not be called more than once, or if Compress() returned ERROR.
+ virtual bool CreateFooter(std::string* output) = 0;
+};
+
+// Estimates the compressed size, without performing compression (except in
+// unit tests, where performance is of lesser importance).
+// This interface allows unit tests to simulate specific cases, such as
+// over/under-estimation, and show that the code using the LogCompressor
+// deals with them correctly. (E.g., if the estimation expects the compression
+// to not go over-budget, but then it does.)
+// The estimator is expected to be stateful. That is, the order of calls to
+// EstimateCompressedSize() should correspond to the order of calls
+// to Compress().
+class CompressedSizeEstimator {
+ public:
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+ virtual std::unique_ptr<CompressedSizeEstimator> Create() const = 0;
+ };
+
+ virtual ~CompressedSizeEstimator() = default;
+
+ virtual size_t EstimateCompressedSize(const std::string& input) const = 0;
+};
+
+// Provides a conservative estimation of the number of bytes required to
+// compress a string using GZIP. This estimation is not expected to ever
+// be overly optimistic, but the code using it should nevertheless be prepared
+// to deal with that theoretical possibility.
+class DefaultGzippedSizeEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ ~DefaultGzippedSizeEstimator() override = default;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Interface for producing LogCompressorGzip objects.
+class GzipLogCompressorFactory : public LogCompressor::Factory {
+ public:
+ explicit GzipLogCompressorFactory(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory);
+ ~GzipLogCompressorFactory() override;
+
+ size_t MinSizeBytes() const override;
+
+ std::unique_ptr<LogCompressor> Create(
+ base::Optional<size_t> max_size_bytes) const override;
+
+ private:
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory_;
+};
+
+// Produces LogFileWriter instances that perform compression using GZIP.
+class GzippedLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+ explicit GzippedLogFileWriterFactory(
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory);
+
+ ~GzippedLogFileWriterFactory() override;
+
+ size_t MinFileSizeBytes() const override;
+
+ base::FilePath::StringPieceType Extension() const override;
+
+ std::unique_ptr<LogFileWriter> Create(
+ const base::FilePath& path,
+ base::Optional<size_t> max_file_size_bytes) const override;
+
+ private:
+ std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory_;
+};
+
+// Create a random identifier of 32 hexadecimal (uppercase) characters.
+std::string CreateWebRtcEventLogId();
+
+// Translate a BrowserContext into an ID. This lets us associate PeerConnections
+// with BrowserContexts, while making sure that we never call the
+// BrowserContext's methods outside of the UI thread (because we can't call them
+// at all without a cast that would alert us to the danger).
+WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
+ const content::BrowserContext* browser_context);
+
+// Fetches the BrowserContext associated with the render process ID, then
+// returns its BrowserContextId. (If the render process has already died,
+// it would have no BrowserContext associated, so the ID associated with a
+// null BrowserContext will be returned.)
+WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
+ int render_process_id);
+
+// Given a BrowserContext's directory, return the path to the directory where
+// we store the pending remote-bound logs associated with this BrowserContext.
+// This function may be called on any task queue.
+base::FilePath GetRemoteBoundWebRtcEventLogsDir(
+ const base::FilePath& browser_context_dir);
+
+// Produce the path to a remote-bound WebRTC event log file with the given
+// log ID, web-app ID and extension, in the given directory.
+base::FilePath WebRtcEventLogPath(
+ const base::FilePath& remote_logs_dir,
+ const std::string& log_id,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension);
+
+// Checks whether the path/filename would be a valid reference to a remote-bound
+// even log. These functions do not examine the file's content or its extension.
+bool IsValidRemoteBoundLogFilename(const std::string& filename);
+bool IsValidRemoteBoundLogFilePath(const base::FilePath& path);
+
+// Given WebRTC event log's path, return the path to the history file that
+// is, or would be, associated with it.
+base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path);
+
+// Attempts to extract the local ID from the file's path. Returns the empty
+// string in case of an error.
+std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
+ const base::FilePath& path);
+
+// Attempts to extract the web-app ID from the file's path.
+// Returns kInvalidWebRtcEventLogWebAppId in case of an error.
+size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
+ const base::FilePath& path);
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
new file mode 100644
index 00000000000..400d4391500
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
@@ -0,0 +1,655 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+#include <memory>
+#include <numeric>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/optional.h"
+#include "base/rand_util.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace webrtc_event_logging {
+
+namespace {
+constexpr LogCompressor::Result OK = LogCompressor::Result::OK;
+constexpr LogCompressor::Result DISALLOWED = LogCompressor::Result::DISALLOWED;
+constexpr LogCompressor::Result ERROR_ENCOUNTERED =
+ LogCompressor::Result::ERROR_ENCOUNTERED;
+} // namespace
+
+// Tests for GzipLogCompressor.
+// Note that these tests may not use GzippedSize(), or they would be assuming
+// what they set out to prove. (Subsequent tests may use it, though.)
+class GzipLogCompressorTest : public ::testing::Test {
+ public:
+ ~GzipLogCompressorTest() override = default;
+
+ void Init(
+ std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory) {
+ DCHECK(!compressor_factory_);
+ DCHECK(estimator_factory);
+ compressor_factory_ = std::make_unique<GzipLogCompressorFactory>(
+ std::move(estimator_factory));
+ }
+
+ std::string Decompress(const std::string& input) {
+ std::string output;
+ EXPECT_TRUE(compression::GzipUncompress(input, &output));
+ return output;
+ }
+
+ std::unique_ptr<GzipLogCompressorFactory> compressor_factory_;
+};
+
+TEST_F(GzipLogCompressorTest,
+ GzipLogCompressorFactoryCreatesCompressorIfMinimalSizeOrAbove) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ auto compressor = compressor_factory_->Create(min_size);
+ EXPECT_TRUE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest,
+ GzipLogCompressorFactoryDoesNotCreateCompressorIfBelowMinimalSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ ASSERT_GE(min_size, 1u);
+ auto compressor = compressor_factory_->Create(min_size - 1);
+ EXPECT_FALSE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamReasonableMaxSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamMinimalSize) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const size_t min_size = compressor_factory_->MinSizeBytes();
+ auto compressor = compressor_factory_->Create(min_size);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest, SingleCallToCompress) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::string input = "Some random text.";
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, MultipleCallsToCompress) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::vector<std::string> inputs = {
+ "Some random text.",
+ "This text is also random. I give you my word for it. 100% random.",
+ "nejnnc pqmnx0981 mnl<D@ikjed90~~,z."};
+
+ std::vector<std::string> logs(inputs.size());
+ for (size_t i = 0; i < inputs.size(); i++) {
+ ASSERT_EQ(compressor->Compress(inputs[i], &logs[i]), OK);
+ }
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const auto input = std::accumulate(begin(inputs), end(inputs), std::string());
+ const auto log = std::accumulate(begin(logs), end(logs), std::string());
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, UnlimitedBudgetSanity) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(base::Optional<size_t>());
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ const std::string input = "Some random text.";
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+// Test once with a big input, to provide coverage over inputs that could
+// exceed the size of some local buffers in the UUT.
+TEST_F(GzipLogCompressorTest, CompressionBigInput) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ constexpr size_t kRealisticSizeBytes = 1000 * 1000;
+ const std::string input = base::RandBytesAsString(kRealisticSizeBytes);
+ std::string log;
+ ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+ std::string footer;
+ ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+ const std::string simulated_file = header + log + footer;
+ EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, BudgetExceededByFirstCompressYieldsEmptyFile) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const std::string input = "This won't fit.";
+
+ auto compressor = compressor_factory_->Create(GzippedSize(input) - 1);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ // Focal point #1 - Compress() returns DISALLOWED.
+ std::string log;
+ EXPECT_EQ(compressor->Compress(input, &log), DISALLOWED);
+
+ // Focal point #2 - CreateFooter() still succeeds;
+ std::string footer;
+ EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+ // Focal point #3 - the resulting log is parsable, and contains only those
+ // logs for which Compress() was successful.
+ // Note that |log| is not supposed to be written to the file, because
+ // Compress() has disallowed it.
+ const std::string simulated_file = header + footer;
+ EXPECT_EQ(Decompress(simulated_file), std::string());
+}
+
+TEST_F(GzipLogCompressorTest,
+ BudgetExceededByNonFirstCompressYieldsPartialFile) {
+ Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+ const std::string short_input = "short";
+ const std::string long_input = "A somewhat longer input string. @$%^&*()!!2";
+
+ // Allocate enough budget that |short_input| would be produced, and not yet
+ // exhaust the budget, but |long_input| won't fit.
+ auto compressor = compressor_factory_->Create(GzippedSize(short_input) + 1);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ std::string short_log;
+ ASSERT_EQ(compressor->Compress(short_input, &short_log), OK);
+
+ // Focal point #1 - Compress() returns DISALLOWED.
+ std::string long_log;
+ EXPECT_EQ(compressor->Compress(long_input, &long_log), DISALLOWED);
+ EXPECT_TRUE(long_log.empty());
+
+ // Focal point #2 - CreateFooter() still succeeds;
+ std::string footer;
+ EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+ // Focal point #3 - the resulting log is parsable, and contains only those
+ // logs for which Compress() was successful.
+ // Note that |long_log| is not supposed to be written to the file, because
+ // Compress() has disallowed it.
+ const std::string simulated_file = header + short_log + footer;
+ EXPECT_EQ(Decompress(simulated_file), short_input);
+}
+
+TEST_F(GzipLogCompressorTest,
+ ExceedingBudgetDueToOverlyOptimisticEstimationYieldsError) {
+ // Use an estimator that will always be overly optimistic.
+ Init(std::make_unique<NullEstimator::Factory>());
+
+ // Set a budget that will easily be exceeded.
+ auto compressor = compressor_factory_->Create(kGzipOverheadBytes + 5);
+ ASSERT_TRUE(compressor);
+
+ std::string header;
+ compressor->CreateHeader(&header);
+
+ // Prepare to compress an input that is guaranteed to exceed the budget.
+ const std::string input = "A string that would not fit in five bytes.";
+
+ // The estimation allowed the compression, but then the compressed output
+ // ended up being over-budget.
+ std::string compressed;
+ EXPECT_EQ(compressor->Compress(input, &compressed), ERROR_ENCOUNTERED);
+ EXPECT_TRUE(compressed.empty());
+}
+
+// Tests relevant to all LogFileWriter subclasses.
+class LogFileWriterTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<WebRtcEventLogCompression> {
+ public:
+ LogFileWriterTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ ~LogFileWriterTest() override {}
+
+ void Init(WebRtcEventLogCompression compression) {
+ DCHECK(!compression_.has_value()) << "Must only be called once.";
+ compression_ = compression;
+ log_file_writer_factory_ = CreateLogFileWriterFactory(compression);
+ path_ = temp_dir_.GetPath()
+ .Append(FILE_PATH_LITERAL("arbitrary_filename"))
+ .AddExtension(log_file_writer_factory_->Extension());
+ }
+
+ std::unique_ptr<LogFileWriter> CreateWriter(base::Optional<size_t> max_size) {
+ return log_file_writer_factory_->Create(path_, max_size);
+ }
+
+ void ExpectFileContents(const base::FilePath& file_path,
+ const std::string& expected_contents) {
+ DCHECK(compression_.has_value()) << "Must call Init().";
+
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+
+ switch (compression_.value()) {
+ case WebRtcEventLogCompression::NONE: {
+ EXPECT_EQ(file_contents, expected_contents);
+ break;
+ }
+ case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+ case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION: {
+ std::string uncompressed;
+ ASSERT_TRUE(compression::GzipUncompress(file_contents, &uncompressed));
+ EXPECT_EQ(uncompressed, expected_contents);
+ break;
+ }
+ default: { NOTREACHED(); }
+ }
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::Optional<WebRtcEventLogCompression> compression_; // Set in Init().
+ base::ScopedTempDir temp_dir_;
+ base::FilePath path_;
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+};
+
+TEST_P(LogFileWriterTest, FactoryCreatesLogFileWriter) {
+ Init(GetParam());
+ EXPECT_TRUE(CreateWriter(log_file_writer_factory_->MinFileSizeBytes()));
+}
+
+#if defined(OS_POSIX)
+TEST_P(LogFileWriterTest, FactoryReturnsEmptyUniquePtrIfCantCreateFile) {
+ Init(GetParam());
+ RemoveWritePermissions(temp_dir_.GetPath());
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ EXPECT_FALSE(writer);
+}
+#endif // defined(OS_POSIX)
+
+TEST_P(LogFileWriterTest, CloseSucceedsWhenNoErrorsOccurred) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Close());
+}
+
+// Other tests check check the case of compression where the estimation is
+// close to the file's capacity, reaches or exceeds it.
+TEST_P(LogFileWriterTest, CallToWriteSuccedsWhenCapacityFarOff) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, CallToWriteWithEmptyStringSucceeds) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, UnlimitedBudgetSanity) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(base::Optional<size_t>());
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesUnclosedFile) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ writer->Delete();
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesClosedFile) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Close());
+
+ writer->Delete();
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+#if !defined(OS_WIN) // Deleting the open file does not work on Windows.
+TEST_P(LogFileWriterTest, WriteDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Write("log");
+}
+
+TEST_P(LogFileWriterTest, CloseDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Close();
+}
+
+TEST_P(LogFileWriterTest, DeleteDoesNotCrashIfFileRemovedExternally) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+ ASSERT_FALSE(base::PathExists(path_)); // Sanity on the test itself.
+
+ // It's up to the OS whether this will succeed or fail, but it must not crash.
+ writer->Delete();
+}
+#endif // !defined(OS_WIN)
+
+TEST_P(LogFileWriterTest, PathReturnsTheCorrectPath) {
+ Init(GetParam());
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ ASSERT_EQ(writer->path(), path_);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Compression,
+ LogFileWriterTest,
+ ::testing::Values(WebRtcEventLogCompression::NONE,
+ WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION));
+
+// Tests for UncompressedLogFileWriterTest only.
+class UncompressedLogFileWriterTest : public LogFileWriterTest {
+ public:
+ ~UncompressedLogFileWriterTest() override = default;
+};
+
+TEST_F(UncompressedLogFileWriterTest,
+ MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ ASSERT_TRUE(writer->Write(log));
+
+ EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size());
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log)); // (CallToWriteSuccedsWhenCapacityReached)
+
+ EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size());
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteFailsWhenCapacityExceeded) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(log.size() - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_FALSE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, std::string());
+}
+
+TEST_F(UncompressedLogFileWriterTest, WriteCompleteMessagesOnly) {
+ Init(WebRtcEventLogCompression::NONE);
+
+ const std::string log1 = "01234";
+ const std::string log2 = "56789";
+
+ auto writer = CreateWriter(log1.size() + log2.size() - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log1));
+
+ EXPECT_FALSE(writer->Write(log2));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log1);
+}
+
+// Tests for GzippedLogFileWriterTest only.
+class GzippedLogFileWriterTest : public LogFileWriterTest {
+ public:
+ ~GzippedLogFileWriterTest() override = default;
+};
+
+TEST_F(GzippedLogFileWriterTest, FactoryDeletesFileIfMaxSizeBelowMin) {
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const size_t min_size = log_file_writer_factory_->MinFileSizeBytes();
+ ASSERT_GE(min_size, 1u);
+
+ auto writer = CreateWriter(min_size - 1);
+ ASSERT_FALSE(writer);
+
+ EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+ ASSERT_TRUE(writer);
+
+ const std::string log = "log";
+ ASSERT_TRUE(writer->Write(log));
+ EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+ // By using a 0 estimation, we allow the compressor to keep going to
+ // the point of budget saturation.
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log));
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log)); // (CallToWriteSuccedsWhenCapacityReached)
+ EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+ Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log));
+ ASSERT_TRUE(writer);
+
+ EXPECT_TRUE(writer->Write(log));
+
+ ASSERT_TRUE(writer->Close());
+ ExpectFileContents(path_, log);
+}
+
+// Also tests the scenario WriteCompleteMessagesOnly.
+TEST_F(GzippedLogFileWriterTest,
+ CallToWriteFailsWhenCapacityWouldBeExceededButEstimationPreventedWrite) {
+ Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+ const std::string log1 = "abcde";
+ const std::string log2 = "fghij";
+ const std::vector<std::string> logs = {log1, log2};
+
+ // Find out the size necessary for compressing log1 and log2 in two calls.
+ const size_t compressed_len = GzippedSize(logs); // Vector version.
+
+ auto writer = CreateWriter(compressed_len - 1);
+ ASSERT_TRUE(writer);
+
+ ASSERT_TRUE(writer->Write(log1));
+
+ EXPECT_FALSE(writer->Write(log2));
+
+ // The second write was succesfully prevented; no error should have occurred,
+ // and it should be possible to produce a meaningful gzipped log file.
+ EXPECT_TRUE(writer->Close());
+
+ ExpectFileContents(path_, log1); // Only the in-budget part was written.
+}
+
+// This tests the case when the estimation fails to warn us of a pending
+// over-budget write, which leaves us unable to produce a valid compression
+// footer for the truncated file. This forces us to discard the file.
+TEST_F(GzippedLogFileWriterTest,
+ CallToWriteFailsWhenCapacityExceededDespiteEstimationAllowingIt) {
+ // By using a 0 estimation, we allow the compressor to keep going to
+ // the point of budget saturation.
+ Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "log";
+
+ auto writer = CreateWriter(GzippedSize(log) - 1);
+ ASSERT_TRUE(writer);
+
+ EXPECT_FALSE(writer->Write(log));
+
+ EXPECT_FALSE(writer->Close());
+ EXPECT_FALSE(base::PathExists(path_)); // Errored files deleted by Close().
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc
new file mode 100644
index 00000000000..ec5401e454a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.cc
@@ -0,0 +1,36 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h"
+
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace webrtc_event_logging {
+
+WebRtcEventLogManagerKeyedService::WebRtcEventLogManagerKeyedService(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+ DCHECK(!browser_context_->IsOffTheRecord());
+
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ if (manager) {
+ manager->EnableForBrowserContext(browser_context_, base::OnceClosure());
+ reported_ = true;
+ } else {
+ reported_ = false;
+ }
+}
+
+void WebRtcEventLogManagerKeyedService::Shutdown() {
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ if (manager) {
+ DCHECK(reported_) << "WebRtcEventLogManager constructed too late.";
+ manager->DisableForBrowserContext(browser_context_, base::OnceClosure());
+ }
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h
new file mode 100644
index 00000000000..c12cf66f999
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h
@@ -0,0 +1,43 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// KeyedService working on behalf of WebRtcEventLogManager, informing it when
+// new BrowserContext-s are loaded.
+class WebRtcEventLogManagerKeyedService : public KeyedService {
+ public:
+ explicit WebRtcEventLogManagerKeyedService(
+ content::BrowserContext* browser_context);
+
+ ~WebRtcEventLogManagerKeyedService() override = default;
+
+ void Shutdown() override;
+
+ private:
+ // The BrowserContext associated with this instance of the service.
+ content::BrowserContext* const browser_context_;
+
+ // Whether the singleton content::WebRtcEventLogger existed at the time this
+ // service was instantiated, and therefore got the report that this
+ // BrowserContext was loaded.
+ // See usage for rationale.
+ bool reported_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerKeyedService);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc
new file mode 100644
index 00000000000..5660683eebe
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.cc
@@ -0,0 +1,40 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h"
+
+#include "base/logging.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace webrtc_event_logging {
+
+// static
+WebRtcEventLogManagerKeyedServiceFactory*
+WebRtcEventLogManagerKeyedServiceFactory::GetInstance() {
+ return base::Singleton<WebRtcEventLogManagerKeyedServiceFactory>::get();
+}
+
+WebRtcEventLogManagerKeyedServiceFactory::
+ WebRtcEventLogManagerKeyedServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "WebRtcEventLogManagerKeyedService",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+WebRtcEventLogManagerKeyedServiceFactory::
+ ~WebRtcEventLogManagerKeyedServiceFactory() = default;
+
+bool WebRtcEventLogManagerKeyedServiceFactory::
+ ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+KeyedService* WebRtcEventLogManagerKeyedServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ DCHECK(!context->IsOffTheRecord());
+ return new WebRtcEventLogManagerKeyedService(context);
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h
new file mode 100644
index 00000000000..6e7195758f1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_keyed_service_factory.h
@@ -0,0 +1,44 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class KeyedService;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrtc_event_logging {
+
+// Produces WebRtcEventLogManagerKeyedService-s for non-incognito profiles.
+class WebRtcEventLogManagerKeyedServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static WebRtcEventLogManagerKeyedServiceFactory* GetInstance();
+
+ protected:
+ bool ServiceIsCreatedWithBrowserContext() const override;
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ WebRtcEventLogManagerKeyedServiceFactory>;
+
+ WebRtcEventLogManagerKeyedServiceFactory();
+ ~WebRtcEventLogManagerKeyedServiceFactory() override;
+
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerKeyedServiceFactory);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_KEYED_SERVICE_FACTORY_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
new file mode 100644
index 00000000000..d13b25244aa
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
@@ -0,0 +1,252 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_local.h"
+
+#include "base/files/file_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToString16
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+namespace webrtc_event_logging {
+
+#if defined(OS_ANDROID)
+const size_t kDefaultMaxLocalLogFileSizeBytes = 10000000;
+const size_t kMaxNumberLocalWebRtcEventLogFiles = 3;
+#else
+const size_t kDefaultMaxLocalLogFileSizeBytes = 60000000;
+const size_t kMaxNumberLocalWebRtcEventLogFiles = 5;
+#endif
+
+WebRtcLocalEventLogManager::WebRtcLocalEventLogManager(
+ WebRtcLocalEventLogsObserver* observer)
+ : observer_(observer),
+ clock_for_testing_(nullptr),
+ max_log_file_size_bytes_(kDefaultMaxLocalLogFileSizeBytes) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DETACH_FROM_SEQUENCE(io_task_sequence_checker_);
+}
+
+WebRtcLocalEventLogManager::~WebRtcLocalEventLogManager() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+bool WebRtcLocalEventLogManager::PeerConnectionAdded(
+ const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ const auto insertion_result = active_peer_connections_.insert(key);
+ if (!insertion_result.second) {
+ return false; // Attempt to re-add the PeerConnection.
+ }
+
+ if (!base_path_.empty() &&
+ log_files_.size() < kMaxNumberLocalWebRtcEventLogFiles) {
+ // Note that success/failure of starting the local log file is unrelated
+ // to the success/failure of PeerConnectionAdded().
+ StartLogFile(key);
+ }
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::PeerConnectionRemoved(
+ const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ auto peer_connection = active_peer_connections_.find(key);
+
+ if (peer_connection == active_peer_connections_.end()) {
+ DCHECK(log_files_.find(key) == log_files_.end());
+ return false;
+ }
+
+ auto local_log = log_files_.find(key);
+ if (local_log != log_files_.end()) {
+ // Note that success/failure of stopping the local log file is unrelated
+ // to the success/failure of PeerConnectionRemoved().
+ CloseLogFile(local_log);
+ }
+
+ active_peer_connections_.erase(peer_connection);
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::EnableLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ if (!base_path_.empty()) {
+ return false;
+ }
+
+ DCHECK_EQ(log_files_.size(), 0u);
+
+ base_path_ = base_path;
+
+ max_log_file_size_bytes_ =
+ (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize)
+ ? base::Optional<size_t>()
+ : base::Optional<size_t>(max_file_size_bytes);
+
+ for (const PeerConnectionKey& peer_connection : active_peer_connections_) {
+ if (log_files_.size() >= kMaxNumberLocalWebRtcEventLogFiles) {
+ break;
+ }
+ StartLogFile(peer_connection);
+ }
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::DisableLogging() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ if (base_path_.empty()) {
+ return false;
+ }
+
+ for (auto local_log = log_files_.begin(); local_log != log_files_.end();) {
+ local_log = CloseLogFile(local_log);
+ }
+
+ base_path_.clear(); // Marks local-logging as disabled.
+ max_log_file_size_bytes_ = kDefaultMaxLocalLogFileSizeBytes;
+
+ return true;
+}
+
+bool WebRtcLocalEventLogManager::EventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ auto it = log_files_.find(key);
+ if (it == log_files_.end()) {
+ return false;
+ }
+
+ const bool write_successful = it->second->Write(message);
+
+ if (!write_successful || it->second->MaxSizeReached()) {
+ CloseLogFile(it);
+ }
+
+ return write_successful;
+}
+
+void WebRtcLocalEventLogManager::RenderProcessHostExitedDestroyed(
+ int render_process_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ // Remove all of the peer connections associated with this render process.
+ auto pc_it = active_peer_connections_.begin();
+ while (pc_it != active_peer_connections_.end()) {
+ if (pc_it->render_process_id == render_process_id) {
+ pc_it = active_peer_connections_.erase(pc_it);
+ } else {
+ ++pc_it;
+ }
+ }
+
+ // Close all of the files that were associated with peer connections which
+ // belonged to this render process.
+ auto log_it = log_files_.begin();
+ while (log_it != log_files_.end()) {
+ if (log_it->first.render_process_id == render_process_id) {
+ log_it = CloseLogFile(log_it);
+ } else {
+ ++log_it;
+ }
+ }
+}
+
+void WebRtcLocalEventLogManager::SetClockForTesting(base::Clock* clock) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ clock_for_testing_ = clock;
+}
+
+void WebRtcLocalEventLogManager::StartLogFile(const PeerConnectionKey& key) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+ DCHECK(log_files_.find(key) == log_files_.end());
+
+ // Add some information to the name given by the caller.
+ base::FilePath file_path = GetFilePath(base_path_, key);
+ CHECK(!file_path.empty()) << "Couldn't set path for local WebRTC log file.";
+
+ // In the unlikely case that this filename is already taken, find a unique
+ // number to append to the filename, if possible.
+ file_path = base::GetUniquePath(file_path);
+ if (file_path.empty()) {
+ return; // No available file path was found.
+ }
+
+ auto log_file =
+ log_file_writer_factory_.Create(file_path, max_log_file_size_bytes_);
+ if (!log_file) {
+ LOG(WARNING) << "Couldn't create and/or open local WebRTC event log file.";
+ return;
+ }
+
+ const auto it = log_files_.emplace(key, std::move(log_file));
+ DCHECK(it.second);
+
+ // The observer needs to be able to run on any TaskQueue.
+ if (observer_) {
+ LogFilesMap::iterator map_iter = it.first;
+ // map_iter->second is a std::unique_ptr<LogFileWriter>.
+ observer_->OnLocalLogStarted(key, map_iter->second->path());
+ }
+}
+
+WebRtcLocalEventLogManager::LogFilesMap::iterator
+WebRtcLocalEventLogManager::CloseLogFile(LogFilesMap::iterator it) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ const PeerConnectionKey peer_connection = it->first;
+
+ it->second->Close();
+ it = log_files_.erase(it);
+
+ if (observer_) {
+ observer_->OnLocalLogStopped(peer_connection);
+ }
+
+ return it;
+}
+
+base::FilePath WebRtcLocalEventLogManager::GetFilePath(
+ const base::FilePath& base_path,
+ const PeerConnectionKey& key) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+
+ base::Time::Exploded now;
+ if (clock_for_testing_) {
+ clock_for_testing_->Now().LocalExplode(&now);
+ } else {
+ base::Time::Now().LocalExplode(&now);
+ }
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ char stamp[100];
+ int written =
+ base::snprintf(stamp, base::size(stamp), "%04d%02d%02d_%02d%02d_%d_%d",
+ now.year, now.month, now.day_of_month, now.hour,
+ now.minute, key.render_process_id, key.lid);
+ CHECK_GT(written, 0);
+ CHECK_LT(static_cast<size_t>(written), base::size(stamp));
+
+ return base_path.InsertBeforeExtension(FILE_PATH_LITERAL("_"))
+ .AddExtension(log_file_writer_factory_.Extension())
+ .InsertBeforeExtensionASCII(base::StringPiece(stamp));
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
new file mode 100644
index 00000000000..6ebf3e49185
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
@@ -0,0 +1,98 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/time/clock.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+class WebRtcLocalEventLogManager final {
+ using LogFilesMap =
+ std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ public:
+ explicit WebRtcLocalEventLogManager(WebRtcLocalEventLogsObserver* observer);
+ ~WebRtcLocalEventLogManager();
+
+ bool PeerConnectionAdded(const PeerConnectionKey& key);
+ bool PeerConnectionRemoved(const PeerConnectionKey& key);
+
+ bool EnableLogging(const base::FilePath& base_path,
+ size_t max_file_size_bytes);
+ bool DisableLogging();
+
+ bool EventLogWrite(const PeerConnectionKey& key, const std::string& message);
+
+ void RenderProcessHostExitedDestroyed(int render_process_id);
+
+ // This function is public, but this entire class is a protected
+ // implementation detail of WebRtcEventLogManager, which hides this
+ // function from everybody except its own unit tests.
+ void SetClockForTesting(base::Clock* clock);
+
+ private:
+ // Create a local log file.
+ void StartLogFile(const PeerConnectionKey& key);
+
+ // Closes an active log file.
+ // Returns an iterator to the next active log file.
+ LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it);
+
+ // Derives the name of a local log file. The format is:
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ base::FilePath GetFilePath(const base::FilePath& base_path,
+ const PeerConnectionKey& key) const;
+
+ // This object is expected to be created and destroyed on the UI thread,
+ // but live on its owner's internal, IO-capable task queue.
+ SEQUENCE_CHECKER(io_task_sequence_checker_);
+
+ // Produces LogFileWriter instances, for writing the logs to files.
+ BaseLogFileWriterFactory log_file_writer_factory_;
+
+ // Observer which will be informed whenever a local log file is started or
+ // stopped. Through this, the owning WebRtcEventLogManager can be informed,
+ // and decide whether it wants to turn notifications from WebRTC on/off.
+ WebRtcLocalEventLogsObserver* const observer_;
+
+ // For unit tests only, and specifically for unit tests that verify the
+ // filename format (derived from the current time as well as the renderer PID
+ // and PeerConnection local ID), we want to make sure that the time and date
+ // cannot change between the time the clock is read by the unit under test
+ // (namely WebRtcEventLogManager) and the time it's read by the test.
+ base::Clock* clock_for_testing_;
+
+ // Currently active peer connections. PeerConnections which have been closed
+ // are not considered active, regardless of whether they have been torn down.
+ std::set<PeerConnectionKey> active_peer_connections_;
+
+ // Local log files, stored at the behest of the user (via WebRTCInternals).
+ LogFilesMap log_files_;
+
+ // If |base_path_| is empty, local logging is disabled.
+ // If nonempty, local logging is enabled, and all local logs will be saved
+ // to this directory.
+ base::FilePath base_path_;
+
+ // The maximum size for local logs, in bytes.
+ // If !has_value(), the value is unlimited.
+ base::Optional<size_t> max_log_file_size_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLocalEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_LOCAL_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
new file mode 100644
index 00000000000..a69b8d02b3a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
@@ -0,0 +1,1376 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h"
+
+#include <algorithm>
+#include <iterator>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace webrtc_event_logging {
+
+// TODO(crbug.com/775415): Change max back to (1u << 29) after resolving the
+// issue where we read the entire file into memory.
+const size_t kMaxRemoteLogFileSizeBytes = 50000000u;
+
+const int kDefaultOutputPeriodMs = 5000;
+const int kMaxOutputPeriodMs = 60000;
+
+namespace {
+const base::TimeDelta kDefaultProactivePruningDelta =
+ base::TimeDelta::FromMinutes(5);
+
+const base::TimeDelta kDefaultWebRtcRemoteEventLogUploadDelay =
+ base::TimeDelta::FromSeconds(30);
+
+// Because history files are rarely used, their existence is not kept in memory.
+// That means that pruning them involves inspecting data on disk. This is not
+// terribly cheap (up to kMaxWebRtcEventLogHistoryFiles files per profile), and
+// should therefore be done somewhat infrequently.
+const base::TimeDelta kProactiveHistoryFilesPruneDelta =
+ base::TimeDelta::FromMinutes(30);
+
+base::TimeDelta GetProactivePendingLogsPruneDelta() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta)) {
+ const std::string delta_seconds_str =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta);
+ int64_t seconds;
+ if (base::StringToInt64(delta_seconds_str, &seconds) && seconds >= 0) {
+ return base::TimeDelta::FromSeconds(seconds);
+ } else {
+ LOG(WARNING) << "Proactive pruning delta could not be parsed.";
+ }
+ }
+
+ return kDefaultProactivePruningDelta;
+}
+
+base::TimeDelta GetUploadDelay() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs)) {
+ const std::string delta_seconds_str =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs);
+ int64_t ms;
+ if (base::StringToInt64(delta_seconds_str, &ms) && ms >= 0) {
+ return base::TimeDelta::FromMilliseconds(ms);
+ } else {
+ LOG(WARNING) << "Upload delay could not be parsed; using default delay.";
+ }
+ }
+
+ return kDefaultWebRtcRemoteEventLogUploadDelay;
+}
+
+bool TimePointInRange(const base::Time& time_point,
+ const base::Time& range_begin,
+ const base::Time& range_end) {
+ DCHECK(!time_point.is_null());
+ DCHECK(range_begin.is_null() || range_end.is_null() ||
+ range_begin <= range_end);
+ return (range_begin.is_null() || range_begin <= time_point) &&
+ (range_end.is_null() || time_point < range_end);
+}
+
+// Do not attempt to upload when there is no active connection.
+// Do not attempt to upload if the connection is known to be a mobile one.
+// Note #1: A device may have multiple connections, so this is not bullet-proof.
+// Note #2: Does not attempt to recognize mobile hotspots.
+bool UploadSupportedUsingConnectionType(
+ network::mojom::ConnectionType connection) {
+ return connection != network::mojom::ConnectionType::CONNECTION_NONE &&
+ connection != network::mojom::ConnectionType::CONNECTION_2G &&
+ connection != network::mojom::ConnectionType::CONNECTION_3G &&
+ connection != network::mojom::ConnectionType::CONNECTION_4G;
+}
+
+// Produce a history file for a given file.
+void CreateHistoryFile(const base::FilePath& log_file_path,
+ const base::Time& capture_time) {
+ std::unique_ptr<WebRtcEventLogHistoryFileWriter> writer =
+ WebRtcEventLogHistoryFileWriter::Create(
+ GetWebRtcEventLogHistoryFilePath(log_file_path));
+ if (!writer) {
+ LOG(ERROR) << "Could not create history file.";
+ return;
+ }
+
+ if (!writer->WriteCaptureTime(capture_time)) {
+ LOG(ERROR) << "Could not write capture time to history file.";
+ writer->Delete();
+ return;
+ }
+}
+
+// The following is a list of entry types used to transmit information
+// from GetHistory() to the caller (normally - the UI).
+// Each entry is of type UploadList::UploadInfo. Depending on the entry
+// type, the fields in the UploadInfo have different values:
+// 1+2. Currently-being-captured or pending -> State::Pending && !upload_time.
+// 3. Currently-being-uploaded -> State::Pending && upload_time.
+// 4. Pruned before being uploaded -> State::NotUploaded && !upload_time.
+// 5. Unsuccessful upload attempt -> State::NotUploaded && upload_time.
+// 6. Successfully uploaded -> State::Uploaded.
+//
+// As for the meaning of the local_id field, its semantics change according to
+// the above entry type.
+// * For cases 1-3 above, it is the filename, since the log is still on disk.
+// * For cases 5-6 above, it is the local log ID that the now-deleted file used
+// * to have.
+namespace history {
+UploadList::UploadInfo CreateActivelyCapturedLogEntry(
+ const base::FilePath& path,
+ const base::Time& capture_time) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), base::Time(), filename,
+ capture_time, State::Pending);
+}
+
+UploadList::UploadInfo CreatePendingLogEntry(
+ const WebRtcLogFileInfo& log_info) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = log_info.path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), base::Time(), filename,
+ log_info.last_modified, State::Pending);
+}
+
+UploadList::UploadInfo CreateActivelyUploadedLogEntry(
+ const WebRtcLogFileInfo& log_info,
+ const base::Time& upload_time) {
+ using State = UploadList::UploadInfo::State;
+ const std::string filename = log_info.path.BaseName().MaybeAsASCII();
+ DCHECK(!filename.empty());
+ return UploadList::UploadInfo(std::string(), upload_time, filename,
+ log_info.last_modified, State::Pending);
+}
+
+UploadList::UploadInfo CreateEntryFromHistoryFileReader(
+ const WebRtcEventLogHistoryFileReader& reader) {
+ using State = UploadList::UploadInfo::State;
+ const auto state =
+ reader.UploadId().empty() ? State::NotUploaded : State::Uploaded;
+ return UploadList::UploadInfo(reader.UploadId(), reader.UploadTime(),
+ reader.LocalId(), reader.CaptureTime(), state);
+}
+} // namespace history
+} // namespace
+
+const size_t kMaxActiveRemoteBoundWebRtcEventLogs = 3;
+const size_t kMaxPendingRemoteBoundWebRtcEventLogs = 5;
+static_assert(kMaxActiveRemoteBoundWebRtcEventLogs <=
+ kMaxPendingRemoteBoundWebRtcEventLogs,
+ "This assumption affects unit test coverage.");
+const size_t kMaxWebRtcEventLogHistoryFiles = 50;
+
+// Maximum time to keep remote-bound logs on disk.
+const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention =
+ base::TimeDelta::FromDays(7);
+
+// Maximum time to keep history files on disk. These serve to display an upload
+// on chrome://webrtc-logs/. It is persisted for longer than the log itself.
+const base::TimeDelta kHistoryFileRetention = base::TimeDelta::FromDays(30);
+
+WebRtcRemoteEventLogManager::WebRtcRemoteEventLogManager(
+ WebRtcRemoteEventLogsObserver* observer,
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : upload_suppression_disabled_(
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadNoSuppression)),
+ upload_delay_(GetUploadDelay()),
+ proactive_pending_logs_prune_delta_(GetProactivePendingLogsPruneDelta()),
+ proactive_prune_scheduling_started_(false),
+ observer_(observer),
+ network_connection_tracker_(nullptr),
+ uploading_supported_for_connection_type_(false),
+ scheduled_upload_tasks_(0),
+ uploader_factory_(
+ std::make_unique<WebRtcEventLogUploaderImpl::Factory>()),
+ task_runner_(task_runner),
+ weak_ptr_factory_(
+ std::make_unique<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>(
+ this)) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // Proactive pruning would not do anything at the moment; it will be started
+ // with the first enabled browser context. This will all have the benefit
+ // of doing so on |task_runner_| rather than the UI thread.
+}
+
+WebRtcRemoteEventLogManager::~WebRtcRemoteEventLogManager() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // TODO(crbug.com/775415): Purge from disk files which were being uploaded
+ // while destruction took place, thereby avoiding endless attempts to upload
+ // the same file.
+
+ if (weak_ptr_factory_) {
+ // Not a unit test; that would have gone through ShutDownForTesting().
+ const bool will_delete =
+ task_runner_->DeleteSoon(FROM_HERE, weak_ptr_factory_.release());
+ DCHECK(!will_delete)
+ << "Task runners must have been stopped by this stage of shutdown.";
+ }
+
+ if (network_connection_tracker_) {
+ // * |network_connection_tracker_| might already have posted a task back
+ // to us, but it will not run, because |task_runner_| has already been
+ // stopped.
+ // * RemoveNetworkConnectionObserver() should generally be called on the
+ // same thread as AddNetworkConnectionObserver(), but in this case it's
+ // okay to remove on a separate thread, because this only happens during
+ // Chrome shutdown, when no others tasks are running; there can be no
+ // concurrently executing notification from the tracker.
+ network_connection_tracker_->RemoveNetworkConnectionObserver(this);
+ }
+}
+
+void WebRtcRemoteEventLogManager::SetNetworkConnectionTracker(
+ network::NetworkConnectionTracker* network_connection_tracker) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker);
+ DCHECK(!network_connection_tracker_);
+
+ // |this| is only destroyed (on the UI thread) after |task_runner_| stops,
+ // so AddNetworkConnectionObserver() is safe.
+
+ network_connection_tracker_ = network_connection_tracker;
+ network_connection_tracker_->AddNetworkConnectionObserver(this);
+
+ auto callback =
+ base::BindOnce(&WebRtcRemoteEventLogManager::OnConnectionChanged,
+ weak_ptr_factory_->GetWeakPtr());
+ network::mojom::ConnectionType connection_type;
+ const bool sync_answer = network_connection_tracker_->GetConnectionType(
+ &connection_type, std::move(callback));
+
+ if (sync_answer) {
+ OnConnectionChanged(connection_type);
+ }
+
+ // Because this happens while enabling the first browser context, there is no
+ // necessity to consider uploading yet.
+ DCHECK_EQ(enabled_browser_contexts_.size(), 0u);
+}
+
+void WebRtcRemoteEventLogManager::SetLogFileWriterFactory(
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_file_writer_factory);
+ DCHECK(!log_file_writer_factory_);
+ log_file_writer_factory_ = std::move(log_file_writer_factory);
+}
+
+void WebRtcRemoteEventLogManager::EnableForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(network_connection_tracker_)
+ << "SetNetworkConnectionTracker not called.";
+ DCHECK(log_file_writer_factory_) << "SetLogFileWriterFactory() not called.";
+ DCHECK(!BrowserContextEnabled(browser_context_id)) << "Already enabled.";
+
+ const base::FilePath remote_bound_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ if (!MaybeCreateLogsDirectory(remote_bound_logs_dir)) {
+ LOG(WARNING)
+ << "WebRtcRemoteEventLogManager couldn't create logs directory.";
+ return;
+ }
+
+ enabled_browser_contexts_.emplace(browser_context_id, remote_bound_logs_dir);
+
+ LoadLogsDirectory(browser_context_id, remote_bound_logs_dir);
+
+ if (!proactive_prune_scheduling_started_) {
+ proactive_prune_scheduling_started_ = true;
+
+ if (!proactive_pending_logs_prune_delta_.is_zero()) {
+ RecurringlyPrunePendingLogs();
+ }
+
+ RecurringlyPruneHistoryFiles();
+ }
+}
+
+void WebRtcRemoteEventLogManager::DisableForBrowserContext(
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ return; // Enabling may have failed due to lacking permissions.
+ }
+
+ enabled_browser_contexts_.erase(browser_context_id);
+
+#if DCHECK_IS_ON()
+ // DisableForBrowserContext() is called in one of two cases:
+ // 1. If Chrome is shutting down. In that case, all the RPHs associated with
+ // this BrowserContext must already have exited, which should have
+ // implicitly stopped all active logs.
+ // 2. Remote-bound logging is no longer allowed for this BrowserContext.
+ // In that case, some peer connections associated with this BrowserContext
+ // might still be active, or become active at a later time, but all
+ // logs must have already been stopped.
+ auto pred = [browser_context_id](decltype(active_logs_)::value_type& log) {
+ return log.first.browser_context_id == browser_context_id;
+ };
+ DCHECK(std::count_if(active_logs_.begin(), active_logs_.end(), pred) == 0u);
+#endif
+
+ // Pending logs for this BrowserContext are no longer eligible for upload.
+ for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
+ if (it->browser_context_id == browser_context_id) {
+ it = pending_logs_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Active uploads of logs associated with this BrowserContext must be stopped.
+ MaybeCancelUpload(base::Time::Min(), base::Time::Max(), browser_context_id);
+
+ // Active logs may have been removed, which could remove upload suppression,
+ // or pending logs which were about to be uploaded may have been removed,
+ // so uploading may no longer be possible.
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionAdded(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ const auto result = active_peer_connections_.emplace(key, std::string());
+
+ // An upload about to start might need to be suppressed.
+ ManageUploadSchedule();
+
+ return result.second;
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionRemoved(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ const auto peer_connection = active_peer_connections_.find(key);
+ if (peer_connection == active_peer_connections_.end()) {
+ return false;
+ }
+
+ MaybeStopRemoteLogging(key);
+
+ active_peer_connections_.erase(peer_connection);
+
+ ManageUploadSchedule(); // Suppression might have been removed.
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::PeerConnectionSessionIdSet(
+ const PeerConnectionKey& key,
+ const std::string& session_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Infrequent event - good opportunity to prune.
+
+ if (session_id.empty()) {
+ LOG(ERROR) << "Empty session ID.";
+ return false;
+ }
+
+ auto peer_connection = active_peer_connections_.find(key);
+ if (peer_connection == active_peer_connections_.end()) {
+ return false; // Unknown peer connection; already closed?
+ }
+
+ if (!peer_connection->second.empty()) {
+ LOG(ERROR) << "Session ID already set.";
+ return false;
+ }
+
+ peer_connection->second = session_id;
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::StartRemoteLogging(
+ int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id,
+ std::string* error_message) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_id);
+ DCHECK(log_id->empty());
+ DCHECK(error_message);
+ DCHECK(error_message->empty());
+
+ if (output_period_ms < 0) {
+ output_period_ms = kDefaultOutputPeriodMs;
+ }
+
+ if (!AreLogParametersValid(max_file_size_bytes, output_period_ms, web_app_id,
+ error_message)) {
+ // |error_message| will have been set by AreLogParametersValid().
+ DCHECK(!error_message->empty()) << "AreLogParametersValid() reported an "
+ "error without an error message.";
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kInvalidArguments);
+ return false;
+ }
+
+ if (session_id.empty()) {
+ *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIllegalSessionId);
+ return false;
+ }
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ // Remote-bound event logging has either not yet been enabled for this
+ // BrowserContext, or has been recently disabled. This error should not
+ // really be reached, barring a timing issue.
+ *error_message = kStartRemoteLoggingFailureLoggingDisabledBrowserContext;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kDisabledBrowserContext);
+ return false;
+ }
+
+ PeerConnectionKey key;
+ if (!FindPeerConnection(render_process_id, session_id, &key)) {
+ *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kUnknownOrInvalidPeerConnection);
+ return false;
+ }
+
+ // May not restart active remote logs.
+ auto it = active_logs_.find(key);
+ if (it != active_logs_.end()) {
+ LOG(ERROR) << "Remote logging already underway for " << session_id << ".";
+ *error_message = kStartRemoteLoggingFailureAlreadyLogging;
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kAlreadyLogging);
+ return false;
+ }
+
+ // This is a good opportunity to prune the list of pending logs, potentially
+ // making room for this file.
+ PrunePendingLogs();
+
+ if (!AdditionalActiveLogAllowed(key.browser_context_id)) {
+ *error_message = kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kNoAdditionalLogsAllowed);
+ return false;
+ }
+
+ return StartWritingLog(key, browser_context_dir, max_file_size_bytes,
+ output_period_ms, web_app_id, log_id, error_message);
+}
+
+bool WebRtcRemoteEventLogManager::EventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ auto it = active_logs_.find(key);
+ if (it == active_logs_.end()) {
+ return false;
+ }
+
+ const bool write_successful = it->second->Write(message);
+ if (!write_successful || it->second->MaxSizeReached()) {
+ // Note: If the file is invalid, CloseLogFile() will discard it.
+ CloseLogFile(it, /*make_pending=*/true);
+ ManageUploadSchedule();
+ }
+
+ return write_successful;
+}
+
+void WebRtcRemoteEventLogManager::ClearCacheForBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ // Rationale for the order:
+ // 1. Active logs cancelled. This has no side effects, and can be safely
+ // done before anything else.
+ // 2. Pending logs removed, before they can be considered as the
+ // next log to be uploaded. This may cause history files to be created.
+ // 3. Remove history files, including those that #2 might have created.
+ // 4. Cancel any active upload precisely at a time when nothing being cleared
+ // by ClearCacheForBrowserContext() could accidentally replace it.
+ // 5. Explicitly consider uploading, now that things have changed.
+ MaybeCancelActiveLogs(delete_begin, delete_end, browser_context_id);
+ MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id,
+ /*is_cache_clear=*/true);
+ MaybeRemoveHistoryFiles(delete_begin, delete_end, browser_context_id);
+ MaybeCancelUpload(delete_begin, delete_end, browser_context_id);
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::vector<UploadList::UploadInfo> history;
+
+ if (!BrowserContextEnabled(browser_context_id)) {
+ LOG(ERROR) << "Unknown |browser_context_id|.";
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply), history));
+ return;
+ }
+
+ PrunePendingLogs(browser_context_id);
+
+ const base::Time now = base::Time::Now();
+
+ std::set<WebRtcEventLogHistoryFileReader> history_files =
+ PruneAndLoadHistoryFilesForBrowserContext(
+ base::Time::Min(), now - kHistoryFileRetention, browser_context_id);
+ for (const auto& history_file : history_files) {
+ history.push_back(history::CreateEntryFromHistoryFileReader(history_file));
+ }
+
+ for (const WebRtcLogFileInfo& log_info : pending_logs_) {
+ if (browser_context_id == log_info.browser_context_id) {
+ history.push_back(history::CreatePendingLogEntry(log_info));
+ }
+ }
+
+ for (const auto& it : active_logs_) {
+ if (browser_context_id == it.first.browser_context_id) {
+ history.push_back(
+ history::CreateActivelyCapturedLogEntry(it.second->path(), now));
+ }
+ }
+
+ if (uploader_) {
+ const WebRtcLogFileInfo log_info = uploader_->GetWebRtcLogFileInfo();
+ if (browser_context_id == log_info.browser_context_id) {
+ history.push_back(history::CreateActivelyUploadedLogEntry(log_info, now));
+ }
+ }
+
+ // Sort according to capture time, for consistent orders regardless of
+ // future operations on the log files.
+ auto cmp = [](const UploadList::UploadInfo& lhs,
+ const UploadList::UploadInfo& rhs) {
+ if (lhs.capture_time == rhs.capture_time) {
+ // Resolve ties arbitrarily, but consistently. (Local ID expected to be
+ // distinct for distinct items; if not, anything goes.)
+ return lhs.local_id < rhs.local_id;
+ }
+ return (lhs.capture_time < rhs.capture_time);
+ };
+ std::sort(history.begin(), history.end(), cmp);
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply), history));
+}
+
+void WebRtcRemoteEventLogManager::RemovePendingLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!BrowserContextEnabled(browser_context_id));
+ const base::FilePath remote_bound_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ if (!base::DeleteFile(remote_bound_logs_dir, /*recursive=*/true)) {
+ LOG(ERROR) << "Failed to delete `" << remote_bound_logs_dir << ".";
+ }
+}
+
+void WebRtcRemoteEventLogManager::RenderProcessHostExitedDestroyed(
+ int render_process_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Remove all of the peer connections associated with this render process.
+ // It's important to do this before closing the actual files, because closing
+ // files can trigger a new upload if no active peer connections are present.
+ auto pc_it = active_peer_connections_.begin();
+ while (pc_it != active_peer_connections_.end()) {
+ if (pc_it->first.render_process_id == render_process_id) {
+ pc_it = active_peer_connections_.erase(pc_it);
+ } else {
+ ++pc_it;
+ }
+ }
+
+ // Close all of the files that were associated with peer connections which
+ // belonged to this render process.
+ auto log_it = active_logs_.begin();
+ while (log_it != active_logs_.end()) {
+ if (log_it->first.render_process_id == render_process_id) {
+ log_it = CloseLogFile(log_it, /*make_pending=*/true);
+ } else {
+ ++log_it;
+ }
+ }
+
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::OnConnectionChanged(
+ network::mojom::ConnectionType type) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ // Even if switching from WiFi to Ethernet, or between to WiFi connections,
+ // reset the timer (if running) until an upload is permissible due to stable
+ // upload-supporting conditions.
+ time_when_upload_conditions_met_ = base::TimeTicks();
+
+ uploading_supported_for_connection_type_ =
+ UploadSupportedUsingConnectionType(type);
+
+ ManageUploadSchedule();
+
+ // TODO(crbug.com/775415): Support pausing uploads when connection goes down,
+ // or switches to an unsupported connection type.
+}
+
+void WebRtcRemoteEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(uploader_factory);
+ uploader_factory_ = std::move(uploader_factory);
+}
+
+void WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting(
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(callback), UploadConditionsHold()));
+}
+
+void WebRtcRemoteEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ weak_ptr_factory_->InvalidateWeakPtrs();
+ weak_ptr_factory_.reset();
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(std::move(reply)));
+}
+
+bool WebRtcRemoteEventLogManager::AreLogParametersValid(
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* error_message) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
+ LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
+ *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
+ return false;
+ }
+
+ if (max_file_size_bytes < log_file_writer_factory_->MinFileSizeBytes()) {
+ LOG(WARNING) << "File size below minimum allowed.";
+ *error_message = kStartRemoteLoggingFailureMaxSizeTooSmall;
+ return false;
+ }
+
+ if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
+ LOG(WARNING) << "File size exceeds maximum allowed.";
+ *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
+ return false;
+ }
+
+ if (output_period_ms > kMaxOutputPeriodMs) {
+ LOG(WARNING) << "Output period (ms) exceeds maximum allowed.";
+ *error_message = kStartRemoteLoggingFailureOutputPeriodMsTooLarge;
+ return false;
+ }
+
+ if (web_app_id < kMinWebRtcEventLogWebAppId ||
+ web_app_id > kMaxWebRtcEventLogWebAppId) {
+ LOG(WARNING) << "Illegal web-app identifier.";
+ *error_message = kStartRemoteLoggingFailureIllegalWebAppId;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcRemoteEventLogManager::BrowserContextEnabled(
+ BrowserContextId browser_context_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ const auto it = enabled_browser_contexts_.find(browser_context_id);
+ return it != enabled_browser_contexts_.cend();
+}
+
+WebRtcRemoteEventLogManager::LogFilesMap::iterator
+WebRtcRemoteEventLogManager::CloseLogFile(LogFilesMap::iterator it,
+ bool make_pending) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const PeerConnectionKey peer_connection = it->first; // Copy, not reference.
+
+ const bool valid_file = it->second->Close();
+ if (valid_file) {
+ if (make_pending) {
+ // The current time is a good enough approximation of the file's last
+ // modification time.
+ const base::Time last_modified = base::Time::Now();
+
+ // The stopped log becomes a pending log.
+ const auto emplace_result =
+ pending_logs_.emplace(peer_connection.browser_context_id,
+ it->second->path(), last_modified);
+ DCHECK(emplace_result.second); // No pre-existing entry.
+ } else {
+ const base::FilePath log_file_path = it->second->path();
+ if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << log_file_path << ".";
+ }
+ }
+ } else { // !valid_file
+ // Close() deleted the file.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileWriteError);
+ }
+
+ it = active_logs_.erase(it);
+
+ if (observer_) {
+ observer_->OnRemoteLogStopped(peer_connection);
+ }
+
+ return it;
+}
+
+bool WebRtcRemoteEventLogManager::MaybeCreateLogsDirectory(
+ const base::FilePath& remote_bound_logs_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (base::PathExists(remote_bound_logs_dir)) {
+ if (!base::DirectoryExists(remote_bound_logs_dir)) {
+ LOG(ERROR) << "Path for remote-bound logs is taken by a non-directory.";
+ return false;
+ }
+ } else if (!base::CreateDirectory(remote_bound_logs_dir)) {
+ LOG(ERROR) << "Failed to create the local directory for remote-bound logs.";
+ return false;
+ }
+
+ // TODO(crbug.com/775415): Test for appropriate permissions.
+
+ return true;
+}
+
+void WebRtcRemoteEventLogManager::LoadLogsDirectory(
+ BrowserContextId browser_context_id,
+ const base::FilePath& remote_bound_logs_dir) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const auto separator =
+ base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
+ const base::Time now = base::Time::Now();
+
+ std::set<std::pair<base::FilePath, base::Time>> log_files_to_delete;
+ std::set<base::FilePath> history_files_to_delete;
+
+ // Iterate over all of the files in the directory; find the ones that need
+ // to be deleted. Skip unknown files; they may belong to the OS.
+ base::FileEnumerator enumerator(remote_bound_logs_dir,
+ /*recursive=*/false,
+ base::FileEnumerator::FILES);
+ for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
+ const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ const base::FilePath::StringType extension = info.GetName().Extension();
+ if (extension == separator + kWebRtcEventLogUncompressedExtension ||
+ extension == separator + kWebRtcEventLogGzippedExtension) {
+ const bool loaded = LoadPendingLogInfo(
+ browser_context_id, path, enumerator.GetInfo().GetLastModifiedTime());
+ if (!loaded) {
+ log_files_to_delete.insert(
+ std::make_pair(path, info.GetLastModifiedTime()));
+ }
+ } else if (extension == separator + kWebRtcEventLogHistoryExtension) {
+ auto reader = LoadHistoryFile(browser_context_id, path, base::Time::Min(),
+ now - kHistoryFileRetention);
+ if (!reader) {
+ history_files_to_delete.insert(path);
+ }
+ }
+ }
+
+ // Remove expired logs.
+ for (const auto& file_to_delete : log_files_to_delete) {
+ // Produce history file, unless we're discarding this log file precisely
+ // because we see it has a history file associated.
+ const base::FilePath& log_file_path = file_to_delete.first;
+ if (!base::PathExists(GetWebRtcEventLogHistoryFilePath(log_file_path))) {
+ const base::Time capture_time = file_to_delete.second;
+ CreateHistoryFile(log_file_path, capture_time);
+ }
+
+ // Remove the log file itself.
+ if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << file_to_delete.first << ".";
+ }
+ }
+
+ // Remove expired history files.
+ for (const base::FilePath& history_file_path : history_files_to_delete) {
+ if (!base::DeleteFile(history_file_path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << history_file_path << ".";
+ }
+ }
+
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::LoadPendingLogInfo(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!IsValidRemoteBoundLogFilePath(path)) {
+ return false;
+ }
+
+ const base::FilePath history_path = GetWebRtcEventLogHistoryFilePath(path);
+ if (base::PathExists(history_path)) {
+ // Log file has associated history file, indicating an upload was started
+ // for it. We should delete the original log from disk.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kIncompletePastUpload);
+ return false;
+ }
+
+ const base::Time now = base::Time::Now();
+ if (last_modified + kRemoteBoundWebRtcEventLogsMaxRetention < now) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kExpiredLogFileAtChromeStart);
+ return false;
+ }
+
+ auto it = pending_logs_.emplace(browser_context_id, path, last_modified);
+ DCHECK(it.second); // No pre-existing entry.
+
+ return true;
+}
+
+std::unique_ptr<WebRtcEventLogHistoryFileReader>
+WebRtcRemoteEventLogManager::LoadHistoryFile(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ const base::Time& prune_begin,
+ const base::Time& prune_end) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!IsValidRemoteBoundLogFilePath(path)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<WebRtcEventLogHistoryFileReader> reader =
+ WebRtcEventLogHistoryFileReader::Create(path);
+ if (!reader) {
+ return nullptr;
+ }
+
+ const base::Time capture_time = reader->CaptureTime();
+ if (prune_begin <= capture_time && capture_time <= prune_end) {
+ return nullptr;
+ }
+
+ const base::Time upload_time = reader->UploadTime();
+ if (!upload_time.is_null()) {
+ if (prune_begin <= upload_time && upload_time <= prune_end) {
+ return nullptr;
+ }
+ }
+
+ return reader;
+}
+
+std::set<WebRtcEventLogHistoryFileReader>
+WebRtcRemoteEventLogManager::PruneAndLoadHistoryFilesForBrowserContext(
+ const base::Time& prune_begin,
+ const base::Time& prune_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ std::set<WebRtcEventLogHistoryFileReader> history_files;
+
+ auto browser_contexts_it = enabled_browser_contexts_.find(browser_context_id);
+ if (browser_contexts_it == enabled_browser_contexts_.end()) {
+ return history_files;
+ }
+
+ std::set<base::FilePath> files_to_delete;
+
+ base::FileEnumerator enumerator(browser_contexts_it->second,
+ /*recursive=*/false,
+ base::FileEnumerator::FILES);
+
+ for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
+ const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ const base::FilePath::StringType extension = info.GetName().Extension();
+ const auto separator =
+ base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
+ if (extension != separator + kWebRtcEventLogHistoryExtension) {
+ continue;
+ }
+
+ if (uploader_) {
+ const base::FilePath log_path = uploader_->GetWebRtcLogFileInfo().path;
+ const base::FilePath history_path =
+ GetWebRtcEventLogHistoryFilePath(log_path);
+ if (path == history_path) {
+ continue;
+ }
+ }
+
+ auto reader =
+ LoadHistoryFile(browser_context_id, path, prune_begin, prune_end);
+ if (reader) {
+ history_files.insert(std::move(*reader));
+ reader.reset(); // |reader| in undetermined state after move().
+ } else { // Defective or expired.
+ files_to_delete.insert(path);
+ }
+ }
+
+ // |history_files| is sorted by log capture time in ascending order;
+ // remove the oldest entries until kMaxWebRtcEventLogHistoryFiles is obeyed.
+ size_t num_history_files = history_files.size();
+ for (auto it = history_files.begin();
+ num_history_files > kMaxWebRtcEventLogHistoryFiles;
+ --num_history_files) {
+ DCHECK(it != history_files.end());
+ files_to_delete.insert(it->path());
+ it = history_files.erase(it);
+ }
+
+ for (const base::FilePath& path : files_to_delete) {
+ if (!base::DeleteFile(path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << path << ".";
+ }
+ }
+
+ return history_files;
+}
+
+bool WebRtcRemoteEventLogManager::StartWritingLog(
+ const PeerConnectionKey& key,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_out,
+ std::string* error_message_out) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // The log is assigned a universally unique ID (with high probability).
+ const std::string log_id = CreateWebRtcEventLogId();
+
+ // Use the log ID as part of the filename. In the highly unlikely event that
+ // this filename is already taken, or that an earlier log with the same name
+ // existed and left a history file behind, it will be treated the same way as
+ // any other failure to start the log file.
+ // TODO(crbug.com/775415): Add a unit test for above comment.
+ const base::FilePath remote_logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
+ const base::FilePath log_path =
+ WebRtcEventLogPath(remote_logs_dir, log_id, web_app_id,
+ log_file_writer_factory_->Extension());
+
+ if (base::PathExists(log_path)) {
+ LOG(ERROR) << "Previously used ID selected.";
+ *error_message_out = kStartRemoteLoggingFailureFilePathUsedLog;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kLogPathNotAvailable);
+ return false;
+ }
+
+ const base::FilePath history_file_path =
+ GetWebRtcEventLogHistoryFilePath(log_path);
+ if (base::PathExists(history_file_path)) {
+ LOG(ERROR) << "Previously used ID selected.";
+ *error_message_out = kStartRemoteLoggingFailureFilePathUsedHistory;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kHistoryPathNotAvailable);
+ return false;
+ }
+
+ // The log is now ACTIVE.
+ DCHECK_NE(max_file_size_bytes, kWebRtcEventLogManagerUnlimitedFileSize);
+ auto log_file =
+ log_file_writer_factory_->Create(log_path, max_file_size_bytes);
+ if (!log_file) {
+ LOG(ERROR) << "Failed to initialize remote-bound WebRTC event log file.";
+ *error_message_out = kStartRemoteLoggingFailureFileCreationError;
+ UmaRecordWebRtcEventLoggingApi(
+ WebRtcEventLoggingApiUma::kFileCreationError);
+ return false;
+ }
+ const auto it = active_logs_.emplace(key, std::move(log_file));
+ DCHECK(it.second);
+
+ observer_->OnRemoteLogStarted(key, it.first->second->path(),
+ output_period_ms);
+
+ UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kSuccess);
+
+ *log_id_out = log_id;
+ return true;
+}
+
+void WebRtcRemoteEventLogManager::MaybeStopRemoteLogging(
+ const PeerConnectionKey& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ const auto it = active_logs_.find(key);
+ if (it == active_logs_.end()) {
+ return;
+ }
+
+ CloseLogFile(it, /*make_pending=*/true);
+
+ ManageUploadSchedule();
+}
+
+void WebRtcRemoteEventLogManager::PrunePendingLogs(
+ base::Optional<BrowserContextId> browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ MaybeRemovePendingLogs(
+ base::Time::Min(),
+ base::Time::Now() - kRemoteBoundWebRtcEventLogsMaxRetention,
+ browser_context_id, /*is_cache_clear=*/false);
+}
+
+void WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!proactive_pending_logs_prune_delta_.is_zero());
+ DCHECK(proactive_prune_scheduling_started_);
+
+ PrunePendingLogs();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs,
+ weak_ptr_factory_->GetWeakPtr()),
+ proactive_pending_logs_prune_delta_);
+}
+
+void WebRtcRemoteEventLogManager::PruneHistoryFiles() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ for (auto it = enabled_browser_contexts_.begin();
+ it != enabled_browser_contexts_.end(); ++it) {
+ const BrowserContextId browser_context_id = it->first;
+ MaybeRemoveHistoryFiles(base::Time::Min(),
+ base::Time::Now() - kHistoryFileRetention,
+ browser_context_id);
+ }
+}
+
+void WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(proactive_prune_scheduling_started_);
+
+ PruneHistoryFiles();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles,
+ weak_ptr_factory_->GetWeakPtr()),
+ kProactiveHistoryFilesPruneDelta);
+}
+
+void WebRtcRemoteEventLogManager::MaybeCancelActiveLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ for (auto it = active_logs_.begin(); it != active_logs_.end();) {
+ // Since the file is active, assume it's still being modified.
+ if (MatchesFilter(it->first.browser_context_id, base::Time::Now(),
+ browser_context_id, delete_begin, delete_end)) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kActiveLogCancelledDueToCacheClear);
+ it = CloseLogFile(it, /*make_pending=*/false);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void WebRtcRemoteEventLogManager::MaybeRemovePendingLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::Optional<BrowserContextId> browser_context_id,
+ bool is_cache_clear) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
+ if (MatchesFilter(it->browser_context_id, it->last_modified,
+ browser_context_id, delete_begin, delete_end)) {
+ UmaRecordWebRtcEventLoggingUpload(
+ is_cache_clear
+ ? WebRtcEventLoggingUploadUma::kPendingLogDeletedDueToCacheClear
+ : WebRtcEventLoggingUploadUma::kExpiredLogFileDuringSession);
+
+ if (!base::DeleteFile(it->path, /*recursive=*/false)) {
+ LOG(ERROR) << "Failed to delete " << it->path << ".";
+ }
+
+ // Produce a history file (they have longer retention) to replace the log.
+ if (is_cache_clear) { // Will be immediately deleted otherwise.
+ CreateHistoryFile(it->path, it->last_modified);
+ }
+
+ it = pending_logs_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // The last pending log might have been removed.
+ if (!UploadConditionsHold()) {
+ time_when_upload_conditions_met_ = base::TimeTicks();
+ }
+}
+
+void WebRtcRemoteEventLogManager::MaybeRemoveHistoryFiles(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ PruneAndLoadHistoryFilesForBrowserContext(delete_begin, delete_end,
+ browser_context_id);
+ return;
+}
+
+void WebRtcRemoteEventLogManager::MaybeCancelUpload(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ if (!uploader_) {
+ return;
+ }
+
+ const WebRtcLogFileInfo& info = uploader_->GetWebRtcLogFileInfo();
+ if (!MatchesFilter(info.browser_context_id, info.last_modified,
+ browser_context_id, delete_begin, delete_end)) {
+ return;
+ }
+
+ // Cancel the upload.
+ // * If the upload has asynchronously completed by now, the uploader would
+ // have posted a task back to our queue to delete it and move on to the
+ // next file; cancellation is reported as unsuccessful in that case. In that
+ // case, we avoid resetting |uploader_| until that callback task executes.
+ // * If the upload was still underway when we cancelled it, then we can
+ // safely reset |uploader_| and move on to the next file the next time
+ // ManageUploadSchedule() is called.
+ const bool cancelled = uploader_->Cancel();
+ if (cancelled) {
+ uploader_.reset();
+ }
+}
+
+bool WebRtcRemoteEventLogManager::MatchesFilter(
+ BrowserContextId log_browser_context_id,
+ const base::Time& log_last_modification,
+ base::Optional<BrowserContextId> filter_browser_context_id,
+ const base::Time& filter_range_begin,
+ const base::Time& filter_range_end) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (filter_browser_context_id &&
+ *filter_browser_context_id != log_browser_context_id) {
+ return false;
+ }
+ return TimePointInRange(log_last_modification, filter_range_begin,
+ filter_range_end);
+}
+
+bool WebRtcRemoteEventLogManager::AdditionalActiveLogAllowed(
+ BrowserContextId browser_context_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Limit over concurrently active logs (across BrowserContext-s).
+ if (active_logs_.size() >= kMaxActiveRemoteBoundWebRtcEventLogs) {
+ return false;
+ }
+
+ // Limit over the number of pending logs (per BrowserContext). We count active
+ // logs too, since they become pending logs once completed.
+ const size_t active_count = std::count_if(
+ active_logs_.begin(), active_logs_.end(),
+ [browser_context_id](const decltype(active_logs_)::value_type& log) {
+ return log.first.browser_context_id == browser_context_id;
+ });
+ const size_t pending_count = std::count_if(
+ pending_logs_.begin(), pending_logs_.end(),
+ [browser_context_id](const decltype(pending_logs_)::value_type& log) {
+ return log.browser_context_id == browser_context_id;
+ });
+ return active_count + pending_count < kMaxPendingRemoteBoundWebRtcEventLogs;
+}
+
+bool WebRtcRemoteEventLogManager::UploadSuppressed() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return !upload_suppression_disabled_ && !active_peer_connections_.empty();
+}
+
+bool WebRtcRemoteEventLogManager::UploadConditionsHold() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return !uploader_ && !pending_logs_.empty() && !UploadSuppressed() &&
+ uploading_supported_for_connection_type_;
+}
+
+void WebRtcRemoteEventLogManager::ManageUploadSchedule() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ PrunePendingLogs(); // Avoid uploading freshly expired files.
+
+ if (!UploadConditionsHold()) {
+ time_when_upload_conditions_met_ = base::TimeTicks();
+ return;
+ }
+
+ if (!time_when_upload_conditions_met_.is_null()) {
+ // Conditions have been holding for a while; MaybeStartUploading() has
+ // already been scheduled when |time_when_upload_conditions_met_| was set.
+ return;
+ }
+
+ ++scheduled_upload_tasks_;
+
+ time_when_upload_conditions_met_ = base::TimeTicks::Now();
+
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcRemoteEventLogManager::MaybeStartUploading,
+ weak_ptr_factory_->GetWeakPtr()),
+ upload_delay_);
+}
+
+void WebRtcRemoteEventLogManager::MaybeStartUploading() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_GT(scheduled_upload_tasks_, 0u);
+
+ // Since MaybeStartUploading() was scheduled, conditions might have stopped
+ // holding at some point. They may have even stopped and started several times
+ // while the currently running task was scheduled, meaning several tasks could
+ // be pending now, only the last of which should really end up uploading.
+
+ if (time_when_upload_conditions_met_.is_null()) {
+ // Conditions no longer hold; no way to know how many (now irrelevant) other
+ // similar tasks are pending, if any.
+ } else if (base::TimeTicks::Now() - time_when_upload_conditions_met_ <
+ upload_delay_) {
+ // Conditions have stopped holding, then started holding again; there has
+ // to be a more recent task scheduled, that will take over later.
+ DCHECK_GT(scheduled_upload_tasks_, 1u);
+ } else {
+ // It's up to the rest of the code to turn |scheduled_upload_tasks_| off
+ // if the conditions have at some point stopped holding, or it wouldn't
+ // know to turn it on when they resume.
+ DCHECK(UploadConditionsHold());
+
+ // When the upload we're about to start finishes, there will be another
+ // delay of length |upload_delay_| before the next one starts.
+ time_when_upload_conditions_met_ = base::TimeTicks();
+
+ auto callback = base::BindOnce(
+ &WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete,
+ weak_ptr_factory_->GetWeakPtr());
+
+ // The uploader takes ownership of the file; it's no longer considered to be
+ // pending. (If the upload fails, the log will be deleted.)
+ // TODO(crbug.com/775415): Add more refined retry behavior, so that we would
+ // not delete the log permanently if the network is just down, on the one
+ // hand, but also would not be uploading unlimited data on endless retries
+ // on the other hand.
+ // TODO(crbug.com/775415): Rename the file before uploading, so that we
+ // would not retry the upload after restarting Chrome, if the upload is
+ // interrupted.
+ uploader_ =
+ uploader_factory_->Create(*pending_logs_.begin(), std::move(callback));
+ pending_logs_.erase(pending_logs_.begin());
+ }
+
+ --scheduled_upload_tasks_;
+}
+
+void WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete(
+ const base::FilePath& log_file,
+ bool upload_successful) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(uploader_);
+ uploader_.reset();
+ ManageUploadSchedule();
+}
+
+bool WebRtcRemoteEventLogManager::FindPeerConnection(
+ int render_process_id,
+ const std::string& session_id,
+ PeerConnectionKey* key) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!session_id.empty());
+
+ const auto it = FindNextPeerConnection(active_peer_connections_.cbegin(),
+ render_process_id, session_id);
+ if (it == active_peer_connections_.cend()) {
+ return false;
+ }
+
+ // Make sure that the session ID is unique for the renderer process,
+ // though not necessarily between renderer processes.
+ // (The helper exists solely to allow this DCHECK.)
+ DCHECK(FindNextPeerConnection(std::next(it), render_process_id, session_id) ==
+ active_peer_connections_.cend());
+
+ *key = it->first;
+ return true;
+}
+
+WebRtcRemoteEventLogManager::PeerConnectionMap::const_iterator
+WebRtcRemoteEventLogManager::FindNextPeerConnection(
+ PeerConnectionMap::const_iterator begin,
+ int render_process_id,
+ const std::string& session_id) const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!session_id.empty());
+ const auto end = active_peer_connections_.cend();
+ for (auto it = begin; it != end; ++it) {
+ if (it->first.render_process_id == render_process_id &&
+ it->second == session_id) {
+ return it;
+ }
+ }
+ return end;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
new file mode 100644
index 00000000000..ff1f93a93e6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
@@ -0,0 +1,487 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+#include "components/upload_list/upload_list.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+namespace webrtc_event_logging {
+
+class WebRtcRemoteEventLogManager final
+ : public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+ using LogFilesMap =
+ std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
+ using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+
+ public:
+ WebRtcRemoteEventLogManager(
+ WebRtcRemoteEventLogsObserver* observer,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ ~WebRtcRemoteEventLogManager() override;
+
+ // Sets a network::NetworkConnectionTracker which will be used to track
+ // network connectivity.
+ // Must not be called more than once.
+ // Must be called before any call to EnableForBrowserContext().
+ void SetNetworkConnectionTracker(
+ network::NetworkConnectionTracker* network_connection_tracker);
+
+ // Sets a LogFileWriter factory.
+ // Must not be called more than once.
+ // Must be called before any call to EnableForBrowserContext().
+ void SetLogFileWriterFactory(
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
+
+ // Enables remote-bound logging for a given BrowserContext. Logs stored during
+ // previous sessions become eligible for upload, and recording of new logs for
+ // peer connections associated with this BrowserContext, in the
+ // BrowserContext's user-data directory, becomes possible.
+ // This method would typically be called when a BrowserContext is initialized.
+ // Enabling for the same BrowserContext twice in a row, without disabling
+ // in between, is an error.
+ void EnableForBrowserContext(BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir);
+
+ // Disables remote-bound logging for a given BrowserContext. Pending logs from
+ // earlier (while it was enabled) may no longer be uploaded, additional
+ // logs will not be created, and any active uploads associated with the
+ // BrowserContext will be cancelled.
+ // Disabling for a BrowserContext which was not enabled is not an error,
+ // because the caller is not required to know whether a previous call
+ // to EnableForBrowserContext() was successful.
+ void DisableForBrowserContext(BrowserContextId browser_context_id);
+
+ // Called to inform |this| of peer connections being added/removed.
+ // This information is used to:
+ // 1. Make decisions about when to upload previously finished logs.
+ // 2. When a peer connection is removed, if it was being logged, its log
+ // changes from ACTIVE to PENDING.
+ // The return value of both methods indicates only the consistency of the
+ // information with previously received information (e.g. can't remove a
+ // peer connection that was never added, etc.).
+ bool PeerConnectionAdded(const PeerConnectionKey& key);
+ bool PeerConnectionRemoved(const PeerConnectionKey& key);
+
+ // Called to inform |this| that a peer connection has been associated
+ // with |session_id|. After this, it is possible to refer to that peer
+ // connection using StartRemoteLogging() by providing |session_id|.
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key,
+ const std::string& session_id);
+
+ // Attempt to start logging the WebRTC events of an active peer connection.
+ // Logging is subject to several restrictions:
+ // 1. May not log more than kMaxNumberActiveRemoteWebRtcEventLogFiles logs
+ // at the same time.
+ // 2. Each browser context may have only kMaxPendingLogFilesPerBrowserContext
+ // pending logs. Since active logs later become pending logs, it is also
+ // forbidden to start a remote-bound log that would, once completed, become
+ // a pending log that would exceed that limit.
+ // 3. The maximum file size must be sensible.
+ //
+ // If all of the restrictions were observed, and if a file was successfully
+ // created, true will be returned.
+ //
+ // If the call succeeds, the log's identifier will be written to |log_id|.
+ // The log identifier is exactly 32 uppercase ASCII characters from the
+ // ranges 0-9 and A-F.
+ //
+ // The log's filename will also incorporate |web_app_id|.
+ // |web_app_id| must be between 1 and 99 (inclusive); error otherwise.
+ //
+ // If the call fails, an error message is written to |error_message|.
+ // The error message will be specific to the failure (as opposed to a generic
+ // one) is produced only if that error message is useful for the caller:
+ // * Bad parameters.
+ // * Function called at a time when the caller could know it would fail,
+ // such as for a peer connection that was already logged.
+ // We intentionally avoid giving specific errors in some cases, so as
+ // to avoid leaking information such as having too many active and/or
+ // pending logs.
+ bool StartRemoteLogging(int render_process_id,
+ BrowserContextId browser_context_id,
+ const std::string& session_id,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id,
+ std::string* error_message);
+
+ // If an active remote-bound log exists for the given peer connection, this
+ // will append |message| to that log.
+ // If writing |message| to the log would exceed the log's maximum allowed
+ // size, the write is disallowed and the file is closed instead (and changes
+ // from ACTIVE to PENDING).
+ // If the log file's capacity is exhausted as a result of this function call,
+ // or if a write error occurs, the file is closed, and the remote-bound log
+ // changes from ACTIVE to PENDING.
+ // True is returned if and only if |message| was written in its entirety to
+ // an active log.
+ bool EventLogWrite(const PeerConnectionKey& key, const std::string& message);
+
+ // Clear PENDING WebRTC event logs associated with a given browser context,
+ // in a given time range, then post |reply| back to the thread from which
+ // the method was originally invoked (which can be any thread).
+ // Log files currently being written are *not* interrupted.
+ // Active uploads *are* interrupted.
+ void ClearCacheForBrowserContext(BrowserContextId browser_context_id,
+ const base::Time& delete_begin,
+ const base::Time& delete_end);
+
+ // See documentation of same method in WebRtcEventLogManager for details.
+ void GetHistory(
+ BrowserContextId browser_context_id,
+ base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
+ reply);
+
+ // Works on not-enabled BrowserContext-s, which means the logs are never made
+ // eligible for upload. Useful when a BrowserContext is loaded which in
+ // the past had remote-logging enabled, but no longer does.
+ void RemovePendingLogsForNotEnabledBrowserContext(
+ BrowserContextId browser_context_id,
+ const base::FilePath& browser_context_dir);
+
+ // An implicit PeerConnectionRemoved() on all of the peer connections that
+ // were associated with the renderer process.
+ void RenderProcessHostExitedDestroyed(int render_process_id);
+
+ // network::NetworkConnectionTracker::NetworkConnectionObserver implementation
+ void OnConnectionChanged(network::mojom::ConnectionType type) override;
+
+ // Unit tests may use this to inject null uploaders, or ones which are
+ // directly controlled by the unit test (succeed or fail according to the
+ // test's needs).
+ // Note that for simplicity's sake, this may be called from outside the
+ // task queue on which this object lives (WebRtcEventLogManager::task_queue_).
+ // Therefore, if a test calls this, it should call it before it initializes
+ // any BrowserContext with pending log files in its directory.
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory);
+
+ // Exposes UploadConditionsHold() to unit tests. See WebRtcEventLogManager's
+ // documentation for the rationale.
+ void UploadConditionsHoldForTesting(base::OnceCallback<void(bool)> callback);
+
+ // In production code, |task_runner_| stops running tasks as part of Chrome's
+ // shut-down process, before |this| is torn down. In unit tests, this is
+ // not the case.
+ void ShutDownForTesting(base::OnceClosure reply);
+
+ private:
+ using PeerConnectionMap = std::map<PeerConnectionKey, std::string>;
+
+ // Validates log parameters.
+ // If valid, returns true. Otherwise, false, and |error_message| gets
+ // a relevant error.
+ bool AreLogParametersValid(size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* error_message) const;
+
+ // Checks whether a browser context has already been enabled via a call to
+ // EnableForBrowserContext(), and not yet disabled using a call to
+ // DisableForBrowserContext().
+ bool BrowserContextEnabled(BrowserContextId browser_context_id) const;
+
+ // Closes an active log file.
+ // If |make_pending| is true, closing the file changes its state from ACTIVE
+ // to PENDING. If |make_pending| is false, or if the file couldn't be closed
+ // correctly, the file will be deleted.
+ // Returns an iterator to the next ACTIVE file.
+ LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it,
+ bool make_pending);
+
+ // Attempts to create the directory where we'll write the logs, if it does
+ // not already exist. Returns true if the directory exists (either it already
+ // existed, or it was successfully created).
+ bool MaybeCreateLogsDirectory(const base::FilePath& remote_bound_logs_dir);
+
+ // Scans the user data directory associated with the BrowserContext
+ // associated with the given BrowserContextId remote-bound logs that were
+ // created during previous Chrome sessions and for history files,
+ // then process them (discard expired files, etc.)
+ void LoadLogsDirectory(BrowserContextId browser_context_id,
+ const base::FilePath& remote_bound_logs_dir);
+
+ // Loads the pending log file whose path is |path|, into the BrowserContext
+ // indicated by |browser_context_id|. Note that the contents of the file are
+ // note read by this method.
+ // Returns true if the file was loaded correctly, and should be kept on disk;
+ // false if the file was not loaded (e.g. incomplete or expired), and needs
+ // to be deleted.
+ bool LoadPendingLogInfo(BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ base::Time last_modified);
+
+ // Loads a history file. Returns a WebRtcEventLogHistoryFileReader if the
+ // file was loaded correctly, and should be kept on disk; nullptr otherwise,
+ // signaling that the file should be deleted.
+ // |prune_begin| and |prune_end| define a time range where, if the log falls
+ // within the range, it will not be loaded.
+ std::unique_ptr<WebRtcEventLogHistoryFileReader> LoadHistoryFile(
+ BrowserContextId browser_context_id,
+ const base::FilePath& path,
+ const base::Time& prune_begin,
+ const base::Time& prune_end);
+
+ // Deletes any history logs associated with |browser_context_id| captured or
+ // uploaded between |prune_begin| and |prune_end|, inclusive, then returns a
+ // set of readers for the remaining (meaning not-pruned) history files.
+ std::set<WebRtcEventLogHistoryFileReader>
+ PruneAndLoadHistoryFilesForBrowserContext(
+ const base::Time& prune_begin,
+ const base::Time& prune_end,
+ BrowserContextId browser_context_id);
+
+ // Attempts the creation of a locally stored file into which a remote-bound
+ // log may be written. The log-identifier is returned if successful, the empty
+ // string otherwise.
+ bool StartWritingLog(const PeerConnectionKey& key,
+ const base::FilePath& browser_context_dir,
+ size_t max_file_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_out,
+ std::string* error_message_out);
+
+ // Checks if the referenced peer connection has an associated active
+ // remote-bound log. If it does, the log is changed from ACTIVE to PENDING.
+ void MaybeStopRemoteLogging(const PeerConnectionKey& key);
+
+ // Get rid of pending logs whose age exceeds our retention policy.
+ // On the one hand, we want to remove expired files as soon as possible, but
+ // on the other hand, we don't want to waste CPU by checking this too often.
+ // Therefore, we prune pending files:
+ // 1. When a new BrowserContext is initalized, thereby also pruning the
+ // pending logs contributed by that BrowserContext.
+ // 2. Before initiating a new upload, thereby avoiding uploading a file that
+ // has just now expired.
+ // 3. On infrequent events - peer connection addition/removal, but NOT
+ // on something that could potentially be frequent, such as EventLogWrite.
+ // Note that the last modification date of a file, which is the value measured
+ // against for retention, is only read from disk once per file, meaning
+ // this check is not too expensive.
+ // If a |browser_context_id| is provided, logs are only pruned for it.
+ void PrunePendingLogs(
+ base::Optional<BrowserContextId> browser_context_id = base::nullopt);
+
+ // PrunePendingLogs() and schedule the next proactive pending logs prune.
+ void RecurringlyPrunePendingLogs();
+
+ // Removes expired history files.
+ // Since these are small, and since looking for them is not as cheap as
+ // looking for pending logs, we do not make an effort to remove them as
+ // soon as possible.
+ void PruneHistoryFiles();
+
+ // PruneHistoryFiles() and schedule the next proactive history files prune.
+ void RecurringlyPruneHistoryFiles();
+
+ // Cancels and deletes active logs which match the given filter criteria, as
+ // described by MatchesFilter's documentation.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeCancelActiveLogs(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // Removes pending logs files which match the given filter criteria, as
+ // described by MatchesFilter's documentation.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeRemovePendingLogs(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ base::Optional<BrowserContextId> browser_context_id,
+ bool is_cache_clear);
+
+ // Remove all history files associated with |browser_context_id| which were
+ // either captured or uploaded between |delete_begin| and |delete_end|.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeRemoveHistoryFiles(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // If the currently uploaded file matches the given filter criteria, as
+ // described by MatchesFilter's documentation, the upload will be
+ // cancelled, and the log file deleted. If this happens, the next pending log
+ // file will be considered for upload.
+ // This method is used to ensure that clearing of browsing data by the user
+ // does not leave the currently-uploaded file on disk, even for the duration
+ // of the upload.
+ // This method not trigger any pending logs to be uploaded, allowing it to
+ // be safely used in a context that clears browsing data.
+ void MaybeCancelUpload(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ BrowserContextId browser_context_id);
+
+ // Checks whether a log file matches a range and (potentially) BrowserContext:
+ // * A file matches if its last modification date was at or later than
+ // |filter_range_begin|, and earlier than |filter_range_end|.
+ // * If a null time-point is given as either |filter_range_begin| or
+ // |filter_range_end|, it is treated as "beginning-of-time" or
+ // "end-of-time", respectively.
+ // * If |filter_browser_context_id| is set, only log files associated with it
+ // can match the filter.
+ bool MatchesFilter(BrowserContextId log_browser_context_id,
+ const base::Time& log_last_modification,
+ base::Optional<BrowserContextId> filter_browser_context_id,
+ const base::Time& filter_range_begin,
+ const base::Time& filter_range_end) const;
+
+ // Return |true| if and only if we can start another active log (with respect
+ // to limitations on the numbers active and pending logs).
+ bool AdditionalActiveLogAllowed(BrowserContextId browser_context_id) const;
+
+ // Uploading suppressed while active peer connections exist (unless
+ // suppression) is turned off from the command line.
+ bool UploadSuppressed() const;
+
+ // Check whether all the conditions necessary for uploading log files are
+ // currently satisfied.
+ // 1. There may be no active peer connections which might be adversely
+ // affected by the bandwidth consumption of the upload.
+ // 2. Chrome has a network connection, and that conneciton is either a wired
+ // one, or WiFi. (That is, not 3G, etc.)
+ // 3. Naturally, a file pending upload must exist.
+ bool UploadConditionsHold() const;
+
+ // When the conditions necessary for uploading first hold, schedule a delayed
+ // task to upload (MaybeStartUploading). If they ever stop holding, void it.
+ void ManageUploadSchedule();
+
+ // Posted as a delayed task by ManageUploadSchedule. If not voided until
+ // executed, will initiate an upload of the next log file.
+ void MaybeStartUploading();
+
+ // Callback for the success/failure of an upload.
+ // When an upload is complete, it might be time to upload the next file.
+ // Note: |log_file| and |upload_successful| are ignored in production; they
+ // are used in unit tests, so we keep them here to make things simpler, so
+ // that this method would match WebRtcEventLogUploader::UploadResultCallback
+ // without adaptation.
+ void OnWebRtcEventLogUploadComplete(const base::FilePath& log_file,
+ bool upload_successful);
+
+ // Given a renderer process ID and peer connection's session ID, find the
+ // peer connection to which they refer.
+ bool FindPeerConnection(int render_process_id,
+ const std::string& session_id,
+ PeerConnectionKey* key) const;
+
+ // Find the next peer connection in a map to which the renderer process ID
+ // and session ID refer.
+ // This helper allows FindPeerConnection() to DCHECK on uniqueness of the ID
+ // without descending down a recursive rabbit hole.
+ PeerConnectionMap::const_iterator FindNextPeerConnection(
+ PeerConnectionMap::const_iterator begin,
+ int render_process_id,
+ const std::string& session_id) const;
+
+ // Normally, uploading is suppressed while there are active peer connections.
+ // This may be disabled from the command line.
+ const bool upload_suppression_disabled_;
+
+ // The conditions for upload must hold for this much time, uninterrupted,
+ // before an upload may be initiated.
+ const base::TimeDelta upload_delay_;
+
+ // If non-zero, every |proactive_pending_logs_prune_delta_|, pending logs
+ // will be pruned. This avoids them staying around on disk for longer than
+ // their expiration if no event occurs which triggers reactive pruning.
+ const base::TimeDelta proactive_pending_logs_prune_delta_;
+
+ // Proactive pruning, if enabled, starts with the first enabled browser
+ // context. To avoid unnecessary complexity, if that browser context is
+ // disabled, proactive pruning is not disabled.
+ bool proactive_prune_scheduling_started_;
+
+ // This is used to inform WebRtcEventLogManager when remote-bound logging
+ // of a peer connection starts/stops, which allows WebRtcEventLogManager to
+ // decide when to ask WebRTC to start/stop sending event logs.
+ WebRtcRemoteEventLogsObserver* const observer_;
+
+ // The IDs of the BrowserContexts for which logging is enabled, mapped to
+ // the directory where each BrowserContext's remote-bound logs are stored.
+ std::map<BrowserContextId, base::FilePath> enabled_browser_contexts_;
+
+ // Currently active peer connections, mapped to their session IDs (once the
+ // session ID is set).
+ // PeerConnections which have been closed are not considered active,
+ // regardless of whether they have been torn down.
+ PeerConnectionMap active_peer_connections_;
+
+ // Creates LogFileWriter instances (compressed/uncompressed, etc.).
+ std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+
+ // Remote-bound logs which we're currently in the process of writing to disk.
+ LogFilesMap active_logs_;
+
+ // Remote-bound logs which have been written to disk before (either during
+ // this Chrome session or during an earlier one), and which are no waiting to
+ // be uploaded.
+ std::set<WebRtcLogFileInfo> pending_logs_;
+
+ // Null if no ongoing upload, or an uploader which owns a file, and is
+ // currently busy uploading it to a remote server.
+ std::unique_ptr<WebRtcEventLogUploader> uploader_;
+
+ // Provides notifications of network changes.
+ network::NetworkConnectionTracker* network_connection_tracker_;
+
+ // Whether the network we are currently connected to, if any, is one over
+ // which we may upload.
+ bool uploading_supported_for_connection_type_;
+
+ // If the conditions for initiating an upload do not hold, this will be
+ // set to an empty base::TimeTicks.
+ // If the conditions were found to hold, this will record the time when they
+ // started holding. (It will be set back to 0 if they ever cease holding.)
+ base::TimeTicks time_when_upload_conditions_met_;
+
+ // This is a vehicle for DCHECKs to ensure code sanity. It counts the number
+ // of scheduled tasks of MaybeStartUploading(), and proves that we never
+ // end up with a scheduled upload that never occurs.
+ size_t scheduled_upload_tasks_;
+
+ // Producer of uploader objects. (In unit tests, this would create
+ // null-implementation uploaders, or uploaders whose behavior is controlled
+ // by the unit test.)
+ std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory_;
+
+ // |this| is created and destroyed on the UI thread, but operates on the
+ // following IO-capable sequenced task runner.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Weak pointer factory. Only expected to be useful for unit tests, because
+ // in production, |task_runner_| is stopped during shut-down, so tasks will
+ // either find the pointer to be valid, or not run because the runner has
+ // already been stopped.
+ // Note that the unique_ptr is used just to make it clearer that ownership is
+ // here. In reality, this is never auto-destroyed; see destructor for details.
+ std::unique_ptr<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>
+ weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRemoteEventLogManager);
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_REMOTE_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
new file mode 100644
index 00000000000..e887b44737b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
@@ -0,0 +1,4934 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+
+#include <algorithm>
+#include <list>
+#include <map>
+#include <memory>
+#include <numeric>
+#include <queue>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_command_line.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_clock.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/testing_pref_store.h"
+#include "components/sync_preferences/pref_service_mock_factory.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#endif
+
+namespace webrtc_event_logging {
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToString16
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+
+using BrowserContext = content::BrowserContext;
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+using MockRenderProcessHost = content::MockRenderProcessHost;
+using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
+using RenderProcessHost = content::RenderProcessHost;
+
+using Compression = WebRtcEventLogCompression;
+
+namespace {
+
+#if !defined(OS_ANDROID)
+
+auto SaveFilePathTo(base::Optional<base::FilePath>* output) {
+ return [output](PeerConnectionKey ignored_key, base::FilePath file_path,
+ int output_period_ms = 0) { *output = file_path; };
+}
+
+auto SaveKeyAndFilePathTo(base::Optional<PeerConnectionKey>* key_output,
+ base::Optional<base::FilePath>* file_path_output) {
+ return [key_output, file_path_output](PeerConnectionKey key,
+ base::FilePath file_path) {
+ *key_output = key;
+ *file_path_output = file_path;
+ };
+}
+
+const int kMaxActiveRemoteLogFiles =
+ static_cast<int>(kMaxActiveRemoteBoundWebRtcEventLogs);
+const int kMaxPendingRemoteLogFiles =
+ static_cast<int>(kMaxPendingRemoteBoundWebRtcEventLogs);
+const char kSessionId[] = "session_id";
+
+base::Time GetLastModificationTime(const base::FilePath& file_path) {
+ base::File::Info file_info;
+ if (!base::GetFileInfo(file_path, &file_info)) {
+ EXPECT_TRUE(false);
+ return base::Time();
+ }
+ return file_info.last_modified;
+}
+
+#endif
+
+// Common default/arbitrary values.
+constexpr int kLid = 478;
+constexpr size_t kWebAppId = 42;
+
+PeerConnectionKey GetPeerConnectionKey(RenderProcessHost* rph, int lid) {
+ const BrowserContext* browser_context = rph->GetBrowserContext();
+ const auto browser_context_id = GetBrowserContextId(browser_context);
+ return PeerConnectionKey(rph->GetID(), lid, browser_context_id);
+}
+
+bool CreateRemoteBoundLogFile(const base::FilePath& dir,
+ size_t web_app_id,
+ const base::FilePath::StringPieceType& extension,
+ base::Time capture_time,
+ base::FilePath* file_path,
+ base::File* file) {
+ *file_path =
+ dir.AsEndingWithSeparator()
+ .InsertBeforeExtensionASCII(kRemoteBoundWebRtcEventLogFileNamePrefix)
+ .InsertBeforeExtensionASCII("_")
+ .InsertBeforeExtensionASCII(std::to_string(web_app_id))
+ .InsertBeforeExtensionASCII("_")
+ .InsertBeforeExtensionASCII(CreateWebRtcEventLogId())
+ .AddExtension(extension);
+
+ constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE;
+ file->Initialize(*file_path, file_flags);
+ if (!file->IsValid() || !file->created()) {
+ return false;
+ }
+
+ if (!base::TouchFile(*file_path, capture_time, capture_time)) {
+ return false;
+ }
+
+ return true;
+}
+
+// This implementation does not upload files, nor pretends to have finished an
+// upload. Most importantly, it does not get rid of the locally-stored log file
+// after finishing a simulated upload; this is useful because it keeps the file
+// on disk, where unit tests may inspect it.
+// This class enforces an expectation over the upload being cancelled or not.
+class NullWebRtcEventLogUploader : public WebRtcEventLogUploader {
+ public:
+ NullWebRtcEventLogUploader(const WebRtcLogFileInfo& log_file,
+ bool cancellation_expected)
+ : log_file_(log_file),
+ cancellation_expected_(cancellation_expected),
+ was_cancelled_(false) {}
+
+ ~NullWebRtcEventLogUploader() override {
+ EXPECT_EQ(was_cancelled_, cancellation_expected_);
+ }
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override {
+ return log_file_;
+ }
+
+ bool Cancel() override {
+ EXPECT_TRUE(cancellation_expected_);
+ if (was_cancelled_) { // Should not be called more than once.
+ EXPECT_TRUE(false);
+ return false;
+ }
+ was_cancelled_ = true;
+ return true;
+ }
+
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ Factory(bool cancellation_expected,
+ base::Optional<size_t> expected_instance_count = base::nullopt)
+ : cancellation_expected_(cancellation_expected),
+ expected_instance_count_(expected_instance_count),
+ instance_count_(0) {}
+
+ ~Factory() override {
+ if (expected_instance_count_.has_value()) {
+ EXPECT_EQ(instance_count_, expected_instance_count_.value());
+ }
+ }
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override {
+ if (expected_instance_count_.has_value()) {
+ EXPECT_LE(++instance_count_, expected_instance_count_.value());
+ }
+ return std::make_unique<NullWebRtcEventLogUploader>(
+ log_file, cancellation_expected_);
+ }
+
+ private:
+ const bool cancellation_expected_;
+ const base::Optional<size_t> expected_instance_count_;
+ size_t instance_count_;
+ };
+
+ private:
+ const WebRtcLogFileInfo log_file_;
+ const bool cancellation_expected_;
+ bool was_cancelled_;
+};
+
+class MockWebRtcLocalEventLogsObserver : public WebRtcLocalEventLogsObserver {
+ public:
+ ~MockWebRtcLocalEventLogsObserver() override = default;
+ MOCK_METHOD2(OnLocalLogStarted,
+ void(PeerConnectionKey, const base::FilePath&));
+ MOCK_METHOD1(OnLocalLogStopped, void(PeerConnectionKey));
+};
+
+class MockWebRtcRemoteEventLogsObserver : public WebRtcRemoteEventLogsObserver {
+ public:
+ ~MockWebRtcRemoteEventLogsObserver() override = default;
+ MOCK_METHOD3(OnRemoteLogStarted,
+ void(PeerConnectionKey, const base::FilePath&, int));
+ MOCK_METHOD1(OnRemoteLogStopped, void(PeerConnectionKey));
+};
+
+} // namespace
+
+class WebRtcEventLogManagerTestBase : public ::testing::Test {
+ public:
+ WebRtcEventLogManagerTestBase()
+ : test_shared_url_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ run_loop_(std::make_unique<base::RunLoop>()),
+ uploader_run_loop_(std::make_unique<base::RunLoop>()),
+ browser_context_(nullptr),
+ browser_context_id_(GetBrowserContextId(browser_context_.get())) {
+ TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
+ test_shared_url_loader_factory_);
+
+ // Avoid proactive pruning; it has the potential to mess up tests, as well
+ // as keep pendings tasks around with a dangling reference to the unit
+ // under test. (Zero is a sentinel value for disabling proactive pruning.)
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogProactivePruningDelta, "0");
+
+ EXPECT_TRUE(local_logs_base_dir_.CreateUniqueTempDir());
+ local_logs_base_path_ = local_logs_base_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("local_event_logs"));
+
+ EXPECT_TRUE(profiles_dir_.CreateUniqueTempDir());
+ }
+
+ ~WebRtcEventLogManagerTestBase() override {
+ WaitForPendingTasks();
+
+ base::RunLoop run_loop;
+ event_log_manager_->ShutDownForTesting(run_loop.QuitClosure());
+ run_loop.Run();
+
+ // We do not want to satisfy any unsatisfied expectations by destroying
+ // |rph_|, |browser_context_|, etc., at the end of the test, before we
+ // destroy |event_log_manager_|. However, we must also make sure that their
+ // destructors do not attempt to access |event_log_manager_|, which in
+ // normal code lives forever, but not in the unit tests.
+ event_log_manager_.reset();
+
+ // Guard against unexpected state changes.
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+ }
+
+ void SetUp() override {
+ SetUpNetworkConnection(true,
+ network::mojom::ConnectionType::CONNECTION_ETHERNET);
+ SetLocalLogsObserver(&local_observer_);
+ SetRemoteLogsObserver(&remote_observer_);
+ LoadMainTestProfile();
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::BrowserPolicyConnectorBase::SetPolicyProviderForTesting(&provider_);
+#endif
+ }
+
+ void TearDown() override {
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ TestingBrowserProcess::GetGlobal()->ShutdownBrowserPolicyConnector();
+#endif
+ }
+
+ void SetUpNetworkConnection(bool respond_synchronously,
+ network::mojom::ConnectionType connection_type) {
+ auto* tracker = network::TestNetworkConnectionTracker::GetInstance();
+ tracker->SetRespondSynchronously(respond_synchronously);
+ tracker->SetConnectionType(connection_type);
+ }
+
+ void SetConnectionType(network::mojom::ConnectionType connection_type) {
+ network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+ connection_type);
+ }
+
+ void CreateWebRtcEventLogManager(
+ base::Optional<Compression> remote = base::nullopt) {
+ DCHECK(!event_log_manager_);
+
+ event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+
+ local_log_extension_ = kWebRtcEventLogUncompressedExtension;
+
+ if (remote.has_value()) {
+ auto factory = CreateLogFileWriterFactory(remote.value());
+ remote_log_extension_ = factory->Extension();
+ event_log_manager_->SetRemoteLogFileWriterFactoryForTesting(
+ std::move(factory));
+ } else {
+ // kWebRtcRemoteEventLogGzipped is turned on by default.
+ remote_log_extension_ = kWebRtcEventLogGzippedExtension;
+ }
+ }
+
+ void LoadMainTestProfile() {
+ browser_context_ = CreateBrowserContext("browser_context_");
+ browser_context_id_ = GetBrowserContextId(browser_context_.get());
+ rph_ = std::make_unique<MockRenderProcessHost>(browser_context_.get());
+ }
+
+ void UnloadMainTestProfile() {
+ rph_.reset();
+ browser_context_.reset();
+ browser_context_id_ = GetBrowserContextId(browser_context_.get());
+ }
+
+ void WaitForReply() {
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop); // Allow re-blocking.
+ }
+
+ void Reply() { run_loop_->QuitWhenIdle(); }
+
+ base::OnceClosure ReplyClosure() {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)() =
+ &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this));
+ }
+
+ template <typename T>
+ void Reply(T* output, T val) {
+ *output = val;
+ run_loop_->QuitWhenIdle();
+ }
+
+ template <typename T>
+ base::OnceCallback<void(T)> ReplyClosure(T* output) {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)(T*, T) =
+ &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this), output);
+ }
+
+ void Reply(bool* output_bool,
+ std::string* output_str1,
+ std::string* output_str2,
+ bool bool_val,
+ const std::string& str1_val,
+ const std::string& str2_val) {
+ *output_bool = bool_val;
+ *output_str1 = str1_val;
+ *output_str2 = str2_val;
+ run_loop_->QuitWhenIdle();
+ }
+
+ base::OnceCallback<void(bool, const std::string&, const std::string&)>
+ ReplyClosure(bool* output_bool,
+ std::string* output_str1,
+ std::string* output_str2) {
+ // Intermediary pointer used to help the compiler distinguish between
+ // the overloaded Reply() functions.
+ void (WebRtcEventLogManagerTestBase::*function)(
+ bool*, std::string*, std::string*, bool, const std::string&,
+ const std::string&) = &WebRtcEventLogManagerTestBase::Reply;
+ return base::BindOnce(function, base::Unretained(this), output_bool,
+ output_str1, output_str2);
+ }
+
+ bool PeerConnectionAdded(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionAdded(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionRemoved(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionRemoved(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key,
+ const std::string& session_id) {
+ bool result;
+ event_log_manager_->PeerConnectionSessionIdSet(
+ key.render_process_id, key.lid, session_id, ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool PeerConnectionSessionIdSet(const PeerConnectionKey& key) {
+ return PeerConnectionSessionIdSet(key, GetUniqueId(key));
+ }
+
+ bool PeerConnectionStopped(const PeerConnectionKey& key) {
+ bool result;
+ event_log_manager_->PeerConnectionStopped(key.render_process_id, key.lid,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool EnableLocalLogging(
+ size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
+ return EnableLocalLogging(local_logs_base_path_, max_size_bytes);
+ }
+
+ bool EnableLocalLogging(
+ base::FilePath local_logs_base_path,
+ size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
+ bool result;
+ event_log_manager_->EnableLocalLogging(local_logs_base_path, max_size_bytes,
+ ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool DisableLocalLogging() {
+ bool result;
+ event_log_manager_->DisableLocalLogging(ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ const std::string& session_id,
+ size_t max_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ bool result;
+ std::string log_id;
+ std::string error_message;
+
+ event_log_manager_->StartRemoteLogging(
+ key.render_process_id, session_id, max_size_bytes, output_period_ms,
+ web_app_id, ReplyClosure(&result, &log_id, &error_message));
+
+ WaitForReply();
+
+ // If successful, only |log_id|. If unsuccessful, only |error_message| set.
+ DCHECK_EQ(result, !log_id.empty());
+ DCHECK_EQ(!result, !error_message.empty());
+
+ if (log_id_output) {
+ *log_id_output = log_id;
+ }
+
+ if (error_message_output) {
+ *error_message_output = error_message;
+ }
+
+ return result;
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ const std::string& session_id,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ return StartRemoteLogging(key, session_id, kMaxRemoteLogFileSizeBytes, 0,
+ kWebAppId, log_id_output, error_message_output);
+ }
+
+ bool StartRemoteLogging(const PeerConnectionKey& key,
+ std::string* log_id_output = nullptr,
+ std::string* error_message_output = nullptr) {
+ return StartRemoteLogging(key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes,
+ 0, kWebAppId, log_id_output,
+ error_message_output);
+ }
+
+ void ClearCacheForBrowserContext(
+ const content::BrowserContext* browser_context,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ event_log_manager_->ClearCacheForBrowserContext(
+ browser_context, delete_begin, delete_end, ReplyClosure());
+ WaitForReply();
+ }
+
+ std::vector<UploadList::UploadInfo> GetHistory(
+ BrowserContextId browser_context_id) {
+ std::vector<UploadList::UploadInfo> result;
+
+ base::RunLoop run_loop;
+
+ auto reply = [](base::RunLoop* run_loop,
+ std::vector<UploadList::UploadInfo>* output,
+ const std::vector<UploadList::UploadInfo>& input) {
+ *output = input;
+ run_loop->Quit();
+ };
+ event_log_manager_->GetHistory(browser_context_id,
+ base::BindOnce(reply, &run_loop, &result));
+ run_loop.Run();
+
+ return result;
+ }
+
+ void SetLocalLogsObserver(WebRtcLocalEventLogsObserver* observer) {
+ event_log_manager_->SetLocalLogsObserver(observer, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetRemoteLogsObserver(WebRtcRemoteEventLogsObserver* observer) {
+ event_log_manager_->SetRemoteLogsObserver(observer, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetWebRtcEventLogUploaderFactoryForTesting(
+ std::unique_ptr<WebRtcEventLogUploader::Factory> factory) {
+ event_log_manager_->SetWebRtcEventLogUploaderFactoryForTesting(
+ std::move(factory), ReplyClosure());
+ WaitForReply();
+ }
+
+ std::pair<bool, bool> OnWebRtcEventLogWrite(const PeerConnectionKey& key,
+ const std::string& message) {
+ std::pair<bool, bool> result;
+ event_log_manager_->OnWebRtcEventLogWrite(key.render_process_id, key.lid,
+ message, ReplyClosure(&result));
+ WaitForReply();
+ return result;
+ }
+
+ void FreezeClockAt(const base::Time::Exploded& frozen_time_exploded) {
+ base::Time frozen_time;
+ ASSERT_TRUE(
+ base::Time::FromLocalExploded(frozen_time_exploded, &frozen_time));
+ frozen_clock_.SetNow(frozen_time);
+ event_log_manager_->SetClockForTesting(&frozen_clock_, ReplyClosure());
+ WaitForReply();
+ }
+
+ void SetWebRtcEventLoggingState(const PeerConnectionKey& key,
+ bool event_logging_enabled) {
+ webrtc_state_change_instructions_.emplace(key, event_logging_enabled);
+ }
+
+ void ExpectWebRtcStateChangeInstruction(const PeerConnectionKey& key,
+ bool enabled) {
+ ASSERT_FALSE(webrtc_state_change_instructions_.empty());
+ auto& instruction = webrtc_state_change_instructions_.front();
+ EXPECT_EQ(instruction.key.render_process_id, key.render_process_id);
+ EXPECT_EQ(instruction.key.lid, key.lid);
+ EXPECT_EQ(instruction.enabled, enabled);
+ webrtc_state_change_instructions_.pop();
+ }
+
+ void SetPeerConnectionTrackerProxyForTesting(
+ std::unique_ptr<WebRtcEventLogManager::PeerConnectionTrackerProxy>
+ pc_tracker_proxy) {
+ event_log_manager_->SetPeerConnectionTrackerProxyForTesting(
+ std::move(pc_tracker_proxy), ReplyClosure());
+ WaitForReply();
+ }
+
+ // Allows either creating a TestingProfile with a predetermined name
+ // (useful when trying to "reload" a profile), or one with an arbitrary name.
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext() {
+ return CreateBrowserContext(std::string());
+ }
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name) {
+ return CreateBrowserContext(profile_name, true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ true /* policy_allows_remote_logging */);
+ }
+ virtual std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name,
+ bool is_managed_profile,
+ bool has_device_level_policies,
+ base::Optional<bool> policy_allows_remote_logging) {
+ // If profile name not specified, select a unique name.
+ if (profile_name.empty()) {
+ static size_t index = 0;
+ profile_name = std::to_string(++index);
+ }
+
+ // Set a directory for the profile, derived from its name, so that
+ // recreating the profile will get the same directory.
+ const base::FilePath profile_path =
+ profiles_dir_.GetPath().AppendASCII(profile_name);
+ if (base::PathExists(profile_path)) {
+ EXPECT_TRUE(base::DirectoryExists(profile_path));
+ } else {
+ EXPECT_TRUE(base::CreateDirectory(profile_path));
+ }
+
+ // Prepare to specify preferences for the profile.
+ sync_preferences::PrefServiceMockFactory factory;
+ factory.set_user_prefs(base::WrapRefCounted(new TestingPrefStore()));
+ scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
+ new user_prefs::PrefRegistrySyncable);
+ sync_preferences::PrefServiceSyncable* regular_prefs =
+ factory.CreateSyncable(registry.get()).release();
+
+ // Set the preference associated with the policy for WebRTC remote-bound
+ // event logging.
+ RegisterUserProfilePrefs(registry.get());
+ if (policy_allows_remote_logging.has_value()) {
+ regular_prefs->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ policy_allows_remote_logging.value());
+ }
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::PolicyMap policy_map;
+ if (has_device_level_policies) {
+ policy_map.Set("test-policy", policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_MACHINE,
+ policy::POLICY_SOURCE_PLATFORM,
+ std::make_unique<base::Value>("test"), nullptr);
+ }
+ provider_.UpdateChromePolicy(policy_map);
+#else
+ if (has_device_level_policies) {
+ // This should never happen.
+ // Device level policies cannot be set on Chrome OS and Android.
+ EXPECT_TRUE(false);
+ }
+#endif
+
+ // Build the profile.
+ TestingProfile::Builder profile_builder;
+ profile_builder.SetProfileName(profile_name);
+ profile_builder.SetPath(profile_path);
+ profile_builder.SetPrefService(base::WrapUnique(regular_prefs));
+ profile_builder.OverridePolicyConnectorIsManagedForTesting(
+ is_managed_profile);
+ std::unique_ptr<TestingProfile> profile = profile_builder.Build();
+
+ // Blocks on the unit under test's task runner, so that we won't proceed
+ // with the test (e.g. check that files were created) before finished
+ // processing this even (which is signaled to it from
+ // BrowserContext::EnableForBrowserContext).
+ WaitForPendingTasks();
+
+ return profile;
+ }
+
+ base::FilePath RemoteBoundLogsDir(BrowserContext* browser_context) const {
+ return RemoteBoundLogsDir(browser_context->GetPath());
+ }
+
+ base::FilePath RemoteBoundLogsDir(
+ const base::FilePath& browser_context_base_dir) const {
+ return GetRemoteBoundWebRtcEventLogsDir(browser_context_base_dir);
+ }
+
+ // Initiate an arbitrary synchronous operation, allowing any tasks pending
+ // on the manager's internal task queue to be completed.
+ // If given a RunLoop, we first block on it. The reason to do both is that
+ // with the RunLoop we wait on some tasks which we know also post additional
+ // tasks, then, after that chain is completed, we also wait for any potential
+ // leftovers. For example, the run loop could wait for n-1 files to be
+ // uploaded, then it is released when the last one's upload is initiated,
+ // then we wait for the last file's upload to be completed.
+ void WaitForPendingTasks(base::RunLoop* run_loop = nullptr) {
+ if (run_loop) {
+ run_loop->Run();
+ }
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ event_log_manager_->GetTaskRunnerForTesting()->PostTask(
+ FROM_HERE,
+ base::BindOnce([](base::WaitableEvent* event) { event->Signal(); },
+ &event));
+ event.Wait();
+ }
+
+ void SuppressUploading() {
+ if (!upload_suppressing_browser_context_) { // First suppression.
+ upload_suppressing_browser_context_ = CreateBrowserContext();
+ }
+ DCHECK(!upload_suppressing_rph_) << "Uploading already suppressed.";
+ upload_suppressing_rph_ = std::make_unique<MockRenderProcessHost>(
+ upload_suppressing_browser_context_.get());
+ const auto key = GetPeerConnectionKey(upload_suppressing_rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ void UnsuppressUploading() {
+ DCHECK(upload_suppressing_rph_) << "Uploading not suppressed.";
+ const auto key = GetPeerConnectionKey(upload_suppressing_rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ upload_suppressing_rph_.reset();
+ }
+
+ void ExpectLocalFileContents(const base::FilePath& file_path,
+ const std::string& expected_contents) {
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+ EXPECT_EQ(file_contents, expected_contents);
+ }
+
+ void ExpectRemoteFileContents(const base::FilePath& file_path,
+ const std::string& expected_event_log) {
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+
+ if (remote_log_extension_ == kWebRtcEventLogUncompressedExtension) {
+ EXPECT_EQ(file_contents, expected_event_log);
+ } else if (remote_log_extension_ == kWebRtcEventLogGzippedExtension) {
+ std::string uncompressed_log;
+ ASSERT_TRUE(
+ compression::GzipUncompress(file_contents, &uncompressed_log));
+ EXPECT_EQ(uncompressed_log, expected_event_log);
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ // When the peer connection's ID is not the focus of the test, this allows
+ // us to conveniently assign unique IDs to peer connections.
+ std::string GetUniqueId(int render_process_id, int lid) {
+ return std::to_string(render_process_id) + "_" + std::to_string(lid);
+ }
+ std::string GetUniqueId(const PeerConnectionKey& key) {
+ return GetUniqueId(key.render_process_id, key.lid);
+ }
+
+ bool UploadConditionsHold() {
+ base::RunLoop run_loop;
+ bool result;
+
+ auto callback = [](base::RunLoop* run_loop, bool* result_out, bool result) {
+ *result_out = result;
+ run_loop->QuitWhenIdle();
+ };
+
+ event_log_manager_->UploadConditionsHoldForTesting(
+ base::BindOnce(callback, &run_loop, &result));
+ run_loop.Run();
+
+ return result;
+ }
+
+ // Testing utilities.
+ content::BrowserTaskEnvironment task_environment_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+ base::test::ScopedCommandLine scoped_command_line_;
+ base::SimpleTestClock frozen_clock_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_;
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+ policy::MockConfigurationPolicyProvider provider_;
+#endif
+
+ // The main loop, which allows waiting for the operations invoked on the
+ // unit-under-test to be completed. Do not use this object directly from the
+ // tests, since that would be error-prone. (Specifically, one must not produce
+ // two events that could produce replies, without waiting on the first reply
+ // in between.)
+ std::unique_ptr<base::RunLoop> run_loop_;
+
+ // Allows waiting for upload operations.
+ std::unique_ptr<base::RunLoop> uploader_run_loop_;
+
+ // Unit under test.
+ std::unique_ptr<WebRtcEventLogManager> event_log_manager_;
+
+ // Extensions associated with local/remote-bound event logs. Depends on
+ // whether they're compressed.
+ base::FilePath::StringPieceType local_log_extension_;
+ base::FilePath::StringPieceType remote_log_extension_;
+
+ // The directory which will contain all profiles.
+ base::ScopedTempDir profiles_dir_;
+
+ // Default BrowserContext and RenderProcessHost, to be used by tests which
+ // do not require anything fancy (such as seeding the BrowserContext with
+ // pre-existing logs files from a previous session, or working with multiple
+ // BrowserContext objects).
+
+ std::unique_ptr<TestingProfile> browser_context_;
+ BrowserContextId browser_context_id_;
+ std::unique_ptr<MockRenderProcessHost> rph_;
+
+ // Used for suppressing the upload of finished files, by creating an active
+ // remote-bound log associated with an independent BrowserContext which
+ // does not otherwise interfere with the test.
+ std::unique_ptr<TestingProfile> upload_suppressing_browser_context_;
+ std::unique_ptr<MockRenderProcessHost> upload_suppressing_rph_;
+
+ // The directory where we'll save local log files.
+ base::ScopedTempDir local_logs_base_dir_;
+ // local_logs_base_dir_ + log files' name prefix.
+ base::FilePath local_logs_base_path_;
+
+ // WebRtcEventLogManager instructs WebRTC, via PeerConnectionTracker, to
+ // only send WebRTC messages for certain peer connections. Some tests make
+ // sure that this is done correctly, by waiting for these notifications, then
+ // testing them.
+ // Because a single action - disabling of local logging - could crease a
+ // series of such instructions, we keep a queue of them. However, were one
+ // to actually test that scenario, one would have to account for the lack
+ // of a guarantee over the order in which these instructions are produced.
+ struct WebRtcStateChangeInstruction {
+ WebRtcStateChangeInstruction(PeerConnectionKey key, bool enabled)
+ : key(key), enabled(enabled) {}
+ PeerConnectionKey key;
+ bool enabled;
+ };
+ std::queue<WebRtcStateChangeInstruction> webrtc_state_change_instructions_;
+
+ // Observers for local/remote logging being started/stopped. By having them
+ // here, we achieve two goals:
+ // 1. Reduce boilerplate in the tests themselves.
+ // 2. Avoid lifetime issues, where the observer might be deallocated before
+ // a RenderProcessHost is deallocated (which can potentially trigger a
+ // callback on the observer).
+ NiceMock<MockWebRtcLocalEventLogsObserver> local_observer_;
+ NiceMock<MockWebRtcRemoteEventLogsObserver> remote_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcEventLogManagerTestBase);
+};
+
+#if !defined(OS_ANDROID)
+
+class WebRtcEventLogManagerTest : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ WebRtcEventLogManagerTest() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+ }
+
+ ~WebRtcEventLogManagerTest() override = default;
+
+ void SetUp() override {
+ CreateWebRtcEventLogManager(Compression::GZIP_PERFECT_ESTIMATION);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+ }
+};
+
+class WebRtcEventLogManagerTestCacheClearing
+ : public WebRtcEventLogManagerTest {
+ public:
+ ~WebRtcEventLogManagerTestCacheClearing() override = default;
+
+ void CreatePendingLogFiles(BrowserContext* browser_context) {
+ ASSERT_TRUE(pending_logs_.find(browser_context) == pending_logs_.end());
+ auto& elements = pending_logs_[browser_context];
+ elements = std::make_unique<BrowserContextAssociatedElements>();
+
+ for (size_t i = 0; i < kMaxActiveRemoteBoundWebRtcEventLogs; ++i) {
+ elements->rphs.push_back(
+ std::make_unique<MockRenderProcessHost>(browser_context));
+ const auto key = GetPeerConnectionKey(elements->rphs[i].get(), kLid);
+ elements->file_paths.push_back(CreatePendingRemoteLogFile(key));
+ ASSERT_TRUE(elements->file_paths[i]);
+ ASSERT_TRUE(base::PathExists(*elements->file_paths[i]));
+
+ pending_latest_mod_ = GetLastModificationTime(*elements->file_paths[i]);
+ if (pending_earliest_mod_.is_null()) { // First file.
+ pending_earliest_mod_ = pending_latest_mod_;
+ }
+ }
+ }
+
+ void ClearPendingLogFiles() { pending_logs_.clear(); }
+
+ base::Optional<base::FilePath> CreateRemoteLogFile(
+ const PeerConnectionKey& key,
+ bool pending) {
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ EXPECT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key));
+ if (pending) {
+ // Transition from ACTIVE to PENDING.
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+ }
+ return file_path;
+ }
+
+ base::Optional<base::FilePath> CreateActiveRemoteLogFile(
+ const PeerConnectionKey& key) {
+ return CreateRemoteLogFile(key, false);
+ }
+
+ base::Optional<base::FilePath> CreatePendingRemoteLogFile(
+ const PeerConnectionKey& key) {
+ return CreateRemoteLogFile(key, true);
+ }
+
+ protected:
+ // When closing a file, rather than check its last modification date, which
+ // is potentially expensive, WebRtcRemoteEventLogManager reads the system
+ // clock, which should be close enough. For tests, however, the difference
+ // could be enough to flake the tests, if not for this epsilon. Given the
+ // focus of the tests that use this, this epsilon can be arbitrarily large.
+ static const base::TimeDelta kEpsion;
+
+ struct BrowserContextAssociatedElements {
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ std::vector<base::Optional<base::FilePath>> file_paths;
+ };
+
+ std::map<const BrowserContext*,
+ std::unique_ptr<BrowserContextAssociatedElements>>
+ pending_logs_;
+
+ // Latest modification times of earliest and latest pending log files.
+ base::Time pending_earliest_mod_;
+ base::Time pending_latest_mod_;
+};
+
+const base::TimeDelta WebRtcEventLogManagerTestCacheClearing::kEpsion =
+ base::TimeDelta::FromHours(1);
+
+class WebRtcEventLogManagerTestWithRemoteLoggingDisabled
+ : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ WebRtcEventLogManagerTestWithRemoteLoggingDisabled()
+ : feature_enabled_(GetParam()), policy_enabled_(!feature_enabled_) {
+ if (feature_enabled_) {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kWebRtcRemoteEventLog);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ features::kWebRtcRemoteEventLog);
+ }
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestWithRemoteLoggingDisabled() override = default;
+
+ // Override CreateBrowserContext() to use policy_enabled_.
+ std::unique_ptr<TestingProfile> CreateBrowserContext() override {
+ return CreateBrowserContext(std::string());
+ }
+ std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name) override {
+ return CreateBrowserContext(profile_name, policy_enabled_,
+ false /* has_device_level_policies */,
+ policy_enabled_);
+ }
+ std::unique_ptr<TestingProfile> CreateBrowserContext(
+ std::string profile_name,
+ bool is_managed_profile,
+ bool has_device_level_policies,
+ base::Optional<bool> policy_allows_remote_logging) override {
+ DCHECK_EQ(policy_enabled_, policy_allows_remote_logging.value());
+ return WebRtcEventLogManagerTestBase::CreateBrowserContext(
+ profile_name, is_managed_profile, has_device_level_policies,
+ policy_allows_remote_logging);
+ }
+
+ private:
+ const bool feature_enabled_; // Whether the Finch kill-switch is engaged.
+ const bool policy_enabled_; // Whether the policy is enabled for the profile.
+};
+
+class WebRtcEventLogManagerTestPolicy : public WebRtcEventLogManagerTestBase {
+ public:
+ ~WebRtcEventLogManagerTestPolicy() override = default;
+
+ // Defer to setup from the body.
+ void SetUp() override {}
+
+ void SetUp(bool feature_enabled) {
+ if (feature_enabled) {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kWebRtcRemoteEventLog);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ features::kWebRtcRemoteEventLog);
+ }
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+
+ CreateWebRtcEventLogManager(Compression::GZIP_PERFECT_ESTIMATION);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+};
+
+class WebRtcEventLogManagerTestUploadSuppressionDisablingFlag
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestUploadSuppressionDisablingFlag() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
+ ::switches::kWebRtcRemoteEventLogUploadNoSuppression);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestUploadSuppressionDisablingFlag() override = default;
+};
+
+class WebRtcEventLogManagerTestForNetworkConnectivity
+ : public WebRtcEventLogManagerTestBase,
+ public ::testing::WithParamInterface<
+ std::tuple<bool,
+ network::mojom::ConnectionType,
+ network::mojom::ConnectionType>> {
+ public:
+ WebRtcEventLogManagerTestForNetworkConnectivity()
+ : get_conn_type_is_sync_(std::get<0>(GetParam())),
+ supported_type_(std::get<1>(GetParam())),
+ unsupported_type_(std::get<2>(GetParam())) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ // Use a low delay, or the tests would run for quite a long time.
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestForNetworkConnectivity() override = default;
+
+ void UnloadProfileAndSeedPendingLog() {
+ DCHECK(browser_context_path_.empty()) << "Not expected to be called twice.";
+
+ // Unload the profile, but remember where it stores its files (for sanity).
+ browser_context_path_ = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with one log file, simulating the
+ // creation of logs in a previous session.
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ base::FilePath file_path;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(
+ remote_logs_dir, kWebAppId, remote_log_extension_, base::Time::Now(),
+ &file_path, &file_));
+
+ expected_files_.emplace_back(browser_context_id_, file_path,
+ GetLastModificationTime(file_path));
+ }
+
+ const bool get_conn_type_is_sync_;
+ const network::mojom::ConnectionType supported_type_;
+ const network::mojom::ConnectionType unsupported_type_;
+
+ base::FilePath browser_context_path_; // For sanity over the test itself.
+ std::list<WebRtcLogFileInfo> expected_files_;
+ base::File file_;
+};
+
+class WebRtcEventLogManagerTestUploadDelay
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ ~WebRtcEventLogManagerTestUploadDelay() override = default;
+
+ void SetUp() override {
+ // Intercept and block the call to SetUp(). The test body will call
+ // the version that sets an upload delay instead.
+ }
+
+ void SetUp(size_t upload_delay_ms) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs,
+ std::to_string(upload_delay_ms));
+
+ CreateWebRtcEventLogManager();
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+
+ // There's a trade-off between the test runtime and the likelihood of a
+ // false-positive (lowered when the time is increased).
+ // Since false-positives can be caught handled even if only manifesting
+ // occasionally, this value should be enough.
+ static const size_t kDefaultUploadDelayMs = 500;
+
+ // For tests where we don't intend to wait, prevent flakiness by setting
+ // an unrealistically long delay.
+ static const size_t kIntentionallyExcessiveDelayMs = 1000 * 1000 * 1000;
+};
+
+// For testing compression issues.
+class WebRtcEventLogManagerTestCompression
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestCompression() {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+ }
+
+ ~WebRtcEventLogManagerTestCompression() override = default;
+
+ void SetUp() override {
+ // Defer until Init(), which will allow the test body more control.
+ }
+
+ void Init(base::Optional<WebRtcEventLogCompression> remote_compression =
+ base::Optional<WebRtcEventLogCompression>()) {
+ CreateWebRtcEventLogManager(remote_compression);
+
+ WebRtcEventLogManagerTestBase::SetUp();
+ }
+};
+
+class WebRtcEventLogManagerTestIncognito
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestIncognito() : incognito_profile_(nullptr) {
+ scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestIncognito() override {
+ incognito_rph_.reset();
+ if (incognito_profile_) {
+ DCHECK(browser_context_);
+ browser_context_->DestroyOffTheRecordProfile();
+ }
+ }
+
+ void SetUp() override {
+ WebRtcEventLogManagerTestBase::SetUp();
+
+ incognito_profile_ = browser_context_->GetOffTheRecordProfile();
+ incognito_rph_ =
+ std::make_unique<MockRenderProcessHost>(incognito_profile_);
+ }
+
+ Profile* incognito_profile_;
+ std::unique_ptr<MockRenderProcessHost> incognito_rph_;
+};
+
+class WebRtcEventLogManagerTestHistory : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestHistory() {
+ scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+ ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+
+ CreateWebRtcEventLogManager();
+ }
+
+ ~WebRtcEventLogManagerTestHistory() override = default;
+
+ // Allows us to test that a time is as expected, down to UNIX time's
+ // lower resolution than our clock.
+ static bool IsSameTimeWhenTruncatedToSeconds(base::Time a, base::Time b) {
+ if (a.is_null() || b.is_null()) {
+ return false;
+ }
+ const base::TimeDelta delta = std::max(a, b) - std::min(a, b);
+ return delta.InSeconds() == 0;
+ }
+
+ // Allows us to check that the timestamps are roughly what we expect.
+ // Doing better than this would require too much effort.
+ static bool IsSmallTimeDelta(base::Time a, base::Time b) {
+ if (a.is_null() || b.is_null()) {
+ return false;
+ }
+
+ // Way more than is "small", to make sure tests don't become flaky.
+ // If the timestamp is ever off, it's likely to be off by more than this,
+ // though, or the problem would not truly be severe enough to worry about.
+ const base::TimeDelta small_delta = base::TimeDelta::FromMinutes(15);
+
+ return (std::max(a, b) - std::min(a, b) <= small_delta);
+ }
+};
+
+namespace {
+
+class PeerConnectionTrackerProxyForTesting
+ : public WebRtcEventLogManager::PeerConnectionTrackerProxy {
+ public:
+ explicit PeerConnectionTrackerProxyForTesting(
+ WebRtcEventLogManagerTestBase* test)
+ : test_(test) {}
+
+ ~PeerConnectionTrackerProxyForTesting() override = default;
+
+ void EnableWebRtcEventLogging(const PeerConnectionKey& key,
+ int output_period_ms) override {
+ test_->SetWebRtcEventLoggingState(key, true);
+ }
+ void DisableWebRtcEventLogging(const PeerConnectionKey& key) override {
+ test_->SetWebRtcEventLoggingState(key, false);
+ }
+
+ private:
+ WebRtcEventLogManagerTestBase* const test_;
+};
+
+// The factory for the following fake uploader produces a sequence of
+// uploaders which fail the test if given a file other than that which they
+// expect. The factory itself likewise fails the test if destroyed before
+// producing all expected uploaders, or if it's asked for more uploaders than
+// it expects to create. This allows us to test for sequences of uploads.
+class FileListExpectingWebRtcEventLogUploader : public WebRtcEventLogUploader {
+ public:
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ Factory(std::list<WebRtcLogFileInfo>* expected_files,
+ bool result,
+ base::RunLoop* run_loop)
+ : result_(result), run_loop_(run_loop) {
+ expected_files_.swap(*expected_files);
+ if (expected_files_.empty()) {
+ run_loop_->QuitWhenIdle();
+ }
+ }
+
+ ~Factory() override { EXPECT_TRUE(expected_files_.empty()); }
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override {
+ if (expected_files_.empty()) {
+ EXPECT_FALSE(true); // More files uploaded than expected.
+ } else {
+ EXPECT_EQ(log_file.path, expected_files_.front().path);
+ // Because LoadMainTestProfile() and UnloadMainTestProfile() mess up the
+ // BrowserContextId in ways that would not happen in production,
+ // we cannot verify |log_file.browser_context_id| is correct.
+ // This is unimportant to the test.
+
+ base::DeleteFile(log_file.path, false);
+ expected_files_.pop_front();
+ }
+
+ if (expected_files_.empty()) {
+ run_loop_->QuitWhenIdle();
+ }
+
+ return std::make_unique<FileListExpectingWebRtcEventLogUploader>(
+ log_file, result_, std::move(callback));
+ }
+
+ private:
+ std::list<WebRtcLogFileInfo> expected_files_;
+ const bool result_;
+ base::RunLoop* const run_loop_;
+ };
+
+ // The logic is in the factory; the uploader just reports success so that the
+ // next file may become eligible for uploading.
+ FileListExpectingWebRtcEventLogUploader(const WebRtcLogFileInfo& log_file,
+ bool result,
+ UploadResultCallback callback)
+ : log_file_(log_file) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), log_file_.path, result));
+ }
+
+ ~FileListExpectingWebRtcEventLogUploader() override = default;
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override {
+ return log_file_;
+ }
+
+ bool Cancel() override {
+ NOTREACHED() << "Incompatible with this kind of test.";
+ return true;
+ }
+
+ private:
+ const WebRtcLogFileInfo log_file_;
+};
+
+} // namespace
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionAddedReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionAddedReturnsFalseIfAlreadyAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_FALSE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionRemovedReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedReturnsFalseIfNeverAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedReturnsFalseIfAlreadyRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, PeerConnectionSessionIdSetReturnsTrue) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfEmptyString) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, ""));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfPeerConnectionNeverAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfAlreadyCalledSameId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfPeerConnectionAlreadyRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetReturnsFalseIfAlreadyCalledDifferentId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, "id1"));
+ EXPECT_FALSE(PeerConnectionSessionIdSet(key, "id2"));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionSessionIdSetCalledOnRecreatedPeerConnectionSanity) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest, EnableLocalLoggingReturnsTrue) {
+ EXPECT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ EnableLocalLoggingReturnsFalseIfCalledWhenAlreadyEnabled) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_FALSE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsTrue) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsIfNeverEnabled) {
+ EXPECT_FALSE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest, DisableLocalLoggingReturnsIfAlreadyDisabled) {
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_FALSE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsFalseAndFalseWhenAllLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ // Note that EnableLocalLogging() and StartRemoteLogging() weren't called.
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsFalseAndFalseForUnknownPeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ // Note that PeerConnectionAdded() wasn't called.
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsLocalTrueWhenPcKnownAndLocalLoggingOn) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(true, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsRemoteTrueWhenPcKnownAndRemoteLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, true));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteReturnsTrueAndTrueeWhenAllLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(true, true));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedNotCalledIfLocalLoggingEnabledWithoutPeerConnections) {
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedNotCalledIfLocalLoggingDisabledWithoutPeerConnections) {
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedCalledForPeerConnectionAddedAndLocalLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStartedCalledForLocalLoggingEnabledAndPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedCalledAfterLocalLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(DisableLocalLogging());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnLocalLogStoppedCalledAfterPeerConnectionRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogCreatesEmptyFileWhenStarted) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogCreateAndWriteToFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log = "To strive, to seek, to find, and not to yield.";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleWritesToSameFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string logs[] = {"Old age hath yet his honour and his toil;",
+ "Death closes all: but something ere the end,",
+ "Some work of noble note, may yet be done,",
+ "Not unbecoming men that strove with Gods."};
+ for (const std::string& log : logs) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, false));
+ }
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogFileSizeLimitNotExceeded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "There lies the port; the vessel puffs her sail:";
+ const size_t file_size_limit_bytes = log.length() / 2;
+
+ ASSERT_TRUE(EnableLocalLogging(file_size_limit_bytes));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Failure is reported, because not everything could be written. The file
+ // will also be closed.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Additional calls to Write() have no effect.
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, "ignored"),
+ std::make_pair(false, false));
+
+ ExpectLocalFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogSanityOverUnlimitedFileSizes) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging(kWebRtcEventLogManagerUnlimitedFileSize));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log1 = "Who let the dogs out?";
+ const std::string log2 = "Woof, woof, woof, woof, woof!";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(true, false));
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log1 + log2);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogNoWriteAfterLogStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string log_before = "log_before_stop";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log_before),
+ std::make_pair(true, false));
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const std::string log_after = "log_after_stop";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log_after),
+ std::make_pair(false, false));
+
+ ExpectLocalFileContents(*file_path, log_before);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogOnlyWritesTheLogsAfterStarted) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Calls to Write() before the log was started are ignored.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ const std::string log1 = "The lights begin to twinkle from the rocks:";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(false, false));
+ ASSERT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // Calls after the log started have an effect. The calls to Write() from
+ // before the log started are not remembered.
+ const std::string log2 = "The long day wanes: the slow moon climbs: the deep";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(true, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*file_path, log2);
+}
+
+// Note: This test also covers the scenario LocalLogExistingFilesNotOverwritten,
+// which is therefore not explicitly tested.
+TEST_F(WebRtcEventLogManagerTest, LocalLoggingRestartCreatesNewFile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ const std::vector<std::string> logs = {"<setup>", "<punchline>", "<encore>"};
+ std::vector<base::Optional<PeerConnectionKey>> keys(logs.size());
+ std::vector<base::Optional<base::FilePath>> file_paths(logs.size());
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ON_CALL(local_observer_, OnLocalLogStarted(_, _))
+ .WillByDefault(Invoke(SaveKeyAndFilePathTo(&keys[i], &file_paths[i])));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(keys[i]);
+ ASSERT_EQ(*keys[i], key);
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(true, false));
+ ASSERT_TRUE(DisableLocalLogging());
+ }
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ExpectLocalFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleActiveFiles) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ std::list<MockRenderProcessHost> rphs;
+ for (size_t i = 0; i < 3; ++i) {
+ rphs.emplace_back(browser_context_.get()); // (MockRenderProcessHost ctor)
+ }
+
+ std::vector<PeerConnectionKey> keys;
+ for (auto& rph : rphs) {
+ keys.push_back(GetPeerConnectionKey(&rph, kLid));
+ }
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(local_observer_, OnLocalLogStarted(keys[i], _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(kLid));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(true, false));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ ASSERT_TRUE(DisableLocalLogging());
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectLocalFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogLimitActiveLocalLogFiles) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+// When a log reaches its maximum size limit, it is closed, and no longer
+// counted towards the limit.
+TEST_F(WebRtcEventLogManagerTest, LocalLogFilledLogNotCountedTowardsLogsLimit) {
+ const std::string log = "very_short_log";
+ ASSERT_TRUE(EnableLocalLogging(log.size()));
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ // By writing to one of the logs, we fill it and end up closing it, allowing
+ // an additional log to be written.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_EQ(OnWebRtcEventLogWrite(removed_key, log),
+ std::make_pair(true, false));
+
+ // We now have room for one additional log.
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(last_key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LocalLogForRemovedPeerConnectionNotCountedTowardsLogsLimit) {
+ ASSERT_TRUE(EnableLocalLogging());
+
+ const int kMaxLocalLogFiles =
+ static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
+ for (int i = 0; i < kMaxLocalLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ }
+
+ // When one peer connection is removed, one log is stopped, thereby allowing
+ // an additional log to be opened.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(removed_key)).Times(1);
+ ASSERT_TRUE(PeerConnectionRemoved(removed_key));
+
+ // We now have room for one additional log.
+ const auto last_key = GetPeerConnectionKey(rph_.get(), kMaxLocalLogFiles);
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(last_key, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogIllegalPath) {
+ // Since the log file won't be properly opened, these will not be called.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ // See the documentation of the function for why |true| is expected despite
+ // the path being illegal.
+ const base::FilePath illegal_path(FILE_PATH_LITERAL(":!@#$%|`^&*\\/"));
+ EXPECT_TRUE(EnableLocalLogging(illegal_path));
+
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogManagerTest, LocalLogLegalPathWithoutPermissionsSanity) {
+ RemoveWritePermissions(local_logs_base_dir_.GetPath());
+
+ // Since the log file won't be properly opened, these will not be called.
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+
+ // See the documentation of the function for why |true| is expected despite
+ // the path being illegal.
+ EXPECT_TRUE(EnableLocalLogging(local_logs_base_path_));
+
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ // Write() has no effect (but is handled gracefully).
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "Why did the chicken cross the road?"),
+ std::make_pair(false, false));
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+
+ // Logging was enabled, even if it had no effect because of the lacking
+ // permissions; therefore, the operation of disabling it makes sense.
+ EXPECT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(base::IsDirectoryEmpty(local_logs_base_dir_.GetPath()));
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogEmptyStringHandledGracefully) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // By writing a log after the empty string, we show that no odd behavior is
+ // encountered, such as closing the file (an actual bug from WebRTC).
+ const std::vector<std::string> logs = {"<setup>", "", "<encore>"};
+
+ base::Optional<base::FilePath> file_path;
+
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(true, false));
+ }
+ ASSERT_TRUE(DisableLocalLogging());
+
+ ExpectLocalFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogFilenameMatchesExpectedFormat) {
+ using StringType = base::FilePath::StringType;
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const base::Time::Exploded frozen_time_exploded{
+ 2017, // Four digit year "2007"
+ 9, // 1-based month (values 1 = January, etc.)
+ 3, // 0-based day of week (0 = Sunday, etc.)
+ 6, // 1-based day of month (1-31)
+ 10, // Hour within the current day (0-23)
+ 43, // Minute within the current hour (0-59)
+ 29, // Second within the current minute.
+ 0 // Milliseconds within the current second (0-999)
+ };
+ ASSERT_TRUE(frozen_time_exploded.HasValidValues());
+ FreezeClockAt(frozen_time_exploded);
+
+ const StringType user_defined = FILE_PATH_LITERAL("user_defined");
+ const base::FilePath local_logs_base_path =
+ local_logs_base_dir_.GetPath().Append(user_defined);
+
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ const StringType date = FILE_PATH_LITERAL("20170906");
+ const StringType time = FILE_PATH_LITERAL("1043");
+ base::FilePath expected_path = local_logs_base_path;
+ expected_path = local_logs_base_path.InsertBeforeExtension(
+ FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
+ FILE_PATH_LITERAL("_") + NumberToStringType(rph_->GetID()) +
+ FILE_PATH_LITERAL("_") + NumberToStringType(kLid));
+ expected_path = expected_path.AddExtension(local_log_extension_);
+
+ EXPECT_EQ(file_path, expected_path);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LocalLogFilenameMatchesExpectedFormatRepeatedFilename) {
+ using StringType = base::FilePath::StringType;
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path_1;
+ base::Optional<base::FilePath> file_path_2;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillOnce(Invoke(SaveFilePathTo(&file_path_1)))
+ .WillOnce(Invoke(SaveFilePathTo(&file_path_2)));
+
+ const base::Time::Exploded frozen_time_exploded{
+ 2017, // Four digit year "2007"
+ 9, // 1-based month (values 1 = January, etc.)
+ 3, // 0-based day of week (0 = Sunday, etc.)
+ 6, // 1-based day of month (1-31)
+ 10, // Hour within the current day (0-23)
+ 43, // Minute within the current hour (0-59)
+ 29, // Second within the current minute.
+ 0 // Milliseconds within the current second (0-999)
+ };
+ ASSERT_TRUE(frozen_time_exploded.HasValidValues());
+ FreezeClockAt(frozen_time_exploded);
+
+ const StringType user_defined_portion = FILE_PATH_LITERAL("user_defined");
+ const base::FilePath local_logs_base_path =
+ local_logs_base_dir_.GetPath().Append(user_defined_portion);
+
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(file_path_1);
+ ASSERT_FALSE(file_path_1->empty());
+
+ // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
+ const StringType date = FILE_PATH_LITERAL("20170906");
+ const StringType time = FILE_PATH_LITERAL("1043");
+ base::FilePath expected_path_1 = local_logs_base_path;
+ expected_path_1 = local_logs_base_path.InsertBeforeExtension(
+ FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
+ FILE_PATH_LITERAL("_") + NumberToStringType(rph_->GetID()) +
+ FILE_PATH_LITERAL("_") + NumberToStringType(kLid));
+ expected_path_1 = expected_path_1.AddExtension(local_log_extension_);
+
+ ASSERT_EQ(file_path_1, expected_path_1);
+
+ ASSERT_TRUE(DisableLocalLogging());
+ ASSERT_TRUE(EnableLocalLogging(local_logs_base_path));
+ ASSERT_TRUE(file_path_2);
+ ASSERT_FALSE(file_path_2->empty());
+
+ const base::FilePath expected_path_2 =
+ expected_path_1.InsertBeforeExtension(FILE_PATH_LITERAL(" (1)"));
+
+ // Focus of the test - starting the same log again produces a new file,
+ // with an expected new filename.
+ ASSERT_EQ(file_path_2, expected_path_2);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStartedNotCalledIfRemoteLoggingNotEnabled) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStoppedNotCalledIfRemoteLoggingNotEnabled) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ EXPECT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStartedCalledIfRemoteLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnRemoteLogStoppedCalledIfRemoteLoggingEnabledThenPcRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ BrowserContextInitializationCreatesDirectoryForRemoteLogs) {
+ auto browser_context = CreateBrowserContext();
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context.get());
+ EXPECT_TRUE(base::DirectoryExists(remote_logs_path));
+ EXPECT_TRUE(base::IsDirectoryEmpty(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnknownPeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), 0);
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, "id", nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnknownSessionId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, "wrong_id", nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsTrueIfKnownSessionId) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_TRUE(StartRemoteLogging(key, kSessionId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfRestartAttempt) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(StartRemoteLogging(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureAlreadyLogging);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfUnlimitedFileSize) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId,
+ kWebRtcEventLogManagerUnlimitedFileSize, 0,
+ kWebAppId, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureUnlimitedSizeDisallowed);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsTrueIfFileSizeAtOrBelowLimit) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ EXPECT_TRUE(StartRemoteLogging(key, kSessionId, kMaxRemoteLogFileSizeBytes, 0,
+ kWebAppId));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfFileSizeToSmall) {
+ const size_t min_size =
+ CreateLogFileWriterFactory(Compression::GZIP_NULL_ESTIMATION)
+ ->MinFileSizeBytes();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, min_size - 1, 0, kWebAppId,
+ nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureMaxSizeTooSmall);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfExcessivelyLargeFileSize) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId,
+ kMaxRemoteLogFileSizeBytes + 1, 0, kWebAppId,
+ nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureMaxSizeTooLarge);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfExcessivelyLargeOutputPeriodMs) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, kMaxRemoteLogFileSizeBytes,
+ kMaxOutputPeriodMs + 1, kWebAppId, nullptr,
+ &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureOutputPeriodMsTooLarge);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsFalseIfPeerConnectionAlreadyClosed) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, kSessionId, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureUnknownOrInactivePeerConnection);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingDoesNotReturnIdWhenUnsuccessful) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ std::string log_id;
+ ASSERT_FALSE(StartRemoteLogging(key, kSessionId, &log_id));
+
+ EXPECT_TRUE(log_id.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingReturnsLegalIdWhenSuccessful) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+
+ std::string log_id;
+ ASSERT_TRUE(StartRemoteLogging(key, kSessionId, &log_id));
+
+ EXPECT_EQ(log_id.size(), 32u);
+ EXPECT_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingSavesToFileWithCorrectFileNameFormat) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ std::string log_id;
+ ASSERT_TRUE(StartRemoteLogging(key, &log_id));
+
+ // Compare filename (without extension).
+ const std::string filename =
+ file_path->BaseName().RemoveExtension().MaybeAsASCII();
+ ASSERT_FALSE(filename.empty());
+
+ const std::string expected_filename =
+ std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
+ std::to_string(kWebAppId) + "_" + log_id;
+ EXPECT_EQ(filename, expected_filename);
+
+ // Compare extension.
+ EXPECT_EQ(
+ base::FilePath::kExtensionSeparator + remote_log_extension_.as_string(),
+ file_path->Extension());
+}
+
+TEST_F(WebRtcEventLogManagerTest, StartRemoteLoggingCreatesEmptyFile) {
+ base::Optional<base::FilePath> file_path;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ // Close file before examining its contents.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogFileCreatedInCorrectDirectory) {
+ // Set up separate browser contexts; each one will get one log.
+ constexpr size_t kLogsNum = 3;
+ std::unique_ptr<TestingProfile> browser_contexts[kLogsNum];
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs.emplace_back(
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get()));
+ }
+
+ // Prepare to store the logs' paths in distinct memory locations.
+ base::Optional<base::FilePath> file_paths[kLogsNum];
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ const auto key = GetPeerConnectionKey(rphs[i].get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ // Start one log for each browser context.
+ for (const auto& rph : rphs) {
+ const auto key = GetPeerConnectionKey(&*rph, kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ // All log files must be created in their own context's directory.
+ for (size_t i = 0; i < base::size(browser_contexts); ++i) {
+ ASSERT_TRUE(file_paths[i]);
+ EXPECT_TRUE(browser_contexts[i]->GetPath().IsParent(*file_paths[i]));
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingSanityIfDuplicateIdsInDifferentRendererProcesses) {
+ std::unique_ptr<MockRenderProcessHost> rphs[2] = {
+ std::make_unique<MockRenderProcessHost>(browser_context_.get()),
+ std::make_unique<MockRenderProcessHost>(browser_context_.get()),
+ };
+
+ PeerConnectionKey keys[2] = {GetPeerConnectionKey(rphs[0].get(), 0),
+ GetPeerConnectionKey(rphs[1].get(), 0)};
+
+ // The ID is shared, but that's not a problem, because the renderer process
+ // are different.
+ const std::string id = "shared_id";
+ ASSERT_TRUE(PeerConnectionAdded(keys[0]));
+ PeerConnectionSessionIdSet(keys[0], id);
+ ASSERT_TRUE(PeerConnectionAdded(keys[1]));
+ PeerConnectionSessionIdSet(keys[1], id);
+
+ // Make sure the logs get written to separate files.
+ base::Optional<base::FilePath> file_paths[2];
+ for (size_t i = 0; i < 2; ++i) {
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ EXPECT_TRUE(StartRemoteLogging(keys[0], id));
+ EXPECT_TRUE(StartRemoteLogging(keys[1], id));
+
+ EXPECT_TRUE(file_paths[0]);
+ EXPECT_TRUE(file_paths[1]);
+ EXPECT_NE(file_paths[0], file_paths[1]);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteWritesToTheRemoteBoundFile) {
+ base::Optional<base::FilePath> file_path;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ const char* const log = "1 + 1 = 3";
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+
+ // Close file before examining its contents.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(*file_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, WriteToBothLocalAndRemoteFiles) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> local_path;
+ EXPECT_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&local_path)));
+
+ base::Optional<base::FilePath> remote_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&remote_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ ASSERT_TRUE(local_path);
+ ASSERT_FALSE(local_path->empty());
+ ASSERT_TRUE(remote_path);
+ ASSERT_FALSE(remote_path->empty());
+
+ const char* const log = "logloglog";
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(true, true));
+
+ // Ensure the flushing of the file to disk before attempting to read them.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*local_path, log);
+ ExpectRemoteFileContents(*remote_path, log);
+}
+
+TEST_F(WebRtcEventLogManagerTest, MultipleWritesToSameRemoteBoundLogfile) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ const std::string logs[] = {"ABC", "DEF", "XYZ"};
+ for (const std::string& log : logs) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+ }
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFileSizeLimitNotExceededSingleWrite) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "tpyo";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(
+ StartRemoteLogging(key, kSessionId, GzippedSize(log) - 1, 0, kWebAppId));
+
+ // Failure is reported, because not everything could be written. The file
+ // will also be closed.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // No partial writes occurred.
+ ExpectRemoteFileContents(*file_path, std::string());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFileSizeLimitNotExceededMultipleWrites) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log1 = "abcabc";
+ const std::string log2 = "defghijklmnopqrstuvwxyz";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key, kSessionId));
+ ASSERT_TRUE(
+ StartRemoteLogging(key, kSessionId, 1 + GzippedSize(log1), 0, kWebAppId));
+
+ // First write works.
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log1), std::make_pair(false, true));
+
+ // On the second write, failure is reported, because not everything could be
+ // written. The file will also be closed.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, log2), std::make_pair(false, false));
+
+ ExpectRemoteFileContents(*file_path, log1);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LogMultipleActiveRemoteLogsSameBrowserContext) {
+ const std::vector<PeerConnectionKey> keys = {
+ GetPeerConnectionKey(rph_.get(), 0), GetPeerConnectionKey(rph_.get(), 1),
+ GetPeerConnectionKey(rph_.get(), 2)};
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i]));
+ ASSERT_TRUE(StartRemoteLogging(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(i));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ for (auto& key : keys) {
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ LogMultipleActiveRemoteLogsDifferentBrowserContexts) {
+ constexpr size_t kLogsNum = 3;
+ std::unique_ptr<TestingProfile> browser_contexts[kLogsNum];
+ std::vector<std::unique_ptr<MockRenderProcessHost>> rphs;
+ for (size_t i = 0; i < kLogsNum; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs.emplace_back(
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get()));
+ }
+
+ std::vector<PeerConnectionKey> keys;
+ for (auto& rph : rphs) {
+ keys.push_back(GetPeerConnectionKey(rph.get(), kLid));
+ }
+
+ std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i]));
+ ASSERT_TRUE(StartRemoteLogging(keys[i]));
+ ASSERT_TRUE(file_paths[i]);
+ ASSERT_FALSE(file_paths[i]->empty());
+ }
+
+ std::vector<std::string> logs;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ logs.emplace_back(std::to_string(rph_->GetID()) + std::to_string(i));
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ }
+
+ // Make sure the file woulds be closed, so that we could safely read them.
+ for (auto& key : keys) {
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, DifferentRemoteLogsMayHaveDifferentMaximums) {
+ const std::string logs[2] = {"abra", "cadabra"};
+ std::vector<base::Optional<base::FilePath>> file_paths(base::size(logs));
+ std::vector<PeerConnectionKey> keys;
+ for (size_t i = 0; i < base::size(logs); ++i) {
+ keys.push_back(GetPeerConnectionKey(rph_.get(), i));
+ ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ ASSERT_TRUE(PeerConnectionAdded(keys[i]));
+ const std::string session_id = GetUniqueId(keys[i]);
+ ASSERT_TRUE(PeerConnectionSessionIdSet(keys[i], session_id));
+ ASSERT_TRUE(StartRemoteLogging(keys[i], session_id, GzippedSize(logs[i]), 0,
+ kWebAppId));
+ }
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ // The write is successful, but the file closed, indicating that the
+ // maximum file size has been reached.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(keys[i])).Times(1);
+ ASSERT_EQ(OnWebRtcEventLogWrite(keys[i], logs[i]),
+ std::make_pair(false, true));
+ ASSERT_TRUE(file_paths[i]);
+ ExpectRemoteFileContents(*file_paths[i], logs[i]);
+ }
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogFileClosedWhenCapacityReached) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ const std::string log = "Let X equal X.";
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log), 0,
+ kWebAppId));
+ ASSERT_TRUE(file_path);
+
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, true));
+}
+
+#if defined(OS_POSIX)
+// TODO(crbug.com/775415): Add unit tests for lacking read permissions when
+// looking to upload the file.
+TEST_F(WebRtcEventLogManagerTest,
+ FailureToCreateRemoteLogsDirHandledGracefully) {
+ const base::FilePath browser_context_dir = browser_context_->GetPath();
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context_.get());
+
+ // Unload the profile, delete its remove logs directory, and remove write
+ // permissions from it, thereby preventing it from being created again.
+ UnloadMainTestProfile();
+ ASSERT_TRUE(base::DeleteFile(remote_logs_path, /*recursive=*/true));
+ RemoveWritePermissions(browser_context_dir);
+
+ // Graceful handling by BrowserContext::EnableForBrowserContext, despite
+ // failing to create the remote logs' directory..
+ LoadMainTestProfile();
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+
+ // Graceful handling of PeerConnectionAdded: True returned because the
+ // remote-logs' manager can still safely reason about the state of peer
+ // connections even if one of its browser contexts is defective.)
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Graceful handling of StartRemoteLogging: False returned because it's
+ // impossible to write the log to a file.
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureLoggingDisabledBrowserContext);
+
+ // Graceful handling of OnWebRtcEventLogWrite: False returned because the
+ // log could not be written at all, let alone in its entirety.
+ const char* const log = "This is not a log.";
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // Graceful handling of PeerConnectionRemoved: True returned because the
+ // remote-logs' manager can still safely reason about the state of peer
+ // connections even if one of its browser contexts is defective.
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest, GracefullyHandleFailureToStartRemoteLogFile) {
+ // WebRTC logging will not be turned on.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+
+ // Remove write permissions from the directory.
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(browser_context_.get());
+ ASSERT_TRUE(base::DirectoryExists(remote_logs_path));
+ RemoveWritePermissions(remote_logs_path);
+
+ // StartRemoteLogging() will now fail.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureFileCreationError);
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "abc"), std::make_pair(false, false));
+ EXPECT_TRUE(base::IsDirectoryEmpty(remote_logs_path));
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogLimitActiveLogFiles) {
+ for (int i = 0; i < kMaxActiveRemoteLogFiles + 1; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ }
+
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(_, _, _)).Times(0);
+ const auto new_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_FALSE(StartRemoteLogging(new_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogFilledLogNotCountedTowardsLogsLimit) {
+ const std::string log = "very_short_log";
+
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log), 0,
+ kWebAppId));
+ }
+
+ // By writing to one of the logs until it reaches capacity, we fill it,
+ // causing it to close, therefore allowing an additional log.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ EXPECT_EQ(OnWebRtcEventLogWrite(removed_key, log),
+ std::make_pair(false, true));
+
+ // We now have room for one additional log.
+ const auto new_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(new_key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(new_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(new_key));
+ ASSERT_TRUE(StartRemoteLogging(new_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ RemoteLogForRemovedPeerConnectionNotCountedTowardsLogsLimit) {
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _)).Times(1);
+ ASSERT_TRUE(StartRemoteLogging(key));
+ }
+
+ // By removing a peer connection associated with one of the logs, we allow
+ // an additional log.
+ const auto removed_key = GetPeerConnectionKey(rph_.get(), 0);
+ ASSERT_TRUE(PeerConnectionRemoved(removed_key));
+
+ // We now have room for one additional log.
+ const auto last_key =
+ GetPeerConnectionKey(rph_.get(), kMaxActiveRemoteLogFiles);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(last_key, _, _)).Times(1);
+ ASSERT_TRUE(PeerConnectionAdded(last_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(last_key));
+ ASSERT_TRUE(StartRemoteLogging(last_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ ActiveLogsForBrowserContextCountedTowardsItsPendingsLogsLimit) {
+ SuppressUploading();
+
+ // Produce kMaxPendingRemoteLogFiles pending logs.
+ for (int i = 0; i < kMaxPendingRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rph_.get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ // It is now impossible to start another *active* log for that BrowserContext,
+ // because we have too many pending logs (and active logs become pending
+ // once completed).
+ const auto forbidden =
+ GetPeerConnectionKey(rph_.get(), kMaxPendingRemoteLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(forbidden));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(forbidden));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(forbidden, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ ObserveLimitOnMaximumPendingLogsPerBrowserContext) {
+ SuppressUploading();
+
+ // Create additional BrowserContexts for the test.
+ std::unique_ptr<TestingProfile> browser_contexts[2] = {
+ CreateBrowserContext(), CreateBrowserContext()};
+ std::unique_ptr<MockRenderProcessHost> rphs[2] = {
+ std::make_unique<MockRenderProcessHost>(browser_contexts[0].get()),
+ std::make_unique<MockRenderProcessHost>(browser_contexts[1].get())};
+
+ // Allowed to start kMaxPendingRemoteLogFiles for each BrowserContext.
+ // Specifically, we can do it for the first BrowserContext.
+ for (int i = 0; i < kMaxPendingRemoteLogFiles; ++i) {
+ const auto key = GetPeerConnectionKey(rphs[0].get(), i);
+ // The log could be opened:
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ // The log changes state from ACTIVE to PENDING:
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+ }
+
+ // Not allowed to start any more remote-bound logs for the BrowserContext on
+ // which the limit was reached.
+ const auto key0 =
+ GetPeerConnectionKey(rphs[0].get(), kMaxPendingRemoteLogFiles);
+ ASSERT_TRUE(PeerConnectionAdded(key0));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key0));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key0, nullptr, &error_message));
+ EXPECT_EQ(error_message,
+ kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed);
+
+ // Other BrowserContexts aren't limit by the previous one's limit.
+ const auto key1 = GetPeerConnectionKey(rphs[1].get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(key1));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key1));
+ EXPECT_TRUE(StartRemoteLogging(key1));
+}
+
+// This also tests the scenario UploadOrderDependsOnLastModificationTime.
+TEST_F(WebRtcEventLogManagerTest,
+ LogsFromPreviousSessionBecomePendingLogsWhenBrowserContextInitialized) {
+ // Unload the profile, but remember where it stores its files.
+ const base::FilePath browser_context_path = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with log files, simulating the
+ // creation of logs in a previous session.
+ std::list<WebRtcLogFileInfo> expected_files;
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ // Avoid arbitrary ordering due to files being created in the same second.
+ // This is OK in production, but can confuse the test, which expects a
+ // specific order.
+ base::Time time =
+ base::Time::Now() -
+ base::TimeDelta::FromSeconds(kMaxPendingRemoteBoundWebRtcEventLogs);
+
+ for (size_t i = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+ time += base::TimeDelta::FromSeconds(1);
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_logs_dir, kWebAppId,
+ remote_log_extension_, time,
+ &file_path, &file));
+
+ expected_files.emplace_back(browser_context_id_, file_path, time);
+ }
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+// It is possible for remote-bound logs to be compressed or uncompressed.
+// We show that logs from a previous session are captured even if they are
+// different, with regards to compression, compared to last time.
+TEST_F(WebRtcEventLogManagerTest,
+ LogsCapturedPreviouslyMadePendingEvenIfDifferentExtensionsUsed) {
+ // Unload the profile, but remember where it stores its files.
+ const base::FilePath browser_context_path = browser_context_->GetPath();
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+ UnloadMainTestProfile();
+
+ // Seed the remote logs' directory with log files, simulating the
+ // creation of logs in a previous session.
+ std::list<WebRtcLogFileInfo> expected_files;
+ ASSERT_TRUE(base::CreateDirectory(remote_logs_dir));
+
+ base::FilePath::StringPieceType extensions[] = {
+ kWebRtcEventLogUncompressedExtension, kWebRtcEventLogGzippedExtension};
+ ASSERT_LE(base::size(extensions), kMaxPendingRemoteBoundWebRtcEventLogs)
+ << "Lacking test coverage.";
+
+ // Avoid arbitrary ordering due to files being created in the same second.
+ // This is OK in production, but can confuse the test, which expects a
+ // specific order.
+ base::Time time =
+ base::Time::Now() -
+ base::TimeDelta::FromSeconds(kMaxPendingRemoteBoundWebRtcEventLogs);
+
+ for (size_t i = 0, ext = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+ time += base::TimeDelta::FromSeconds(1);
+
+ const auto& extension = extensions[ext];
+ ext = (ext + 1) % base::size(extensions);
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_logs_dir, kWebAppId, extension,
+ time, &file_path, &file));
+
+ expected_files.emplace_back(browser_context_id_, file_path, time);
+ }
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTest,
+ WhenPeerConnectionRemovedFinishedRemoteLogUploadedAndFileDeleted) {
+ // |upload_result| show that the files are deleted independent of the
+ // upload's success / failure.
+ const bool upload_result = GetParam();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, upload_result, &run_loop));
+
+ // Peer connection removal triggers next upload.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+
+ EXPECT_TRUE(
+ base::IsDirectoryEmpty(RemoteBoundLogsDir(browser_context_.get())));
+}
+
+TEST_P(WebRtcEventLogManagerTest, DestroyedRphTriggersLogUpload) {
+ // |upload_result| show that the files are deleted independent of the
+ // upload's success / failure.
+ const bool upload_result = GetParam();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, upload_result, &run_loop));
+
+ // RPH destruction stops all active logs and triggers next upload.
+ rph_.reset();
+
+ WaitForPendingTasks(&run_loop);
+
+ EXPECT_TRUE(
+ base::IsDirectoryEmpty(RemoteBoundLogsDir(browser_context_.get())));
+}
+
+// Note that SuppressUploading() and UnSuppressUploading() use the behavior
+// guaranteed by this test.
+TEST_F(WebRtcEventLogManagerTest, UploadOnlyWhenNoActivePeerConnections) {
+ const auto untracked = GetPeerConnectionKey(rph_.get(), 0);
+ const auto tracked = GetPeerConnectionKey(rph_.get(), 1);
+
+ // Suppresses the uploading of the "tracked" peer connection's log.
+ ASSERT_TRUE(PeerConnectionAdded(untracked));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(untracked));
+
+ // The tracked peer connection's log is not uploaded when finished, because
+ // another peer connection is still active.
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(tracked, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(tracked));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(tracked));
+ ASSERT_TRUE(StartRemoteLogging(tracked));
+ ASSERT_TRUE(log_file);
+ ASSERT_TRUE(PeerConnectionRemoved(tracked));
+
+ // Perform another action synchronously, so that we may be assured that the
+ // observer's lack of callbacks was not a timing fluke.
+ OnWebRtcEventLogWrite(untracked, "Ook!");
+
+ // Having been convinced that |tracked|'s log was not uploded while
+ // |untracked| was active, close |untracked| and see that |tracked|'s log
+ // is now uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_uploads = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_uploads, true, &run_loop));
+ ASSERT_TRUE(PeerConnectionRemoved(untracked));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTest, ExpiredFilesArePrunedRatherThanUploaded) {
+ constexpr size_t kExpired = 0;
+ constexpr size_t kFresh = 1;
+ DCHECK_GE(kMaxPendingRemoteBoundWebRtcEventLogs, 2u)
+ << "Please restructure the test to use separate browser contexts.";
+
+ const base::FilePath remote_logs_dir =
+ RemoteBoundLogsDir(browser_context_.get());
+
+ UnloadMainTestProfile();
+
+ base::FilePath file_paths[2];
+ for (size_t i = 0; i < 2; ++i) {
+ base::File file;
+ ASSERT_TRUE(CreateRemoteBoundLogFile(
+ remote_logs_dir, kWebAppId, remote_log_extension_, base::Time::Now(),
+ &file_paths[i], &file));
+ }
+
+ // Touch() requires setting the last access time as well. Keep it current,
+ // showing that only the last modification time matters.
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(file_paths[0], &file_info));
+
+ // Set the expired file's last modification time to past max retention.
+ const base::Time expired_mod_time = base::Time::Now() -
+ kRemoteBoundWebRtcEventLogsMaxRetention -
+ base::TimeDelta::FromSeconds(1);
+ ASSERT_TRUE(base::TouchFile(file_paths[kExpired], file_info.last_accessed,
+ expired_mod_time));
+
+ // Show that the expired file is not uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {
+ WebRtcLogFileInfo(browser_context_id_, file_paths[kFresh],
+ GetLastModificationTime(file_paths[kFresh]))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Recognize the files as pending by initializing their BrowserContext.
+ LoadMainTestProfile();
+
+ WaitForPendingTasks(&run_loop);
+
+ // Both the uploaded file as well as the expired file have no been removed
+ // from local disk.
+ for (const base::FilePath& file_path : file_paths) {
+ EXPECT_FALSE(base::PathExists(file_path));
+ }
+}
+
+// TODO(crbug.com/775415): Add a test showing that a file expiring while another
+// is being uploaded, is not uploaded after the current upload is completed.
+// This is significant because Chrome might stay up for a long time.
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogEmptyStringHandledGracefully) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // By writing a log after the empty string, we show that no odd behavior is
+ // encountered, such as closing the file (an actual bug from WebRTC).
+ const std::vector<std::string> logs = {"<setup>", "", "<encore>"};
+
+ base::Optional<base::FilePath> file_path;
+
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ for (size_t i = 0; i < logs.size(); ++i) {
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, logs[i]), std::make_pair(false, true));
+ }
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectRemoteFileContents(
+ *file_path,
+ std::accumulate(std::begin(logs), std::end(logs), std::string()));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogManagerTest,
+ UnopenedRemoteLogFilesNotCountedTowardsActiveLogsLimit) {
+ std::unique_ptr<TestingProfile> browser_contexts[2];
+ std::unique_ptr<MockRenderProcessHost> rphs[2];
+ for (size_t i = 0; i < 2; ++i) {
+ browser_contexts[i] = CreateBrowserContext();
+ rphs[i] =
+ std::make_unique<MockRenderProcessHost>(browser_contexts[i].get());
+ }
+
+ constexpr size_t without_permissions = 0;
+ constexpr size_t with_permissions = 1;
+
+ // Remove write permissions from one directory.
+ const base::FilePath permissions_lacking_remote_logs_path =
+ RemoteBoundLogsDir(browser_contexts[without_permissions].get());
+ ASSERT_TRUE(base::DirectoryExists(permissions_lacking_remote_logs_path));
+ RemoveWritePermissions(permissions_lacking_remote_logs_path);
+
+ // Fail to start a log associated with the permission-lacking directory.
+ const auto without_permissions_key =
+ GetPeerConnectionKey(rphs[without_permissions].get(), 0);
+ ASSERT_TRUE(PeerConnectionAdded(without_permissions_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(without_permissions_key));
+ std::string error;
+ ASSERT_FALSE(StartRemoteLogging(without_permissions_key, nullptr, &error));
+ EXPECT_EQ(error, kStartRemoteLoggingFailureFileCreationError);
+
+ // Show that this was not counted towards the limit of active files.
+ for (int i = 0; i < kMaxActiveRemoteLogFiles; ++i) {
+ const auto with_permissions_key =
+ GetPeerConnectionKey(rphs[with_permissions].get(), i);
+ ASSERT_TRUE(PeerConnectionAdded(with_permissions_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(with_permissions_key));
+ EXPECT_TRUE(StartRemoteLogging(with_permissions_key));
+ }
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogManagerTest,
+ NoStartWebRtcSendingEventLogsWhenLocalEnabledWithoutPeerConnection) {
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ NoStartWebRtcSendingEventLogsWhenPeerConnectionButNoLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenLocalEnabledThenPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenPeerConnectionAddedThenLocalEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartWebRtcSendingEventLogsWhenRemoteLoggingEnabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenLocalLoggingStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(DisableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #1 - Local logging was the cause of the logs.
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenPeerConnectionRemoved1) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #2 - Remote logging was the cause of the logs.
+TEST_F(WebRtcEventLogManagerTest,
+ InstructWebRtcToStopSendingEventLogsWhenPeerConnectionRemoved2) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+// #1 - Local logging added first.
+TEST_F(WebRtcEventLogManagerTest,
+ SecondLoggingTargetDoesNotInitiateWebRtcLogging1) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(StartRemoteLogging(key));
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+// #2 - Remote logging added first.
+TEST_F(WebRtcEventLogManagerTest,
+ SecondLoggingTargetDoesNotInitiateWebRtcLogging2) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ DisablingLocalLoggingWhenRemoteLoggingEnabledDoesNotStopWebRtcLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+
+ // Cleanup
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ DisablingLocalLoggingAfterPcRemovalHasNoEffectOnWebRtcLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ // Setup
+ SetPeerConnectionTrackerProxyForTesting(
+ std::make_unique<PeerConnectionTrackerProxyForTesting>(this));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ExpectWebRtcStateChangeInstruction(key, true);
+
+ // Test
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ExpectWebRtcStateChangeInstruction(key, false);
+ ASSERT_TRUE(DisableLocalLogging());
+ EXPECT_TRUE(webrtc_state_change_instructions_.empty());
+}
+
+// Once a peer connection with a given key was removed, it may not again be
+// added. But, if this impossible case occurs, WebRtcEventLogManager will
+// not crash.
+TEST_F(WebRtcEventLogManagerTest, SanityOverRecreatingTheSamePeerConnection) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ OnWebRtcEventLogWrite(key, "log1");
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ OnWebRtcEventLogWrite(key, "log2");
+}
+
+// The logs would typically be binary. However, the other tests only cover ASCII
+// characters, for readability. This test shows that this is not a problem.
+TEST_F(WebRtcEventLogManagerTest, LogAllPossibleCharacters) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> local_log_file_path;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&local_log_file_path)));
+
+ base::Optional<base::FilePath> remote_log_file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&remote_log_file_path)));
+
+ ASSERT_TRUE(EnableLocalLogging());
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(local_log_file_path);
+ ASSERT_FALSE(local_log_file_path->empty());
+ ASSERT_TRUE(remote_log_file_path);
+ ASSERT_FALSE(remote_log_file_path->empty());
+
+ std::string all_chars;
+ for (size_t i = 0; i < 256; ++i) {
+ all_chars += static_cast<uint8_t>(i);
+ }
+ ASSERT_EQ(OnWebRtcEventLogWrite(key, all_chars), std::make_pair(true, true));
+
+ // Make sure the file would be closed, so that we could safely read it.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ExpectLocalFileContents(*local_log_file_path, all_chars);
+ ExpectRemoteFileContents(*remote_log_file_path, all_chars);
+}
+
+TEST_F(WebRtcEventLogManagerTest, LocalLogsClosedWhenRenderProcessHostExits) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // The expectation for OnLocalLogStopped() will be saturated by this
+ // destruction of the RenderProcessHost, which triggers an implicit
+ // removal of all PeerConnections associated with it.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(key)).Times(1);
+ rph_.reset();
+}
+
+TEST_F(WebRtcEventLogManagerTest, RemoteLogsClosedWhenRenderProcessHostExits) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ // The expectation for OnRemoteLogStopped() will be saturated by this
+ // destruction of the RenderProcessHost, which triggers an implicit
+ // removal of all PeerConnections associated with it.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ rph_.reset();
+}
+
+// Once a RenderProcessHost exits/crashes, its PeerConnections are removed,
+// which means that they can no longer suppress an upload.
+TEST_F(WebRtcEventLogManagerTest,
+ RenderProcessHostExitCanRemoveUploadSuppression) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_FALSE(file_path->empty());
+
+ // The above removal is not sufficient to trigger an upload (so the test will
+ // not be flaky). It's only once we destroy the RPH with which the suppressing
+ // PeerConnection is associated, that upload will take place.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *file_path, GetLastModificationTime(*file_path))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // We destroy the RPH without explicitly removing its PeerConnection (unlike
+ // a call to UnsuppressUploading()).
+ upload_suppressing_rph_.reset();
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionAddedOverDestroyedRphReturnsFalse) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionAdded(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionRemovedOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionRemoved(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ PeerConnectionStoppedOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ EXPECT_FALSE(PeerConnectionStopped(key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingOverDestroyedRphReturnsFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ // Test
+ rph_.reset();
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureDeadRenderProcessHost);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ OnWebRtcEventLogWriteOverDestroyedRphReturnsFalseAndFalse) {
+ // Setup - make sure the |false| returned by the function being tested is
+ // related to the RPH being dead, and not due other restrictions.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // Test
+ rph_.reset();
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTest, DifferentProfilesCanHaveDifferentPolicies) {
+ auto policy_disabled_profile =
+ CreateBrowserContext("disabled", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ false /* policy_allows_remote_logging */);
+ auto policy_disabled_rph =
+ std::make_unique<MockRenderProcessHost>(policy_disabled_profile.get());
+ const auto disabled_key =
+ GetPeerConnectionKey(policy_disabled_rph.get(), kLid);
+
+ auto policy_enabled_profile =
+ CreateBrowserContext("enabled", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ true /* policy_allows_remote_logging */);
+ auto policy_enabled_rph =
+ std::make_unique<MockRenderProcessHost>(policy_enabled_profile.get());
+ const auto enabled_key = GetPeerConnectionKey(policy_enabled_rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(disabled_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(disabled_key));
+
+ ASSERT_TRUE(PeerConnectionAdded(enabled_key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(enabled_key));
+
+ EXPECT_FALSE(StartRemoteLogging(disabled_key));
+ EXPECT_TRUE(StartRemoteLogging(enabled_key));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithTooLowWebAppIdRejected) {
+ const size_t web_app_id = kMinWebRtcEventLogWebAppId - 1;
+ ASSERT_LT(web_app_id, kMinWebRtcEventLogWebAppId); // Avoid wrap-around.
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithTooHighWebAppIdRejected) {
+ const size_t web_app_id = kMaxWebRtcEventLogWebAppId + 1;
+ ASSERT_GT(web_app_id, kMaxWebRtcEventLogWebAppId); // Avoid wrap-around.
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithInRangeWebAppIdAllowedMin) {
+ const size_t web_app_id = kMinWebRtcEventLogWebAppId;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWithInRangeWebAppIdAllowedMax) {
+ const size_t web_app_id = kMaxWebRtcEventLogWebAppId;
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0, web_app_id));
+}
+
+// Only one remote-bound event log allowed per
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingOverMultipleWebAppsDisallowed) {
+ // Test assumes there are at least two legal web-app IDs.
+ ASSERT_NE(kMinWebRtcEventLogWebAppId, kMaxWebRtcEventLogWebAppId);
+ const size_t web_app_ids[2] = {kMinWebRtcEventLogWebAppId,
+ kMaxWebRtcEventLogWebAppId};
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(StartRemoteLogging(
+ key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes, 0, web_app_ids[0]));
+ EXPECT_FALSE(StartRemoteLogging(
+ key, GetUniqueId(key), kMaxRemoteLogFileSizeBytes, 0, web_app_ids[1]));
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+ StartRemoteLoggingWebAppIdIncorporatedIntoFileName) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> file_path;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ const size_t expected_web_app_id = kWebAppId;
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key),
+ kMaxRemoteLogFileSizeBytes, 0,
+ expected_web_app_id));
+ ASSERT_TRUE(file_path);
+
+ const size_t written_web_app_id =
+ ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(*file_path);
+ EXPECT_EQ(written_web_app_id, expected_web_app_id);
+}
+
+INSTANTIATE_TEST_SUITE_P(UploadCompleteResult,
+ WebRtcEventLogManagerTest,
+ ::testing::Bool());
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextRemovesPendingFilesInRange) {
+ SuppressUploading();
+
+ auto browser_context = CreateBrowserContext("name");
+ CreatePendingLogFiles(browser_context.get());
+ auto& elements = *(pending_logs_[browser_context.get()]);
+
+ const base::Time earliest_mod = pending_earliest_mod_ - kEpsion;
+ const base::Time latest_mod = pending_latest_mod_ + kEpsion;
+
+ // Test - ClearCacheForBrowserContext() removed all of the files in the range.
+ ClearCacheForBrowserContext(browser_context.get(), earliest_mod, latest_mod);
+ for (size_t i = 0; i < elements.file_paths.size(); ++i) {
+ EXPECT_FALSE(base::PathExists(*elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextCancelsActiveLogFilesIfInRange) {
+ SuppressUploading();
+
+ // Setup
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+
+ // Test
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+ ClearCacheForBrowserContext(
+ browser_context_.get(), base::Time::Now() - base::TimeDelta::FromHours(1),
+ base::Time::Now() + base::TimeDelta::FromHours(1));
+ EXPECT_FALSE(base::PathExists(*file_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextCancelsFileUploadIfInRange) {
+ // This factory will enforce the expectation that the upload is cancelled.
+ // WebRtcEventLogUploaderImplTest.CancelOnOngoingUploadDeletesFile is in
+ // charge of making sure that when the upload is cancelled, the file is
+ // removed from disk.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory
+ // should now be satisfied.
+ ClearCacheForBrowserContext(browser_context_.get(), mod_time - kEpsion,
+ mod_time + kEpsion);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotRemovePendingFilesOutOfRange) {
+ SuppressUploading();
+
+ auto browser_context = CreateBrowserContext("name");
+ CreatePendingLogFiles(browser_context.get());
+ auto& elements = *(pending_logs_[browser_context.get()]);
+
+ // Get a range whose intersection with the files' range is empty.
+ const base::Time earliest_mod =
+ pending_earliest_mod_ - base::TimeDelta::FromHours(2);
+ const base::Time latest_mod =
+ pending_earliest_mod_ - base::TimeDelta::FromHours(1);
+ ASSERT_LT(latest_mod, pending_latest_mod_);
+
+ // Test - ClearCacheForBrowserContext() does not remove files not in range.
+ // (Range chosen to be earlier than the oldest file
+ ClearCacheForBrowserContext(browser_context.get(), earliest_mod, latest_mod);
+ for (size_t i = 0; i < elements.file_paths.size(); ++i) {
+ EXPECT_TRUE(base::PathExists(*elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelActiveLogFilesIfOutOfRange) {
+ SuppressUploading();
+
+ // Setup
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ base::Optional<base::FilePath> file_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&file_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+
+ // Test
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(_)).Times(0);
+ ClearCacheForBrowserContext(
+ browser_context_.get(), base::Time::Now() - base::TimeDelta::FromHours(2),
+ base::Time::Now() - base::TimeDelta::FromHours(1));
+ EXPECT_TRUE(base::PathExists(*file_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelFileUploadIfOutOfRange) {
+ // This factory will enforce the expectation that the upload is not cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory,
+ // that the upload will not be cancelled, should be shown to hold true.
+ // should now be satisfied.
+ ClearCacheForBrowserContext(browser_context_.get(), mod_time + kEpsion,
+ mod_time + 2 * kEpsion);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotRemovePendingFilesFromOtherProfiles) {
+ SuppressUploading();
+
+ auto cleared_browser_context = CreateBrowserContext("cleared");
+ CreatePendingLogFiles(cleared_browser_context.get());
+ auto& cleared_elements = *(pending_logs_[cleared_browser_context.get()]);
+
+ auto const uncleared_browser_context = CreateBrowserContext("pristine");
+ CreatePendingLogFiles(uncleared_browser_context.get());
+ auto& uncleared_elements = *(pending_logs_[uncleared_browser_context.get()]);
+
+ ASSERT_EQ(cleared_elements.file_paths.size(),
+ uncleared_elements.file_paths.size());
+ const size_t kFileCount = cleared_elements.file_paths.size();
+
+ const base::Time earliest_mod = pending_earliest_mod_ - kEpsion;
+ const base::Time latest_mod = pending_latest_mod_ + kEpsion;
+
+ // Test - ClearCacheForBrowserContext() only removes the files which belong
+ // to the cleared context.
+ ClearCacheForBrowserContext(cleared_browser_context.get(), earliest_mod,
+ latest_mod);
+ for (size_t i = 0; i < kFileCount; ++i) {
+ EXPECT_FALSE(base::PathExists(*cleared_elements.file_paths[i]));
+ EXPECT_TRUE(base::PathExists(*uncleared_elements.file_paths[i]));
+ }
+
+ ClearPendingLogFiles();
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelActiveLogsFromOtherProfiles) {
+ SuppressUploading();
+
+ // Remote-bound active log file that *will* be cleared.
+ auto cleared_browser_context = CreateBrowserContext("cleared");
+ auto cleared_rph =
+ std::make_unique<MockRenderProcessHost>(cleared_browser_context.get());
+ const auto cleared_key = GetPeerConnectionKey(cleared_rph.get(), kLid);
+ base::Optional<base::FilePath> cleared_file_path =
+ CreateActiveRemoteLogFile(cleared_key);
+
+ // Remote-bound active log file that will *not* be cleared.
+ auto uncleared_browser_context = CreateBrowserContext("pristine");
+ auto uncleared_rph =
+ std::make_unique<MockRenderProcessHost>(uncleared_browser_context.get());
+ const auto uncleared_key = GetPeerConnectionKey(uncleared_rph.get(), kLid);
+ base::Optional<base::FilePath> uncleared_file_path =
+ CreateActiveRemoteLogFile(uncleared_key);
+
+ // Test - ClearCacheForBrowserContext() only removes the files which belong
+ // to the cleared context.
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(cleared_key)).Times(1);
+ EXPECT_CALL(remote_observer_, OnRemoteLogStopped(uncleared_key)).Times(0);
+ ClearCacheForBrowserContext(cleared_browser_context.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_FALSE(base::PathExists(*cleared_file_path));
+ EXPECT_TRUE(base::PathExists(*uncleared_file_path));
+
+ // Cleanup - uncleared_file_path will be closed as part of the shutdown. It
+ // is time to clear its expectation.
+ testing::Mock::VerifyAndClearExpectations(&remote_observer_);
+}
+
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotCancelFileUploadFromOtherProfiles) {
+ // This factory will enforce the expectation that the upload is not cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
+
+ // Set up and trigger the uploading of a file.
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> file_path = CreatePendingRemoteLogFile(key);
+
+ ASSERT_TRUE(file_path);
+ ASSERT_TRUE(base::PathExists(*file_path));
+ const base::Time mod_time = GetLastModificationTime(*file_path);
+
+ // Main part of test - the expectation set up in the the uploader factory,
+ // that the upload will not be cancelled, should be shown to hold true.
+ // should now be satisfied.
+ auto const different_browser_context = CreateBrowserContext();
+ ClearCacheForBrowserContext(different_browser_context.get(),
+ mod_time - kEpsion, mod_time + kEpsion);
+}
+
+// Show that clearing browser cache, while it removes remote-bound logs, does
+// not interfere with local-bound logging, even if that happens on the same PC.
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ ClearCacheForBrowserContextDoesNotInterfereWithLocalLogs) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+
+ base::Optional<base::FilePath> local_log;
+ ON_CALL(local_observer_, OnLocalLogStarted(key, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&local_log)));
+ ASSERT_TRUE(EnableLocalLogging());
+
+ // This adds a peer connection for |key|, which also triggers
+ // OnLocalLogStarted() on |local_observer_|.
+ auto pending_remote_log = CreatePendingRemoteLogFile(key);
+
+ // Test focus - local logging is uninterrupted.
+ EXPECT_CALL(local_observer_, OnLocalLogStopped(_)).Times(0);
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_TRUE(base::PathExists(*local_log));
+
+ // Sanity on the test itself; the remote log should have been cleared.
+ ASSERT_FALSE(base::PathExists(*pending_remote_log));
+}
+
+// When cache clearing cancels the active upload, the next (non-deleted) pending
+// file becomes eligible for upload.
+TEST_F(WebRtcEventLogManagerTestCacheClearing,
+ UploadCancellationTriggersUploadOfNextPendingFile) {
+ // The first created file will start being uploaded, but then cancelled.
+ // The second file will never be uploaded (deleted while pending).
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true));
+
+ // Create the files that will be deleted when cache is cleared.
+ CreatePendingRemoteLogFile(GetPeerConnectionKey(rph_.get(), 0));
+ CreatePendingRemoteLogFile(GetPeerConnectionKey(rph_.get(), 1));
+
+ // Create the not-deleted file under a different profile, to easily make sure
+ // it does not fit in the ClearCacheForBrowserContext range (less fiddly than
+ // a time range).
+ auto other_browser_context = CreateBrowserContext();
+ auto other_rph =
+ std::make_unique<MockRenderProcessHost>(other_browser_context.get());
+ const auto key = GetPeerConnectionKey(other_rph.get(), kLid);
+ base::Optional<base::FilePath> other_file = CreatePendingRemoteLogFile(key);
+ ASSERT_TRUE(other_file);
+
+ // Switch the uploader factory to one that will allow us to ensure that the
+ // new file, which is not deleted, is uploaded.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {
+ WebRtcLogFileInfo(GetBrowserContextId(other_browser_context.get()),
+ *other_file, GetLastModificationTime(*other_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Clearing the cache for the first profile, should now trigger the upload
+ // of the last remaining unclear pending log file - |other_file|.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionAdded) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ EXPECT_TRUE(PeerConnectionAdded(key));
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionRemoved) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_TRUE(PeerConnectionRemoved(key));
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityPeerConnectionStopped) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ PeerConnectionStopped(key); // No crash.
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityEnableLocalLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(EnableLocalLogging());
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityDisableLocalLogging) {
+ ASSERT_TRUE(EnableLocalLogging());
+ EXPECT_TRUE(DisableLocalLogging());
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityStartRemoteLogging) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ std::string error_message;
+ EXPECT_FALSE(StartRemoteLogging(key, nullptr, &error_message));
+ EXPECT_EQ(error_message, kStartRemoteLoggingFailureFeatureDisabled);
+}
+
+TEST_P(WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ SanityOnWebRtcEventLogWrite) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcEventLogManagerTestWithRemoteLoggingDisabled,
+ ::testing::Bool());
+
+// This test is redundant; it is provided for completeness; see following tests.
+TEST_F(WebRtcEventLogManagerTestPolicy, StartsEnabledAllowsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// This test is redundant; it is provided for completeness; see following tests.
+TEST_F(WebRtcEventLogManagerTestPolicy, StartsDisabledRejectsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = false;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy, NotManagedRejectsRemoteLogging) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = false;
+ auto browser_context = CreateBrowserContext(
+ "name", false /* is_managed_profile */,
+ false /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ ManagedProfileAllowsRemoteLoggingByDefault) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ ManagedByPlatformPoliciesAllowsRemoteLoggingByDefault) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ const bool allow_remote_logging = true;
+ auto browser_context =
+ CreateBrowserContext("name", false /* is_managed_profile */,
+ true /* has_device_level_policies */, base::nullopt);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+#endif
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledRejectsRemoteLogging1) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledRejectsRemoteLogging2) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledAllowsRemoteLogging1) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+// #1 and #2 differ in the order of AddPeerConnection and the changing of
+// the pref value.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledAllowsRemoteLogging2) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ EXPECT_EQ(StartRemoteLogging(key), allow_remote_logging);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsDisabledThenEnabledUploadsPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ bool allow_remote_logging = false;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn on before StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledDoesNotUploadPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ UnsuppressUploading();
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledDeletesPendingLogFiles) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ // Make the file PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ ASSERT_TRUE(base::PathExists(*log_file)); // Test sanity; exists before.
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ WaitForPendingTasks(&run_loop);
+
+ // Test focus - file deleted without being uploaded.
+ EXPECT_FALSE(base::PathExists(*log_file));
+
+ // Still not uploaded.
+ UnsuppressUploading();
+ WaitForPendingTasks();
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ StartsEnabledThenDisabledCancelsAndDeletesCurrentlyUploadedLogFile) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ // This factory expects exactly one log to be uploaded, then cancelled.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 1));
+
+ bool allow_remote_logging = true;
+ auto profile = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ auto rph = std::make_unique<MockRenderProcessHost>(profile.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(log_file);
+
+ // Log file's upload commences.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ ASSERT_TRUE(base::PathExists(*log_file)); // Test sanity; exists before.
+
+ allow_remote_logging = !allow_remote_logging;
+ profile->GetPrefs()->SetBoolean(prefs::kWebRtcEventLogCollectionAllowed,
+ allow_remote_logging);
+
+ WaitForPendingTasks();
+
+ // Test focus - file deleted without being uploaded.
+ // When the test terminates, the NullWebRtcEventLogUploader::Factory's
+ // expectation that one log file was uploaded, and that the upload was
+ // cancelled, is enforced.
+ // Deletion of the file not performed by NullWebRtcEventLogUploader; instead,
+ // WebRtcEventLogUploaderImplTest.CancelOnOngoingUploadDeletesFile tests that.
+}
+
+// This test makes sure that if the policy was enabled in the past, but was
+// disabled while Chrome was not running, pending logs created during the
+// earlier session will be deleted from disk.
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ PendingLogsFromPreviousSessionRemovedIfPolicyDisabledAtNewSessionStart) {
+ SetUp(true); // Feature generally enabled (kill-switch not engaged).
+
+ SuppressUploading();
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 0));
+
+ bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+
+ const base::FilePath browser_context_dir =
+ RemoteBoundLogsDir(browser_context.get());
+ ASSERT_TRUE(base::DirectoryExists(browser_context_dir));
+
+ auto rph = std::make_unique<MockRenderProcessHost>(browser_context.get());
+ const auto key = GetPeerConnectionKey(rph.get(), kLid);
+
+ // Produce an empty log file in the BrowserContext. It's not uploaded
+ // because uploading is suppressed.
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(allow_remote_logging)
+ << "Must turn off after StartRemoteLogging, to test the right thing.";
+ ASSERT_EQ(StartRemoteLogging(key), allow_remote_logging);
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Reload the BrowserContext, but this time with the policy disabling
+ // the feature.
+ rph.reset();
+ browser_context.reset();
+ ASSERT_TRUE(base::DirectoryExists(browser_context_dir)); // Test sanity
+ allow_remote_logging = false;
+ browser_context = CreateBrowserContext("name", true /* is_managed_profile */,
+ false /* has_device_level_policies */,
+ allow_remote_logging);
+
+ // Test focus - pending log files removed, as well as any potential metadata
+ // associated with remote-bound logging for |browser_context|.
+ ASSERT_FALSE(base::DirectoryExists(browser_context_dir));
+
+ // When NullWebRtcEventLogUploader::Factory is destroyed, it will show that
+ // the deleted log file was never uploaded.
+ UnsuppressUploading();
+ WaitForPendingTasks();
+}
+
+TEST_F(WebRtcEventLogManagerTestPolicy,
+ PendingLogsFromPreviousSessionRemovedIfRemoteLoggingKillSwitchEngaged) {
+ SetUp(false); // Feature generally disabled (kill-switch engaged).
+
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 0));
+
+ const std::string name = "name";
+ const base::FilePath browser_context_dir =
+ profiles_dir_.GetPath().AppendASCII(name);
+ const base::FilePath remote_bound_dir =
+ RemoteBoundLogsDir(browser_context_dir);
+ ASSERT_FALSE(base::PathExists(remote_bound_dir));
+
+ base::FilePath file_path;
+ base::File file;
+ ASSERT_TRUE(base::CreateDirectory(remote_bound_dir));
+ ASSERT_TRUE(CreateRemoteBoundLogFile(remote_bound_dir, kWebAppId,
+ remote_log_extension_, base::Time::Now(),
+ &file_path, &file));
+ file.Close();
+
+ const bool allow_remote_logging = true;
+ auto browser_context = CreateBrowserContext(
+ "name", true /* is_managed_profile */,
+ false /* has_device_level_policies */, allow_remote_logging);
+ ASSERT_EQ(browser_context->GetPath(), browser_context_dir); // Test sanity
+
+ WaitForPendingTasks();
+
+ EXPECT_FALSE(base::PathExists(remote_bound_dir));
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadSuppressionDisablingFlag,
+ UploadingNotSuppressedByActivePeerConnections) {
+ SuppressUploading();
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ DoNotUploadPendingLogsIfConnectedToUnsupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ std::list<WebRtcLogFileInfo> empty_expected_files_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_expected_files_list, true, &run_loop));
+
+ // Peer connection removal MAY trigger upload, depending on network.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsIfConnectedToSupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, supported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Peer connection removal MAY trigger upload, depending on network.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsIfConnectionTypeChangesFromUnsupportedToSupported) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ // That a peer connection upload is not initiated by this point, is verified
+ // by previous tests.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+ WaitForPendingTasks();
+
+ // Test focus - an upload will be initiated after changing the network type.
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+ SetConnectionType(supported_type_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ DoNotUploadPendingLogsAtStartupIfConnectedToUnsupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, unsupported_type_);
+
+ UnloadProfileAndSeedPendingLog();
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ std::list<WebRtcLogFileInfo> empty_expected_files_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_expected_files_list, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_P(WebRtcEventLogManagerTestForNetworkConnectivity,
+ UploadPendingLogsAtStartupIfConnectedToSupportedNetworkType) {
+ SetUpNetworkConnection(get_conn_type_is_sync_, supported_type_);
+
+ UnloadProfileAndSeedPendingLog();
+
+ // This factory enforces the expectation that the files will be uploaded,
+ // all of them, only them, and in the order expected.
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files_, true, &run_loop));
+
+ LoadMainTestProfile();
+ ASSERT_EQ(browser_context_->GetPath(), browser_context_path_);
+
+ WaitForPendingTasks(&run_loop);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ UploadSupportingConnectionTypes,
+ WebRtcEventLogManagerTestForNetworkConnectivity,
+ ::testing::Combine(
+ // Wehther GetConnectionType() responds synchronously.
+ ::testing::Bool(),
+ // The upload-supporting network type to be used.
+ ::testing::Values(network::mojom::ConnectionType::CONNECTION_ETHERNET,
+ network::mojom::ConnectionType::CONNECTION_WIFI,
+ network::mojom::ConnectionType::CONNECTION_UNKNOWN),
+ // The upload-unsupporting network type to be used.
+ ::testing::Values(network::mojom::ConnectionType::CONNECTION_NONE,
+ network::mojom::ConnectionType::CONNECTION_4G)));
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay, DoNotInitiateUploadBeforeDelay) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Wait a bit and see that the upload was not initiated. (Due to technical
+ // constraints, we cannot wait forever.)
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ event.TimedWait(base::TimeDelta::FromMilliseconds(500));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+// WhenPeerConnectionRemovedFinishedRemoteLogUploadedAndFileDeleted has some
+// overlap with this, but we still include this test for explicitness and
+// clarity.
+TEST_F(WebRtcEventLogManagerTestUploadDelay, InitiateUploadAfterDelay) {
+ SetUp(kDefaultUploadDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_file);
+
+ base::RunLoop run_loop;
+ std::list<WebRtcLogFileInfo> expected_files = {WebRtcLogFileInfo(
+ browser_context_id_, *log_file, GetLastModificationTime(*log_file))};
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &expected_files, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay,
+ PeerConnectionAddedDuringDelaySuppressesUpload) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key1 = GetPeerConnectionKey(rph_.get(), 1);
+ const auto key2 = GetPeerConnectionKey(rph_.get(), 2);
+
+ ASSERT_TRUE(PeerConnectionAdded(key1));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key1));
+ ASSERT_TRUE(StartRemoteLogging(key1));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key1));
+
+ // Test focus - after adding a peer connection, the conditions for the upload
+ // are no longer considered to hold.
+ // (Test implemented with a glimpse into the black box due to technical
+ // limitations and the desire to avoid flakiness.)
+ ASSERT_TRUE(PeerConnectionAdded(key2));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key2));
+ EXPECT_FALSE(UploadConditionsHold());
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestUploadDelay,
+ ClearCacheForBrowserContextDuringDelayCancelsItsUpload) {
+ SetUp(kIntentionallyExcessiveDelayMs);
+
+ const auto key = GetPeerConnectionKey(rph_.get(), 1);
+
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key));
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Change log file from ACTIVE to PENDING.
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ // Test focus - after clearing browser cache, the conditions for the upload
+ // are no longer considered to hold, because the file about to be uploaded
+ // was deleted.
+ // (Test implemented with a glimpse into the black box due to technical
+ // limitations and the desire to avoid flakiness.)
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ EXPECT_FALSE(UploadConditionsHold());
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestCompression,
+ ErroredFilesDueToBadEstimationDeletedRatherThanUploaded) {
+ Init(Compression::GZIP_NULL_ESTIMATION);
+
+ const std::string log = "It's better than bad; it's good.";
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ base::Optional<base::FilePath> log_file;
+ ON_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_TRUE(StartRemoteLogging(key, GetUniqueId(key), GzippedSize(log) - 1, 0,
+ kWebAppId));
+ ASSERT_TRUE(log_file);
+
+ std::list<WebRtcLogFileInfo> empty_list;
+ base::RunLoop run_loop;
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+ &empty_list, true, &run_loop));
+
+ // Writing fails because the budget is exceeded.
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, log), std::make_pair(false, false));
+
+ // The file was deleted due to the error we've instigated (by using an
+ // intentionally over-optimistic estimation).
+ EXPECT_FALSE(base::PathExists(*log_file));
+
+ // If the file is incorrectly still eligible for an upload, this will trigger
+ // the upload (which will be a test failure).
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ WaitForPendingTasks(&run_loop);
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ NoRemoteBoundLogsDirectoryCreatedWhenProfileLoaded) {
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(incognito_profile_);
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito, StartRemoteLoggingFails) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ StartRemoteLoggingDoesNotCreateDirectoryOrFiles) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+
+ const base::FilePath remote_logs_path =
+ RemoteBoundLogsDir(incognito_profile_);
+ EXPECT_FALSE(base::DirectoryExists(remote_logs_path));
+}
+
+TEST_F(WebRtcEventLogManagerTestIncognito,
+ OnWebRtcEventLogWriteReturnsFalseForRemotePart) {
+ const auto key = GetPeerConnectionKey(incognito_rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ ASSERT_FALSE(StartRemoteLogging(key));
+ EXPECT_EQ(OnWebRtcEventLogWrite(key, "log"), std::make_pair(false, false));
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ CorrectHistoryReturnedForActivelyWrittenLog) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory, CorrectHistoryReturnedForPendingLog) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ CorrectHistoryReturnedForActivelyUploadedLog) {
+ // This factory expects exactly one log to be uploaded; cancellation is
+ // expected during tear-down.
+ SetWebRtcEventLogUploaderFactoryForTesting(
+ std::make_unique<NullWebRtcEventLogUploader::Factory>(true, 1));
+
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(path);
+ ASSERT_FALSE(path->BaseName().MaybeAsASCII().empty());
+
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::Pending);
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.capture_time, base::Time::Now()));
+ EXPECT_EQ(history_entry.local_id, path->BaseName().MaybeAsASCII());
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(IsSmallTimeDelta(history_entry.upload_time, base::Time::Now()));
+ EXPECT_LE(history_entry.capture_time, history_entry.upload_time);
+
+ // Test tear down - trigger uploader cancellation.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+}
+
+// See ExpiredLogFilesAreReplacedByHistoryFiles for verification of the
+// creation of history files of this type.
+TEST_F(WebRtcEventLogManagerTestHistory,
+ ExpiredLogFilesReplacedByHistoryFilesAndGetHistoryReportsAccordingly) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ UnloadMainTestProfile();
+
+ // Test sanity.
+ ASSERT_TRUE(base::PathExists(*log_path));
+
+ // Pretend more time than kRemoteBoundWebRtcEventLogsMaxRetention has passed.
+ const base::TimeDelta elapsed_time =
+ kRemoteBoundWebRtcEventLogsMaxRetention + base::TimeDelta::FromHours(1);
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(*log_path, &file_info));
+
+ const auto modified_capture_time = file_info.last_modified - elapsed_time;
+ ASSERT_TRUE(base::TouchFile(*log_path, file_info.last_accessed - elapsed_time,
+ modified_capture_time));
+
+ LoadMainTestProfile();
+
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ const auto history = GetHistory(browser_context_id_);
+ ASSERT_EQ(history.size(), 1u);
+ const auto history_entry = history[0];
+
+ EXPECT_EQ(history_entry.state, UploadList::UploadInfo::State::NotUploaded);
+ EXPECT_TRUE(IsSameTimeWhenTruncatedToSeconds(history_entry.capture_time,
+ modified_capture_time));
+ EXPECT_EQ(history_entry.local_id,
+ ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(*log_path));
+ EXPECT_TRUE(history_entry.upload_id.empty());
+ EXPECT_TRUE(history_entry.upload_time.is_null());
+}
+
+// Since the uploader mocks do not write the history files, it is not easy
+// to check that the correct result is returned for GetHistory() for either
+// a successful or an unsuccessful upload from the WebRtcEventLogManager level.
+// Instead, this is checked by WebRtcEventLogUploaderImplTest.
+// TODO(crbug.com/775415): Add the tests mention in the comment above.
+
+TEST_F(WebRtcEventLogManagerTestHistory, ClearingCacheRemovesHistoryFiles) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ SuppressUploading();
+ ASSERT_TRUE(PeerConnectionRemoved(key));
+
+ UnloadMainTestProfile();
+
+ // Test sanity.
+ ASSERT_TRUE(base::PathExists(*log_path));
+
+ // Pretend more time than kRemoteBoundWebRtcEventLogsMaxRetention has passed.
+ const base::TimeDelta elapsed_time =
+ kRemoteBoundWebRtcEventLogsMaxRetention + base::TimeDelta::FromHours(1);
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(*log_path, &file_info));
+
+ const auto modified_capture_time = file_info.last_modified - elapsed_time;
+ ASSERT_TRUE(base::TouchFile(*log_path, file_info.last_accessed - elapsed_time,
+ modified_capture_time));
+
+ LoadMainTestProfile();
+
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ // Setup complete; we now have a history file on disk. Time to see that it is
+ // removed when cache is cleared.
+
+ // Sanity.
+ const auto history_path = GetWebRtcEventLogHistoryFilePath(*log_path);
+ ASSERT_TRUE(base::PathExists(history_path));
+ ASSERT_EQ(GetHistory(browser_context_id_).size(), 1u);
+
+ // Test.
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ ASSERT_FALSE(base::PathExists(history_path));
+ ASSERT_EQ(GetHistory(browser_context_id_).size(), 0u);
+}
+
+TEST_F(WebRtcEventLogManagerTestHistory,
+ ClearingCacheDoesNotLeaveBehindHistoryForRemovedLogs) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+
+ base::Optional<base::FilePath> log_path;
+ EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _, _))
+ .Times(1)
+ .WillOnce(Invoke(SaveFilePathTo(&log_path)));
+ ASSERT_TRUE(StartRemoteLogging(key));
+ ASSERT_TRUE(log_path);
+ ASSERT_FALSE(log_path->BaseName().MaybeAsASCII().empty());
+
+ ASSERT_TRUE(base::PathExists(*log_path));
+ ClearCacheForBrowserContext(browser_context_.get(), base::Time::Min(),
+ base::Time::Max());
+ ASSERT_FALSE(base::PathExists(*log_path));
+
+ const auto history = GetHistory(browser_context_id_);
+ EXPECT_EQ(history.size(), 0u);
+}
+
+// TODO(crbug.com/775415): Add a test for the limit on the number of history
+// files allowed to remain on disk.
+
+#else // defined(OS_ANDROID)
+
+class WebRtcEventLogManagerTestOnMobileDevices
+ : public WebRtcEventLogManagerTestBase {
+ public:
+ WebRtcEventLogManagerTestOnMobileDevices() {
+ // features::kWebRtcRemoteEventLog not defined on mobile, and can therefore
+ // not be forced on. This test is here to make sure that when the feature
+ // is changed to be on by default, it will still be off for mobile devices.
+ CreateWebRtcEventLogManager();
+ }
+};
+
+TEST_F(WebRtcEventLogManagerTestOnMobileDevices, RemoteBoundLoggingDisabled) {
+ const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+ ASSERT_TRUE(PeerConnectionAdded(key));
+ ASSERT_TRUE(PeerConnectionSessionIdSet(key));
+ EXPECT_FALSE(StartRemoteLogging(key));
+}
+
+#endif
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
new file mode 100644
index 00000000000..30fb9113e19
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc_event_logging {
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+ WebRtcEventLogCompression compression) {
+ switch (compression) {
+ case WebRtcEventLogCompression::NONE:
+ return std::make_unique<BaseLogFileWriterFactory>();
+ case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION:
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<NullEstimator::Factory>()));
+ case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+ return std::make_unique<GzippedLogFileWriterFactory>(
+ std::make_unique<GzipLogCompressorFactory>(
+ std::make_unique<PerfectGzipEstimator::Factory>()));
+ }
+ NOTREACHED();
+ return nullptr; // Appease compiler.
+}
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path) {
+ int permissions;
+ ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
+ constexpr int write_permissions = base::FILE_PERMISSION_WRITE_BY_USER |
+ base::FILE_PERMISSION_WRITE_BY_GROUP |
+ base::FILE_PERMISSION_WRITE_BY_OTHERS;
+ permissions &= ~write_permissions;
+ ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
+}
+#endif // defined(OS_POSIX)
+
+std::unique_ptr<CompressedSizeEstimator> NullEstimator::Factory::Create()
+ const {
+ return std::make_unique<NullEstimator>();
+}
+
+size_t NullEstimator::EstimateCompressedSize(const std::string& input) const {
+ return 0;
+}
+
+std::unique_ptr<CompressedSizeEstimator> PerfectGzipEstimator::Factory::Create()
+ const {
+ return std::make_unique<PerfectGzipEstimator>();
+}
+
+PerfectGzipEstimator::PerfectGzipEstimator() {
+ // This factory will produce an optimistic compressor that will always
+ // think it can compress additional inputs, which will therefore allow
+ // us to find out what the real compressed size it, since compression
+ // will never be suppressed.
+ GzipLogCompressorFactory factory(std::make_unique<NullEstimator::Factory>());
+
+ compressor_ = factory.Create(base::Optional<size_t>());
+ DCHECK(compressor_);
+
+ std::string ignored;
+ compressor_->CreateHeader(&ignored);
+}
+
+PerfectGzipEstimator::~PerfectGzipEstimator() = default;
+
+size_t PerfectGzipEstimator::EstimateCompressedSize(
+ const std::string& input) const {
+ std::string output;
+ EXPECT_EQ(compressor_->Compress(input, &output), LogCompressor::Result::OK);
+ return output.length();
+}
+
+size_t GzippedSize(const std::string& uncompressed) {
+ PerfectGzipEstimator perfect_estimator;
+ return kGzipOverheadBytes +
+ perfect_estimator.EstimateCompressedSize(uncompressed);
+}
+
+size_t GzippedSize(const std::vector<std::string>& uncompressed) {
+ PerfectGzipEstimator perfect_estimator;
+
+ size_t result = kGzipOverheadBytes;
+ for (const std::string& str : uncompressed) {
+ result += perfect_estimator.EstimateCompressedSize(str);
+ }
+
+ return result;
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
new file mode 100644
index 00000000000..0fe270ef634
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+namespace webrtc_event_logging {
+
+// Which type of compression, if any, LogFileWriterTest should use.
+enum class WebRtcEventLogCompression {
+ NONE,
+ GZIP_NULL_ESTIMATION,
+ GZIP_PERFECT_ESTIMATION
+};
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+ WebRtcEventLogCompression compression);
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path);
+#endif // defined(OS_POSIX)
+
+// Always estimates strings to be compressed to zero bytes.
+class NullEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ ~NullEstimator() override = default;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Provides a perfect estimation of the compressed size by cheating - performing
+// actual compression, then reporting the resulting size.
+// This class is stateful; the number, nature and order of calls to
+// EstimateCompressedSize() is important.
+class PerfectGzipEstimator : public CompressedSizeEstimator {
+ public:
+ class Factory : public CompressedSizeEstimator::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<CompressedSizeEstimator> Create() const override;
+ };
+
+ PerfectGzipEstimator();
+
+ ~PerfectGzipEstimator() override;
+
+ size_t EstimateCompressedSize(const std::string& input) const override;
+
+ private:
+ // This compressor allows EstimateCompressedSize to return an exact estimate.
+ // EstimateCompressedSize is normally const, but here we fake it, so we set
+ // it as mutable.
+ mutable std::unique_ptr<LogCompressor> compressor_;
+};
+
+// Check the gzipped size of |uncompressed|, including header and footer,
+// assuming it were gzipped on its own.
+size_t GzippedSize(const std::string& uncompressed);
+
+// Same as other version, but with elements compressed in sequence.
+size_t GzippedSize(const std::vector<std::string>& uncompressed);
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
new file mode 100644
index 00000000000..17f542efa75
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
@@ -0,0 +1,383 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "ui/base/text/bytes_formatting.h"
+
+namespace webrtc_event_logging {
+
+namespace {
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+const char kUploadContentType[] = "multipart/form-data";
+const char kBoundary[] = "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
+
+constexpr size_t kExpectedMimeOverheadBytes = 1000; // Intentional overshot.
+
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+#if defined(OS_WIN)
+const char kProduct[] = "Chrome";
+#elif defined(OS_MACOSX)
+const char kProduct[] = "Chrome_Mac";
+#elif defined(OS_LINUX)
+const char kProduct[] = "Chrome_Linux";
+#elif defined(OS_ANDROID)
+const char kProduct[] = "Chrome_Android";
+#elif defined(OS_CHROMEOS)
+const char kProduct[] = "Chrome_ChromeOS";
+#else
+#error Platform not supported.
+#endif
+
+constexpr net::NetworkTrafficAnnotationTag
+ kWebrtcEventLogUploaderTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("webrtc_event_log_uploader", R"(
+ semantics {
+ sender: "WebRTC Event Log uploader module"
+ description:
+ "Uploads a WebRTC event log to a server called Crash. These logs "
+ "will not contain private information. They will be used to "
+ "improve WebRTC (fix bugs, tune performance, etc.)."
+ trigger:
+ "A Google service (e.g. Hangouts/Meet) has requested a peer "
+ "connection to be logged, and the resulting event log to be uploaded "
+ "at a time deemed to cause the least interference to the user (i.e., "
+ "when the user is not busy making other VoIP calls)."
+ data:
+ "WebRTC events such as the timing of audio playout (but not the "
+ "content), timing and size of RTP packets sent/received, etc."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "Feature controlled only through Chrome policy; "
+ "no user-facing control surface."
+ chrome_policy {
+ WebRtcEventLogCollectionAllowed {
+ WebRtcEventLogCollectionAllowed: false
+ }
+ }
+ })");
+
+void AddFileContents(const char* filename,
+ const std::string& file_contents,
+ const std::string& content_type,
+ std::string* post_data) {
+ // net::AddMultipartValueForUpload does almost what we want to do here, except
+ // that it does not add the "filename" attribute. We hack it to force it to.
+ std::string mime_value_name =
+ base::StringPrintf("%s\"; filename=\"%s\"", filename, filename);
+ net::AddMultipartValueForUpload(mime_value_name, file_contents, kBoundary,
+ content_type, post_data);
+}
+
+std::string MimeContentType() {
+ const char kBoundaryKeywordAndMisc[] = "; boundary=";
+
+ std::string content_type;
+ content_type.reserve(sizeof(content_type) + sizeof(kBoundaryKeywordAndMisc) +
+ sizeof(kBoundary));
+
+ content_type.append(kUploadContentType);
+ content_type.append(kBoundaryKeywordAndMisc);
+ content_type.append(kBoundary);
+
+ return content_type;
+}
+
+void BindURLLoaderFactoryReceiver(
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory>
+ url_loader_factory_receiver) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory =
+ g_browser_process->shared_url_loader_factory();
+ DCHECK(shared_url_loader_factory);
+ shared_url_loader_factory->Clone(std::move(url_loader_factory_receiver));
+}
+
+void OnURLLoadUploadProgress(uint64_t current, uint64_t total) {
+ ui::DataUnits unit = ui::GetByteDisplayUnits(total);
+ VLOG(1) << "WebRTC event log upload progress: "
+ << FormatBytesWithUnits(current, unit, false) << " / "
+ << FormatBytesWithUnits(total, unit, true) << ".";
+}
+} // namespace
+
+const char WebRtcEventLogUploaderImpl::kUploadURL[] =
+ "https://clients2.google.com/cr/report";
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::Create(const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), kMaxRemoteLogFileSizeBytes);
+}
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::CreateWithCustomMaxSizeForTesting(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), max_log_file_size_bytes);
+}
+
+WebRtcEventLogUploaderImpl::WebRtcEventLogUploaderImpl(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes)
+ : log_file_(log_file),
+ callback_(std::move(callback)),
+ max_log_file_size_bytes_(max_log_file_size_bytes),
+ io_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+ history_file_writer_ = WebRtcEventLogHistoryFileWriter::Create(
+ GetWebRtcEventLogHistoryFilePath(log_file_.path));
+ if (!history_file_writer_) {
+ // File either could not be created, or, if a different error occurred,
+ // Create() will have tried to remove the file it has created.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileCreationError);
+ ReportResult(false);
+ return;
+ }
+
+ const base::Time now = std::max(base::Time::Now(), log_file.last_modified);
+ if (!history_file_writer_->WriteCaptureTime(log_file.last_modified) ||
+ !history_file_writer_->WriteUploadTime(now)) {
+ LOG(ERROR) << "Writing to history file failed.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileWriteError);
+ DeleteHistoryFile(); // Avoid partial, potentially-corrupt history files.
+ ReportResult(false);
+ return;
+ }
+
+ std::string upload_data;
+ if (!PrepareUploadData(&upload_data)) {
+ // History file will reflect a failed upload attempt.
+ ReportResult(false); // UMA recorded by PrepareUploadData().
+ return;
+ }
+
+ StartUpload(upload_data);
+}
+
+WebRtcEventLogUploaderImpl::~WebRtcEventLogUploaderImpl() {
+ // WebRtcEventLogUploaderImpl objects' deletion scenarios:
+ // 1. Upload started and finished - |url_loader_| should have been reset
+ // so that we would be able to DCHECK and demonstrate that the determinant
+ // is maintained.
+ // 2. Upload started and cancelled - behave similarly to a finished upload.
+ // 3. The upload was never started, due to an early failure (e.g. file not
+ // found). In that case, |url_loader_| will not have been set.
+ // 4. Chrome shutdown.
+ if (io_task_runner_->RunsTasksInCurrentSequence()) { // Scenarios 1-3.
+ DCHECK(!url_loader_);
+ } else { // # Scenario #4 - Chrome shutdown.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ bool will_delete =
+ io_task_runner_->DeleteSoon(FROM_HERE, url_loader_.release());
+ DCHECK(!will_delete)
+ << "Task runners must have been stopped by this stage of shutdown.";
+ }
+}
+
+const WebRtcLogFileInfo& WebRtcEventLogUploaderImpl::GetWebRtcLogFileInfo()
+ const {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ return log_file_;
+}
+
+bool WebRtcEventLogUploaderImpl::Cancel() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // The upload could already have been completed, or maybe was never properly
+ // started (due to a file read failure, etc.).
+ const bool upload_was_active = (url_loader_.get() != nullptr);
+
+ // Note that in this case, it might still be that the last bytes hit the
+ // wire right as we attempt to cancel the upload. OnURLFetchComplete, however,
+ // will not be called.
+ url_loader_.reset();
+
+ DeleteLogFile();
+ DeleteHistoryFile();
+
+ if (upload_was_active) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kUploadCancelled);
+ }
+
+ return upload_was_active;
+}
+
+bool WebRtcEventLogUploaderImpl::PrepareUploadData(std::string* upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ std::string log_file_contents;
+ if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
+ max_log_file_size_bytes_)) {
+ LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileReadError);
+ return false;
+ }
+
+ DCHECK(upload_data->empty());
+ upload_data->reserve(log_file_contents.size() + kExpectedMimeOverheadBytes);
+
+ const std::string filename_str = log_file_.path.BaseName().MaybeAsASCII();
+ if (filename_str.empty()) {
+ LOG(WARNING) << "Log filename is not according to acceptable format.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileNameError);
+ return false;
+ }
+
+ const char* filename = filename_str.c_str();
+
+ net::AddMultipartValueForUpload("prod", kProduct, kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("ver",
+ version_info::GetVersionNumber() + "-webrtc",
+ kBoundary, std::string(), upload_data);
+ net::AddMultipartValueForUpload("guid", "0", kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("type", filename, kBoundary, std::string(),
+ upload_data);
+ AddFileContents(filename, log_file_contents, "application/log", upload_data);
+ net::AddMultipartFinalDelimiterForUpload(kBoundary, upload_data);
+
+ return true;
+}
+
+void WebRtcEventLogUploaderImpl::StartUpload(const std::string& upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GURL(kUploadURL);
+ resource_request->method = "POST";
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ // Create a new mojo pipe. It's safe to pass this around and use
+ // immediately, even though it needs to finish initialization on the UI
+ // thread.
+ network::mojom::URLLoaderFactoryPtr url_loader_factory_ptr;
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(BindURLLoaderFactoryReceiver,
+ mojo::MakeRequest(&url_loader_factory_ptr)));
+
+ url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), kWebrtcEventLogUploaderTrafficAnnotation);
+ url_loader_->AttachStringForUpload(upload_data, MimeContentType());
+ url_loader_->SetOnUploadProgressCallback(
+ base::BindRepeating(OnURLLoadUploadProgress));
+
+ // See comment in destructor for an explanation about why using
+ // base::Unretained(this) is safe here.
+ url_loader_->DownloadToString(
+ url_loader_factory_ptr.get(),
+ base::BindOnce(&WebRtcEventLogUploaderImpl::OnURLLoadComplete,
+ base::Unretained(this)),
+ kWebRtcEventLogMaxUploadIdBytes);
+}
+
+void WebRtcEventLogUploaderImpl::OnURLLoadComplete(
+ std::unique_ptr<std::string> response_body) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(url_loader_);
+
+ if (response_body.get() != nullptr && response_body->empty()) {
+ LOG(WARNING) << "SimpleURLLoader reported upload successful, "
+ << "but report ID unknown.";
+ }
+
+ const bool upload_successful =
+ (response_body.get() != nullptr && !response_body->empty());
+
+ // NetError() is 0 when no error occurred.
+ UmaRecordWebRtcEventLoggingNetErrorType(url_loader_->NetError());
+
+ DCHECK(history_file_writer_);
+ if (upload_successful) {
+ if (!history_file_writer_->WriteUploadId(*response_body)) {
+ // Discard the incomplete, potentially now corrupt history file, but the
+ // upload is still considered successful.
+ LOG(ERROR) << "Failed to write upload ID to history file.";
+ DeleteHistoryFile();
+ }
+ } else {
+ LOG(WARNING) << "Upload unsuccessful.";
+ // By not writing an UploadId to the history file, it is inferrable that
+ // the upload was initiated, but did not end successfully.
+ }
+
+ UmaRecordWebRtcEventLoggingUpload(
+ upload_successful ? WebRtcEventLoggingUploadUma::kSuccess
+ : WebRtcEventLoggingUploadUma::kUploadFailure);
+
+ url_loader_.reset(); // Explicitly maintain determinant.
+
+ ReportResult(upload_successful);
+}
+
+void WebRtcEventLogUploaderImpl::ReportResult(bool result) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // * If the upload was successful, the file is no longer needed.
+ // * If the upload failed, we don't want to retry, because we run the risk of
+ // uploading significant amounts of data once again, only for the upload to
+ // fail again after (as an example) wasting 50MBs of upload bandwidth.
+ // * If the file was not found, this will simply have no effect (other than
+ // to LOG() an error).
+ // TODO(crbug.com/775415): Provide refined retrial behavior.
+ DeleteLogFile();
+
+ // Release hold of history file, allowing it to be read, moved or deleted.
+ history_file_writer_.reset();
+
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), log_file_.path, result));
+}
+
+void WebRtcEventLogUploaderImpl::DeleteLogFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ const bool deletion_successful =
+ base::DeleteFile(log_file_.path, /*recursive=*/false);
+ if (!deletion_successful) {
+ // This is a somewhat serious (though unlikely) error, because now we'll
+ // try to upload this file again next time Chrome launches.
+ LOG(ERROR) << "Could not delete pending WebRTC event log file.";
+ }
+}
+
+void WebRtcEventLogUploaderImpl::DeleteHistoryFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (!history_file_writer_) {
+ LOG(ERROR) << "Deletion of history file attempted after uploader "
+ << "has relinquished ownership of it.";
+ return;
+ }
+ history_file_writer_->Delete();
+ history_file_writer_.reset();
+}
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
new file mode 100644
index 00000000000..f0699503180
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
@@ -0,0 +1,148 @@
+// Copyright 2018 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
+
+namespace network {
+class SimpleURLLoader;
+} // namespace network
+
+namespace webrtc_event_logging {
+
+// A sublcass of this interface will take ownership of a file, and either
+// upload it to a remote server (actual implementation), or pretend to do so
+// (in unit tests). Upon completion, success/failure will be reported by posting
+// an UploadResultCallback task to the task queue on which this object lives.
+class WebRtcEventLogUploader {
+ public:
+ using UploadResultCallback =
+ base::OnceCallback<void(const base::FilePath& log_file,
+ bool upload_successful)>;
+
+ // Since we'll need more than one instance of the abstract
+ // WebRtcEventLogUploader, we'll need an abstract factory for it.
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ // Creates uploaders. The observer is passed to each call of Create,
+ // rather than be memorized by the factory's constructor, because factories
+ // created by unit tests have no visibility into the real implementation's
+ // observer (WebRtcRemoteEventLogManager).
+ // This takes ownership of the file. The caller must not attempt to access
+ // the file after invoking Create().
+ virtual std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) = 0;
+ };
+
+ virtual ~WebRtcEventLogUploader() = default;
+
+ // Getter for the details of the file this uploader is handling.
+ // Can be called for ongoing, completed, failed or cancelled uploads.
+ virtual const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const = 0;
+
+ // Cancels the upload, then deletes the log file and its history file.
+ // Returns true if the upload was cancelled due to this call, and false if
+ // the upload was already completed or aborted before this call.
+ // (Aborted uploads are ones where the file could not be read, etc.)
+ virtual bool Cancel() = 0;
+};
+
+// Primary implementation of WebRtcEventLogUploader. Uploads log files to crash.
+// Deletes log files whether they were successfully uploaded or not.
+class WebRtcEventLogUploaderImpl : public WebRtcEventLogUploader {
+ public:
+ class Factory : public WebRtcEventLogUploader::Factory {
+ public:
+ ~Factory() override = default;
+
+ std::unique_ptr<WebRtcEventLogUploader> Create(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) override;
+
+ protected:
+ friend class WebRtcEventLogUploaderImplTest;
+
+ std::unique_ptr<WebRtcEventLogUploader> CreateWithCustomMaxSizeForTesting(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_remote_log_file_size_bytes);
+ };
+
+ WebRtcEventLogUploaderImpl(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_remote_log_file_size_bytes);
+ ~WebRtcEventLogUploaderImpl() override;
+
+ const WebRtcLogFileInfo& GetWebRtcLogFileInfo() const override;
+
+ bool Cancel() override;
+
+ private:
+ friend class WebRtcEventLogUploaderImplTest;
+
+ // Primes the log file for uploading. Returns true if the file could be read,
+ // in which case |upload_data| will be populated with the data to be uploaded
+ // (both the log file's contents as well as history for Crash).
+ // TODO(crbug.com/775415): Avoid reading the entire file into memory.
+ bool PrepareUploadData(std::string* upload_data);
+
+ // Initiates the file's upload.
+ void StartUpload(const std::string& upload_data);
+
+ // Callback invoked when the file upload has finished.
+ // If the |url_loader_| instance it was bound to is deleted before
+ // its invocation, the callback will not be called.
+ void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
+
+ // Cleanup and posting of the result callback.
+ void ReportResult(bool result);
+
+ // Remove the log file which is owned by |this|.
+ void DeleteLogFile();
+
+ // Remove the log file which is owned by |this|.
+ void DeleteHistoryFile();
+
+ // The URL used for uploading the logs.
+ static const char kUploadURL[];
+
+ // Housekeeping information about the uploaded file (path, time of last
+ // modification, associated BrowserContext).
+ const WebRtcLogFileInfo log_file_;
+
+ // Callback posted back to signal success or failure.
+ UploadResultCallback callback_;
+
+ // Maximum allowed file size. In production code, this is a hard-coded,
+ // but unit tests may set other values.
+ const size_t max_log_file_size_bytes_;
+
+ // Owns a history file which allows the state of the uploaded log to be
+ // remembered after it has been uploaded and/or deleted.
+ std::unique_ptr<WebRtcEventLogHistoryFileWriter> history_file_writer_;
+
+ // This object is in charge of the actual upload.
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+ // The object lives on this IO-capable task runner.
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+};
+
+} // namespace webrtc_event_logging
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_UPLOADER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc
new file mode 100644
index 00000000000..853cc085998
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc_event_logging {
+
+using ::testing::StrictMock;
+using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
+
+namespace {
+class UploadObserver {
+ public:
+ explicit UploadObserver(base::OnceClosure on_complete_callback)
+ : on_complete_callback_(std::move(on_complete_callback)) {}
+
+ // Combines the mock functionality via a helper (CompletionCallback),
+ // as well as unblocks its owner through |on_complete_callback_|.
+ void OnWebRtcEventLogUploadComplete(const base::FilePath& log_file,
+ bool upload_successful) {
+ CompletionCallback(log_file, upload_successful);
+ std::move(on_complete_callback_).Run();
+ }
+
+ MOCK_METHOD2(CompletionCallback, void(const base::FilePath&, bool));
+
+ private:
+ base::OnceClosure on_complete_callback_;
+};
+
+#if defined(OS_POSIX)
+void RemovePermissions(const base::FilePath& path, int removed_permissions) {
+ int permissions;
+ ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
+ permissions &= ~removed_permissions;
+ ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
+}
+
+void RemoveReadPermissions(const base::FilePath& path) {
+ constexpr int read_permissions = base::FILE_PERMISSION_READ_BY_USER |
+ base::FILE_PERMISSION_READ_BY_GROUP |
+ base::FILE_PERMISSION_READ_BY_OTHERS;
+ RemovePermissions(path, read_permissions);
+}
+#endif // defined(OS_POSIX)
+} // namespace
+
+class WebRtcEventLogUploaderImplTest : public ::testing::Test {
+ public:
+ WebRtcEventLogUploaderImplTest()
+ : test_shared_url_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ observer_run_loop_(),
+ observer_(observer_run_loop_.QuitWhenIdleClosure()) {
+ TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
+ test_shared_url_loader_factory_);
+
+ EXPECT_TRUE(base::Time::FromString("30 Dec 1983", &kReasonableTime));
+
+ uploader_factory_ = std::make_unique<WebRtcEventLogUploaderImpl::Factory>();
+ }
+
+ ~WebRtcEventLogUploaderImplTest() override {
+ task_environment_.RunUntilIdle();
+ }
+
+ void SetUp() override {
+ testing_profile_manager_ = std::make_unique<TestingProfileManager>(
+ TestingBrowserProcess::GetGlobal());
+ EXPECT_TRUE(profiles_dir_.CreateUniqueTempDir());
+ EXPECT_TRUE(testing_profile_manager_->SetUp(profiles_dir_.GetPath()));
+
+ testing_profile_ =
+ testing_profile_manager_->CreateTestingProfile("arbitrary_name");
+
+ browser_context_id_ = GetBrowserContextId(testing_profile_);
+
+ // Create the sub-dir for the remote-bound logs that would have been set
+ // up by WebRtcEventLogManager, if WebRtcEventLogManager were instantiated.
+ // Note that the testing profile's overall directory is a temporary one.
+ const base::FilePath logs_dir =
+ GetRemoteBoundWebRtcEventLogsDir(testing_profile_->GetPath());
+ ASSERT_TRUE(base::CreateDirectory(logs_dir));
+
+ // Create a log file and put some arbitrary data in it.
+ // Note that the testing profile's overall directory is a temporary one.
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(logs_dir, &log_file_));
+ constexpr size_t kLogFileSizeBytes = 100u;
+ const std::string file_contents(kLogFileSizeBytes, 'A');
+ ASSERT_EQ(
+ base::WriteFile(log_file_, file_contents.c_str(), file_contents.size()),
+ static_cast<int>(file_contents.size()));
+ }
+
+ // For tests which imitate a response (or several).
+ void SetURLLoaderResponse(net::HttpStatusCode http_code, int net_error) {
+ DCHECK(test_shared_url_loader_factory_);
+ const std::string kResponseId = "ec1ed029734b8f7e"; // Arbitrary.
+ test_url_loader_factory_.AddResponse(
+ GURL(WebRtcEventLogUploaderImpl::kUploadURL),
+ network::CreateURLResponseHead(http_code), kResponseId,
+ network::URLLoaderCompletionStatus(net_error));
+ }
+
+ void StartAndWaitForUpload(
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->Create(log_file_info, ResultCallback());
+
+ observer_run_loop_.Run(); // Observer was given quit-closure by ctor.
+ }
+
+ void StartAndWaitForUploadWithCustomMaxSize(
+ size_t max_log_size_bytes,
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->CreateWithCustomMaxSizeForTesting(
+ log_file_info, ResultCallback(), max_log_size_bytes);
+
+ observer_run_loop_.Run(); // Observer was given quit-closure by ctor.
+ }
+
+ void StartUploadThatWillNotTerminate(
+ BrowserContextId browser_context_id = BrowserContextId(),
+ base::Time last_modified_time = base::Time()) {
+ DCHECK(test_shared_url_loader_factory_);
+
+ if (last_modified_time.is_null()) {
+ last_modified_time = kReasonableTime;
+ }
+
+ const WebRtcLogFileInfo log_file_info(browser_context_id, log_file_,
+ last_modified_time);
+
+ uploader_ = uploader_factory_->Create(log_file_info, ResultCallback());
+ }
+
+ WebRtcEventLogUploader::UploadResultCallback ResultCallback() {
+ return base::BindOnce(&UploadObserver::OnWebRtcEventLogUploadComplete,
+ base::Unretained(&observer_));
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+
+ base::Time kReasonableTime;
+
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_;
+
+ base::RunLoop observer_run_loop_;
+
+ base::ScopedTempDir profiles_dir_;
+ std::unique_ptr<TestingProfileManager> testing_profile_manager_;
+ TestingProfile* testing_profile_; // |testing_profile_manager_| owns.
+ BrowserContextId browser_context_id_;
+
+ base::FilePath log_file_;
+
+ StrictMock<UploadObserver> observer_;
+
+ // These (uploader-factory and uploader) are the units under test.
+ std::unique_ptr<WebRtcEventLogUploaderImpl::Factory> uploader_factory_;
+ std::unique_ptr<WebRtcEventLogUploader> uploader_;
+};
+
+TEST_F(WebRtcEventLogUploaderImplTest, SuccessfulUploadReportedToObserver) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+// Version #1 - request reported as successful, but got an error (404) as the
+// HTTP return code.
+// Due to the simplicitly of both tests, this also tests the scenario
+// FileDeletedAfterUnsuccessfulUpload, rather than giving each its own test.
+TEST_F(WebRtcEventLogUploaderImplTest, UnsuccessfulUploadReportedToObserver1) {
+ SetURLLoaderResponse(net::HTTP_NOT_FOUND, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+// Version #2 - request reported as failed; HTTP return code ignored, even
+// if it's a purported success.
+TEST_F(WebRtcEventLogUploaderImplTest, UnsuccessfulUploadReportedToObserver2) {
+ SetURLLoaderResponse(net::HTTP_NOT_FOUND, net::ERR_FAILED);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+#if defined(OS_POSIX)
+TEST_F(WebRtcEventLogUploaderImplTest, FailureToReadFileReportedToObserver) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ RemoveReadPermissions(log_file_);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, NonExistentFileReportedToObserver) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ log_file_ = log_file_.Append(FILE_PATH_LITERAL("garbage"));
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(WebRtcEventLogUploaderImplTest, FilesUpToMaxSizeUploaded) {
+ int64_t log_file_size_bytes;
+ ASSERT_TRUE(base::GetFileSize(log_file_, &log_file_size_bytes));
+
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUploadWithCustomMaxSize(log_file_size_bytes);
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, ExcessivelyLargeFilesNotUploaded) {
+ int64_t log_file_size_bytes;
+ ASSERT_TRUE(base::GetFileSize(log_file_, &log_file_size_bytes));
+
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUploadWithCustomMaxSize(log_file_size_bytes - 1);
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ CancelBeforeUploadCompletionReturnsTrue) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ EXPECT_TRUE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnCancelledUploadReturnsFalse) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ ASSERT_TRUE(uploader_->Cancel());
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ CancelAfterUploadCompletionReturnsFalse) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+ StartAndWaitForUpload();
+
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnAbortedUploadReturnsFalse) {
+ // Show the failure was independent of the URLLoaderFactory's primed return
+ // value.
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+
+ log_file_ = log_file_.Append(FILE_PATH_LITERAL("garbage"));
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, false)).Times(1);
+ StartAndWaitForUpload();
+
+ EXPECT_FALSE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest, CancelOnOngoingUploadDeletesFile) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+ ASSERT_TRUE(uploader_->Cancel());
+
+ EXPECT_FALSE(base::PathExists(log_file_));
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoBeforeUploadDone) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+
+ // Test tear-down.
+ ASSERT_TRUE(uploader_->Cancel());
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoAfterUploadSucceeded) {
+ SetURLLoaderResponse(net::HTTP_OK, net::OK);
+ EXPECT_CALL(observer_, CompletionCallback(log_file_, true)).Times(1);
+
+ const base::Time last_modified = base::Time::Now();
+ StartAndWaitForUpload(browser_context_id_, last_modified);
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+}
+
+TEST_F(WebRtcEventLogUploaderImplTest,
+ GetWebRtcLogFileInfoReturnsCorrectInfoWhenCalledOnCancelledUpload) {
+ const base::Time last_modified = base::Time::Now();
+ StartUploadThatWillNotTerminate(browser_context_id_, last_modified);
+ ASSERT_TRUE(uploader_->Cancel());
+
+ const WebRtcLogFileInfo info = uploader_->GetWebRtcLogFileInfo();
+ EXPECT_EQ(info.browser_context_id, browser_context_id_);
+ EXPECT_EQ(info.path, log_file_);
+ EXPECT_EQ(info.last_modified, last_modified);
+}
+
+// TODO(crbug.com/775415): Add a unit test that shows that files with
+// non-ASCII filenames are discard. (Or, alternatively, add support for them.)
+
+} // namespace webrtc_event_logging
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
new file mode 100644
index 00000000000..44027f50260
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -0,0 +1,196 @@
+// Copyright 2018 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 <string>
+
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+namespace {
+
+static const char kMainHtmlPage[] = "/webrtc/webrtc_getdisplaymedia_test.html";
+
+struct TestConfig {
+ const char* display_surface;
+ const char* logical_surface;
+ const char* cursor;
+};
+
+} // namespace
+
+// Base class for top level tests for getDisplayMedia().
+class WebRtcGetDisplayMediaBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void RunGetDisplayMedia(content::WebContents* tab,
+ const std::string& constraints,
+ bool is_fake_ui = false) {
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(),
+ base::StringPrintf("runGetDisplayMedia(%s);", constraints.c_str()),
+ &result));
+#if defined(OS_MACOSX)
+ // Starting from macOS 10.15, screen capture requires system permissions
+ // that are disabled by default. The permission is reported as granted
+ // if the fake UI is used.
+ EXPECT_EQ(result, base::mac::IsAtMostOS10_14() || is_fake_ui
+ ? "getdisplaymedia-success"
+ : "getdisplaymedia-failure");
+#else
+ EXPECT_EQ(result, "getdisplaymedia-success");
+#endif
+ }
+};
+
+// Top level test for getDisplayMedia(). Pops picker Ui and selects desktop
+// capture by default.
+class WebRtcGetDisplayMediaBrowserTestWithPicker
+ : public WebRtcGetDisplayMediaBrowserTest {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(
+ switches::kEnableExperimentalWebPlatformFeatures);
+ command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
+ "Entire screen");
+ }
+};
+
+// Real desktop capture is flaky on below platforms.
+#if defined(OS_CHROMEOS) || defined(OS_WIN)
+#define MAYBE_GetDisplayMediaVideo DISABLED_GetDisplayMediaVideo
+#else
+#define MAYBE_GetDisplayMediaVideo GetDisplayMediaVideo
+#endif // defined(OS_CHROMEOS) || defined(OS_WIN)
+IN_PROC_BROWSER_TEST_F(WebRtcGetDisplayMediaBrowserTestWithPicker,
+ MAYBE_GetDisplayMediaVideo) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true}");
+ RunGetDisplayMedia(tab, constraints);
+}
+
+// Real desktop capture is flaky on below platforms.
+#if defined(OS_CHROMEOS) || defined(OS_WIN)
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+// On linux debug bots, it's flaky as well.
+#elif (defined(OS_LINUX) && !defined(NDEBUG))
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+// On linux asan bots, it's flaky as well - msan and other rel bot are fine.
+#elif (defined(OS_LINUX) && defined(ADDRESS_SANITIZER))
+#define MAYBE_GetDisplayMediaVideoAndAudio DISABLED_GetDisplayMediaVideoAndAudio
+#else
+#define MAYBE_GetDisplayMediaVideoAndAudio GetDisplayMediaVideoAndAudio
+#endif // defined(OS_CHROMEOS) || defined(OS_WIN)
+IN_PROC_BROWSER_TEST_F(WebRtcGetDisplayMediaBrowserTestWithPicker,
+ MAYBE_GetDisplayMediaVideoAndAudio) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true, audio:true}");
+ RunGetDisplayMedia(tab, constraints);
+}
+
+// Top level test for getDisplayMedia(). Skips picker UI and uses fake device
+// with specified type.
+class WebRtcGetDisplayMediaBrowserTestWithFakeUI
+ : public WebRtcGetDisplayMediaBrowserTest,
+ public testing::WithParamInterface<TestConfig> {
+ public:
+ WebRtcGetDisplayMediaBrowserTestWithFakeUI() {
+ test_config_ = GetParam();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(
+ switches::kEnableExperimentalWebPlatformFeatures);
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ base::StringPrintf("display-media-type=%s",
+ test_config_.display_surface));
+ }
+
+ protected:
+ TestConfig test_config_;
+};
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaVideo) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true}");
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getDisplaySurfaceSetting();", &result));
+ EXPECT_EQ(result, test_config_.display_surface);
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getLogicalSurfaceSetting();", &result));
+ EXPECT_EQ(result, test_config_.logical_surface);
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getCursorSetting();", &result));
+ EXPECT_EQ(result, test_config_.cursor);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaVideoAndAudio) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ std::string constraints("{video:true, audio:true}");
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "hasAudioTrack();", &result));
+ EXPECT_EQ(result, "true");
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ GetDisplayMediaWithConstraints) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
+ const int kMaxWidth = 200;
+ const int kMaxFrameRate = 6;
+ const std::string& constraints =
+ base::StringPrintf("{video: {width: {max: %d}, frameRate: {max: %d}}}",
+ kMaxWidth, kMaxFrameRate);
+ RunGetDisplayMedia(tab, constraints, /*is_fake_ui=*/true);
+
+ std::string result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getWidthSetting();", &result));
+ EXPECT_EQ(result, base::StringPrintf("%d", kMaxWidth));
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+ tab->GetMainFrame(), "getFrameRateSetting();", &result));
+ EXPECT_EQ(result, base::StringPrintf("%d", kMaxFrameRate));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetDisplayMediaBrowserTestWithFakeUI,
+ testing::Values(TestConfig{"monitor", "true", "never"},
+ TestConfig{"window", "true", "never"},
+ TestConfig{"browser", "true",
+ "never"}));
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc
new file mode 100644
index 00000000000..b5fea966103
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_getmediadevices_browsertest.cc
@@ -0,0 +1,360 @@
+// Copyright 2014 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/string_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+namespace {
+
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+
+const char kDeviceKindAudioInput[] = "audioinput";
+const char kDeviceKindVideoInput[] = "videoinput";
+const char kDeviceKindAudioOutput[] = "audiooutput";
+
+} // namespace
+
+// Integration test for WebRTC enumerateDevices. It always uses fake devices.
+// It needs to be a browser test (and not content browser test) to be able to
+// test that labels are cleared or not depending on if access to devices has
+// been granted.
+class WebRtcGetMediaDevicesBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<bool> {
+ public:
+ WebRtcGetMediaDevicesBrowserTest()
+ : has_audio_output_devices_initialized_(false),
+ has_audio_output_devices_(false) {
+ std::vector<base::Feature> audio_service_oop_features = {
+ features::kAudioServiceAudioStreams,
+ features::kAudioServiceOutOfProcess};
+ if (GetParam()) {
+ // Force audio service out of process to enabled.
+ audio_service_features_.InitWithFeatures(audio_service_oop_features, {});
+ } else {
+ // Force audio service out of process to disabled.
+ audio_service_features_.InitWithFeatures({}, audio_service_oop_features);
+ }
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Always use fake devices.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ protected:
+ // This is used for media devices and sources.
+ struct MediaDeviceInfo {
+ std::string device_id; // Domain specific device ID.
+ std::string kind;
+ std::string label;
+ std::string group_id;
+ };
+
+ void EnumerateDevices(content::WebContents* tab,
+ std::vector<MediaDeviceInfo>* devices) {
+ std::string devices_as_json = ExecuteJavascript("enumerateDevices()", tab);
+ EXPECT_FALSE(devices_as_json.empty());
+
+ int error_code;
+ std::string error_message;
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::ReadAndReturnErrorDeprecated(
+ devices_as_json, base::JSON_ALLOW_TRAILING_COMMAS, &error_code,
+ &error_message);
+
+ ASSERT_TRUE(value.get() != NULL) << error_message;
+ EXPECT_EQ(value->type(), base::Value::Type::LIST);
+
+ base::ListValue* values;
+ ASSERT_TRUE(value->GetAsList(&values));
+ ASSERT_FALSE(values->empty());
+ bool found_audio_input = false;
+ bool found_video_input = false;
+
+ for (auto it = values->begin(); it != values->end(); ++it) {
+ const base::DictionaryValue* dict;
+ MediaDeviceInfo device;
+ ASSERT_TRUE(it->GetAsDictionary(&dict));
+ ASSERT_TRUE(dict->GetString("deviceId", &device.device_id));
+ ASSERT_TRUE(dict->GetString("kind", &device.kind));
+ ASSERT_TRUE(dict->GetString("label", &device.label));
+ ASSERT_TRUE(dict->GetString("groupId", &device.group_id));
+
+ // Should be HMAC SHA256.
+ if (!media::AudioDeviceDescription::IsDefaultDevice(device.device_id) &&
+ !(device.device_id ==
+ media::AudioDeviceDescription::kCommunicationsDeviceId)) {
+ EXPECT_EQ(64ul, device.device_id.length());
+ EXPECT_TRUE(
+ base::ContainsOnlyChars(device.device_id, "0123456789abcdef"));
+ }
+
+ EXPECT_TRUE(device.kind == kDeviceKindAudioInput ||
+ device.kind == kDeviceKindVideoInput ||
+ device.kind == kDeviceKindAudioOutput);
+ if (device.kind == kDeviceKindAudioInput) {
+ found_audio_input = true;
+ } else if (device.kind == kDeviceKindVideoInput) {
+ found_video_input = true;
+ }
+
+ EXPECT_FALSE(device.group_id.empty());
+ devices->push_back(device);
+ }
+
+ EXPECT_TRUE(found_audio_input);
+ EXPECT_TRUE(found_video_input);
+ }
+
+ static void CheckEnumerationsAreDifferent(
+ const std::vector<MediaDeviceInfo>& devices,
+ const std::vector<MediaDeviceInfo>& devices2) {
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ if (device.device_id == media::AudioDeviceDescription::kDefaultDeviceId ||
+ device.device_id ==
+ media::AudioDeviceDescription::kCommunicationsDeviceId) {
+ EXPECT_NE(it, devices2.end());
+ } else {
+ EXPECT_EQ(it, devices2.end());
+ }
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+ }
+
+ bool has_audio_output_devices_initialized_;
+ bool has_audio_output_devices_;
+
+ private:
+ base::test::ScopedFeatureList audio_service_features_;
+};
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ EnumerateDevicesWithoutAccess) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ // Labels should be empty if access has not been allowed.
+ for (const auto& device_info : devices) {
+ EXPECT_TRUE(device_info.label.empty());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ EnumerateDevicesWithAccess) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ EXPECT_TRUE(GetUserMediaAndAccept(tab));
+
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ // Labels should be non-empty if access has been allowed.
+ for (const auto& device_info : devices) {
+ EXPECT_TRUE(!device_info.label.empty());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdSameGroupIdDiffersAfterReload) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ EXPECT_NE(it, devices2.end());
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdSameGroupIdDiffersAcrossTabs) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab1 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab1, &devices);
+
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab2 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab2, &devices2);
+
+ EXPECT_NE(tab1, tab2);
+ EXPECT_EQ(devices.size(), devices2.size());
+ for (auto& device : devices) {
+ auto it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.device_id == device_info.device_id;
+ });
+ EXPECT_NE(it, devices2.end());
+
+ it = std::find_if(devices2.begin(), devices2.end(),
+ [&device](const MediaDeviceInfo& device_info) {
+ return device.group_id == device_info.group_id;
+ });
+ EXPECT_EQ(it, devices2.end());
+ }
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersAfterClearingCookies) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ auto* remover =
+ content::BrowserContext::GetBrowsingDataRemover(browser()->profile());
+ content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
+ remover->RemoveAndReply(
+ base::Time(), base::Time::Max(),
+ content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+ content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
+ &completion_observer);
+ completion_observer.BlockUntilCompletion();
+
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersAcrossTabsWithCookiesDisabled) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
+ content::WebContents* tab1 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab1, &devices);
+
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab2 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab2, &devices2);
+
+ EXPECT_NE(tab1, tab2);
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+IN_PROC_BROWSER_TEST_P(WebRtcGetMediaDevicesBrowserTest,
+ DeviceIdDiffersSameTabAfterReloadWithCookiesDisabled) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ CookieSettingsFactory::GetForProfile(browser()->profile())
+ ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices;
+ EnumerateDevices(tab, &devices);
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ tab = browser()->tab_strip_model()->GetActiveWebContents();
+ std::vector<MediaDeviceInfo> devices2;
+ EnumerateDevices(tab, &devices2);
+
+ EXPECT_EQ(devices.size(), devices2.size());
+ CheckEnumerationsAreDifferent(devices, devices2);
+}
+
+// We run these tests with the audio service both in and out of the the browser
+// process to have waterfall coverage while the feature rolls out. It should be
+// removed after launch.
+#if defined(OS_WIN) || defined(OS_MACOSX) || \
+ (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// Platforms where the out of process audio service is supported.
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetMediaDevicesBrowserTest,
+ ::testing::Values(true));
+#else
+// Platforms where the out of process audio service is not supported.
+INSTANTIATE_TEST_SUITE_P(,
+ WebRtcGetMediaDevicesBrowserTest,
+ ::testing::Values(false));
+#endif
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc
new file mode 100644
index 00000000000..3c49d176b84
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_internals_integration_browsertest.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2018 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/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using webrtc_event_logging::WebRtcEventLogManager;
+
+namespace {
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+}
+
+class WebRTCInternalsIntegrationBrowserTest : public WebRtcTestBase {
+ public:
+ ~WebRTCInternalsIntegrationBrowserTest() override = default;
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
+
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(local_logs_dir_.CreateUniqueTempDir());
+ }
+ command_line->AppendSwitchASCII(switches::kWebRtcLocalEventLogging,
+ local_logs_dir_.GetPath().MaybeAsASCII());
+ }
+
+ // To avoid flaky tests, we need to synchronize with WebRtcEventLogger's
+ // internal task runners (if any exist) before we examine anything we
+ // expect to be produced by WebRtcEventLogger (files, etc.).
+ void WaitForEventLogProcessing() {
+ WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
+ ASSERT_TRUE(manager);
+
+ base::RunLoop run_loop;
+ manager->PostNullTaskForTesting(run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ }
+
+ bool IsDirectoryEmpty(const base::FilePath& path) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ return base::IsDirectoryEmpty(path);
+ }
+
+ base::ScopedTempDir local_logs_dir_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsIntegrationBrowserTest,
+ IntegrationWithWebRtcEventLogger) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ ASSERT_TRUE(IsDirectoryEmpty(local_logs_dir_.GetPath())); // Sanity on test.
+
+ // Local WebRTC event logging turned on from command line using the
+ // kWebRtcLocalEventLogging flag. When we set up a peer connection, it
+ // will be logged to a file under |local_logs_dir_|.
+ SetupPeerconnectionWithLocalStream(tab);
+
+ WaitForEventLogProcessing();
+
+ EXPECT_FALSE(IsDirectoryEmpty(local_logs_dir_.GetPath()));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc
new file mode 100644
index 00000000000..08e26798891
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_internals_perf_browsertest.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 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 <memory>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_perf.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_test.h"
+#include "third_party/blink/public/common/features.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+std::string MakePerfTestLabel(std::string base, bool opus_dtx) {
+ if (opus_dtx) {
+ return base + "_with_opus_dtx";
+ }
+ return base;
+}
+
+// Performance browsertest for WebRTC. This test is manual since it takes long
+// to execute and requires the reference files provided by the webrtc.DEPS
+// solution (which is only available on WebRTC internal bots).
+// Gets its metrics from "chrome://webrtc-internals".
+class WebRtcInternalsPerfBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Play a suitable, somewhat realistic video file.
+ base::FilePath input_video = test::GetReferenceFilesDir()
+ .Append(test::kReferenceFileName360p)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ input_video);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ // Tries to extract data from peerConnectionDataStore in the webrtc-internals
+ // tab. The caller owns the parsed data. Returns NULL on failure.
+ base::DictionaryValue* GetWebrtcInternalsData(
+ content::WebContents* webrtc_internals_tab) {
+ std::string all_stats_json = ExecuteJavascript(
+ "window.domAutomationController.send("
+ " JSON.stringify(peerConnectionDataStore));",
+ webrtc_internals_tab);
+
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(all_stats_json);
+ base::DictionaryValue* result;
+ if (parsed_json.get() && parsed_json->GetAsDictionary(&result)) {
+ ignore_result(parsed_json.release());
+ return result;
+ }
+
+ return NULL;
+ }
+
+ const base::DictionaryValue* GetDataOnPeerConnection(
+ const base::DictionaryValue* all_data,
+ int peer_connection_index) {
+ base::DictionaryValue::Iterator iterator(*all_data);
+
+ for (int i = 0; i < peer_connection_index && !iterator.IsAtEnd();
+ --peer_connection_index) {
+ iterator.Advance();
+ }
+
+ const base::DictionaryValue* result;
+ if (!iterator.IsAtEnd() && iterator.value().GetAsDictionary(&result))
+ return result;
+
+ return NULL;
+ }
+
+ std::unique_ptr<base::DictionaryValue> MeasureWebRtcInternalsData(
+ int duration_msec) {
+ chrome::AddTabAt(browser(), GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser(), GURL("chrome://webrtc-internals"));
+ content::WebContents* webrtc_internals_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // TODO(https://crbug.com/1004239): Stop relying on the legacy getStats()
+ // API.
+ ChangeToLegacyGetStats(webrtc_internals_tab);
+ test::SleepInJavascript(webrtc_internals_tab, duration_msec);
+
+ return std::unique_ptr<base::DictionaryValue>(
+ GetWebrtcInternalsData(webrtc_internals_tab));
+ }
+
+ void RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ const std::string& video_codec,
+ bool prefer_hw_video_codec = false,
+ const std::string& video_codec_profile = std::string(),
+ const std::string& video_codec_print_modifier = std::string()) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ SetDefaultVideoCodec(right_tab, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(left_tab, "remote-view");
+ StartDetectingVideo(right_tab, "remote-view");
+
+ WaitForVideoToPlay(left_tab);
+ WaitForVideoToPlay(right_tab);
+
+ // Let values stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab, 60000);
+
+ // Start measurements.
+ std::unique_ptr<base::DictionaryValue> all_data =
+ MeasureWebRtcInternalsData(10000);
+ ASSERT_TRUE(all_data.get() != NULL);
+
+ const base::DictionaryValue* first_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 0);
+ ASSERT_TRUE(first_pc_dict != NULL);
+ const std::string print_modifier = video_codec_print_modifier.empty()
+ ? video_codec
+ : video_codec_print_modifier;
+ test::PrintBweForVideoMetrics(*first_pc_dict, "", print_modifier);
+ test::PrintMetricsForAllStreams(*first_pc_dict, "", print_modifier);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ }
+
+ void RunsOneWayCall60SecsAndLogsInternalMetrics(
+ const std::string& video_codec,
+ bool opus_dtx) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ content::WebContents* right_tab =
+ OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithoutLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, false /* prefer_hw_codec */);
+ SetDefaultVideoCodec(right_tab, video_codec, false /* prefer_hw_codec */);
+ }
+ if (opus_dtx) {
+ EnableOpusDtx(left_tab);
+ EnableOpusDtx(right_tab);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ // Remote video will only play in one tab since the call is one-way.
+ StartDetectingVideo(right_tab, "remote-view");
+ WaitForVideoToPlay(right_tab);
+
+ // Let values stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab, 60000);
+
+ std::unique_ptr<base::DictionaryValue> all_data =
+ MeasureWebRtcInternalsData(10000);
+ ASSERT_TRUE(all_data.get() != NULL);
+
+ // This assumes the sending peer connection is always listed first in the
+ // data store, and the receiving second.
+ const base::DictionaryValue* first_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 0);
+ ASSERT_TRUE(first_pc_dict != NULL);
+ test::PrintBweForVideoMetrics(
+ *first_pc_dict, MakePerfTestLabel("_sendonly", opus_dtx), video_codec);
+ test::PrintMetricsForSendStreams(
+ *first_pc_dict, MakePerfTestLabel("_sendonly", opus_dtx), video_codec);
+
+ const base::DictionaryValue* second_pc_dict =
+ GetDataOnPeerConnection(all_data.get(), 1);
+ ASSERT_TRUE(second_pc_dict != NULL);
+ test::PrintBweForVideoMetrics(
+ *second_pc_dict, MakePerfTestLabel("_recvonly", opus_dtx), video_codec);
+ test::PrintMetricsForRecvStreams(
+ *second_pc_dict, MakePerfTestLabel("_recvonly", opus_dtx), video_codec);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ }
+};
+
+// This is manual for its long execution time.
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics("VP9");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsVp9Profile2) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ "VP9", true /* prefer_hw_video_codec */,
+ WebRtcTestBase::kVP9Profile2Specifier, "VP9p2");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsH264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING)
+ << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcInternalsPerfBrowserTest."
+ "MANUAL_RunsAudioVideoCall60SecsAndLogsInternalMetricsH264 (test "
+ "\"OK\")";
+ return;
+ }
+ RunsAudioVideoCall60SecsAndLogsInternalMetrics(
+ "H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsOneWayCall60SecsAndLogsInternalMetricsDefault) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsOneWayCall60SecsAndLogsInternalMetrics("", false);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcInternalsPerfBrowserTest,
+ MANUAL_RunsOneWayCall60SecsAndLogsInternalMetricsWithOpusDtx) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsOneWayCall60SecsAndLogsInternalMetrics("", true);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc
new file mode 100644
index 00000000000..14e87d6621d
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.cc
@@ -0,0 +1,42 @@
+// Copyright 2019 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 "chrome/browser/media/webrtc/webrtc_log_buffer.h"
+
+#include "base/logging.h"
+
+WebRtcLogBuffer::WebRtcLogBuffer()
+ : buffer_(),
+ circular_(&buffer_[0], sizeof(buffer_), sizeof(buffer_) / 2, false),
+ read_only_(false) {}
+
+WebRtcLogBuffer::~WebRtcLogBuffer() {
+#if DCHECK_IS_ON()
+ DCHECK(read_only_ || sequence_checker_.CalledOnValidSequence());
+#endif
+}
+
+void WebRtcLogBuffer::Log(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!read_only_);
+ circular_.Write(message.c_str(), message.length());
+ const char eol = '\n';
+ circular_.Write(&eol, 1);
+}
+
+webrtc_logging::PartialCircularBuffer WebRtcLogBuffer::Read() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(read_only_);
+ return webrtc_logging::PartialCircularBuffer(&buffer_[0], sizeof(buffer_));
+}
+
+void WebRtcLogBuffer::SetComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!read_only_) << "Already set? (programmer error)";
+ read_only_ = true;
+ // Detach from the current sequence so that we can check reads on a different
+ // sequence. This is to make sure that Read()s still happen on one sequence
+ // only.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h
new file mode 100644
index 00000000000..66b3c283f60
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_buffer.h
@@ -0,0 +1,47 @@
+// Copyright 2019 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
+
+#include <string>
+
+#include "base/sequence_checker.h"
+#include "build/build_config.h"
+#include "components/webrtc_logging/common/partial_circular_buffer.h"
+
+#if defined(OS_ANDROID)
+const size_t kWebRtcLogSize = 1 * 1024 * 1024; // 1 MB
+#else
+const size_t kWebRtcLogSize = 6 * 1024 * 1024; // 6 MB
+#endif
+
+class WebRtcLogBuffer {
+ public:
+ WebRtcLogBuffer();
+ ~WebRtcLogBuffer();
+
+ void Log(const std::string& message);
+
+ // Returns a circular buffer instance for reading the internal log buffer.
+ // Must only be called after the log has been marked as complete
+ // (see SetComplete) and the caller must ensure that the WebRtcLogBuffer
+ // instance remains in scope for the lifetime of the returned circular buffer.
+ webrtc_logging::PartialCircularBuffer Read();
+
+ // Switches the buffer to read-only mode, where access to the internal
+ // buffer is allowed from different threads than were used to contribute
+ // to the log. Calls to Log() won't be allowed after calling
+ // SetComplete() and the call to SetComplete() must be done on the same
+ // thread as constructed the buffer and calls Log().
+ void SetComplete();
+
+ private:
+ SEQUENCE_CHECKER(sequence_checker_);
+ uint8_t buffer_[kWebRtcLogSize];
+ webrtc_logging::PartialCircularBuffer circular_;
+ bool read_only_;
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_BUFFER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc
new file mode 100644
index 00000000000..1b386f4a803
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.cc
@@ -0,0 +1,633 @@
+// 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 "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+
+#include <stddef.h>
+#include <cstdlib>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/pickle.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "components/version_info/version_info.h"
+#include "components/webrtc_logging/browser/log_cleanup.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "components/webrtc_logging/common/partial_circular_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+const int kLogCountLimit = 5;
+const uint32_t kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB
+const int kLogListLimitLines = 50;
+
+const char kWebrtcLogUploadContentType[] = "multipart/form-data";
+const char kWebrtcLogMultipartBoundary[] =
+ "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
+
+// Adds the header section for a gzip file to the multipart |post_data|.
+void AddMultipartFileContentHeader(std::string* post_data,
+ const std::string& content_name) {
+ post_data->append("--");
+ post_data->append(kWebrtcLogMultipartBoundary);
+ post_data->append("\r\nContent-Disposition: form-data; name=\"");
+ post_data->append(content_name);
+ post_data->append("\"; filename=\"");
+ post_data->append(content_name + ".gz");
+ post_data->append("\"\r\nContent-Type: application/gzip\r\n\r\n");
+}
+
+// Adds |compressed_log| to |post_data|.
+void AddLogData(std::string* post_data, const std::string& compressed_log) {
+ AddMultipartFileContentHeader(post_data, "webrtc_log");
+ post_data->append(compressed_log);
+ post_data->append("\r\n");
+}
+
+// Adds the RTP dump data to |post_data|.
+void AddRtpDumpData(std::string* post_data,
+ const std::string& name,
+ const std::string& dump_data) {
+ AddMultipartFileContentHeader(post_data, name);
+ post_data->append(dump_data.data(), dump_data.size());
+ post_data->append("\r\n");
+}
+
+// Helper for WebRtcLogUploader::CompressLog().
+void ResizeForNextOutput(std::string* compressed_log, z_stream* stream) {
+ size_t old_size = compressed_log->size() - stream->avail_out;
+ compressed_log->resize(old_size + kIntermediateCompressionBufferBytes);
+ stream->next_out =
+ reinterpret_cast<unsigned char*>(&(*compressed_log)[old_size]);
+ stream->avail_out = kIntermediateCompressionBufferBytes;
+}
+
+} // namespace
+
+WebRtcLogUploader::UploadDoneData::UploadDoneData() = default;
+WebRtcLogUploader::UploadDoneData::UploadDoneData(
+ const WebRtcLogUploader::UploadDoneData& other) = default;
+WebRtcLogUploader::UploadDoneData::~UploadDoneData() = default;
+
+WebRtcLogUploader::WebRtcLogUploader()
+ : main_task_runner_(base::SequencedTaskRunnerHandle::Get()),
+ background_task_runner_(
+ base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT})) {}
+
+WebRtcLogUploader::~WebRtcLogUploader() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(pending_uploads_.empty());
+ DCHECK(shutdown_);
+}
+
+bool WebRtcLogUploader::ApplyForStartLogging() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ if (log_count_ < kLogCountLimit && !shutdown_) {
+ ++log_count_;
+ return true;
+ }
+ return false;
+}
+
+void WebRtcLogUploader::LoggingStoppedDontUpload() {
+ DecreaseLogCount();
+}
+
+void WebRtcLogUploader::LoggingStoppedDoUpload(
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(log_buffer.get());
+ DCHECK(meta_data.get());
+ DCHECK(!upload_done_data.paths.directory.empty());
+
+ std::string compressed_log = CompressLog(log_buffer.get());
+
+ std::string local_log_id;
+
+ if (base::PathExists(upload_done_data.paths.directory)) {
+ webrtc_logging::DeleteOldWebRtcLogFiles(upload_done_data.paths.directory);
+
+ local_log_id = base::NumberToString(base::Time::Now().ToDoubleT());
+ base::FilePath log_file_path =
+ upload_done_data.paths.directory.AppendASCII(local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+ WriteCompressedLogToFile(compressed_log, log_file_path);
+
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ upload_done_data.paths.directory);
+ AddLocallyStoredLogInfoToUploadListFile(log_list_path, local_log_id);
+ }
+
+ UploadDoneData upload_done_data_with_log_id = upload_done_data;
+ upload_done_data_with_log_id.local_log_id = local_log_id;
+ PrepareMultipartPostData(compressed_log, std::move(meta_data),
+ upload_done_data_with_log_id);
+}
+
+void WebRtcLogUploader::PrepareMultipartPostData(
+ const std::string& compressed_log,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!compressed_log.empty());
+ DCHECK(meta_data.get());
+
+ std::unique_ptr<std::string> post_data(new std::string());
+ SetupMultipart(post_data.get(), compressed_log,
+ upload_done_data.paths.incoming_rtp_dump,
+ upload_done_data.paths.outgoing_rtp_dump, *meta_data.get());
+
+ // If a test has set the test string pointer, write to it and skip uploading.
+ // Still fire the upload callback so that we can run an extension API test
+ // using the test framework for that without hanging.
+ // TODO(grunell): Remove this when the api test for this feature is fully
+ // implemented according to the test plan. http://crbug.com/257329.
+ if (post_data_) {
+ *post_data_ = *post_data;
+ NotifyUploadDoneAndLogStats(net::HTTP_OK, net::OK, "", upload_done_data);
+ return;
+ }
+
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::UploadCompressedLog,
+ base::Unretained(this), upload_done_data,
+ std::move(post_data)));
+}
+
+void WebRtcLogUploader::UploadStoredLog(
+ const WebRtcLogUploader::UploadDoneData& upload_data) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!upload_data.local_log_id.empty());
+ DCHECK(!upload_data.paths.directory.empty());
+
+ base::FilePath native_log_path =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ std::string compressed_log;
+ if (!base::ReadFileToString(native_log_path, &compressed_log)) {
+ DPLOG(WARNING) << "Could not read WebRTC log file.";
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed",
+ upload_data.web_app_id);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kStoredLogNotFound);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(upload_data.callback, false, "", "Log doesn't exist."));
+ return;
+ }
+
+ UploadDoneData upload_data_with_rtp = upload_data;
+
+ // Optimistically set the rtp paths to what they should be if they exist.
+ upload_data_with_rtp.paths.incoming_rtp_dump =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".rtp_in"));
+
+ upload_data_with_rtp.paths.outgoing_rtp_dump =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".rtp_out"));
+
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data(new WebRtcLogMetaDataMap());
+ {
+ std::string meta_data_contents;
+ base::FilePath meta_path =
+ upload_data.paths.directory.AppendASCII(upload_data.local_log_id)
+ .AddExtension(FILE_PATH_LITERAL(".meta"));
+ if (base::ReadFileToString(meta_path, &meta_data_contents) &&
+ !meta_data_contents.empty()) {
+ base::Pickle pickle(&meta_data_contents[0], meta_data_contents.size());
+ base::PickleIterator it(pickle);
+ std::string key, value;
+ while (it.ReadString(&key) && it.ReadString(&value))
+ (*meta_data.get())[key] = value;
+ }
+ }
+
+ PrepareMultipartPostData(compressed_log, std::move(meta_data),
+ upload_data_with_rtp);
+}
+
+void WebRtcLogUploader::LoggingStoppedDoStore(
+ const WebRtcLogPaths& log_paths,
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& done_callback) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!log_id.empty());
+ DCHECK(log_buffer.get());
+ DCHECK(!log_paths.directory.empty());
+
+ webrtc_logging::DeleteOldWebRtcLogFiles(log_paths.directory);
+
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ log_paths.directory);
+
+ // Store the native log with a ".gz" extension.
+ std::string compressed_log = CompressLog(log_buffer.get());
+ base::FilePath native_log_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".gz"));
+ WriteCompressedLogToFile(compressed_log, native_log_path);
+ AddLocallyStoredLogInfoToUploadListFile(log_list_path, log_id);
+
+ // Move the rtp dump files to the log directory with a name of
+ // <log id>.rtp_[in|out].
+ if (!log_paths.incoming_rtp_dump.empty()) {
+ base::FilePath rtp_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".rtp_in"));
+ base::Move(log_paths.incoming_rtp_dump, rtp_path);
+ }
+
+ if (!log_paths.outgoing_rtp_dump.empty()) {
+ base::FilePath rtp_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".rtp_out"));
+ base::Move(log_paths.outgoing_rtp_dump, rtp_path);
+ }
+
+ if (meta_data.get() && !meta_data->empty()) {
+ base::Pickle pickle;
+ for (const auto& it : *meta_data.get()) {
+ pickle.WriteString(it.first);
+ pickle.WriteString(it.second);
+ }
+ base::FilePath meta_path =
+ log_paths.directory.AppendASCII(log_id).AddExtension(
+ FILE_PATH_LITERAL(".meta"));
+ base::WriteFile(meta_path, static_cast<const char*>(pickle.data()),
+ pickle.size());
+ }
+
+ main_task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(done_callback, true, ""));
+
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::DecreaseLogCount,
+ base::Unretained(this)));
+}
+
+void WebRtcLogUploader::Shutdown() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(!shutdown_);
+
+ // Clear the pending uploads list, which will reset all URL loaders.
+ pending_uploads_.clear();
+ shutdown_ = true;
+}
+
+void WebRtcLogUploader::OnSimpleLoaderComplete(
+ SimpleURLLoaderList::iterator it,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> response_body) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ DCHECK(!shutdown_);
+ network::SimpleURLLoader* loader = it->get();
+ base::Optional<int> response_code;
+ if (loader->ResponseInfo() && loader->ResponseInfo()->headers) {
+ response_code = loader->ResponseInfo()->headers->response_code();
+ }
+ const int network_error_code = loader->NetError();
+ pending_uploads_.erase(it);
+ std::string report_id;
+ if (response_body)
+ report_id = std::move(*response_body);
+ // The log path can be empty here if we failed getting it before. We still
+ // upload the log if that's the case.
+ if (!upload_done_data.paths.directory.empty()) {
+ // TODO(jiayl): Add the RTP dump records to chrome://webrtc-logs.
+ base::FilePath log_list_path =
+ webrtc_logging::TextLogList::GetWebRtcLogListFileForDirectory(
+ upload_done_data.paths.directory);
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile,
+ log_list_path, upload_done_data.local_log_id,
+ report_id));
+ }
+ NotifyUploadDoneAndLogStats(response_code, network_error_code, report_id,
+ upload_done_data);
+}
+
+void WebRtcLogUploader::SetupMultipart(
+ std::string* post_data,
+ const std::string& compressed_log,
+ const base::FilePath& incoming_rtp_dump,
+ const base::FilePath& outgoing_rtp_dump,
+ const std::map<std::string, std::string>& meta_data) {
+#if defined(OS_WIN)
+ const char product[] = "Chrome";
+#elif defined(OS_MACOSX)
+ const char product[] = "Chrome_Mac";
+#elif defined(OS_LINUX)
+#if !defined(ADDRESS_SANITIZER)
+ const char product[] = "Chrome_Linux";
+#else
+ const char product[] = "Chrome_Linux_ASan";
+#endif
+#elif defined(OS_ANDROID)
+ const char product[] = "Chrome_Android";
+#elif defined(OS_CHROMEOS)
+ const char product[] = "Chrome_ChromeOS";
+#else
+#error Platform not supported.
+#endif
+ net::AddMultipartValueForUpload("prod", product, kWebrtcLogMultipartBoundary,
+ "", post_data);
+ net::AddMultipartValueForUpload("ver",
+ version_info::GetVersionNumber() + "-webrtc",
+ kWebrtcLogMultipartBoundary, "", post_data);
+ net::AddMultipartValueForUpload("guid", "0", kWebrtcLogMultipartBoundary, "",
+ post_data);
+ net::AddMultipartValueForUpload("type", "webrtc_log",
+ kWebrtcLogMultipartBoundary, "", post_data);
+
+ // Add custom meta data.
+ for (const auto& it : meta_data) {
+ net::AddMultipartValueForUpload(it.first, it.second,
+ kWebrtcLogMultipartBoundary, "", post_data);
+ }
+
+ AddLogData(post_data, compressed_log);
+
+ // Add the rtp dumps if they exist.
+ base::FilePath rtp_dumps[2] = {incoming_rtp_dump, outgoing_rtp_dump};
+ static const char* const kRtpDumpNames[2] = {"rtpdump_recv", "rtpdump_send"};
+
+ for (size_t i = 0; i < 2; ++i) {
+ if (!rtp_dumps[i].empty() && base::PathExists(rtp_dumps[i])) {
+ std::string dump_data;
+ if (base::ReadFileToString(rtp_dumps[i], &dump_data))
+ AddRtpDumpData(post_data, kRtpDumpNames[i], dump_data);
+ }
+ }
+
+ net::AddMultipartFinalDelimiterForUpload(kWebrtcLogMultipartBoundary,
+ post_data);
+}
+
+std::string WebRtcLogUploader::CompressLog(WebRtcLogBuffer* buffer) {
+ z_stream stream = {0};
+ int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ // windowBits = 15 is default, 16 is added to
+ // produce a gzip header + trailer.
+ 15 + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+
+ std::string compressed_log;
+ ResizeForNextOutput(&compressed_log, &stream);
+
+ uint8_t intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
+ webrtc_logging::PartialCircularBuffer read_buffer(buffer->Read());
+ do {
+ if (stream.avail_in == 0) {
+ uint32_t read = read_buffer.Read(&intermediate_buffer[0],
+ sizeof(intermediate_buffer));
+ stream.next_in = &intermediate_buffer[0];
+ stream.avail_in = read;
+ if (read != kIntermediateCompressionBufferBytes)
+ break;
+ }
+ result = deflate(&stream, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, result);
+ if (stream.avail_out == 0)
+ ResizeForNextOutput(&compressed_log, &stream);
+ } while (true);
+
+ // Ensure we have enough room in the output buffer. Easier to always just do a
+ // resize than looping around and resize if needed.
+ if (stream.avail_out < kIntermediateCompressionBufferBytes)
+ ResizeForNextOutput(&compressed_log, &stream);
+
+ result = deflate(&stream, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+ result = deflateEnd(&stream);
+ DCHECK_EQ(Z_OK, result);
+
+ compressed_log.resize(compressed_log.size() - stream.avail_out);
+ return compressed_log;
+}
+
+void WebRtcLogUploader::UploadCompressedLog(
+ const WebRtcLogUploader::UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> post_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+
+ DecreaseLogCount();
+
+ // We don't log upload failure to UMA in case of shutting down for
+ // consistency, since there are other cases during shutdown were we don't get
+ // a chance to log.
+ if (shutdown_)
+ return;
+
+ std::string content_type = kWebrtcLogUploadContentType;
+ content_type.append("; boundary=");
+ content_type.append(kWebrtcLogMultipartBoundary);
+
+ // Create traffic annotation tag.
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("webrtc_log_upload", R"(
+ semantics {
+ sender: "Webrtc Log Uploader"
+ description: "Uploads WebRTC debug logs for Hangouts."
+ trigger:
+ "When a Hangouts extension or Hangouts services extension signals "
+ "to upload via the private WebRTC logging extension API."
+ data:
+ "WebRTC specific log entries, additional system information, and "
+ "RTP packet headers for incoming and outgoing WebRTC streams. "
+ "Audio or video data is never sent."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature can be disabled by unchecking 'Report additional "
+ "diagnostics to help improve Hangouts.' in Hangouts settings."
+ policy_exception_justification:
+ "Not implemented, it would be good to do so."
+ })");
+
+ constexpr char kUploadURL[] = "https://clients2.google.com/cr/report";
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = !upload_url_for_testing_.is_empty()
+ ? upload_url_for_testing_
+ : GURL(kUploadURL);
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ resource_request->method = "POST";
+ std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
+ network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ simple_url_loader->AttachStringForUpload(*post_data, content_type);
+ auto it = pending_uploads_.insert(pending_uploads_.begin(),
+ std::move(simple_url_loader));
+ network::SimpleURLLoader* raw_loader = it->get();
+ raw_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ g_browser_process->shared_url_loader_factory().get(),
+ base::BindOnce(&WebRtcLogUploader::OnSimpleLoaderComplete,
+ base::Unretained(this), std::move(it), upload_done_data));
+}
+
+void WebRtcLogUploader::DecreaseLogCount() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+ --log_count_;
+}
+
+void WebRtcLogUploader::WriteCompressedLogToFile(
+ const std::string& compressed_log,
+ const base::FilePath& log_file_path) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!compressed_log.empty());
+ base::WriteFile(log_file_path, &compressed_log[0], compressed_log.size());
+}
+
+void WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id) {
+ DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!upload_list_path.empty());
+ DCHECK(!local_log_id.empty());
+
+ std::string contents;
+
+ if (base::PathExists(upload_list_path)) {
+ if (!base::ReadFileToString(upload_list_path, &contents)) {
+ DPLOG(WARNING) << "Could not read WebRTC log list file.";
+ return;
+ }
+
+ // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
+ // for the new entry. Each line including the last ends with a '\n', so hit
+ // n will be before line n-1 (from the back).
+ int lf_count = 0;
+ int i = contents.size() - 1;
+ for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
+ if (contents[i] == '\n')
+ ++lf_count;
+ }
+ if (lf_count >= kLogListLimitLines) {
+ // + 1 to compensate for the for loop decrease before the conditional
+ // check and + 1 to get the length.
+ contents.erase(0, i + 2);
+ }
+ }
+
+ // Write the log ID and capture time to the log list file. Leave the upload
+ // time and report ID empty.
+ contents += ",," + local_log_id + "," +
+ base::NumberToString(base::Time::Now().ToDoubleT()) + '\n';
+
+ int written =
+ base::WriteFile(upload_list_path, &contents[0], contents.size());
+ if (written != static_cast<int>(contents.size())) {
+ DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
+ << written;
+ }
+}
+
+// static
+void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id,
+ const std::string& report_id) {
+ DCHECK(!upload_list_path.empty());
+ DCHECK(!local_log_id.empty());
+ DCHECK(!report_id.empty());
+
+ std::string contents;
+
+ if (base::PathExists(upload_list_path)) {
+ if (!base::ReadFileToString(upload_list_path, &contents)) {
+ DPLOG(WARNING) << "Could not read WebRTC log list file.";
+ return;
+ }
+ }
+
+ // Write the Unix time and report ID to the log list file. We should be able
+ // to find the local log ID, in that case insert the data into the existing
+ // line. Otherwise add it in the end.
+ base::Time time_now = base::Time::Now();
+ std::string time_now_str = base::NumberToString(time_now.ToDoubleT());
+ size_t pos = contents.find(",," + local_log_id);
+ if (pos != std::string::npos) {
+ contents.insert(pos, time_now_str);
+ contents.insert(pos + time_now_str.length() + 1, report_id);
+ } else {
+ contents += time_now_str + "," + report_id + ",," + time_now_str + "\n";
+ }
+
+ int written =
+ base::WriteFile(upload_list_path, &contents[0], contents.size());
+ if (written != static_cast<int>(contents.size())) {
+ DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
+ << written;
+ }
+}
+
+void WebRtcLogUploader::NotifyUploadDoneAndLogStats(
+ base::Optional<int> response_code,
+ int network_error_code,
+ const std::string& report_id,
+ const WebRtcLogUploader::UploadDoneData& upload_done_data) {
+ if (upload_done_data.callback.is_null())
+ return;
+
+ const bool success = response_code == net::HTTP_OK;
+ std::string error_message;
+ if (success) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadSuccessful",
+ upload_done_data.web_app_id);
+ } else {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed",
+ upload_done_data.web_app_id);
+ if (response_code.has_value()) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ response_code.value());
+ } else {
+ DCHECK_NE(network_error_code, net::OK);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kNetworkError);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureNetErrorCode",
+ std::abs(network_error_code));
+ }
+ error_message = base::StrCat(
+ {"Uploading failed, response code: ",
+ response_code.has_value() ? base::NumberToString(response_code.value())
+ : "<no value>"});
+ }
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(upload_done_data.callback, success, report_id,
+ error_message));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h
new file mode 100644
index 00000000000..ea558ca1a7e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader.h
@@ -0,0 +1,236 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/media/webrtc/webrtc_log_buffer.h"
+#include "url/gurl.h"
+
+namespace network {
+class SimpleURLLoader;
+}
+
+typedef struct z_stream_s z_stream;
+
+struct WebRtcLogPaths {
+ base::FilePath directory;
+ base::FilePath incoming_rtp_dump;
+ base::FilePath outgoing_rtp_dump;
+};
+
+typedef std::map<std::string, std::string> WebRtcLogMetaDataMap;
+
+// Upload failure reasons used for UMA stats. A failure reason can be one of
+// those listed here or a response code for the upload HTTP request. The
+// values in this list must be less than 100 and cannot be changed.
+struct WebRtcLogUploadFailureReason {
+ enum {
+ kInvalidState = 0,
+ kStoredLogNotFound = 1,
+ kNetworkError = 2,
+ };
+};
+
+// WebRtcLogUploader uploads WebRTC logs, keeps count of how many logs have
+// been started and denies further logs if a limit is reached. It also adds
+// the timestamp and report ID of the uploded log to a text file. There must
+// only be one object of this type.
+class WebRtcLogUploader {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+ typedef base::Callback<void(bool, const std::string&, const std::string&)>
+ UploadDoneCallback;
+
+ // Used when uploading is done to perform post-upload actions. |paths| is
+ // also used pre-upload.
+ struct UploadDoneData {
+ UploadDoneData();
+ UploadDoneData(const UploadDoneData& other);
+ ~UploadDoneData();
+
+ WebRtcLogPaths paths;
+ UploadDoneCallback callback;
+ std::string local_log_id;
+ // Used for statistics. See |WebRtcLoggingHandlerHost::web_app_id_|.
+ int web_app_id;
+ };
+
+ WebRtcLogUploader();
+ ~WebRtcLogUploader();
+
+ // Returns true is number of logs limit is not reached yet. Increases log
+ // count if true is returned. Must be called before UploadLog().
+ bool ApplyForStartLogging();
+
+ // Notifies that logging has stopped and that the log should not be uploaded.
+ // Decreases log count. May only be called if permission to log has been
+ // granted by calling ApplyForStartLogging() and getting true in return.
+ // After this function has been called, a new permission must be granted.
+ // Call either this function or LoggingStoppedDoUpload().
+ void LoggingStoppedDontUpload();
+
+ // Notifies that that logging has stopped and that the log should be uploaded.
+ // Decreases log count. May only be called if permission to log has been
+ // granted by calling ApplyForStartLogging() and getting true in return. After
+ // this function has been called, a new permission must be granted. Call
+ // either this function or LoggingStoppedDontUpload().
+ // |upload_done_data.local_log_id| is set and used internally and should be
+ // left empty.
+ void LoggingStoppedDoUpload(std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const UploadDoneData& upload_done_data);
+
+ // Uploads a previously stored log (see LoggingStoppedDoStore()).
+ void UploadStoredLog(const UploadDoneData& upload_data);
+
+ // Similarly to LoggingStoppedDoUpload(), we store the log in compressed
+ // format on disk but add the option to specify a unique |log_id| for later
+ // identification and potential upload.
+ void LoggingStoppedDoStore(const WebRtcLogPaths& log_paths,
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogBuffer> log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& done_callback);
+
+ // Cancels URL fetcher operation by deleting all URL fetchers. This cancels
+ // any pending uploads and releases SystemURLRequestContextGetter references.
+ // Sets |shutdown_| which prevents new fetchers from being created.
+ void Shutdown();
+
+ // For testing purposes. If called, the multipart will not be uploaded, but
+ // written to |post_data_| instead.
+ void OverrideUploadWithBufferForTesting(std::string* post_data) {
+ DCHECK((post_data && !post_data_) || (!post_data && post_data_));
+ post_data_ = post_data;
+ }
+
+ // For testing purposes.
+ void SetUploadUrlForTesting(const GURL& url) {
+ DCHECK((!url.is_empty() && upload_url_for_testing_.is_empty()) ||
+ (url.is_empty() && !upload_url_for_testing_.is_empty()));
+ upload_url_for_testing_ = url;
+ }
+
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner()
+ const {
+ return background_task_runner_;
+ }
+
+ private:
+ // Allow the test class to call AddLocallyStoredLogInfoToUploadListFile.
+ friend class WebRtcLogUploaderTest;
+ FRIEND_TEST_ALL_PREFIXES(WebRtcLogUploaderTest,
+ AddLocallyStoredLogInfoToUploadListFile);
+ FRIEND_TEST_ALL_PREFIXES(WebRtcLogUploaderTest,
+ AddUploadedLogInfoToUploadListFile);
+
+ // Sets up a multipart body to be uploaded. The body is produced according
+ // to RFC 2046.
+ void SetupMultipart(std::string* post_data,
+ const std::string& compressed_log,
+ const base::FilePath& incoming_rtp_dump,
+ const base::FilePath& outgoing_rtp_dump,
+ const std::map<std::string, std::string>& meta_data);
+
+ std::string CompressLog(WebRtcLogBuffer* buffer);
+
+ void UploadCompressedLog(const UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> post_data);
+
+ void DecreaseLogCount();
+
+ // Must be called on the FILE thread.
+ void WriteCompressedLogToFile(const std::string& compressed_log,
+ const base::FilePath& log_file_path);
+
+ void PrepareMultipartPostData(const std::string& compressed_log,
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const UploadDoneData& upload_done_data);
+
+ // Append information (upload time, report ID and local ID) about a log to a
+ // log list file, limited to |kLogListLimitLines| entries. This list is used
+ // for viewing the logs under chrome://webrtc-logs, see WebRtcLogUploadList.
+ // The list has the format:
+ // [upload_time],[report_id],[local_id],[capture_time]
+ // Each line represents a log.
+ // * |upload_time| is the time when the log was uploaded in Unix time.
+ // * |report_id| is the ID reported back by the server.
+ // * |local_id| is the ID for the locally stored log. It's the time stored
+ // in Unix time and it's also used as file name.
+ // * |capture_time| is the Unix time when the log was captured.
+ // AddLocallyStoredLogInfoToUploadListFile() will first be called.
+ // |upload_time| and |report_id| will be left empty in the entry written to
+ // the list file. If uploading is successful,
+ // AddUploadedLogInfoToUploadListFile() will be called and those empty fields
+ // will be filled out.
+ // Must be called on the FILE thread.
+ void AddLocallyStoredLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id);
+ static void AddUploadedLogInfoToUploadListFile(
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id,
+ const std::string& report_id);
+
+ // Notifies users that upload has completed and logs UMA stats.
+ // |response_code| not having a value means that no response code could be
+ // retrieved, in which case |network_error_code| should be something other
+ // than net::OK.
+ void NotifyUploadDoneAndLogStats(base::Optional<int> response_code,
+ int network_error_code,
+ const std::string& report_id,
+ const UploadDoneData& upload_done_data);
+
+ using SimpleURLLoaderList =
+ std::list<std::unique_ptr<network::SimpleURLLoader>>;
+
+ void OnSimpleLoaderComplete(SimpleURLLoaderList::iterator it,
+ const UploadDoneData& upload_done_data,
+ std::unique_ptr<std::string> response_body);
+
+ SEQUENCE_CHECKER(main_sequence_checker_);
+
+ // Main sequence where this class was constructed.
+ scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
+
+ // Background sequence where we run background, potentially blocking,
+ // operations.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ // Keeps track of number of currently open logs. Must only be accessed from
+ // the main sequence.
+ int log_count_ = 0;
+
+ // For testing purposes, see OverrideUploadWithBufferForTesting. Only accessed
+ // on the background sequence
+ std::string* post_data_ = nullptr;
+
+ // For testing purposes.
+ GURL upload_url_for_testing_;
+
+ // Only accessed on the main sequence.
+ SimpleURLLoaderList pending_uploads_;
+
+ // When true, don't create new URL loaders.
+ bool shutdown_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLogUploader);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UPLOADER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc
new file mode 100644
index 00000000000..b00d4b99f1a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_uploader_unittest.cc
@@ -0,0 +1,316 @@
+// 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 "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "base/test/task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char kTestTime[] = "time";
+const char kTestReportId[] = "report-id";
+const char kTestLocalId[] = "local-id";
+
+class WebRtcLogUploaderTest : public testing::Test {
+ public:
+ WebRtcLogUploaderTest() {}
+
+ bool VerifyNumberOfLines(int expected_lines) {
+ std::vector<std::string> lines = GetLinesFromListFile();
+ EXPECT_EQ(expected_lines, static_cast<int>(lines.size()));
+ return expected_lines == static_cast<int>(lines.size());
+ }
+
+ bool VerifyLastLineHasAllInfo() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ // The times (indices 0 and 3) is the time when the info was written to the
+ // file which we don't know, so just verify that it's not empty.
+ EXPECT_FALSE(line_parts[0].empty());
+ EXPECT_STREQ(kTestReportId, line_parts[1].c_str());
+ EXPECT_STREQ(kTestLocalId, line_parts[2].c_str());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ // Verify that the last line contains the correct info for a local storage.
+ bool VerifyLastLineHasLocalStorageInfoOnly() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ EXPECT_TRUE(line_parts[0].empty());
+ EXPECT_TRUE(line_parts[1].empty());
+ EXPECT_STREQ(kTestLocalId, line_parts[2].c_str());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ // Verify that the last line contains the correct info for an upload.
+ bool VerifyLastLineHasUploadInfoOnly() {
+ std::string last_line = GetLastLineFromListFile();
+ if (last_line.empty())
+ return false;
+ std::vector<std::string> line_parts = base::SplitString(
+ last_line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_EQ(4u, line_parts.size());
+ if (4u != line_parts.size())
+ return false;
+ EXPECT_FALSE(line_parts[0].empty());
+ EXPECT_STREQ(kTestReportId, line_parts[1].c_str());
+ EXPECT_TRUE(line_parts[2].empty());
+ EXPECT_FALSE(line_parts[3].empty());
+ return true;
+ }
+
+ bool AddLinesToTestFile(int number_of_lines) {
+ base::File test_list_file(test_list_path_,
+ base::File::FLAG_OPEN | base::File::FLAG_APPEND);
+ EXPECT_TRUE(test_list_file.IsValid());
+ if (!test_list_file.IsValid())
+ return false;
+
+ for (int i = 0; i < number_of_lines; ++i) {
+ EXPECT_EQ(static_cast<int>(sizeof(kTestTime)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestTime,
+ sizeof(kTestTime) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestReportId)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestReportId,
+ sizeof(kTestReportId) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestLocalId)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestLocalId,
+ sizeof(kTestLocalId) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos(",", 1));
+ EXPECT_EQ(static_cast<int>(sizeof(kTestTime)) - 1,
+ test_list_file.WriteAtCurrentPos(kTestTime,
+ sizeof(kTestTime) - 1));
+ EXPECT_EQ(1, test_list_file.WriteAtCurrentPos("\n", 1));
+ }
+ return true;
+ }
+
+ std::vector<std::string> GetLinesFromListFile() {
+ std::string contents;
+ int read = base::ReadFileToString(test_list_path_, &contents);
+ EXPECT_GT(read, 0);
+ if (read == 0)
+ return std::vector<std::string>();
+ // Since every line should end with '\n', the last line should be empty. So
+ // we expect at least two lines including the final empty. Remove the empty
+ // line before returning.
+ std::vector<std::string> lines = base::SplitString(
+ contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ EXPECT_GT(lines.size(), 1u);
+ if (lines.size() < 2)
+ return std::vector<std::string>();
+ EXPECT_TRUE(lines.back().empty());
+ if (!lines.back().empty())
+ return std::vector<std::string>();
+ lines.pop_back();
+ return lines;
+ }
+
+ std::string GetLastLineFromListFile() {
+ std::vector<std::string> lines = GetLinesFromListFile();
+ EXPECT_GT(lines.size(), 0u);
+ if (lines.empty())
+ return std::string();
+ return lines[lines.size() - 1];
+ }
+
+ void VerifyRtpDumpInMultipart(const std::string& post_data,
+ const std::string& dump_name,
+ const std::string& dump_content) {
+ std::vector<std::string> lines = base::SplitStringUsingSubstr(
+ post_data, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ std::string name_line = "Content-Disposition: form-data; name=\"";
+ name_line.append(dump_name);
+ name_line.append("\"");
+ name_line.append("; filename=\"");
+ name_line.append(dump_name);
+ name_line.append(".gz\"");
+
+ size_t i = 0;
+ for (; i < lines.size(); ++i) {
+ if (lines[i] == name_line)
+ break;
+ }
+
+ // The RTP dump takes 4 lines: content-disposition, content-type, empty
+ // line, dump content.
+ EXPECT_LT(i, lines.size() - 3);
+
+ EXPECT_EQ("Content-Type: application/gzip", lines[i + 1]);
+ EXPECT_EQ("", lines[i + 2]);
+ EXPECT_EQ(dump_content, lines[i + 3]);
+ }
+
+ static void AddLocallyStoredLogInfoToUploadListFile(
+ WebRtcLogUploader* log_uploader,
+ const base::FilePath& upload_list_path,
+ const std::string& local_log_id) {
+ base::RunLoop run_loop;
+ log_uploader->background_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(
+ &WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile,
+ base::Unretained(log_uploader), upload_list_path, local_log_id),
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ void FlushRunLoop() {
+ base::RunLoop run_loop;
+ base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::FilePath test_list_path_;
+};
+
+TEST_F(WebRtcLogUploaderTest, AddLocallyStoredLogInfoToUploadListFile) {
+ // Get a temporary filename. We don't want the file to exist to begin with
+ // since that's the normal use case, hence the delete.
+ ASSERT_TRUE(base::CreateTemporaryFile(&test_list_path_));
+ EXPECT_TRUE(base::DeleteFile(test_list_path_, false));
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(2));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ const int expected_line_limit = 50;
+ ASSERT_TRUE(AddLinesToTestFile(expected_line_limit - 2));
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ ASSERT_TRUE(AddLinesToTestFile(10));
+ ASSERT_TRUE(VerifyNumberOfLines(60));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(expected_line_limit));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
+
+TEST_F(WebRtcLogUploaderTest, AddUploadedLogInfoToUploadListFile) {
+ // Get a temporary filename. We don't want the file to exist to begin with
+ // since that's the normal use case, hence the delete.
+ ASSERT_TRUE(base::CreateTemporaryFile(&test_list_path_));
+ EXPECT_TRUE(base::DeleteFile(test_list_path_, false));
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ AddLocallyStoredLogInfoToUploadListFile(webrtc_log_uploader.get(),
+ test_list_path_, kTestLocalId);
+ ASSERT_TRUE(VerifyNumberOfLines(1));
+ ASSERT_TRUE(VerifyLastLineHasLocalStorageInfoOnly());
+
+ webrtc_log_uploader->AddUploadedLogInfoToUploadListFile(
+ test_list_path_, kTestLocalId, kTestReportId);
+ ASSERT_TRUE(VerifyNumberOfLines(1));
+ ASSERT_TRUE(VerifyLastLineHasAllInfo());
+
+ // Use a local ID that should not be found in the list.
+ webrtc_log_uploader->AddUploadedLogInfoToUploadListFile(
+ test_list_path_, "dummy id", kTestReportId);
+ ASSERT_TRUE(VerifyNumberOfLines(2));
+ ASSERT_TRUE(VerifyLastLineHasUploadInfoOnly());
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
+
+TEST_F(WebRtcLogUploaderTest, AddRtpDumpsToPostedData) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ std::unique_ptr<WebRtcLogUploader> webrtc_log_uploader(
+ new WebRtcLogUploader());
+
+ std::string post_data;
+ webrtc_log_uploader->OverrideUploadWithBufferForTesting(&post_data);
+
+ // Create the fake dump files.
+ const base::FilePath incoming_dump = temp_dir.GetPath().AppendASCII("recv");
+ const base::FilePath outgoing_dump = temp_dir.GetPath().AppendASCII("send");
+ const std::string incoming_dump_content = "dummy incoming";
+ const std::string outgoing_dump_content = "dummy outgoing";
+
+ base::WriteFile(incoming_dump,
+ &incoming_dump_content[0],
+ incoming_dump_content.size());
+ base::WriteFile(outgoing_dump,
+ &outgoing_dump_content[0],
+ outgoing_dump_content.size());
+
+ WebRtcLogUploader::UploadDoneData upload_done_data;
+ upload_done_data.paths.directory = temp_dir.GetPath().AppendASCII("log");
+
+ upload_done_data.paths.incoming_rtp_dump = incoming_dump;
+ upload_done_data.paths.outgoing_rtp_dump = outgoing_dump;
+
+ std::unique_ptr<WebRtcLogBuffer> log(new WebRtcLogBuffer());
+ log->SetComplete();
+
+ base::RunLoop run_loop;
+ webrtc_log_uploader->background_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoUpload,
+ base::Unretained(webrtc_log_uploader.get()),
+ std::move(log), std::make_unique<WebRtcLogMetaDataMap>(),
+ upload_done_data),
+ run_loop.QuitClosure());
+ run_loop.Run();
+
+ VerifyRtpDumpInMultipart(post_data, "rtpdump_recv", incoming_dump_content);
+ VerifyRtpDumpInMultipart(post_data, "rtpdump_send", outgoing_dump_content);
+
+ webrtc_log_uploader->Shutdown();
+ FlushRunLoop();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc b/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc
new file mode 100644
index 00000000000..f724e04d6e1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_util.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/webrtc_log_util.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/webrtc_logging/browser/log_cleanup.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_thread.h"
+
+// static
+void WebRtcLogUtil::DeleteOldWebRtcLogFilesForAllProfiles() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::vector<ProfileAttributesEntry*> entries =
+ g_browser_process->profile_manager()->GetProfileAttributesStorage().
+ GetAllProfilesAttributes();
+ for (ProfileAttributesEntry* entry : entries) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(
+ &webrtc_logging::DeleteOldWebRtcLogFiles,
+ webrtc_logging::TextLogList::
+ GetWebRtcLogDirectoryForBrowserContextPath(entry->GetPath())));
+ }
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_log_util.h b/chromium/chrome/browser/media/webrtc/webrtc_log_util.h
new file mode 100644
index 00000000000..8f06b483fbf
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_log_util.h
@@ -0,0 +1,15 @@
+// Copyright 2014 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
+
+class WebRtcLogUtil {
+ public:
+ // Calls webrtc_logging::DeleteOldWebRtcLogFiles() for all profiles. Must be
+ // called on the UI thread.
+ static void DeleteOldWebRtcLogFilesForAllProfiles();
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOG_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc
new file mode 100644
index 00000000000..824464ffdca
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.cc
@@ -0,0 +1,589 @@
+// 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 "chrome/browser/media/webrtc/webrtc_logging_controller.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/supports_user_data.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+#include "components/webrtc_logging/browser/text_log_list.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_process_host.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#include "content/public/browser/child_process_security_policy.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+using webrtc_event_logging::WebRtcEventLogManager;
+
+namespace {
+
+// Key used to attach the handler to the RenderProcessHost.
+constexpr char kRenderProcessHostKey[] = "kWebRtcLoggingControllerKey";
+
+} // namespace
+
+// static
+void WebRtcLoggingController::AttachToRenderProcessHost(
+ content::RenderProcessHost* host,
+ WebRtcLogUploader* log_uploader) {
+ host->SetUserData(
+ kRenderProcessHostKey,
+ std::make_unique<base::UserDataAdapter<WebRtcLoggingController>>(
+ new WebRtcLoggingController(host->GetID(), host->GetBrowserContext(),
+ log_uploader)));
+}
+
+// static
+WebRtcLoggingController* WebRtcLoggingController::FromRenderProcessHost(
+ content::RenderProcessHost* host) {
+ return base::UserDataAdapter<WebRtcLoggingController>::Get(
+ host, kRenderProcessHostKey);
+}
+
+void WebRtcLoggingController::SetMetaData(
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Set the web app ID if there's a "client" key, otherwise leave it unchanged.
+ for (const auto& it : *meta_data) {
+ if (it.first == "client") {
+ web_app_id_ = static_cast<int>(base::PersistentHash(it.second));
+ text_log_handler_->SetWebAppId(web_app_id_);
+ break;
+ }
+ }
+
+ text_log_handler_->SetMetaData(std::move(meta_data), callback);
+}
+
+void WebRtcLoggingController::StartLogging(
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Request a log_slot from the LogUploader and start logging.
+ if (text_log_handler_->StartLogging(log_uploader_, callback)) {
+ // Start logging in the renderer. The callback has already been fired since
+ // there is no acknowledgement when the renderer actually starts.
+ content::RenderProcessHost* host =
+ content::RenderProcessHost::FromID(render_process_id_);
+
+ // OK for this to replace an existing logging_agent_ connection.
+ host->BindReceiver(logging_agent_.BindNewPipeAndPassReceiver());
+ logging_agent_.set_disconnect_handler(
+ base::BindOnce(&WebRtcLoggingController::OnAgentDisconnected, this));
+ logging_agent_->Start(receiver_.BindNewPipeAndPassRemote());
+ }
+}
+
+void WebRtcLoggingController::StopLogging(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // Change the state to STOPPING and disable logging in the browser.
+ if (text_log_handler_->StopLogging(callback)) {
+ // Stop logging in the renderer. OnStopped will be called when this is done
+ // to change the state from STOPPING to STOPPED and fire the callback.
+ logging_agent_->Stop();
+ }
+}
+
+void WebRtcLoggingController::UploadLog(const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ // This functions uploads both text logs (mandatory) and RTP dumps (optional).
+ // TODO(terelius): If there's no text log available (either because it hasn't
+ // been started or because it hasn't been stopped), the current implementation
+ // will fire an error callback and leave any RTP dumps in a local directory.
+ // Would it be better to upload whatever logs we have, or would the lack of
+ // an error callback make it harder to debug potential errors?
+
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadStarted", web_app_id_);
+
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::TriggerUpload, this, callback));
+}
+
+void WebRtcLoggingController::UploadStoredLog(
+ const std::string& log_id,
+ const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadStoredStarted",
+ web_app_id_);
+
+ // Make this a method call on log_uploader_
+
+ WebRtcLogUploader::UploadDoneData upload_data;
+ upload_data.callback = callback;
+ upload_data.local_log_id = log_id;
+ upload_data.web_app_id = web_app_id_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(
+ [](WebRtcLogUploader* log_uploader,
+ WebRtcLogUploader::UploadDoneData upload_data,
+ base::RepeatingCallback<base::FilePath(void)>
+ log_directory_getter) {
+ upload_data.paths.directory = log_directory_getter.Run();
+ log_uploader->UploadStoredLog(upload_data);
+ },
+ log_uploader_, upload_data, log_directory_getter_));
+}
+
+void WebRtcLoggingController::DiscardLog(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!text_log_handler_->ExpectLoggingStateStopped(callback)) {
+ // The callback is fired with an error message by ExpectLoggingStateStopped.
+ return;
+ }
+ log_uploader_->LoggingStoppedDontUpload();
+ text_log_handler_->DiscardLog();
+ rtp_dump_handler_.reset();
+ stop_rtp_dump_callback_.Reset();
+ FireGenericDoneCallback(callback, true, "");
+}
+
+// Stores the log locally using a hash of log_id + security origin.
+void WebRtcLoggingController::StoreLog(const std::string& log_id,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!text_log_handler_->ExpectLoggingStateStopped(callback)) {
+ // The callback is fired with an error message by ExpectLoggingStateStopped.
+ return;
+ }
+
+ if (rtp_dump_handler_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(stop_rtp_dump_callback_, true, true));
+
+ rtp_dump_handler_->StopOngoingDumps(base::Bind(
+ &WebRtcLoggingController::StoreLogContinue, this, log_id, callback));
+ return;
+ }
+
+ StoreLogContinue(log_id, callback);
+}
+
+void WebRtcLoggingController::StoreLogContinue(
+ const std::string& log_id,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ std::unique_ptr<WebRtcLogPaths> log_paths(new WebRtcLogPaths());
+ ReleaseRtpDumps(log_paths.get());
+
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::StoreLogInDirectory, this,
+ log_id, base::Passed(&log_paths), callback));
+}
+
+void WebRtcLoggingController::StartRtpDump(
+ RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(stop_rtp_dump_callback_.is_null());
+
+ content::RenderProcessHost* host =
+ content::RenderProcessHost::FromID(render_process_id_);
+
+ // This call cannot fail.
+ stop_rtp_dump_callback_ = host->StartRtpDump(
+ type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH,
+ type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH,
+ base::Bind(&WebRtcLoggingController::OnRtpPacket, this));
+
+ if (!rtp_dump_handler_) {
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::CreateRtpDumpHandlerAndStart,
+ this, type, callback));
+ return;
+ }
+
+ DoStartRtpDump(type, callback);
+}
+
+void WebRtcLoggingController::StopRtpDump(RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (!rtp_dump_handler_) {
+ FireGenericDoneCallback(callback, false, "RTP dump has not been started.");
+ return;
+ }
+
+ if (!stop_rtp_dump_callback_.is_null()) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(stop_rtp_dump_callback_,
+ type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH,
+ type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH));
+ }
+
+ rtp_dump_handler_->StopDump(type, callback);
+}
+
+void WebRtcLoggingController::StartEventLogging(
+ const std::string& session_id,
+ size_t max_log_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ const StartEventLoggingCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ WebRtcEventLogManager::GetInstance()->StartRemoteLogging(
+ render_process_id_, session_id, max_log_size_bytes, output_period_ms,
+ web_app_id, callback);
+}
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+void WebRtcLoggingController::GetLogsDirectory(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::GrantLogsDirectoryAccess, this,
+ callback, error_callback));
+}
+
+void WebRtcLoggingController::GrantLogsDirectoryAccess(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback,
+ const base::FilePath& logs_path) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logs_path.empty()) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(error_callback, "Logs directory not available"));
+ return;
+ }
+
+ storage::IsolatedContext* isolated_context =
+ storage::IsolatedContext::GetInstance();
+ DCHECK(isolated_context);
+
+ std::string registered_name;
+ storage::IsolatedContext::ScopedFSHandle file_system =
+ isolated_context->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal, std::string(), logs_path,
+ &registered_name);
+
+ // Only granting read and delete access to reduce contention with
+ // webrtcLogging APIs that modify files in that folder.
+ content::ChildProcessSecurityPolicy* policy =
+ content::ChildProcessSecurityPolicy::GetInstance();
+ policy->GrantReadFileSystem(render_process_id_, file_system.id());
+ // Delete is needed to prevent accumulation of files.
+ policy->GrantDeleteFromFileSystem(render_process_id_, file_system.id());
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, file_system.id(), registered_name));
+}
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+void WebRtcLoggingController::OnRtpPacket(
+ std::unique_ptr<uint8_t[]> packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // |rtp_dump_handler_| could be null if we are waiting for the FILE thread to
+ // create/ensure the log directory.
+ if (rtp_dump_handler_) {
+ rtp_dump_handler_->OnRtpPacket(packet_header.get(), header_length,
+ packet_length, incoming);
+ }
+}
+
+void WebRtcLoggingController::OnAddMessages(
+ std::vector<chrome::mojom::WebRtcLoggingMessagePtr> messages) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetState() == WebRtcTextLogHandler::STARTED ||
+ text_log_handler_->GetState() == WebRtcTextLogHandler::STOPPING) {
+ for (auto& message : messages)
+ text_log_handler_->LogWebRtcLoggingMessage(message.get());
+ }
+}
+
+void WebRtcLoggingController::OnStopped() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetState() != WebRtcTextLogHandler::STOPPING) {
+ // If an out-of-order response is received, stop_callback_ may be invalid,
+ // and must not be invoked.
+ DLOG(ERROR) << "OnStopped invoked in state "
+ << text_log_handler_->GetState();
+ mojo::ReportBadMessage("WRLHH: OnStopped invoked in unexpected state.");
+ return;
+ }
+ text_log_handler_->StopDone();
+}
+
+WebRtcLoggingController::WebRtcLoggingController(
+ int render_process_id,
+ content::BrowserContext* browser_context,
+ WebRtcLogUploader* log_uploader)
+ : receiver_(this),
+ render_process_id_(render_process_id),
+ log_directory_getter_(base::BindRepeating(
+ &WebRtcLoggingController::GetLogDirectoryAndEnsureExists,
+ browser_context->GetPath())),
+ upload_log_on_render_close_(false),
+ text_log_handler_(
+ std::make_unique<WebRtcTextLogHandler>(render_process_id)),
+ rtp_dump_handler_(),
+ stop_rtp_dump_callback_(),
+ log_uploader_(log_uploader) {
+ DCHECK(log_uploader_);
+}
+
+WebRtcLoggingController::~WebRtcLoggingController() {
+ // If we hit this, then we might be leaking a log reference count (see
+ // ApplyForStartLogging).
+ DCHECK_EQ(WebRtcTextLogHandler::CLOSED, text_log_handler_->GetState());
+}
+
+void WebRtcLoggingController::OnAgentDisconnected() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (text_log_handler_->GetChannelIsClosing())
+ return;
+
+ switch (text_log_handler_->GetState()) {
+ case WebRtcTextLogHandler::STARTING:
+ case WebRtcTextLogHandler::STARTED:
+ case WebRtcTextLogHandler::STOPPING:
+ case WebRtcTextLogHandler::STOPPED:
+ text_log_handler_->ChannelClosing();
+ if (upload_log_on_render_close_) {
+ base::PostTaskAndReplyWithResult(
+ log_uploader_->background_task_runner().get(), FROM_HERE,
+ base::BindOnce(log_directory_getter_),
+ base::BindOnce(&WebRtcLoggingController::TriggerUpload, this,
+ UploadDoneCallback()));
+ } else {
+ log_uploader_->LoggingStoppedDontUpload();
+ text_log_handler_->DiscardLog();
+ }
+ break;
+ case WebRtcTextLogHandler::CLOSED:
+ // Do nothing
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void WebRtcLoggingController::TriggerUpload(
+ const UploadDoneCallback& callback,
+ const base::FilePath& log_directory) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (rtp_dump_handler_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(stop_rtp_dump_callback_, true, true));
+
+ rtp_dump_handler_->StopOngoingDumps(
+ base::Bind(&WebRtcLoggingController::DoUploadLogAndRtpDumps, this,
+ log_directory, callback));
+ return;
+ }
+
+ DoUploadLogAndRtpDumps(log_directory, callback);
+}
+
+void WebRtcLoggingController::StoreLogInDirectory(
+ const std::string& log_id,
+ std::unique_ptr<WebRtcLogPaths> log_paths,
+ const GenericDoneCallback& done_callback,
+ const base::FilePath& directory) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If channel is not closing, storing is only allowed when in STOPPED state.
+ // If channel is closing, storing is allowed for all states except CLOSED.
+ const WebRtcTextLogHandler::LoggingState text_logging_state =
+ text_log_handler_->GetState();
+ const bool channel_is_closing = text_log_handler_->GetChannelIsClosing();
+ if ((!channel_is_closing &&
+ text_logging_state != WebRtcTextLogHandler::STOPPED) ||
+ (channel_is_closing &&
+ text_log_handler_->GetState() == WebRtcTextLogHandler::CLOSED)) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(done_callback, false,
+ "Logging not stopped or no log open."));
+ return;
+ }
+
+ log_paths->directory = directory;
+
+ std::unique_ptr<WebRtcLogBuffer> log_buffer;
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data;
+ text_log_handler_->ReleaseLog(&log_buffer, &meta_data);
+ CHECK(log_buffer.get()) << "State=" << text_log_handler_->GetState()
+ << ", uorc=" << upload_log_on_render_close_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoStore,
+ base::Unretained(log_uploader_), *log_paths,
+ log_id, std::move(log_buffer),
+ std::move(meta_data), done_callback));
+}
+
+void WebRtcLoggingController::DoUploadLogAndRtpDumps(
+ const base::FilePath& log_directory,
+ const UploadDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If channel is not closing, upload is only allowed when in STOPPED state.
+ // If channel is closing, uploading is allowed for all states except CLOSED.
+ const WebRtcTextLogHandler::LoggingState text_logging_state =
+ text_log_handler_->GetState();
+ const bool channel_is_closing = text_log_handler_->GetChannelIsClosing();
+ if ((!channel_is_closing &&
+ text_logging_state != WebRtcTextLogHandler::STOPPED) ||
+ (channel_is_closing &&
+ text_log_handler_->GetState() == WebRtcTextLogHandler::CLOSED)) {
+ // If the channel is not closing the log is expected to be uploaded, so
+ // it's considered a failure if it isn't.
+ // If the channel is closing we don't log failure to UMA for consistency,
+ // since there are other cases during shutdown were we don't get a chance
+ // to log.
+ if (!channel_is_closing) {
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailed", web_app_id_);
+ base::UmaHistogramSparse("WebRtcTextLogging.UploadFailureReason",
+ WebRtcLogUploadFailureReason::kInvalidState);
+ }
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, false, "",
+ "Logging not stopped or no log open."));
+ return;
+ }
+
+ WebRtcLogUploader::UploadDoneData upload_done_data;
+ upload_done_data.paths.directory = log_directory;
+ upload_done_data.callback = callback;
+ upload_done_data.web_app_id = web_app_id_;
+ ReleaseRtpDumps(&upload_done_data.paths);
+
+ std::unique_ptr<WebRtcLogBuffer> log_buffer;
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data;
+ text_log_handler_->ReleaseLog(&log_buffer, &meta_data);
+ CHECK(log_buffer.get()) << "State=" << text_log_handler_->GetState()
+ << ", uorc=" << upload_log_on_render_close_;
+
+ log_uploader_->background_task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WebRtcLogUploader::LoggingStoppedDoUpload,
+ base::Unretained(log_uploader_), std::move(log_buffer),
+ std::move(meta_data), upload_done_data));
+}
+
+void WebRtcLoggingController::CreateRtpDumpHandlerAndStart(
+ RtpDumpType type,
+ const GenericDoneCallback& callback,
+ const base::FilePath& dump_dir) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // |rtp_dump_handler_| may be non-null if StartRtpDump is called again before
+ // GetLogDirectoryAndEnsureExists returns on the FILE thread for a previous
+ // StartRtpDump.
+ if (!rtp_dump_handler_)
+ rtp_dump_handler_.reset(new WebRtcRtpDumpHandler(dump_dir));
+
+ DoStartRtpDump(type, callback);
+}
+
+void WebRtcLoggingController::DoStartRtpDump(
+ RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(rtp_dump_handler_);
+
+ std::string error;
+ bool result = rtp_dump_handler_->StartDump(type, &error);
+ FireGenericDoneCallback(callback, result, error);
+}
+
+bool WebRtcLoggingController::ReleaseRtpDumps(WebRtcLogPaths* log_paths) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(log_paths);
+
+ if (!rtp_dump_handler_)
+ return false;
+
+ WebRtcRtpDumpHandler::ReleasedDumps rtp_dumps(
+ rtp_dump_handler_->ReleaseDumps());
+ log_paths->incoming_rtp_dump = rtp_dumps.incoming_dump_path;
+ log_paths->outgoing_rtp_dump = rtp_dumps.outgoing_dump_path;
+
+ rtp_dump_handler_.reset();
+ stop_rtp_dump_callback_.Reset();
+
+ return true;
+}
+
+void WebRtcLoggingController::FireGenericDoneCallback(
+ const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+ DCHECK_EQ(success, error_message.empty());
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+}
+
+// static
+base::FilePath WebRtcLoggingController::GetLogDirectoryAndEnsureExists(
+ const base::FilePath& browser_context_directory_path) {
+ DCHECK(!browser_context_directory_path.empty());
+ // Since we can be alive after the RenderProcessHost and the BrowserContext
+ // (profile) have gone away, we could create the log directory here after a
+ // profile has been deleted and removed from disk. If that happens it will be
+ // cleaned up (at a higher level) the next browser restart.
+ base::FilePath log_dir_path =
+ webrtc_logging::TextLogList::GetWebRtcLogDirectoryForBrowserContextPath(
+ browser_context_directory_path);
+ base::File::Error error;
+ if (!base::CreateDirectoryAndGetError(log_dir_path, &error)) {
+ DLOG(ERROR) << "Could not create WebRTC log directory, error: " << error;
+ return base::FilePath();
+ }
+ return log_dir_path;
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h
new file mode 100644
index 00000000000..ee1eca46fba
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_logging_controller.h
@@ -0,0 +1,242 @@
+// 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "chrome/browser/media/webrtc/webrtc_text_log_handler.h"
+#include "chrome/common/media/webrtc_logging.mojom.h"
+#include "content/public/browser/render_process_host.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class WebRtcLogUploader;
+class WebRtcRtpDumpHandler;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+// WebRtcLoggingController handles operations regarding the WebRTC logging:
+// - Opens a connection to a WebRtcLoggingAgent that runs in the render process
+// and generates log messages.
+// - Writes basic machine info to the log.
+// - Informs the handler in the render process when to stop logging.
+// - Closes the connection to the WebRtcLoggingAgent (and thereby discarding it)
+// or triggers uploading of the log.
+// - Detects when the agent (e.g., because of a tab closure or crash) is going
+// away and possibly triggers uploading the log.
+class WebRtcLoggingController
+ : public base::RefCounted<WebRtcLoggingController>,
+ public chrome::mojom::WebRtcLoggingClient {
+ public:
+ typedef WebRtcLogUploader::GenericDoneCallback GenericDoneCallback;
+ typedef WebRtcLogUploader::UploadDoneCallback UploadDoneCallback;
+ typedef base::Callback<void(const std::string&, const std::string&)>
+ LogsDirectoryCallback;
+ typedef base::Callback<void(const std::string&)> LogsDirectoryErrorCallback;
+
+ // Argument #1: Indicate success/failure.
+ // Argument #2: If success, the log's ID. Otherwise, empty.
+ // Argument #3: If failure, the error message. Otherwise, empty.
+ typedef base::RepeatingCallback<
+ void(bool, const std::string&, const std::string&)>
+ StartEventLoggingCallback;
+
+ static void AttachToRenderProcessHost(content::RenderProcessHost* host,
+ WebRtcLogUploader* log_uploader);
+ static WebRtcLoggingController* FromRenderProcessHost(
+ content::RenderProcessHost* host);
+
+ // Sets meta data that will be uploaded along with the log and also written
+ // in the beginning of the log. Must be called on the IO thread before calling
+ // StartLogging.
+ void SetMetaData(std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback);
+
+ // Opens a log and starts logging. Must be called on the IO thread.
+ void StartLogging(const GenericDoneCallback& callback);
+
+ // Stops logging. Log will remain open until UploadLog or DiscardLog is
+ // called. Must be called on the IO thread.
+ void StopLogging(const GenericDoneCallback& callback);
+
+ // Uploads the text log and the RTP dumps. Discards the local copy. May only
+ // be called after text logging has stopped. Must be called on the IO thread.
+ void UploadLog(const UploadDoneCallback& callback);
+
+ // Uploads a log that was previously saved via a call to StoreLog().
+ // Otherwise operates in the same way as UploadLog.
+ void UploadStoredLog(const std::string& log_id,
+ const UploadDoneCallback& callback);
+
+ // Discards the log and the RTP dumps. May only be called after logging has
+ // stopped. Must be called on the IO thread.
+ void DiscardLog(const GenericDoneCallback& callback);
+
+ // Stores the log locally using a hash of log_id + security origin.
+ void StoreLog(const std::string& log_id, const GenericDoneCallback& callback);
+
+ // May be called on any thread. |upload_log_on_render_close_| is used
+ // for decision making and it's OK if it changes before the execution based
+ // on that decision has finished.
+ void set_upload_log_on_render_close(bool should_upload) {
+ upload_log_on_render_close_ = should_upload;
+ }
+
+ // Starts dumping the RTP headers for the specified direction. Must be called
+ // on the UI thread. |type| specifies which direction(s) of RTP packets should
+ // be dumped. |callback| will be called when starting the dump is done.
+ void StartRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Stops dumping the RTP headers for the specified direction. Must be called
+ // on the UI thread. |type| specifies which direction(s) of RTP packet dumping
+ // should be stopped. |callback| will be called when stopping the dump is
+ // done.
+ void StopRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Called when an RTP packet is sent or received. Must be called on the UI
+ // thread.
+ void OnRtpPacket(std::unique_ptr<uint8_t[]> packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Start remote-bound event logging for a specific peer connection
+ // (indicated by its session description's ID).
+ // The callback will be posted back, indicating |true| if and only if an
+ // event log was successfully started, in which case the first of the string
+ // arguments will be set to the log-ID. Otherwise, the second of the string
+ // arguments will contain the error message.
+ // This function must be called on the UI thread.
+ void StartEventLogging(const std::string& session_id,
+ size_t max_log_size_bytes,
+ int output_period_ms,
+ size_t web_app_id,
+ const StartEventLoggingCallback& callback);
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Ensures that the WebRTC Logs directory exists and then grants render
+ // process access to the 'WebRTC Logs' directory, and invokes |callback| with
+ // the ids necessary to create a DirectoryEntry object.
+ void GetLogsDirectory(const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback);
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+ // chrome::mojom::WebRtcLoggingClient methods:
+ void OnAddMessages(
+ std::vector<chrome::mojom::WebRtcLoggingMessagePtr> messages) override;
+ void OnStopped() override;
+
+ private:
+ friend class base::RefCounted<WebRtcLoggingController>;
+
+ WebRtcLoggingController(int render_process_id,
+ content::BrowserContext* browser_context,
+ WebRtcLogUploader* log_uploader);
+ ~WebRtcLoggingController() override;
+
+ void OnAgentDisconnected();
+
+ // Called after stopping RTP dumps.
+ void StoreLogContinue(const std::string& log_id,
+ const GenericDoneCallback& callback);
+
+ // Writes a formatted log |message| to the |circular_buffer_|.
+ void LogToCircularBuffer(const std::string& message);
+
+ void TriggerUpload(const UploadDoneCallback& callback,
+ const base::FilePath& log_directory);
+
+ void StoreLogInDirectory(const std::string& log_id,
+ std::unique_ptr<WebRtcLogPaths> log_paths,
+ const GenericDoneCallback& done_callback,
+ const base::FilePath& directory);
+
+ // A helper for TriggerUpload to do the real work.
+ void DoUploadLogAndRtpDumps(const base::FilePath& log_directory,
+ const UploadDoneCallback& callback);
+
+ // Create the RTP dump handler and start dumping. Must be called after making
+ // sure the log directory exists.
+ void CreateRtpDumpHandlerAndStart(RtpDumpType type,
+ const GenericDoneCallback& callback,
+ const base::FilePath& dump_dir);
+
+ // A helper for starting RTP dump assuming the RTP dump handler has been
+ // created.
+ void DoStartRtpDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ bool ReleaseRtpDumps(WebRtcLogPaths* log_paths);
+
+ void FireGenericDoneCallback(
+ const WebRtcLoggingController::GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message);
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Grants the render process access to the 'WebRTC Logs' directory, and
+ // invokes |callback| with the ids necessary to create a DirectoryEntry
+ // object. If the |logs_path| couldn't be created or found, |error_callback|
+ // is run.
+ void GrantLogsDirectoryAccess(
+ const LogsDirectoryCallback& callback,
+ const LogsDirectoryErrorCallback& error_callback,
+ const base::FilePath& logs_path);
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+ static base::FilePath GetLogDirectoryAndEnsureExists(
+ const base::FilePath& browser_context_directory_path);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ mojo::Receiver<chrome::mojom::WebRtcLoggingClient> receiver_;
+ mojo::Remote<chrome::mojom::WebRtcLoggingAgent> logging_agent_;
+
+ // The render process ID this object belongs to.
+ const int render_process_id_;
+
+ // A callback that needs to be run from a blocking worker pool and returns
+ // the browser context directory path associated with our renderer process.
+ base::RepeatingCallback<base::FilePath(void)> log_directory_getter_;
+
+ // True if we should upload whatever log we have when the renderer closes.
+ bool upload_log_on_render_close_;
+
+ // The text log handler owns the WebRtcLogBuffer object and keeps track of
+ // the logging state.
+ std::unique_ptr<WebRtcTextLogHandler> text_log_handler_;
+
+ // The RTP dump handler responsible for creating the RTP header dump files.
+ std::unique_ptr<WebRtcRtpDumpHandler> rtp_dump_handler_;
+
+ // The callback to call when StopRtpDump is called.
+ content::RenderProcessHost::WebRtcStopRtpDumpCallback stop_rtp_dump_callback_;
+
+ // A pointer to the log uploader that's shared for all browser contexts.
+ // Ownership lies with the browser process.
+ WebRtcLogUploader* const log_uploader_;
+
+ // Web app id used for statistics. Created as the hash of the value of a
+ // "client" meta data key, if exists. 0 means undefined, and is the hash of
+ // the empty string.
+ int web_app_id_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcLoggingController);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_LOGGING_CONTROLLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc
new file mode 100644
index 00000000000..f502a329ef1
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+namespace {
+
+static const size_t kMaxOngoingRtpDumpsAllowed = 5;
+
+// The browser process wide total number of ongoing (i.e. started and not
+// released) RTP dumps. Incoming and outgoing in one WebRtcDumpHandler are
+// counted as one dump.
+// Must be accessed on the browser IO thread.
+static size_t g_ongoing_rtp_dumps = 0;
+
+void FireGenericDoneCallback(
+ const WebRtcRtpDumpHandler::GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK(!callback.is_null());
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+}
+
+bool DumpTypeContainsIncoming(RtpDumpType type) {
+ return type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH;
+}
+
+bool DumpTypeContainsOutgoing(RtpDumpType type) {
+ return type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH;
+}
+
+} // namespace
+
+WebRtcRtpDumpHandler::WebRtcRtpDumpHandler(const base::FilePath& dump_dir)
+ : dump_dir_(dump_dir),
+ incoming_state_(STATE_NONE),
+ outgoing_state_(STATE_NONE) {}
+
+WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ // Reset dump writer first to stop writing.
+ if (dump_writer_) {
+ --g_ongoing_rtp_dumps;
+ dump_writer_.reset();
+ }
+
+ if (incoming_state_ != STATE_NONE && !incoming_dump_path_.empty()) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ incoming_dump_path_, false));
+ }
+
+ if (outgoing_state_ != STATE_NONE && !outgoing_dump_path_.empty()) {
+ base::PostTask(
+ FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ outgoing_dump_path_, false));
+ }
+}
+
+bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type,
+ std::string* error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if (!dump_writer_ && g_ongoing_rtp_dumps >= kMaxOngoingRtpDumpsAllowed) {
+ *error_message = "Max RTP dump limit reached.";
+ DVLOG(2) << *error_message;
+ return false;
+ }
+
+ // Returns an error if any type of dump specified by the caller cannot be
+ // started.
+ if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_NONE) ||
+ (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_NONE)) {
+ *error_message =
+ "RTP dump already started for type " + base::NumberToString(type);
+ return false;
+ }
+
+ if (DumpTypeContainsIncoming(type))
+ incoming_state_ = STATE_STARTED;
+
+ if (DumpTypeContainsOutgoing(type))
+ outgoing_state_ = STATE_STARTED;
+
+ DVLOG(2) << "Start RTP dumping: type = " << type;
+
+ if (!dump_writer_) {
+ ++g_ongoing_rtp_dumps;
+
+ static const char kRecvDumpFilePrefix[] = "rtpdump_recv_";
+ static const char kSendDumpFilePrefix[] = "rtpdump_send_";
+ static const size_t kMaxDumpSize = 5 * 1024 * 1024; // 5MB
+
+ std::string dump_id = base::NumberToString(base::Time::Now().ToDoubleT());
+ incoming_dump_path_ =
+ dump_dir_.AppendASCII(std::string(kRecvDumpFilePrefix) + dump_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ outgoing_dump_path_ =
+ dump_dir_.AppendASCII(std::string(kSendDumpFilePrefix) + dump_id)
+ .AddExtension(FILE_PATH_LITERAL(".gz"));
+
+ // WebRtcRtpDumpWriter does not support changing the dump path after it's
+ // created. So we assign both incoming and outgoing dump path even if only
+ // one type of dumping has been started.
+ // For "Unretained(this)", see comments StopDump.
+ dump_writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ kMaxDumpSize,
+ base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached,
+ base::Unretained(this))));
+ }
+
+ return true;
+}
+
+void WebRtcRtpDumpHandler::StopDump(RtpDumpType type,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ // Returns an error if any type of dump specified by the caller cannot be
+ // stopped.
+ if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_STARTED) ||
+ (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_STARTED)) {
+ if (!callback.is_null()) {
+ FireGenericDoneCallback(
+ callback, false,
+ "RTP dump not started or already stopped for type " +
+ base::NumberToString(type));
+ }
+ return;
+ }
+
+ DVLOG(2) << "Stopping RTP dumping: type = " << type;
+
+ if (DumpTypeContainsIncoming(type))
+ incoming_state_ = STATE_STOPPING;
+
+ if (DumpTypeContainsOutgoing(type))
+ outgoing_state_ = STATE_STOPPING;
+
+ // Using "Unretained(this)" because the this object owns the writer and the
+ // writer is guaranteed to cancel the callback before it goes away. Same for
+ // the other posted tasks bound to the writer.
+ dump_writer_->EndDump(
+ type,
+ base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
+ base::Unretained(this),
+ callback.is_null()
+ ? base::Closure()
+ : base::Bind(&FireGenericDoneCallback, callback, true, ""),
+ type));
+}
+
+bool WebRtcRtpDumpHandler::ReadyToRelease() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ return incoming_state_ != STATE_STARTED &&
+ incoming_state_ != STATE_STOPPING &&
+ outgoing_state_ != STATE_STARTED && outgoing_state_ != STATE_STOPPING;
+}
+
+WebRtcRtpDumpHandler::ReleasedDumps WebRtcRtpDumpHandler::ReleaseDumps() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+ DCHECK(ReadyToRelease());
+
+ base::FilePath incoming_dump, outgoing_dump;
+
+ if (incoming_state_ == STATE_STOPPED) {
+ DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_.value();
+
+ incoming_state_ = STATE_NONE;
+ incoming_dump = incoming_dump_path_;
+ }
+
+ if (outgoing_state_ == STATE_STOPPED) {
+ DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_.value();
+
+ outgoing_state_ = STATE_NONE;
+ outgoing_dump = outgoing_dump_path_;
+ }
+ return ReleasedDumps(incoming_dump, outgoing_dump);
+}
+
+void WebRtcRtpDumpHandler::OnRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if ((incoming && incoming_state_ != STATE_STARTED) ||
+ (!incoming && outgoing_state_ != STATE_STARTED)) {
+ return;
+ }
+
+ dump_writer_->WriteRtpPacket(
+ packet_header, header_length, packet_length, incoming);
+}
+
+void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+ DCHECK(!callback.is_null());
+
+ // No ongoing dumps, return directly.
+ if ((incoming_state_ == STATE_NONE || incoming_state_ == STATE_STOPPED) &&
+ (outgoing_state_ == STATE_NONE || outgoing_state_ == STATE_STOPPED)) {
+ callback.Run();
+ return;
+ }
+
+ // If the background task runner is working on stopping the dumps, wait for it
+ // to complete and then check the states again.
+ if (incoming_state_ == STATE_STOPPING || outgoing_state_ == STATE_STOPPING) {
+ dump_writer_->background_task_runner()->PostTaskAndReply(
+ FROM_HERE, base::DoNothing(),
+ base::BindOnce(&WebRtcRtpDumpHandler::StopOngoingDumps,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ // Either incoming or outgoing dump must be ongoing.
+ RtpDumpType type =
+ (incoming_state_ == STATE_STARTED)
+ ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
+ : RTP_DUMP_INCOMING)
+ : RTP_DUMP_OUTGOING;
+
+ if (incoming_state_ == STATE_STARTED)
+ incoming_state_ = STATE_STOPPING;
+
+ if (outgoing_state_ == STATE_STARTED)
+ outgoing_state_ = STATE_STOPPING;
+
+ DVLOG(2) << "Stopping ongoing dumps: type = " << type;
+
+ dump_writer_->EndDump(type,
+ base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
+ base::Unretained(this),
+ callback,
+ type));
+}
+
+void WebRtcRtpDumpHandler::SetDumpWriterForTesting(
+ std::unique_ptr<WebRtcRtpDumpWriter> writer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ dump_writer_ = std::move(writer);
+ ++g_ongoing_rtp_dumps;
+
+ incoming_dump_path_ = dump_dir_.AppendASCII("recv");
+ outgoing_dump_path_ = dump_dir_.AppendASCII("send");
+}
+
+void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ RtpDumpType type =
+ (incoming_state_ == STATE_STARTED)
+ ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
+ : RTP_DUMP_INCOMING)
+ : RTP_DUMP_OUTGOING;
+ StopDump(type, GenericDoneCallback());
+}
+
+void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure& callback,
+ RtpDumpType ended_type,
+ bool incoming_success,
+ bool outgoing_success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
+
+ if (DumpTypeContainsIncoming(ended_type)) {
+ DCHECK_EQ(STATE_STOPPING, incoming_state_);
+ incoming_state_ = STATE_STOPPED;
+
+ if (!incoming_success) {
+ base::PostTask(FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ incoming_dump_path_, false));
+
+ DVLOG(2) << "Deleted invalid incoming dump "
+ << incoming_dump_path_.value();
+ incoming_dump_path_.clear();
+ }
+ }
+
+ if (DumpTypeContainsOutgoing(ended_type)) {
+ DCHECK_EQ(STATE_STOPPING, outgoing_state_);
+ outgoing_state_ = STATE_STOPPED;
+
+ if (!outgoing_success) {
+ base::PostTask(FROM_HERE,
+ {base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT},
+ base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+ outgoing_dump_path_, false));
+
+ DVLOG(2) << "Deleted invalid outgoing dump "
+ << outgoing_dump_path_.value();
+ outgoing_dump_path_.clear();
+ }
+ }
+
+ // Release the writer when it's no longer needed.
+ if (incoming_state_ != STATE_STOPPING && outgoing_state_ != STATE_STOPPING &&
+ incoming_state_ != STATE_STARTED && outgoing_state_ != STATE_STARTED) {
+ dump_writer_.reset();
+ --g_ongoing_rtp_dumps;
+ }
+
+ // This object might be deleted after running the callback.
+ if (!callback.is_null())
+ callback.Run();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h
new file mode 100644
index 00000000000..3c40044ea5a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h
@@ -0,0 +1,139 @@
+// Copyright 2014 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+
+class WebRtcRtpDumpWriter;
+
+// WebRtcRtpDumpHandler handles operations regarding the WebRTC RTP dump:
+// - Starts or stops the RTP dumping on behalf of the client.
+// - Stops the RTP dumping when the max dump file size is reached.
+// - Writes the dump file.
+// - Provides the dump file to the client code to be uploaded when
+// ReleaseRtpDump is called.
+// - Cleans up the dump file if not transferred to the client before the object
+// is destroyed.
+//
+// Must be created/used/destroyed on the browser IO thread.
+class WebRtcRtpDumpHandler {
+ public:
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+
+ struct ReleasedDumps {
+ ReleasedDumps(const base::FilePath& incoming_dump,
+ const base::FilePath& outgoing_dump)
+ : incoming_dump_path(incoming_dump),
+ outgoing_dump_path(outgoing_dump) {}
+
+ const base::FilePath incoming_dump_path;
+ const base::FilePath outgoing_dump_path;
+ };
+
+ // The caller must make sure |dump_dir| exists. RTP dump files are saved under
+ // |dump_dir| as "rtpdump_$DIRECTION_$TIMESTAMP.gz", where $DIRECTION is
+ // 'send' for outgoing dump or 'recv' for incoming dump. $TIMESTAMP is the
+ // dump started time converted to a double number in microsecond precision,
+ // which should guarantee the uniqueness across tabs and dump streams in
+ // practice.
+ explicit WebRtcRtpDumpHandler(const base::FilePath& dump_dir);
+ ~WebRtcRtpDumpHandler();
+
+ // Starts the specified type of dumping. Incoming/outgoing dumping can be
+ // started separately. Returns true if called in a valid state, i.e. the
+ // specified type of dump has not been started.
+ bool StartDump(RtpDumpType type, std::string* error_message);
+
+ // Stops the specified type of dumping. Incoming/outgoing dumping can be
+ // stopped separately. Returns asynchronously through |callback|, where
+ // |success| is true if StopDump is called in a valid state. The callback is
+ // called when the writer finishes writing the dumps.
+ void StopDump(RtpDumpType type, const GenericDoneCallback& callback);
+
+ // Returns true if it's valid to call ReleaseDumps, i.e. no dumping is ongoing
+ // or being stopped.
+ bool ReadyToRelease() const;
+
+ // Releases all the dumps and resets the state.
+ // It should only be called when both incoming and outgoing dumping has been
+ // stopped, i.e. ReadyToRelease() returns true. Returns the dump file paths.
+ //
+ // The caller will own the dump file after the method returns. If ReleaseDump
+ // is not called before this object goes away, the dump file will be deleted
+ // by this object.
+ ReleasedDumps ReleaseDumps();
+
+ // Adds an RTP packet to the dump. The caller must make sure it's a valid RTP
+ // packet.
+ void OnRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Stops all ongoing dumps and call |callback| when finished.
+ void StopOngoingDumps(const base::Closure& callback);
+
+ private:
+ friend class WebRtcRtpDumpHandlerTest;
+
+ // State transitions:
+ // initial --> STATE_NONE
+ // StartDump --> STATE_STARTED
+ // StopDump --> STATE_STOPPED
+ // ReleaseDump --> STATE_RELEASING
+ // ReleaseDump done --> STATE_NONE
+ enum State {
+ STATE_NONE,
+ STATE_STARTED,
+ STATE_STOPPING,
+ STATE_STOPPED,
+ };
+
+ // For unit test to inject a fake writer.
+ void SetDumpWriterForTesting(std::unique_ptr<WebRtcRtpDumpWriter> writer);
+
+ // Callback from the dump writer when the max dump size is reached.
+ void OnMaxDumpSizeReached();
+
+ // Callback from the dump writer when ending dumps finishes. Calls |callback|
+ // when finished.
+ void OnDumpEnded(const base::Closure& callback,
+ RtpDumpType ended_type,
+ bool incoming_succeeded,
+ bool outgoing_succeeded);
+
+ SEQUENCE_CHECKER(main_sequence_);
+
+ // The absolute path to the directory containing the incoming/outgoing dumps.
+ const base::FilePath dump_dir_;
+
+ // The dump file paths.
+ base::FilePath incoming_dump_path_;
+ base::FilePath outgoing_dump_path_;
+
+ // The states of the incoming and outgoing dump.
+ State incoming_state_;
+ State outgoing_state_;
+
+ // The object used to create and write the dump file.
+ std::unique_ptr<WebRtcRtpDumpWriter> dump_writer_;
+
+ base::WeakPtrFactory<WebRtcRtpDumpHandler> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRtpDumpHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc
new file mode 100644
index 00000000000..01c742b0968
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_handler_unittest.cc
@@ -0,0 +1,432 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/webrtc_rtp_dump_handler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class FakeDumpWriter : public WebRtcRtpDumpWriter {
+ public:
+ FakeDumpWriter(size_t max_dump_size,
+ const base::Closure& max_size_reached_callback,
+ bool end_dump_success)
+ : WebRtcRtpDumpWriter(base::FilePath(),
+ base::FilePath(),
+ max_dump_size,
+ base::Closure()),
+ max_dump_size_(max_dump_size),
+ current_dump_size_(0),
+ max_size_reached_callback_(max_size_reached_callback),
+ end_dump_success_(end_dump_success) {}
+
+ void WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) override {
+ current_dump_size_ += header_length;
+ if (current_dump_size_ > max_dump_size_)
+ max_size_reached_callback_.Run();
+ }
+
+ void EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback) override {
+ bool incoming_success = end_dump_success_;
+ bool outgoing_success = end_dump_success_;
+
+ if (type == RTP_DUMP_INCOMING)
+ outgoing_success = false;
+ else if (type == RTP_DUMP_OUTGOING)
+ incoming_success = false;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(finished_callback, incoming_success, outgoing_success));
+ }
+
+ private:
+ size_t max_dump_size_;
+ size_t current_dump_size_;
+ base::Closure max_size_reached_callback_;
+ bool end_dump_success_;
+};
+
+class WebRtcRtpDumpHandlerTest : public testing::Test {
+ public:
+ WebRtcRtpDumpHandlerTest()
+ : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {
+ ResetDumpHandler(base::FilePath(), true);
+ }
+
+ void ResetDumpHandler(const base::FilePath& dir, bool end_dump_success) {
+ handler_.reset(new WebRtcRtpDumpHandler(
+ dir.empty() ? base::FilePath(FILE_PATH_LITERAL("dummy")) : dir));
+
+ std::unique_ptr<WebRtcRtpDumpWriter> writer(new FakeDumpWriter(
+ 10, base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached,
+ base::Unretained(handler_.get())),
+ end_dump_success));
+
+ handler_->SetDumpWriterForTesting(std::move(writer));
+ }
+
+ void DeleteDumpHandler() { handler_.reset(); }
+
+ void WriteFakeDumpFiles(const base::FilePath& dir,
+ base::FilePath* incoming_dump,
+ base::FilePath* outgoing_dump) {
+ *incoming_dump = dir.AppendASCII("recv");
+ *outgoing_dump = dir.AppendASCII("send");
+ const char dummy[] = "dummy";
+ EXPECT_GT(base::WriteFile(*incoming_dump, dummy, base::size(dummy)), 0);
+ EXPECT_GT(base::WriteFile(*outgoing_dump, dummy, base::size(dummy)), 0);
+ }
+
+ void FlushTaskRunners() {
+ base::ThreadPoolInstance::Get()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ MOCK_METHOD2(OnStopDumpFinished,
+ void(bool success, const std::string& error));
+
+ MOCK_METHOD0(OnStopOngoingDumpsFinished, void(void));
+
+ protected:
+ content::BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<WebRtcRtpDumpHandler> handler_;
+};
+
+TEST_F(WebRtcRtpDumpHandlerTest, StateTransition) {
+ std::string error;
+
+ RtpDumpType types[3];
+ types[0] = RTP_DUMP_INCOMING;
+ types[1] = RTP_DUMP_OUTGOING;
+ types[2] = RTP_DUMP_BOTH;
+
+ for (size_t i = 0; i < base::size(types); ++i) {
+ DVLOG(2) << "Verifying state transition: type = " << types[i];
+
+ // Only StartDump is allowed in STATE_NONE.
+ EXPECT_CALL(*this, OnStopDumpFinished(false, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ WebRtcRtpDumpHandler::ReleasedDumps empty_dumps(handler_->ReleaseDumps());
+ EXPECT_TRUE(empty_dumps.incoming_dump_path.empty());
+ EXPECT_TRUE(empty_dumps.outgoing_dump_path.empty());
+ EXPECT_TRUE(handler_->StartDump(types[i], &error));
+ base::RunLoop().RunUntilIdle();
+
+ // Only StopDump is allowed in STATE_STARTED.
+ EXPECT_FALSE(handler_->StartDump(types[i], &error));
+ EXPECT_FALSE(handler_->ReadyToRelease());
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ // Only ReleaseDump is allowed in STATE_STOPPED.
+ EXPECT_FALSE(handler_->StartDump(types[i], &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(false, testing::_));
+ handler_->StopDump(types[i],
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ EXPECT_TRUE(handler_->ReadyToRelease());
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ if (types[i] == RTP_DUMP_INCOMING || types[i] == RTP_DUMP_BOTH)
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+
+ if (types[i] == RTP_DUMP_OUTGOING || types[i] == RTP_DUMP_BOTH)
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+
+ base::RunLoop().RunUntilIdle();
+ ResetDumpHandler(base::FilePath(), true);
+ }
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StoppedWhenMaxSizeReached) {
+ std::string error;
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ base::RunLoop().RunUntilIdle();
+
+ // Dumping should have been stopped, so ready to release.
+ WebRtcRtpDumpHandler::ReleasedDumps dumps = handler_->ReleaseDumps();
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, PacketIgnoredIfDumpingNotStarted) {
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), false);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, PacketIgnoredIfDumpingStopped) {
+ std::string error;
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ std::vector<uint8_t> buffer(100, 0);
+ handler_->OnRtpPacket(&buffer[0], buffer.size(), buffer.size(), true);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, CannotStartMoreThanFiveDumps) {
+ std::string error;
+
+ handler_.reset();
+
+ std::unique_ptr<WebRtcRtpDumpHandler> handlers[6];
+
+ for (size_t i = 0; i < base::size(handlers); ++i) {
+ handlers[i].reset(new WebRtcRtpDumpHandler(base::FilePath()));
+
+ if (i < base::size(handlers) - 1) {
+ EXPECT_TRUE(handlers[i]->StartDump(RTP_DUMP_INCOMING, &error));
+ } else {
+ EXPECT_FALSE(handlers[i]->StartDump(RTP_DUMP_INCOMING, &error));
+ }
+ }
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartStopIncomingThenStartStopOutgoing) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_OUTGOING, &error));
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartIncomingStartOutgoingThenStopBoth) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_INCOMING, &error));
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_OUTGOING, &error));
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StartBothThenStopIncomingStopOutgoing) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DumpsCleanedUpIfNotReleased) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ ResetDumpHandler(temp_dir.GetPath(), true);
+
+ base::FilePath incoming_dump, outgoing_dump;
+ WriteFakeDumpFiles(temp_dir.GetPath(), &incoming_dump, &outgoing_dump);
+
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+
+ handler_.reset();
+ FlushTaskRunners();
+
+ EXPECT_FALSE(base::PathExists(incoming_dump));
+ EXPECT_FALSE(base::PathExists(outgoing_dump));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DumpDeletedIfEndDumpFailed) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Make the writer return failure on EndStream.
+ ResetDumpHandler(temp_dir.GetPath(), false);
+
+ base::FilePath incoming_dump, outgoing_dump;
+ WriteFakeDumpFiles(temp_dir.GetPath(), &incoming_dump, &outgoing_dump);
+
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_)).Times(2);
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+
+ EXPECT_FALSE(base::PathExists(incoming_dump));
+ EXPECT_TRUE(base::PathExists(outgoing_dump));
+
+ handler_->StopDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+ EXPECT_FALSE(base::PathExists(outgoing_dump));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileStoppingDumps) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ testing::InSequence s;
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileDumping) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhenAlreadyStopped) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ {
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+
+ handler_->StopDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunners();
+ }
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, StopOngoingDumpsWhileStoppingOneDump) {
+ std::string error;
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ testing::InSequence s;
+ EXPECT_CALL(*this, OnStopDumpFinished(true, testing::_));
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished());
+
+ handler_->StopDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopDumpFinished,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ FlushTaskRunners();
+
+ WebRtcRtpDumpHandler::ReleasedDumps dumps(handler_->ReleaseDumps());
+ EXPECT_FALSE(dumps.incoming_dump_path.empty());
+ EXPECT_FALSE(dumps.outgoing_dump_path.empty());
+}
+
+TEST_F(WebRtcRtpDumpHandlerTest, DeleteHandlerBeforeStopCallback) {
+ std::string error;
+
+ EXPECT_CALL(*this, OnStopOngoingDumpsFinished())
+ .WillOnce(testing::InvokeWithoutArgs(
+ this, &WebRtcRtpDumpHandlerTest::DeleteDumpHandler));
+
+ EXPECT_TRUE(handler_->StartDump(RTP_DUMP_BOTH, &error));
+
+ handler_->StopOngoingDumps(
+ base::Bind(&WebRtcRtpDumpHandlerTest::OnStopOngoingDumpsFinished,
+ base::Unretained(this)));
+
+ base::RunLoop().RunUntilIdle();
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc
new file mode 100644
index 00000000000..d676f226252
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.cc
@@ -0,0 +1,451 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+#include <string.h>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+static const size_t kMinimumGzipOutputBufferSize = 256; // In bytes.
+
+const unsigned char kRtpDumpFileHeaderFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n";
+static const size_t kRtpDumpFileHeaderSize = 16; // In bytes.
+
+// A helper for writing the header of the dump file.
+void WriteRtpDumpFileHeaderBigEndian(base::TimeTicks start,
+ std::vector<uint8_t>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kRtpDumpFileHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(&(*output)[buffer_start_pos]);
+
+ base::TimeDelta delta = start - base::TimeTicks();
+ uint32_t start_sec = delta.InSeconds();
+ base::WriteBigEndian(buffer, start_sec);
+ buffer += sizeof(start_sec);
+
+ uint32_t start_usec =
+ delta.InMilliseconds() * base::Time::kMicrosecondsPerMillisecond;
+ base::WriteBigEndian(buffer, start_usec);
+ buffer += sizeof(start_usec);
+
+ // Network source, always 0.
+ base::WriteBigEndian(buffer, uint32_t(0));
+ buffer += sizeof(uint32_t);
+
+ // UDP port, always 0.
+ base::WriteBigEndian(buffer, uint16_t(0));
+ buffer += sizeof(uint16_t);
+
+ // 2 bytes padding.
+ base::WriteBigEndian(buffer, uint16_t(0));
+}
+
+// The header size for each packet dump.
+static const size_t kPacketDumpHeaderSize = 8; // In bytes.
+
+// A helper for writing the header for each packet dump.
+// |start| is the time when the recording is started.
+// |dump_length| is the length of the packet dump including this header.
+// |packet_length| is the length of the RTP packet header.
+void WritePacketDumpHeaderBigEndian(const base::TimeTicks& start,
+ uint16_t dump_length,
+ uint16_t packet_length,
+ std::vector<uint8_t>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kPacketDumpHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(&(*output)[buffer_start_pos]);
+
+ base::WriteBigEndian(buffer, dump_length);
+ buffer += sizeof(dump_length);
+
+ base::WriteBigEndian(buffer, packet_length);
+ buffer += sizeof(packet_length);
+
+ uint32_t elapsed =
+ static_cast<uint32_t>((base::TimeTicks::Now() - start).InMilliseconds());
+ base::WriteBigEndian(buffer, elapsed);
+}
+
+// Append |src_len| bytes from |src| to |dest|.
+void AppendToBuffer(const uint8_t* src,
+ size_t src_len,
+ std::vector<uint8_t>* dest) {
+ size_t old_dest_size = dest->size();
+ dest->resize(old_dest_size + src_len);
+ memcpy(&(*dest)[old_dest_size], src, src_len);
+}
+
+} // namespace
+
+// This class runs on the backround task runner, compresses and writes the
+// dump buffer to disk.
+class WebRtcRtpDumpWriter::FileWorker {
+ public:
+ explicit FileWorker(const base::FilePath& dump_path) : dump_path_(dump_path) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+
+ memset(&stream_, 0, sizeof(stream_));
+ int result = deflateInit2(&stream_,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ // windowBits = 15 is default, 16 is added to
+ // produce a gzip header + trailer.
+ 15 + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+ }
+
+ ~FileWorker() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Makes sure all allocations are freed.
+ deflateEnd(&stream_);
+ }
+
+ // Compresses the data in |buffer| and write to the dump file. If |end_stream|
+ // is true, the compression stream will be ended and the dump file cannot be
+ // written to any more.
+ void CompressAndWriteToFileOnFileThread(
+ std::unique_ptr<std::vector<uint8_t>> buffer,
+ bool end_stream,
+ FlushResult* result,
+ size_t* bytes_written) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // This is called either when the in-memory buffer is full or the dump
+ // should be ended.
+ DCHECK(!buffer->empty() || end_stream);
+
+ *result = FLUSH_RESULT_SUCCESS;
+ *bytes_written = 0;
+
+ // There may be nothing to compress/write if there is no RTP packet since
+ // the last flush.
+ if (!buffer->empty()) {
+ *bytes_written = CompressAndWriteBufferToFile(buffer.get(), result);
+ } else if (!base::PathExists(dump_path_)) {
+ // If the dump does not exist, it means there is no RTP packet recorded.
+ // Return FLUSH_RESULT_NO_DATA to indicate no dump file created.
+ *result = FLUSH_RESULT_NO_DATA;
+ }
+
+ if (end_stream && !EndDumpFile())
+ *result = FLUSH_RESULT_FAILURE;
+ }
+
+ private:
+ // Helper for CompressAndWriteToFileOnFileThread to compress and write one
+ // dump.
+ size_t CompressAndWriteBufferToFile(std::vector<uint8_t>* buffer,
+ FlushResult* result) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(buffer->size());
+
+ *result = FLUSH_RESULT_SUCCESS;
+
+ std::vector<uint8_t> compressed_buffer;
+ if (!Compress(buffer, &compressed_buffer)) {
+ DVLOG(2) << "Compressing buffer failed.";
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ int bytes_written = -1;
+
+ if (base::PathExists(dump_path_)) {
+ bytes_written =
+ base::AppendToFile(dump_path_, reinterpret_cast<const char*>(
+ compressed_buffer.data()),
+ compressed_buffer.size())
+ ? compressed_buffer.size()
+ : -1;
+ } else {
+ bytes_written = base::WriteFile(
+ dump_path_,
+ reinterpret_cast<const char*>(&compressed_buffer[0]),
+ compressed_buffer.size());
+ }
+
+ if (bytes_written == -1) {
+ DVLOG(2) << "Writing file failed: " << dump_path_.value();
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ DCHECK_EQ(static_cast<size_t>(bytes_written), compressed_buffer.size());
+ return bytes_written;
+ }
+
+ // Compresses |input| into |output|.
+ bool Compress(std::vector<uint8_t>* input, std::vector<uint8_t>* output) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ int result = Z_OK;
+
+ output->resize(std::max(kMinimumGzipOutputBufferSize, input->size()));
+
+ stream_.next_in = &(*input)[0];
+ stream_.avail_in = input->size();
+ stream_.next_out = &(*output)[0];
+ stream_.avail_out = output->size();
+
+ result = deflate(&stream_, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, result);
+ DCHECK_EQ(0U, stream_.avail_in);
+
+ output->resize(output->size() - stream_.avail_out);
+
+ stream_.next_in = NULL;
+ stream_.next_out = NULL;
+ stream_.avail_out = 0;
+ return true;
+ }
+
+ // Ends the compression stream and completes the dump file.
+ bool EndDumpFile() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::vector<uint8_t> output_buffer;
+ output_buffer.resize(kMinimumGzipOutputBufferSize);
+
+ stream_.next_in = NULL;
+ stream_.avail_in = 0;
+ stream_.next_out = &output_buffer[0];
+ stream_.avail_out = output_buffer.size();
+
+ int result = deflate(&stream_, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+
+ result = deflateEnd(&stream_);
+ DCHECK_EQ(Z_OK, result);
+
+ output_buffer.resize(output_buffer.size() - stream_.avail_out);
+
+ memset(&stream_, 0, sizeof(z_stream));
+
+ DCHECK(!output_buffer.empty());
+ return base::AppendToFile(
+ dump_path_, reinterpret_cast<const char*>(output_buffer.data()),
+ output_buffer.size());
+ }
+
+ const base::FilePath dump_path_;
+
+ z_stream stream_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(FileWorker);
+};
+
+WebRtcRtpDumpWriter::WebRtcRtpDumpWriter(
+ const base::FilePath& incoming_dump_path,
+ const base::FilePath& outgoing_dump_path,
+ size_t max_dump_size,
+ const base::Closure& max_dump_size_reached_callback)
+ : max_dump_size_(max_dump_size),
+ max_dump_size_reached_callback_(max_dump_size_reached_callback),
+ total_dump_size_on_disk_(0),
+ background_task_runner_(
+ base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+ base::TaskPriority::BEST_EFFORT})),
+ incoming_file_thread_worker_(new FileWorker(incoming_dump_path)),
+ outgoing_file_thread_worker_(new FileWorker(outgoing_dump_path)) {}
+
+WebRtcRtpDumpWriter::~WebRtcRtpDumpWriter() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ bool success = background_task_runner_->DeleteSoon(
+ FROM_HERE, incoming_file_thread_worker_.release());
+ DCHECK(success);
+
+ success = background_task_runner_->DeleteSoon(
+ FROM_HERE, outgoing_file_thread_worker_.release());
+ DCHECK(success);
+}
+
+void WebRtcRtpDumpWriter::WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ static const size_t kMaxInMemoryBufferSize = 65536;
+
+ std::vector<uint8_t>* dest_buffer =
+ incoming ? &incoming_buffer_ : &outgoing_buffer_;
+
+ // We use the capacity of the buffer to indicate if the buffer has been
+ // initialized and if the dump file header has been created.
+ if (!dest_buffer->capacity()) {
+ dest_buffer->reserve(std::min(kMaxInMemoryBufferSize, max_dump_size_));
+
+ start_time_ = base::TimeTicks::Now();
+
+ // Writes the dump file header.
+ AppendToBuffer(kRtpDumpFileHeaderFirstLine,
+ base::size(kRtpDumpFileHeaderFirstLine) - 1, dest_buffer);
+ WriteRtpDumpFileHeaderBigEndian(start_time_, dest_buffer);
+ }
+
+ size_t packet_dump_length = kPacketDumpHeaderSize + header_length;
+
+ // Flushes the buffer to disk if the buffer is full.
+ if (dest_buffer->size() + packet_dump_length > dest_buffer->capacity())
+ FlushBuffer(incoming, false, FlushDoneCallback());
+
+ WritePacketDumpHeaderBigEndian(
+ start_time_, packet_dump_length, packet_length, dest_buffer);
+
+ // Writes the actual RTP packet header.
+ AppendToBuffer(packet_header, header_length, dest_buffer);
+}
+
+void WebRtcRtpDumpWriter::EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(type == RTP_DUMP_OUTGOING || incoming_file_thread_worker_ != NULL);
+ DCHECK(type == RTP_DUMP_INCOMING || outgoing_file_thread_worker_ != NULL);
+
+ bool incoming = (type == RTP_DUMP_BOTH || type == RTP_DUMP_INCOMING);
+ EndDumpContext context(type, finished_callback);
+
+ // End the incoming dump first if required. OnDumpEnded will continue to end
+ // the outgoing dump if necessary.
+ FlushBuffer(incoming,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ incoming));
+}
+
+size_t WebRtcRtpDumpWriter::max_dump_size() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return max_dump_size_;
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
+ RtpDumpType type,
+ const EndDumpCallback& callback)
+ : type(type),
+ incoming_succeeded(false),
+ outgoing_succeeded(false),
+ callback(callback) {
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
+ const EndDumpContext& other) = default;
+
+WebRtcRtpDumpWriter::EndDumpContext::~EndDumpContext() {
+}
+
+void WebRtcRtpDumpWriter::FlushBuffer(bool incoming,
+ bool end_stream,
+ const FlushDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::unique_ptr<std::vector<uint8_t>> new_buffer(new std::vector<uint8_t>());
+
+ if (incoming) {
+ new_buffer->reserve(incoming_buffer_.capacity());
+ new_buffer->swap(incoming_buffer_);
+ } else {
+ new_buffer->reserve(outgoing_buffer_.capacity());
+ new_buffer->swap(outgoing_buffer_);
+ }
+
+ std::unique_ptr<FlushResult> result(new FlushResult(FLUSH_RESULT_FAILURE));
+
+ std::unique_ptr<size_t> bytes_written(new size_t(0));
+
+ FileWorker* worker = incoming ? incoming_file_thread_worker_.get()
+ : outgoing_file_thread_worker_.get();
+
+ // Using "Unretained(worker)" because |worker| is owner by this object and it
+ // guaranteed to be deleted on the backround task runner before this object
+ // goes away.
+ base::OnceClosure task = base::BindOnce(
+ &FileWorker::CompressAndWriteToFileOnFileThread, base::Unretained(worker),
+ std::move(new_buffer), end_stream, result.get(), bytes_written.get());
+
+ // OnFlushDone is necessary to avoid running the callback after this
+ // object is gone.
+ base::OnceClosure reply = base::BindOnce(
+ &WebRtcRtpDumpWriter::OnFlushDone, weak_ptr_factory_.GetWeakPtr(),
+ callback, std::move(result), std::move(bytes_written));
+
+ // Define the task and reply outside the method call so that getting and
+ // passing the scoped_ptr does not depend on the argument evaluation order.
+ background_task_runner_->PostTaskAndReply(FROM_HERE, std::move(task),
+ std::move(reply));
+
+ if (end_stream) {
+ bool success = background_task_runner_->DeleteSoon(
+ FROM_HERE, incoming ? incoming_file_thread_worker_.release()
+ : outgoing_file_thread_worker_.release());
+ DCHECK(success);
+ }
+}
+
+void WebRtcRtpDumpWriter::OnFlushDone(
+ const FlushDoneCallback& callback,
+ const std::unique_ptr<FlushResult>& result,
+ const std::unique_ptr<size_t>& bytes_written) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ total_dump_size_on_disk_ += *bytes_written;
+
+ if (total_dump_size_on_disk_ >= max_dump_size_ &&
+ !max_dump_size_reached_callback_.is_null()) {
+ max_dump_size_reached_callback_.Run();
+ }
+
+ // Returns success for FLUSH_RESULT_MAX_SIZE_REACHED since the dump is still
+ // valid.
+ if (!callback.is_null()) {
+ callback.Run(*result != FLUSH_RESULT_FAILURE &&
+ *result != FLUSH_RESULT_NO_DATA);
+ }
+}
+
+void WebRtcRtpDumpWriter::OnDumpEnded(EndDumpContext context,
+ bool incoming,
+ bool success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DVLOG(2) << "Dump ended, incoming = " << incoming
+ << ", succeeded = " << success;
+
+ if (incoming)
+ context.incoming_succeeded = success;
+ else
+ context.outgoing_succeeded = success;
+
+ // End the outgoing dump if needed.
+ if (incoming && context.type == RTP_DUMP_BOTH) {
+ FlushBuffer(false,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ false));
+ return;
+ }
+
+ // This object might be deleted after running the callback.
+ context.callback.Run(context.incoming_succeeded, context.outgoing_succeeded);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h
new file mode 100644
index 00000000000..39af6dfa1b5
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h
@@ -0,0 +1,148 @@
+// Copyright 2014 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/rtp_dump_type.h"
+
+// This class is responsible for creating the compressed RTP header dump file:
+// - Adds the RTP headers to an in-memory buffer.
+// - When the in-memory buffer is full, compresses it, and writes it to the
+// disk.
+// - Notifies the caller when the on-disk file size reaches the max limit.
+// - The uncompressed dump follows the standard RTPPlay format
+// (http://www.cs.columbia.edu/irt/software/rtptools/).
+// - The caller is always responsible for cleaning up the dump file in all
+// cases.
+// - WebRtcRtpDumpWriter does not stop writing to the dump after the max size
+// limit is reached. The caller must stop calling WriteRtpPacket instead.
+//
+// This object must run on the IO thread.
+class WebRtcRtpDumpWriter {
+ public:
+ typedef base::Callback<void(bool incoming_succeeded, bool outgoing_succeeded)>
+ EndDumpCallback;
+
+ // |incoming_dump_path| and |outgoing_dump_path| are the file paths of the
+ // compressed dump files for incoming and outgoing packets respectively.
+ // |max_dump_size| is the max size of the compressed dump file in bytes.
+ // |max_dump_size_reached_callback| will be called when the on-disk file size
+ // reaches |max_dump_size|.
+ WebRtcRtpDumpWriter(const base::FilePath& incoming_dump_path,
+ const base::FilePath& outgoing_dump_path,
+ size_t max_dump_size,
+ const base::Closure& max_dump_size_reached_callback);
+
+ virtual ~WebRtcRtpDumpWriter();
+
+ // Adds a RTP packet to the dump. The caller must make sure it's a valid RTP
+ // packet. No validation is done by this method.
+ virtual void WriteRtpPacket(const uint8_t* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming);
+
+ // Flushes the in-memory buffer to the disk and ends the dump. The caller must
+ // make sure the dump has not already been ended.
+ // |finished_callback| will be called to indicate whether the dump is valid.
+ // If this object is destroyed before the operation is finished, the callback
+ // will be canceled and the dump files will be deleted.
+ virtual void EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback);
+
+ size_t max_dump_size() const;
+
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner()
+ const {
+ return background_task_runner_;
+ }
+
+ private:
+ enum FlushResult {
+ // Flushing has succeeded and the dump size is under the max limit.
+ FLUSH_RESULT_SUCCESS,
+ // Nothing has been written to disk and the dump is empty.
+ FLUSH_RESULT_NO_DATA,
+ // Flushing has failed for other reasons.
+ FLUSH_RESULT_FAILURE
+ };
+
+ class FileWorker;
+
+ typedef base::Callback<void(bool)> FlushDoneCallback;
+
+ // Used by EndDump to cache the input and intermediate results.
+ struct EndDumpContext {
+ EndDumpContext(RtpDumpType type, const EndDumpCallback& callback);
+ EndDumpContext(const EndDumpContext& other);
+ ~EndDumpContext();
+
+ RtpDumpType type;
+ bool incoming_succeeded;
+ bool outgoing_succeeded;
+ EndDumpCallback callback;
+ };
+
+ // Flushes the in-memory buffer to disk. If |incoming| is true, the incoming
+ // buffer will be flushed; otherwise, the outgoing buffer will be flushed.
+ // The dump file will be ended if |end_stream| is true. |callback| will be
+ // called when flushing is done.
+ void FlushBuffer(bool incoming,
+ bool end_stream,
+ const FlushDoneCallback& callback);
+
+ // Called when FlushBuffer finishes. Checks the max dump size limit and
+ // maybe calls the |max_dump_size_reached_callback_|. Also calls |callback|
+ // with the flush result.
+ void OnFlushDone(const FlushDoneCallback& callback,
+ const std::unique_ptr<FlushResult>& result,
+ const std::unique_ptr<size_t>& bytes_written);
+
+ // Called when one type of dump has been ended. It continues to end the other
+ // dump if needed based on |context| and |incoming|, or calls the callback in
+ // |context| if no more dump needs to be ended.
+ void OnDumpEnded(EndDumpContext context, bool incoming, bool success);
+
+ // The max limit on the total size of incoming and outgoing dumps on disk.
+ const size_t max_dump_size_;
+
+ // The callback to call when the max size limit is reached.
+ const base::Closure max_dump_size_reached_callback_;
+
+ // The in-memory buffers for the uncompressed dumps.
+ std::vector<uint8_t> incoming_buffer_;
+ std::vector<uint8_t> outgoing_buffer_;
+
+ // The time when the first packet is dumped.
+ base::TimeTicks start_time_;
+
+ // The total on-disk size of the compressed incoming and outgoing dumps.
+ size_t total_dump_size_on_disk_;
+
+ // File workers must be called and deleted on the backround task runner.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+ std::unique_ptr<FileWorker> incoming_file_thread_worker_;
+ std::unique_ptr<FileWorker> outgoing_file_thread_worker_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<WebRtcRtpDumpWriter> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcRtpDumpWriter);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_RTP_DUMP_WRITER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
new file mode 100644
index 00000000000..606aca3fec2
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
@@ -0,0 +1,375 @@
+// Copyright 2014 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 "chrome/browser/media/webrtc/webrtc_rtp_dump_writer.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+static const size_t kMinimumRtpHeaderLength = 12;
+
+static void CreateFakeRtpPacketHeader(size_t csrc_count,
+ size_t extension_header_count,
+ std::vector<uint8_t>* packet_header) {
+ packet_header->resize(kMinimumRtpHeaderLength +
+ csrc_count * sizeof(uint32_t) +
+ (extension_header_count + 1) * sizeof(uint32_t));
+
+ memset(&(*packet_header)[0], 0, packet_header->size());
+
+ // First byte format: vvpxcccc, where 'vv' is the version, 'p' is padding, 'x'
+ // is the extension bit, 'cccc' is the CSRC count.
+ (*packet_header)[0] = 0;
+ (*packet_header)[0] |= (0x2 << 6); // version.
+ // The extension bit.
+ (*packet_header)[0] |= (extension_header_count > 0 ? (0x1 << 4) : 0);
+ (*packet_header)[0] |= (csrc_count & 0xf);
+
+ // Set extension length.
+ size_t offset = kMinimumRtpHeaderLength +
+ (csrc_count & 0xf) * sizeof(uint32_t) + sizeof(uint16_t);
+ base::WriteBigEndian(reinterpret_cast<char*>(&(*packet_header)[offset]),
+ static_cast<uint16_t>(extension_header_count));
+}
+
+static void FlushTaskRunner(base::SequencedTaskRunner* task_runner) {
+ base::RunLoop run_loop;
+ task_runner->PostTask(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+class WebRtcRtpDumpWriterTest : public testing::Test {
+ public:
+ WebRtcRtpDumpWriterTest()
+ : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
+ temp_dir_(new base::ScopedTempDir()) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
+
+ incoming_dump_path_ = temp_dir_->GetPath().AppendASCII("rtpdump_recv");
+ outgoing_dump_path_ = temp_dir_->GetPath().AppendASCII("rtpdump_send");
+ writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ 4 * 1024 * 1024,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached,
+ base::Unretained(this))));
+ }
+
+ // Verifies that the dump contains records of |rtp_packet| repeated
+ // |packet_count| times.
+ void VerifyDumps(size_t incoming_packet_count, size_t outgoing_packet_count) {
+ std::string incoming_dump;
+ std::string outgoing_dump;
+
+ if (incoming_packet_count) {
+ EXPECT_TRUE(base::ReadFileToString(incoming_dump_path_, &incoming_dump));
+ EXPECT_TRUE(VerifyCompressedDump(&incoming_dump, incoming_packet_count));
+ } else {
+ EXPECT_FALSE(base::PathExists(incoming_dump_path_));
+ }
+
+ if (outgoing_packet_count) {
+ EXPECT_TRUE(base::ReadFileToString(outgoing_dump_path_, &outgoing_dump));
+ EXPECT_TRUE(VerifyCompressedDump(&outgoing_dump, outgoing_packet_count));
+ } else {
+ EXPECT_FALSE(base::PathExists(outgoing_dump_path_));
+ }
+ }
+
+ MOCK_METHOD2(OnEndDumpDone, void(bool, bool));
+ MOCK_METHOD0(OnMaxSizeReached, void(void));
+
+ protected:
+ // Verifies the compressed dump file contains the expected number of packets.
+ bool VerifyCompressedDump(std::string* dump, size_t expected_packet_count) {
+ EXPECT_GT(dump->size(), 0U);
+
+ std::vector<uint8_t> decompressed_dump;
+ EXPECT_TRUE(Decompress(dump, &decompressed_dump));
+
+ size_t actual_packet_count = 0;
+ EXPECT_TRUE(ReadDecompressedDump(decompressed_dump, &actual_packet_count));
+ EXPECT_EQ(expected_packet_count, actual_packet_count);
+
+ return true;
+ }
+
+ // Decompresses the |input| into |output|.
+ bool Decompress(std::string* input, std::vector<uint8_t>* output) {
+ z_stream stream = {0};
+
+ int result = inflateInit2(&stream, 15 + 16);
+ EXPECT_EQ(Z_OK, result);
+
+ output->resize(input->size() * 100);
+
+ stream.next_in =
+ reinterpret_cast<unsigned char*>(const_cast<char*>(&(*input)[0]));
+ stream.avail_in = input->size();
+ stream.next_out = &(*output)[0];
+ stream.avail_out = output->size();
+
+ result = inflate(&stream, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+ result = inflateEnd(&stream);
+ DCHECK_EQ(Z_OK, result);
+
+ output->resize(output->size() - stream.avail_out);
+ return true;
+ }
+
+ // Tries to read |dump| as a rtpplay dump file and returns the number of
+ // packets found in the dump.
+ bool ReadDecompressedDump(const std::vector<uint8_t>& dump,
+ size_t* packet_count) {
+ static const char kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n";
+ static const size_t kDumpFileHeaderSize = 4 * sizeof(uint32_t);
+
+ *packet_count = 0;
+ size_t dump_pos = 0;
+
+ // Verifies the first line.
+ EXPECT_EQ(memcmp(&dump[0], kFirstLine, base::size(kFirstLine) - 1), 0);
+
+ dump_pos += base::size(kFirstLine) - 1;
+ EXPECT_GT(dump.size(), dump_pos);
+
+ // Skips the file header.
+ dump_pos += kDumpFileHeaderSize;
+ EXPECT_GT(dump.size(), dump_pos);
+
+ // Reads each packet dump.
+ while (dump_pos < dump.size()) {
+ size_t packet_dump_length = 0;
+ if (!VerifyPacketDump(&dump[dump_pos],
+ dump.size() - dump_pos,
+ &packet_dump_length)) {
+ DVLOG(0) << "Failed to read the packet dump for packet "
+ << *packet_count << ", dump_pos = " << dump_pos
+ << ", dump_length = " << dump.size();
+ return false;
+ }
+
+ EXPECT_GE(dump.size(), dump_pos + packet_dump_length);
+ dump_pos += packet_dump_length;
+
+ (*packet_count)++;
+ }
+ return true;
+ }
+
+ // Tries to read one packet dump starting at |dump| and returns the size of
+ // the packet dump.
+ bool VerifyPacketDump(const uint8_t* dump,
+ size_t dump_length,
+ size_t* packet_dump_length) {
+ static const size_t kDumpHeaderLength = 8;
+
+ size_t dump_pos = 0;
+ base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
+ reinterpret_cast<uint16_t*>(packet_dump_length));
+ if (*packet_dump_length < kDumpHeaderLength + kMinimumRtpHeaderLength)
+ return false;
+
+ EXPECT_GE(dump_length, *packet_dump_length);
+ dump_pos += sizeof(uint16_t);
+
+ uint16_t rtp_packet_length = 0;
+ base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
+ &rtp_packet_length);
+ if (rtp_packet_length < kMinimumRtpHeaderLength)
+ return false;
+
+ dump_pos += sizeof(uint16_t);
+
+ // Skips the elapsed time field.
+ dump_pos += sizeof(uint32_t);
+
+ return IsValidRtpHeader(dump + dump_pos,
+ *packet_dump_length - kDumpHeaderLength);
+ }
+
+ // Returns true if |header| is a valid RTP header.
+ bool IsValidRtpHeader(const uint8_t* header, size_t length) {
+ if ((header[0] & 0xC0) != 0x80)
+ return false;
+
+ size_t cc_count = header[0] & 0x0F;
+ size_t header_length_without_extn = kMinimumRtpHeaderLength + 4 * cc_count;
+
+ if (length < header_length_without_extn)
+ return false;
+
+ uint16_t extension_count = 0;
+ base::ReadBigEndian(
+ reinterpret_cast<const char*>(header + header_length_without_extn + 2),
+ &extension_count);
+
+ if (length < (extension_count + 1) * 4 + header_length_without_extn)
+ return false;
+
+ return true;
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+ std::unique_ptr<base::ScopedTempDir> temp_dir_;
+ base::FilePath incoming_dump_path_;
+ base::FilePath outgoing_dump_path_;
+ std::unique_ptr<WebRtcRtpDumpWriter> writer_;
+};
+
+TEST_F(WebRtcRtpDumpWriterTest, NoDumpFileIfNoPacketDumped) {
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(false, false));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+ EXPECT_FALSE(base::PathExists(incoming_dump_path_));
+ EXPECT_FALSE(base::PathExists(outgoing_dump_path_));
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, WriteAndFlushSmallSizeDump) {
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(1, 2, &packet_header);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(true, true));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ VerifyDumps(1, 1);
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, WriteOverMaxLimit) {
+ // Reset the writer with a small max size limit.
+ writer_.reset(new WebRtcRtpDumpWriter(
+ incoming_dump_path_,
+ outgoing_dump_path_,
+ 100,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached,
+ base::Unretained(this))));
+
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(3, 4, &packet_header);
+
+ const size_t kPacketCount = 200;
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnMaxSizeReached()).Times(testing::AtLeast(1));
+
+ // Write enough packets to overflow the in-memory buffer and max limit.
+ for (size_t i = 0; i < kPacketCount; ++i) {
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+ }
+
+ EXPECT_CALL(*this, OnEndDumpDone(true, true));
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+ VerifyDumps(kPacketCount, kPacketCount);
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, DestroyWriterBeforeEndDumpCallback) {
+ EXPECT_CALL(*this, OnEndDumpDone(testing::_, testing::_)).Times(0);
+
+ writer_->EndDump(RTP_DUMP_BOTH,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ writer_.reset();
+
+ // Two |RunUntilIdle()| calls are needed as the first run posts a task that
+ // we need to give a chance to run with the second call.
+ base::RunLoop().RunUntilIdle();
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebRtcRtpDumpWriterTest, EndDumpsSeparately) {
+ std::vector<uint8_t> packet_header;
+ CreateFakeRtpPacketHeader(1, 2, &packet_header);
+
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, true);
+ writer_->WriteRtpPacket(
+ &packet_header[0], packet_header.size(), 100, false);
+
+ // The scope is used to make sure the EXPECT_CALL is checked before exiting
+ // the scope.
+ {
+ EXPECT_CALL(*this, OnEndDumpDone(true, false));
+ EXPECT_CALL(*this, OnEndDumpDone(false, true));
+
+ writer_->EndDump(RTP_DUMP_INCOMING,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ writer_->EndDump(RTP_DUMP_OUTGOING,
+ base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone,
+ base::Unretained(this)));
+
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ FlushTaskRunner(writer_->background_task_runner().get());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ VerifyDumps(2, 1);
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc
new file mode 100644
index 00000000000..582f04f5474
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_simulcast_browsertest.cc
@@ -0,0 +1,68 @@
+// Copyright 2015 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/files/file_path.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_test.h"
+#include "ui/gl/gl_switches.h"
+
+static const char kSimulcastTestPage[] = "/webrtc/webrtc-simulcast.html";
+
+// Simulcast integration test. This test ensures 'a=x-google-flag:conference'
+// is working and that Chrome is capable of sending simulcast streams.
+class WebRtcSimulcastBrowserTest : public WebRtcTestBase {
+ public:
+ // TODO(phoglund): Make it possible to enable DetectErrorsInJavaScript() here.
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Just answer 'allow' to GetUserMedia invocations.
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+
+ // Use fake devices in order to run on VMs.
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+};
+
+// Fails/times out on Windows and Chrome OS. Flaky on Mac.
+// http://crbug.com/452623
+// http://crbug.com/1004546
+// MSan reports errors. http://crbug.com/452892
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) || \
+ defined(MEMORY_SANITIZER)
+#define MAYBE_TestVgaReturnsTwoSimulcastStreams DISABLED_TestVgaReturnsTwoSimulcastStreams
+#else
+#define MAYBE_TestVgaReturnsTwoSimulcastStreams TestVgaReturnsTwoSimulcastStreams
+#endif
+IN_PROC_BROWSER_TEST_F(WebRtcSimulcastBrowserTest,
+ MAYBE_TestVgaReturnsTwoSimulcastStreams) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL(kSimulcastTestPage));
+
+ content::WebContents* tab_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ ASSERT_EQ("OK", ExecuteJavascript("testVgaReturnsTwoSimulcastStreams()",
+ tab_contents));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc
new file mode 100644
index 00000000000..049fc2ce87e
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_stats_perf_browsertest.cc
@@ -0,0 +1,391 @@
+// Copyright 2016 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 <string>
+
+#include "base/command_line.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace content {
+
+namespace {
+
+const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
+
+const char kInboundRtp[] = "inbound-rtp";
+const char kOutboundRtp[] = "outbound-rtp";
+
+constexpr int kBitsPerByte = 8;
+
+constexpr char kMetricPrefixAudioStats[] = "WebRtcAudioStats.";
+constexpr char kMetricPrefixVideoStats[] = "WebRtcVideoStats.";
+constexpr char kMetricPrefixGetStats[] = "WebRtcGetStats.";
+constexpr char kMetricSendRateBitsPerS[] = "send_rate";
+constexpr char kMetricReceiveRateBitsPerS[] = "receive_rate";
+constexpr char kMetricInvocationTimeMs[] = "invocation_time";
+
+perf_test::PerfResultReporter SetUpAudioReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixAudioStats, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpVideoReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoStats, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+perf_test::PerfResultReporter SetUpGetStatsReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixGetStats, story);
+ reporter.RegisterFyiMetric(kMetricInvocationTimeMs, "ms");
+ return reporter;
+}
+
+enum class GetStatsVariation {
+ PROMISE_BASED,
+ CALLBACK_BASED
+};
+
+// Sums up "RTC[In/Out]boundRTPStreamStats.bytes_[received/sent]" values.
+double GetTotalRTPStreamBytes(
+ TestStatsReportDictionary* report, const char* type,
+ const char* media_type) {
+ DCHECK(type == kInboundRtp || type == kOutboundRtp);
+ const char* bytes_name =
+ (type == kInboundRtp) ? "bytesReceived" : "bytesSent";
+ double total_bytes = 0.0;
+ report->ForEach([&type, &bytes_name, &media_type, &total_bytes](
+ const TestStatsDictionary& stats) {
+ if (stats.GetString("type") == type &&
+ stats.GetString("mediaType") == media_type) {
+ total_bytes += stats.GetNumber(bytes_name);
+ }
+ });
+ return total_bytes;
+}
+
+double GetAudioBytesSent(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "audio");
+}
+
+double GetAudioBytesReceived(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "audio");
+}
+
+double GetVideoBytesSent(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "video");
+}
+
+double GetVideoBytesReceived(TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "video");
+}
+
+// Performance browsertest for WebRTC. This test is manual since it takes long
+// to execute and requires the reference files provided by the webrtc.DEPS
+// solution (which is only available on WebRTC internal bots).
+// Gets its metrics from the standards conformant "RTCPeerConnection.getStats".
+class WebRtcStatsPerfBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Ensure the infobar is enabled, since we expect that in this test.
+ EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
+
+ // Play a suitable, somewhat realistic video file.
+ base::FilePath input_video = test::GetReferenceFilesDir()
+ .Append(test::kReferenceFileName360p)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ input_video);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ }
+
+ void StartCall(const std::string& audio_codec,
+ const std::string& video_codec,
+ bool prefer_hw_video_codec,
+ const std::string& video_codec_profile) {
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 100000.";
+
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 100)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 100000.";
+
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ left_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+ right_tab_ = OpenTestPageAndGetUserMediaInNewTab(kMainWebrtcTestHtmlPage);
+
+ SetupPeerconnectionWithLocalStream(left_tab_);
+ SetupPeerconnectionWithLocalStream(right_tab_);
+ SetDefaultAudioCodec(left_tab_, audio_codec);
+ SetDefaultAudioCodec(right_tab_, audio_codec);
+ SetDefaultVideoCodec(left_tab_, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ SetDefaultVideoCodec(right_tab_, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+ CreateDataChannel(left_tab_, "data");
+ CreateDataChannel(right_tab_, "data");
+ NegotiateCall(left_tab_, right_tab_);
+ StartDetectingVideo(left_tab_, "remote-view");
+ StartDetectingVideo(right_tab_, "remote-view");
+ WaitForVideoToPlay(left_tab_);
+ WaitForVideoToPlay(right_tab_);
+ }
+
+ void EndCall() {
+ if (left_tab_)
+ HangUp(left_tab_);
+ if (right_tab_)
+ HangUp(right_tab_);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetricsWithAudioCodec(
+ const std::string& audio_codec) {
+ RunsAudioAndVideoCallCollectingMetrics(
+ audio_codec, kUseDefaultVideoCodec, false /* prefer_hw_video_codec */,
+ "" /* video_codec_profile */, "" /* video_codec_print_modifier */);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ const std::string& video_codec,
+ bool prefer_hw_video_codec = false,
+ const std::string& video_codec_profile = std::string(),
+ const std::string& video_codec_print_modifier = std::string()) {
+ RunsAudioAndVideoCallCollectingMetrics(
+ kUseDefaultAudioCodec, video_codec, prefer_hw_video_codec,
+ video_codec_profile, video_codec_print_modifier);
+ }
+
+ void RunsAudioAndVideoCallCollectingMetrics(
+ const std::string& audio_codec,
+ const std::string& video_codec,
+ bool prefer_hw_video_codec,
+ const std::string& video_codec_profile,
+ const std::string& video_codec_print_modifier) {
+ StartCall(audio_codec, video_codec, prefer_hw_video_codec,
+ video_codec_profile);
+
+ // Call for 60 seconds so that values may stabilize, bandwidth ramp up, etc.
+ test::SleepInJavascript(left_tab_, 60000);
+
+ // The ramp-up may vary greatly and impact the resulting total bytes, to get
+ // reliable measurements we do two measurements, at 60 and 70 seconds and
+ // look at the average bytes/second in that window.
+ double audio_bytes_sent_before = 0.0;
+ double audio_bytes_received_before = 0.0;
+ double video_bytes_sent_before = 0.0;
+ double video_bytes_received_before = 0.0;
+
+ scoped_refptr<TestStatsReportDictionary> report =
+ GetStatsReportDictionary(left_tab_);
+ if (audio_codec != kUseDefaultAudioCodec) {
+ audio_bytes_sent_before = GetAudioBytesSent(report.get());
+ audio_bytes_received_before = GetAudioBytesReceived(report.get());
+
+ }
+ if (video_codec != kUseDefaultVideoCodec) {
+ video_bytes_sent_before = GetVideoBytesSent(report.get());
+ video_bytes_received_before = GetVideoBytesReceived(report.get());
+ }
+
+ double measure_duration_seconds = 10.0;
+ test::SleepInJavascript(left_tab_, static_cast<int>(
+ measure_duration_seconds * base::Time::kMillisecondsPerSecond));
+
+ report = GetStatsReportDictionary(left_tab_);
+ if (audio_codec != kUseDefaultAudioCodec) {
+ double audio_bytes_sent_after = GetAudioBytesSent(report.get());
+ double audio_bytes_received_after = GetAudioBytesReceived(report.get());
+
+ double audio_send_rate =
+ (audio_bytes_sent_after - audio_bytes_sent_before) /
+ measure_duration_seconds;
+ double audio_receive_rate =
+ (audio_bytes_received_after - audio_bytes_received_before) /
+ measure_duration_seconds;
+
+ auto reporter = SetUpAudioReporter(audio_codec);
+ reporter.AddResult(kMetricSendRateBitsPerS,
+ audio_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ audio_receive_rate * kBitsPerByte);
+ }
+ if (video_codec != kUseDefaultVideoCodec) {
+ double video_bytes_sent_after = GetVideoBytesSent(report.get());
+ double video_bytes_received_after = GetVideoBytesReceived(report.get());
+
+ double video_send_rate =
+ (video_bytes_sent_after - video_bytes_sent_before) /
+ measure_duration_seconds;
+ double video_receive_rate =
+ (video_bytes_received_after - video_bytes_received_before) /
+ measure_duration_seconds;
+
+ std::string story =
+ (video_codec_print_modifier.empty() ? video_codec
+ : video_codec_print_modifier);
+ auto reporter = SetUpVideoReporter(story);
+ reporter.AddResult(kMetricSendRateBitsPerS,
+ video_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ video_receive_rate * kBitsPerByte);
+ }
+
+ EndCall();
+ }
+
+ void RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation variation) {
+ EXPECT_TRUE(base::TimeTicks::IsHighResolution());
+
+ StartCall(kUseDefaultAudioCodec, kUseDefaultVideoCodec,
+ false /* prefer_hw_video_codec */, "");
+
+ double invocation_time = 0.0;
+ switch (variation) {
+ case GetStatsVariation::PROMISE_BASED:
+ invocation_time = (MeasureGetStatsPerformance(left_tab_) +
+ MeasureGetStatsPerformance(right_tab_)) / 2.0;
+ break;
+ case GetStatsVariation::CALLBACK_BASED:
+ invocation_time =
+ (MeasureGetStatsCallbackPerformance(left_tab_) +
+ MeasureGetStatsCallbackPerformance(right_tab_)) / 2.0;
+ break;
+ }
+ auto reporter = SetUpGetStatsReporter(
+ variation == GetStatsVariation::PROMISE_BASED ? "promise" : "callback");
+ reporter.AddResult(kMetricInvocationTimeMs, invocation_time);
+
+ EndCall();
+ }
+
+ private:
+ content::WebContents* left_tab_ = nullptr;
+ content::WebContents* right_tab_ = nullptr;
+};
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_opus) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("opus");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_ISAC) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("ISAC");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_G722) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("G722");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_PCMU) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("PCMU");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_AudioCodec_PCMA) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithAudioCodec("PCMA");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec("VP8");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec("VP9");
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_VP9Profile2) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ "VP9", true /* prefer_hw_video_codec */,
+ WebRtcTestBase::kVP9Profile2Specifier, "VP9p2");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_H264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcPerfBrowserTest."
+ "MANUAL_RunsAudioAndVideoCallCollectingMetrics_VideoCodec_"
+ "H264 (test "
+ "\"OK\")";
+ return;
+ }
+ RunsAudioAndVideoCallCollectingMetricsWithVideoCodec(
+ "H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallMeasuringGetStatsPerformance_Promise) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation::PROMISE_BASED);
+}
+
+IN_PROC_BROWSER_TEST_F(
+ WebRtcStatsPerfBrowserTest,
+ MANUAL_RunsAudioAndVideoCallMeasuringGetStatsPerformance_Callback) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ RunsAudioAndVideoCallMeasuringGetStatsPerformance(
+ GetStatsVariation::CALLBACK_BASED);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
new file mode 100644
index 00000000000..c0126266d57
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
@@ -0,0 +1,539 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/webrtc_text_log_handler.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/cpu.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/media/webrtc_logging.mojom.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/webrtc_log.h"
+#include "content/public/common/content_features.h"
+#include "gpu/config/gpu_info.h"
+#include "media/audio/audio_manager.h"
+#include "media/webrtc/webrtc_switches.h"
+#include "net/base/ip_address.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_interfaces.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/service_manager/sandbox/features.h"
+#include "services/service_manager/sandbox/sandbox_type.h"
+
+#if defined(OS_LINUX)
+#include "base/linux_util.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/system/statistics_provider.h"
+#endif
+
+using base::NumberToString;
+
+namespace {
+
+void ForwardMessageViaTaskRunner(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::Callback<void(const std::string&)> callback,
+ const std::string& message) {
+ task_runner->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback), message));
+}
+
+std::string Format(const std::string& message,
+ base::Time timestamp,
+ base::Time start_time) {
+ int32_t interval_ms =
+ static_cast<int32_t>((timestamp - start_time).InMilliseconds());
+ return base::StringPrintf("[%03d:%03d] %s", interval_ms / 1000,
+ interval_ms % 1000, message.c_str());
+}
+
+std::string FormatMetaDataAsLogMessage(const WebRtcLogMetaDataMap& meta_data) {
+ std::string message;
+ for (auto& kv : meta_data) {
+ message += kv.first + ": " + kv.second + '\n';
+ }
+ // Remove last '\n'.
+ if (!message.empty())
+ message.erase(message.size() - 1, 1); // TODO(terelius): Use pop_back()
+ return message;
+}
+
+// For privacy reasons when logging IP addresses. The returned "sensitive
+// string" is for release builds a string with the end stripped away. Last
+// octet for IPv4 and last 80 bits (5 groups) for IPv6. String will be
+// "1.2.3.x" and "1.2.3::" respectively. For debug builds, the string is
+// not stripped.
+std::string IPAddressToSensitiveString(const net::IPAddress& address) {
+#if defined(NDEBUG)
+ std::string sensitive_address;
+ switch (address.size()) {
+ case net::IPAddress::kIPv4AddressSize: {
+ sensitive_address = address.ToString();
+ size_t find_pos = sensitive_address.rfind('.');
+ if (find_pos == std::string::npos)
+ return std::string();
+ sensitive_address.resize(find_pos);
+ sensitive_address += ".x";
+ break;
+ }
+ case net::IPAddress::kIPv6AddressSize: {
+ // TODO(grunell): Create a string of format "1:2:3:x:x:x:x:x" to clarify
+ // that the end has been stripped out.
+ net::IPAddressBytes stripped = address.bytes();
+ std::fill(stripped.begin() + 6, stripped.end(), 0);
+ sensitive_address = net::IPAddress(stripped).ToString();
+ break;
+ }
+ default: { break; }
+ }
+ return sensitive_address;
+#else
+ return address.ToString();
+#endif
+}
+
+} // namespace
+
+WebRtcTextLogHandler::WebRtcTextLogHandler(int render_process_id)
+ : render_process_id_(render_process_id), logging_state_(CLOSED) {}
+
+WebRtcTextLogHandler::~WebRtcTextLogHandler() {
+ // If the log isn't closed that means we haven't decremented the log count
+ // in the LogUploader.
+ DCHECK(logging_state_ == CLOSED || channel_is_closing_);
+ DCHECK(!log_buffer_);
+}
+
+WebRtcTextLogHandler::LoggingState WebRtcTextLogHandler::GetState() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return logging_state_;
+}
+
+bool WebRtcTextLogHandler::GetChannelIsClosing() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return channel_is_closing_;
+}
+
+void WebRtcTextLogHandler::SetMetaData(
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "The renderer is closing.");
+ return;
+ }
+
+ if (logging_state_ != CLOSED && logging_state_ != STARTED) {
+ FireGenericDoneCallback(callback, false,
+ "Meta data must be set before stop or upload.");
+ return;
+ }
+
+ if (logging_state_ == LoggingState::STARTED) {
+ std::string meta_data_message = FormatMetaDataAsLogMessage(*meta_data);
+ LogToCircularBuffer(meta_data_message);
+ }
+
+ if (!meta_data_) {
+ // If no meta data has been set previously, set it now.
+ meta_data_ = std::move(meta_data);
+ } else if (meta_data) {
+ // If there is existing meta data, update it and any new fields. The meta
+ // data is kept around to be uploaded separately from the log.
+ for (const auto& it : *meta_data)
+ (*meta_data_)[it.first] = it.second;
+ }
+
+ FireGenericDoneCallback(callback, true, "");
+}
+
+bool WebRtcTextLogHandler::StartLogging(WebRtcLogUploader* log_uploader,
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "The renderer is closing.");
+ return false;
+ }
+
+ if (logging_state_ != CLOSED) {
+ FireGenericDoneCallback(callback, false, "A log is already open.");
+ return false;
+ }
+
+ if (!log_uploader->ApplyForStartLogging()) {
+ FireGenericDoneCallback(callback, false,
+ "Cannot start, maybe the maximum number of "
+ "simultaneuos logs has been reached.");
+ return false;
+ }
+
+ logging_state_ = STARTING;
+
+ DCHECK(!log_buffer_);
+ log_buffer_.reset(new WebRtcLogBuffer());
+ if (!meta_data_)
+ meta_data_.reset(new WebRtcLogMetaDataMap());
+
+ content::GetNetworkService()->GetNetworkList(
+ net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES,
+ base::BindOnce(&WebRtcTextLogHandler::OnGetNetworkInterfaceList,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+ return true;
+}
+
+void WebRtcTextLogHandler::StartDone(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false,
+ "Failed to start log. Renderer is closing.");
+ return;
+ }
+
+ DCHECK_EQ(STARTING, logging_state_);
+
+ base::UmaHistogramSparse("WebRtcTextLogging.Start", web_app_id_);
+
+ logging_started_time_ = base::Time::Now();
+ logging_state_ = STARTED;
+ FireGenericDoneCallback(callback, true, "");
+}
+
+bool WebRtcTextLogHandler::StopLogging(const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(callback, false,
+ "Can't stop log. Renderer is closing.");
+ return false;
+ }
+
+ if (logging_state_ != STARTED) {
+ FireGenericDoneCallback(callback, false, "Logging not started.");
+ return false;
+ }
+
+ stop_callback_ = callback;
+ logging_state_ = STOPPING;
+
+ base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::ClearLogMessageCallback,
+ render_process_id_));
+ return true;
+}
+
+void WebRtcTextLogHandler::StopDone() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(stop_callback_);
+
+ if (channel_is_closing_) {
+ FireGenericDoneCallback(stop_callback_, false,
+ "Failed to stop log. Renderer is closing.");
+ return;
+ }
+
+ // If we aren't in STOPPING state, then there is a bug in the caller, since
+ // it is responsible for checking the state before making the call. If we do
+ // enter here in a bad state, then we can't use the stop_callback_ or we
+ // might fire the same callback multiple times.
+ DCHECK_EQ(STOPPING, logging_state_);
+ if (logging_state_ == STOPPING) {
+ logging_started_time_ = base::Time();
+ logging_state_ = STOPPED;
+ FireGenericDoneCallback(stop_callback_, true, "");
+ stop_callback_.Reset();
+ }
+}
+
+void WebRtcTextLogHandler::ChannelClosing() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ == STARTING || logging_state_ == STARTED) {
+ base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::ClearLogMessageCallback,
+ render_process_id_));
+ }
+ channel_is_closing_ = true;
+}
+
+void WebRtcTextLogHandler::DiscardLog() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(logging_state_ == STOPPED ||
+ (channel_is_closing_ && logging_state_ != CLOSED));
+
+ base::UmaHistogramSparse("WebRtcTextLogging.Discard", web_app_id_);
+
+ log_buffer_.reset();
+ meta_data_.reset();
+ logging_state_ = LoggingState::CLOSED;
+}
+
+void WebRtcTextLogHandler::ReleaseLog(
+ std::unique_ptr<WebRtcLogBuffer>* log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap>* meta_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(logging_state_ == STOPPED ||
+ (channel_is_closing_ && logging_state_ != CLOSED));
+ DCHECK(log_buffer_);
+ DCHECK(meta_data_);
+
+ // Checking log_buffer_ here due to seeing some crashes out in the wild.
+ // See crbug/699960 for more details.
+ // TODO(crbug/807547): Remove if condition.
+ if (log_buffer_) {
+ log_buffer_->SetComplete();
+ *log_buffer = std::move(log_buffer_);
+ }
+
+ if (meta_data_)
+ *meta_data = std::move(meta_data_);
+
+ logging_state_ = LoggingState::CLOSED;
+}
+
+void WebRtcTextLogHandler::LogToCircularBuffer(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_NE(logging_state_, CLOSED);
+ if (log_buffer_) {
+ log_buffer_->Log(message);
+ }
+}
+
+void WebRtcTextLogHandler::LogMessage(const std::string& message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ == STARTED && !channel_is_closing_) {
+ LogToCircularBuffer(
+ Format(message, base::Time::Now(), logging_started_time_));
+ }
+}
+
+void WebRtcTextLogHandler::LogWebRtcLoggingMessage(
+ const chrome::mojom::WebRtcLoggingMessage* message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ LogToCircularBuffer(
+ Format(message->data, message->timestamp, logging_started_time_));
+}
+
+bool WebRtcTextLogHandler::ExpectLoggingStateStopped(
+ const GenericDoneCallback& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (logging_state_ != STOPPED) {
+ FireGenericDoneCallback(callback, false,
+ "Logging not stopped or no log open.");
+ return false;
+ }
+ return true;
+}
+
+void WebRtcTextLogHandler::FireGenericDoneCallback(
+ const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!callback.is_null());
+
+ if (error_message.empty()) {
+ DCHECK(success);
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message));
+ return;
+ }
+
+ DCHECK(!success);
+
+ // Add current logging state to error message.
+ auto state_string = [&] {
+ switch (logging_state_) {
+ case LoggingState::CLOSED:
+ return "closed";
+ case LoggingState::STARTING:
+ return "starting";
+ case LoggingState::STARTED:
+ return "started";
+ case LoggingState::STOPPING:
+ return "stopping";
+ case LoggingState::STOPPED:
+ return "stopped";
+ }
+ NOTREACHED();
+ return "";
+ };
+
+ std::string error_message_with_state =
+ base::StrCat({error_message, ". State=", state_string(), ". Channel is ",
+ channel_is_closing_ ? "" : "not ", "closing."});
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(callback, success, error_message_with_state));
+}
+
+void WebRtcTextLogHandler::SetWebAppId(int web_app_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ web_app_id_ = web_app_id;
+}
+
+void WebRtcTextLogHandler::OnGetNetworkInterfaceList(
+ const GenericDoneCallback& callback,
+ const base::Optional<net::NetworkInterfaceList>& networks) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (logging_state_ != STARTING || channel_is_closing_) {
+ FireGenericDoneCallback(callback, false, "Logging cancelled.");
+ return;
+ }
+
+ // Log start time (current time). We don't use base/i18n/time_formatting.h
+ // here because we don't want the format of the current locale.
+ base::Time::Exploded now = {0};
+ base::Time::Now().LocalExplode(&now);
+ LogToCircularBuffer(base::StringPrintf("Start %d-%02d-%02d %02d:%02d:%02d",
+ now.year, now.month, now.day_of_month,
+ now.hour, now.minute, now.second));
+
+ // Write metadata if received before logging started.
+ if (meta_data_ && !meta_data_->empty()) {
+ std::string info = FormatMetaDataAsLogMessage(*meta_data_);
+ LogToCircularBuffer(info);
+ }
+
+ // Chrome version
+ LogToCircularBuffer("Chrome version: " + version_info::GetVersionNumber() +
+ " " + chrome::GetChannelName());
+
+ // OS
+ LogToCircularBuffer(base::SysInfo::OperatingSystemName() + " " +
+ base::SysInfo::OperatingSystemVersion() + " " +
+ base::SysInfo::OperatingSystemArchitecture());
+#if defined(OS_LINUX)
+ LogToCircularBuffer("Linux distribution: " + base::GetLinuxDistro());
+#endif
+
+ // CPU
+ base::CPU cpu;
+ LogToCircularBuffer(
+ "Cpu: " + NumberToString(cpu.family()) + "." +
+ NumberToString(cpu.model()) + "." + NumberToString(cpu.stepping()) +
+ ", x" + NumberToString(base::SysInfo::NumberOfProcessors()) + ", " +
+ NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB()) + "MB");
+ LogToCircularBuffer("Cpu brand: " + cpu.cpu_brand());
+
+ // Computer model
+ std::string computer_model = "Not available";
+#if defined(OS_MACOSX)
+ computer_model = base::mac::GetModelIdentifier();
+#elif defined(OS_CHROMEOS)
+ chromeos::system::StatisticsProvider::GetInstance()->GetMachineStatistic(
+ chromeos::system::kHardwareClassKey, &computer_model);
+#endif
+ LogToCircularBuffer("Computer model: " + computer_model);
+
+ // GPU
+ gpu::GPUInfo gpu_info = content::GpuDataManager::GetInstance()->GetGPUInfo();
+ const gpu::GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
+ LogToCircularBuffer(
+ "Gpu: machine-model-name=" + gpu_info.machine_model_name +
+ ", machine-model-version=" + gpu_info.machine_model_version +
+ ", vendor-id=" + base::NumberToString(active_gpu.vendor_id) +
+ ", device-id=" + base::NumberToString(active_gpu.device_id) +
+ ", driver-vendor=" + active_gpu.driver_vendor +
+ ", driver-version=" + active_gpu.driver_version);
+ LogToCircularBuffer("OpenGL: gl-vendor=" + gpu_info.gl_vendor +
+ ", gl-renderer=" + gpu_info.gl_renderer +
+ ", gl-version=" + gpu_info.gl_version);
+
+ // AudioService features
+ auto enabled_or_disabled_feature_string = [](auto& feature) {
+ return base::FeatureList::IsEnabled(feature) ? "enabled" : "disabled";
+ };
+ auto enabled_or_disabled_bool_string = [](bool value) {
+ return value ? "enabled" : "disabled";
+ };
+ LogToCircularBuffer(base::StrCat(
+ {"AudioService: AudioStreams=",
+ enabled_or_disabled_feature_string(features::kAudioServiceAudioStreams),
+ ", OutOfProcess=",
+ enabled_or_disabled_feature_string(features::kAudioServiceOutOfProcess),
+ ", LaunchOnStartup=",
+ enabled_or_disabled_feature_string(
+ features::kAudioServiceLaunchOnStartup),
+ ", Sandbox=",
+ enabled_or_disabled_bool_string(
+ service_manager::IsAudioSandboxEnabled()),
+ ", ApmInAudioService=",
+ enabled_or_disabled_bool_string(
+ media::IsWebRtcApmInAudioServiceEnabled())}));
+
+ // Audio manager
+ // On some platforms, this can vary depending on build flags and failure
+ // fallbacks. On Linux for example, we fallback on ALSA if PulseAudio fails to
+ // initialize. TODO(http://crbug/843202): access AudioManager name via Audio
+ // service interface.
+ media::AudioManager* audio_manager = media::AudioManager::Get();
+ LogToCircularBuffer(base::StringPrintf(
+ "Audio manager: %s",
+ audio_manager ? audio_manager->GetName() : "Out of process"));
+
+ // Network interfaces
+ const net::NetworkInterfaceList empty_network_list;
+ const net::NetworkInterfaceList& network_list =
+ networks.has_value() ? *networks : empty_network_list;
+ LogToCircularBuffer("Discovered " +
+ base::NumberToString(network_list.size()) +
+ " network interfaces:");
+ for (const auto& network : network_list) {
+ LogToCircularBuffer(
+ "Name: " + network.friendly_name + ", Address: " +
+ IPAddressToSensitiveString(network.address) + ", Type: " +
+ net::NetworkChangeNotifier::ConnectionTypeToString(network.type));
+ }
+
+ StartDone(callback);
+
+ // After the above data has been written, tell the browser to enable logging.
+ // TODO(terelius): Once we have moved over to Mojo, we could tell the
+ // renderer to start logging here, but for the time being
+ // WebRtcLoggingHandlerHost::StartLogging will be responsible for sending
+ // that IPC message.
+
+ // TODO(darin): Change SetLogMessageCallback to run on the UI thread.
+
+ auto log_message_callback = base::Bind(
+ &ForwardMessageViaTaskRunner, base::SequencedTaskRunnerHandle::Get(),
+ base::Bind(&WebRtcTextLogHandler::LogMessage,
+ weak_factory_.GetWeakPtr()));
+ base::PostTask(
+ FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(&content::WebRtcLog::SetLogMessageCallback,
+ render_process_id_, std::move(log_message_callback)));
+}
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h
new file mode 100644
index 00000000000..02c5f50cc32
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_text_log_handler.h
@@ -0,0 +1,149 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/media/webrtc/webrtc_log_uploader.h"
+#include "net/base/network_interfaces.h"
+
+namespace chrome {
+namespace mojom {
+class WebRtcLoggingMessage;
+} // namespace mojom
+} // namespace chrome
+
+class WebRtcLogBuffer;
+
+class WebRtcTextLogHandler {
+ public:
+ // States used for protecting from function calls made at non-allowed points
+ // in time. For example, StartLogging() is only allowed in CLOSED state.
+ // See also comment on |channel_is_closing_| below.
+ // Transitions: SetMetaData(): CLOSED -> CLOSED, or
+ // STARTED -> STARTED
+ // StartLogging(): CLOSED -> STARTING.
+ // StartDone(): STARTING -> STARTED.
+ // StopLogging(): STARTED -> STOPPING.
+ // StopDone(): STOPPING -> STOPPED.
+ // DiscardLog(): STOPPED -> CLOSED.
+ // ReleaseLog(): STOPPED -> CLOSED.
+ enum LoggingState {
+ CLOSED, // Logging not started, no log in memory.
+ STARTING, // Start logging is in progress.
+ STARTED, // Logging started.
+ STOPPING, // Stop logging is in progress.
+ STOPPED, // Logging has been stopped, log still open in memory.
+ };
+
+ typedef base::Callback<void(bool, const std::string&)> GenericDoneCallback;
+
+ explicit WebRtcTextLogHandler(int render_process_id);
+ ~WebRtcTextLogHandler();
+
+ // Returns the current state of the log.
+ LoggingState GetState() const;
+
+ // Returns true if channel is closing.
+ bool GetChannelIsClosing() const;
+
+ // Sets meta data for log uploading. Merged with any already set meta data.
+ // Values for existing keys are overwritten. The meta data already set at log
+ // start is written to the beginning of the log. Meta data set after log start
+ // is written to the log at that time.
+ void SetMetaData(std::unique_ptr<WebRtcLogMetaDataMap> meta_data,
+ const GenericDoneCallback& callback);
+
+ // Opens a log and starts logging if allowed by the LogUploader.
+ // Returns false if logging could not be started.
+ bool StartLogging(WebRtcLogUploader* log_uploader,
+ const GenericDoneCallback& callback);
+
+ // Stops logging. Log will remain open until UploadLog or DiscardLog is
+ // called.
+ bool StopLogging(const GenericDoneCallback& callback);
+
+ // Called by the WebRtcLoggingHandlerHost when logging has stopped in the
+ // renderer. Should only be called in response to a
+ // WebRtcLoggingMsg_LoggingStopped IPC message.
+ void StopDone();
+
+ // Signals that the renderer is closing, which de facto stops logging but
+ // keeps the log in memory.
+ // Can be called in any state except CLOSED.
+ void ChannelClosing();
+
+ // Discards a stopped log.
+ void DiscardLog();
+
+ // Releases a stopped log to the caller.
+ void ReleaseLog(std::unique_ptr<WebRtcLogBuffer>* log_buffer,
+ std::unique_ptr<WebRtcLogMetaDataMap>* meta_data);
+
+ // Adds a message to the log.
+ void LogMessage(const std::string& message);
+
+ // Adds a message to the log.
+ void LogWebRtcLoggingMessage(
+ const chrome::mojom::WebRtcLoggingMessage* message);
+
+ // Returns true if the logging state is CLOSED and fires an the callback
+ // with an error message otherwise.
+ bool ExpectLoggingStateStopped(const GenericDoneCallback& callback);
+
+ void FireGenericDoneCallback(const GenericDoneCallback& callback,
+ bool success,
+ const std::string& error_message);
+
+ void SetWebAppId(int web_app_id);
+
+ private:
+ void StartDone(const GenericDoneCallback& callback);
+
+ void LogToCircularBuffer(const std::string& message);
+
+ void OnGetNetworkInterfaceList(
+ const GenericDoneCallback& callback,
+ const base::Optional<net::NetworkInterfaceList>& networks);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // The render process ID this object belongs to.
+ const int render_process_id_;
+
+ // Should be created by StartLogging().
+ std::unique_ptr<WebRtcLogBuffer> log_buffer_;
+
+ // Should be created by StartLogging().
+ std::unique_ptr<WebRtcLogMetaDataMap> meta_data_;
+
+ GenericDoneCallback stop_callback_;
+ LoggingState logging_state_;
+
+ // True if renderer is closing. The log (if there is one) can still be
+ // released or discarded (i.e. closed). No new logs can be created. The only
+ // state change possible when channel is closing is from any state to CLOSED.
+ bool channel_is_closing_ = false;
+
+ // The system time in ms when logging is started. Reset when logging_state_
+ // changes to STOPPED.
+ base::Time logging_started_time_;
+
+ // Web app id used for statistics. See
+ // |WebRtcLoggingHandlerHost::web_app_id_|.
+ int web_app_id_ = 0;
+
+ base::WeakPtrFactory<WebRtcTextLogHandler> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(WebRtcTextLogHandler);
+};
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_TEXT_LOG_HANDLER_H_
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
new file mode 100644
index 00000000000..1b90baade1a
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
@@ -0,0 +1,497 @@
+// Copyright 2018 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 <algorithm>
+
+#include "base/json/json_reader.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/trace_event_analyzer.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/test/base/tracing.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/blink/public/common/features.h"
+#include "ui/gl/gl_switches.h"
+
+using trace_analyzer::TraceEvent;
+using trace_analyzer::TraceEventVector;
+using trace_analyzer::Query;
+
+namespace {
+
+// Trace events.
+static const char kStartRenderEventName[] =
+ "RemoteVideoSourceDelegate::RenderFrame";
+static const char kEnqueueFrameEventName[] =
+ "WebMediaPlayerMSCompositor::EnqueueFrame";
+static const char kSetFrameEventName[] =
+ "WebMediaPlayerMSCompositor::SetCurrentFrame";
+static const char kGetFrameEventName[] =
+ "WebMediaPlayerMSCompositor::GetCurrentFrame";
+static const char kVideoResourceEventName[] =
+ "VideoResourceUpdater::ObtainFrameResources";
+static const char kVsyncEventName[] = "Display::DrawAndSwap";
+
+// VideoFrameSubmitter dumps the delay from the handover of a decoded remote
+// VideoFrame from webrtc to the moment the OS acknowledges the swap buffers.
+static const char kVideoFrameSubmitterEventName[] = "VideoFrameSubmitter";
+
+static const char kEventMatchKey[] = "Timestamp";
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_video_display_perf_test.html";
+
+constexpr char kMetricPrefixVideoDisplayPerf[] = "WebRtcVideoDisplayPerf.";
+constexpr char kMetricSkippedFramesPercent[] = "skipped_frames";
+constexpr char kMetricPassingToRenderAlgoLatencyUs[] =
+ "passing_to_render_algorithm_latency";
+constexpr char kMetricRenderAlgoLatencyUs[] = "render_algorithm_latency";
+constexpr char kMetricCompositorPickingFrameLatencyUs[] =
+ "compositor_picking_frame_latency";
+constexpr char kMetricCompositorResourcePreparationLatencyUs[] =
+ "compositor_resource_preparation_latency";
+constexpr char kMetricVsyncLatencyUs[] = "vsync_latency";
+constexpr char kMetricTotalControlledLatencyUs[] = "total_controlled_latency";
+constexpr char kMetricTotalLatencyUs[] = "total_latency";
+constexpr char kMetricPostDecodeToRasterLatencyUs[] =
+ "post_decode_to_raster_latency";
+constexpr char kMetricWebRtcDecodeLatencyUs[] = "webrtc_decode_latency";
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixVideoDisplayPerf, story);
+ reporter.RegisterImportantMetric(kMetricSkippedFramesPercent, "percent");
+ reporter.RegisterImportantMetric(kMetricPassingToRenderAlgoLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricRenderAlgoLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricCompositorPickingFrameLatencyUs,
+ "us");
+ reporter.RegisterImportantMetric(
+ kMetricCompositorResourcePreparationLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricVsyncLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricTotalControlledLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricTotalLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricPostDecodeToRasterLatencyUs, "us");
+ reporter.RegisterImportantMetric(kMetricWebRtcDecodeLatencyUs, "us");
+ return reporter;
+}
+
+struct VideoDisplayPerfTestConfig {
+ int width;
+ int height;
+ int fps;
+ bool disable_render_smoothness_algorithm;
+};
+
+std::string VectorToString(const std::vector<double>& values) {
+ std::string ret = "";
+ for (double val : values) {
+ ret += base::StringPrintf("%.0lf,", val);
+ }
+ // Strip of trailing comma.
+ return ret.substr(0, ret.length() - 1);
+}
+
+void FindEvents(trace_analyzer::TraceAnalyzer* analyzer,
+ const std::string& event_name,
+ const Query& base_query,
+ TraceEventVector* events) {
+ Query query = Query::EventNameIs(event_name) && base_query;
+ analyzer->FindEvents(query, events);
+}
+
+void AssociateEvents(trace_analyzer::TraceAnalyzer* analyzer,
+ const std::vector<std::string>& event_names,
+ const std::string& match_string,
+ const Query& base_query) {
+ for (size_t i = 0; i < event_names.size() - 1; ++i) {
+ Query begin = Query::EventNameIs(event_names[i]);
+ Query end = Query::EventNameIs(event_names[i + 1]);
+ Query match(Query::EventArg(match_string) == Query::OtherArg(match_string));
+ analyzer->AssociateEvents(begin, end, base_query && match);
+ }
+}
+
+content::WebContents* OpenWebrtcInternalsTab(Browser* browser) {
+ chrome::AddTabAt(browser, GURL(), -1, true);
+ ui_test_utils::NavigateToURL(browser, GURL("chrome://webrtc-internals"));
+ return browser->tab_strip_model()->GetActiveWebContents();
+}
+
+std::vector<double> ParseGoogMaxDecodeFromWebrtcInternalsTab(
+ const std::string& webrtc_internals_stats_json) {
+ std::vector<double> goog_decode_ms;
+
+ std::unique_ptr<base::Value> parsed_json =
+ base::JSONReader::ReadDeprecated(webrtc_internals_stats_json);
+ base::DictionaryValue* dictionary = nullptr;
+ if (!parsed_json.get() || !parsed_json->GetAsDictionary(&dictionary))
+ return goog_decode_ms;
+ ignore_result(parsed_json.release());
+
+ // |dictionary| should have exactly two entries, one per ssrc.
+ if (!dictionary || dictionary->size() != 2u)
+ return goog_decode_ms;
+
+ // Only a given |dictionary| entry will have a "stats" entry that has a key
+ // that ends with "recv-googMaxDecodeMs" inside (it will start with the ssrc
+ // id, but we don't care about that). Then collect the string of "values" out
+ // of that key and convert those into the |goog_decode_ms| vector of doubles.
+ for (const auto& dictionary_entry : *dictionary) {
+ for (const auto& ssrc_entry : dictionary_entry.second->DictItems()) {
+ if (ssrc_entry.first != "stats")
+ continue;
+
+ for (const auto& stat_entry : ssrc_entry.second.DictItems()) {
+ if (!base::EndsWith(stat_entry.first, "recv-googMaxDecodeMs",
+ base::CompareCase::SENSITIVE)) {
+ continue;
+ }
+ base::Value* values_entry = stat_entry.second.FindKey({"values"});
+ if (!values_entry)
+ continue;
+ base::StringTokenizer values_tokenizer(values_entry->GetString(),
+ "[,]");
+ while (values_tokenizer.GetNext()) {
+ if (values_tokenizer.token_is_delim())
+ continue;
+ goog_decode_ms.push_back(atof(values_tokenizer.token().c_str()) *
+ base::Time::kMicrosecondsPerMillisecond);
+ }
+ }
+ }
+ }
+ return goog_decode_ms;
+}
+
+} // anonymous namespace
+
+// Tests the performance of Chrome displaying remote video.
+//
+// This test creates a WebRTC peer connection between two tabs and measures the
+// trace events listed in the beginning of this file on the tab receiving
+// remote video. In order to cut down from the encode cost, the tab receiving
+// remote video does not send any video to its peer.
+//
+// This test traces certain categories for a period of time. It follows the
+// lifetime of a single video frame by synchronizing on the timestamps values
+// attached to trace events. Then, it calculates the duration and related stats.
+class WebRtcVideoDisplayPerfBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<
+ std::tuple<gfx::Size /* resolution */,
+ int /* fps */,
+ bool /* disable_render_smoothness_algorithm */>> {
+ public:
+ WebRtcVideoDisplayPerfBrowserTest() {
+ const auto& params = GetParam();
+ const gfx::Size& resolution = std::get<0>(params);
+ test_config_ = {resolution.width(), resolution.height(),
+ std::get<1>(params), std::get<2>(params)};
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitchASCII(
+ switches::kUseFakeDeviceForMediaStream,
+ base::StringPrintf("fps=%d", test_config_.fps));
+ if (test_config_.disable_render_smoothness_algorithm)
+ command_line->AppendSwitch(switches::kDisableRTCSmoothnessAlgorithm);
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ void TestVideoDisplayPerf(const std::string& video_codec) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ // chrome:webrtc-internals doesn't start tracing anything until the
+ // connection(s) are up.
+ content::WebContents* webrtc_internals_tab =
+ OpenWebrtcInternalsTab(browser());
+ EXPECT_TRUE(content::ExecuteScript(
+ webrtc_internals_tab,
+ "currentGetStatsMethod = OPTION_GETSTATS_LEGACY"));
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ base::StringPrintf(
+ "{audio: true, video: {mandatory: {minWidth: %d, maxWidth: %d, "
+ "minHeight: %d, maxHeight: %d}}}",
+ test_config_.width, test_config_.width, test_config_.height,
+ test_config_.height));
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: false}");
+ const int process_id =
+ right_tab->GetRenderViewHost()->GetProcess()->GetProcess().Pid();
+
+ const std::string disable_cpu_adaptation_constraint(
+ "{'optional': [{'googCpuOveruseDetection': false}]}");
+ SetupPeerconnectionWithConstraintsAndLocalStream(
+ left_tab, disable_cpu_adaptation_constraint);
+ SetupPeerconnectionWithConstraintsAndLocalStream(
+ right_tab, disable_cpu_adaptation_constraint);
+
+ if (!video_codec.empty()) {
+ constexpr bool kPreferHwVideoCodec = true;
+ SetDefaultVideoCodec(left_tab, video_codec, kPreferHwVideoCodec);
+ SetDefaultVideoCodec(right_tab, video_codec, kPreferHwVideoCodec);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ StartDetectingVideo(right_tab, "remote-view");
+ WaitForVideoToPlay(right_tab);
+ // Run the connection a bit to ramp up.
+ test::SleepInJavascript(left_tab, 10000);
+
+ ASSERT_TRUE(tracing::BeginTracing("media,viz,webrtc"));
+ // Run the connection for 5 seconds to collect metrics.
+ test::SleepInJavascript(left_tab, 5000);
+
+ const std::string webrtc_internals_stats_json = ExecuteJavascript(
+ "window.domAutomationController.send("
+ " JSON.stringify(peerConnectionDataStore));",
+ webrtc_internals_tab);
+ webrtc_decode_latencies_ =
+ ParseGoogMaxDecodeFromWebrtcInternalsTab(webrtc_internals_stats_json);
+ chrome::CloseWebContents(browser(), webrtc_internals_tab, false);
+
+ std::string json_events;
+ ASSERT_TRUE(tracing::EndTracing(&json_events));
+ std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
+ trace_analyzer::TraceAnalyzer::Create(json_events));
+ analyzer->AssociateAsyncBeginEndEvents();
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+
+ ASSERT_TRUE(CalculatePerfResults(analyzer.get(), process_id));
+ PrintResults(video_codec);
+ }
+
+ private:
+ bool CalculatePerfResults(trace_analyzer::TraceAnalyzer* analyzer,
+ int render_process_id) {
+ Query match_process_id = Query::EventPidIs(render_process_id);
+ const std::vector<std::string> chain_of_events = {
+ kStartRenderEventName, kEnqueueFrameEventName, kSetFrameEventName,
+ kGetFrameEventName, kVideoResourceEventName};
+ AssociateEvents(analyzer, chain_of_events,
+ kEventMatchKey, match_process_id);
+
+ TraceEventVector start_render_events;
+ FindEvents(analyzer, kStartRenderEventName, match_process_id,
+ &start_render_events);
+ if (start_render_events.empty())
+ return false;
+
+ // We are only interested in vsync events coming after the first render
+ // event. Earlier ones are already missed.
+ Query after_first_render_event =
+ Query::EventTime() >
+ Query::Double(start_render_events.front()->timestamp);
+ TraceEventVector vsync_events;
+ FindEvents(analyzer, kVsyncEventName, after_first_render_event,
+ &vsync_events);
+ if (vsync_events.empty())
+ return false;
+
+ size_t found_vsync_index = 0;
+ size_t skipped_frame_count = 0;
+ for (const auto* event : start_render_events) {
+ const double start = event->timestamp;
+
+ const TraceEvent* enqueue_frame_event = event->other_event;
+ if (!enqueue_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double enqueue_frame_duration =
+ enqueue_frame_event->timestamp - start;
+
+ const TraceEvent* set_frame_event = enqueue_frame_event->other_event;
+ if (!set_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double set_frame_duration =
+ set_frame_event->timestamp - enqueue_frame_event->timestamp;
+
+ const TraceEvent* get_frame_event = set_frame_event->other_event;
+ if (!get_frame_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double get_frame_duration =
+ get_frame_event->timestamp - set_frame_event->timestamp;
+
+ const TraceEvent* video_resource_event = get_frame_event->other_event;
+ if (!video_resource_event) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double resource_ready_duration =
+ video_resource_event->timestamp - get_frame_event->timestamp;
+
+ // We try to find the closest vsync event after video resource is ready.
+ const bool found_vsync = FindFirstOf(
+ vsync_events,
+ Query::EventTime() > Query::Double(video_resource_event->timestamp +
+ video_resource_event->duration),
+ found_vsync_index, &found_vsync_index);
+ if (!found_vsync) {
+ skipped_frame_count++;
+ continue;
+ }
+ const double vsync_duration = vsync_events[found_vsync_index]->timestamp -
+ video_resource_event->timestamp;
+ const double total_duration =
+ vsync_events[found_vsync_index]->timestamp - start;
+
+ enqueue_frame_durations_.push_back(enqueue_frame_duration);
+ set_frame_durations_.push_back(set_frame_duration);
+ get_frame_durations_.push_back(get_frame_duration);
+ resource_ready_durations_.push_back(resource_ready_duration);
+ vsync_durations_.push_back(vsync_duration);
+ total_controlled_durations_.push_back(total_duration -
+ set_frame_duration);
+ total_durations_.push_back(total_duration);
+ }
+
+ if (start_render_events.size() == skipped_frame_count)
+ return false;
+
+ // Calculate the percentage by dividing by the number of frames received.
+ skipped_frame_percentage_ =
+ 100.0 * skipped_frame_count / start_render_events.size();
+
+ // |kVideoFrameSubmitterEventName| is in itself an ASYNC latency measurement
+ // from the point where the remote video decode is available (i.e.
+ // kStartRenderEventName) until the platform-dependent swap buffers, so by
+ // definition is larger than the |total_duration|.
+ TraceEventVector video_frame_submitter_events;
+ analyzer->FindEvents(Query::MatchAsyncBeginWithNext() &&
+ Query::EventNameIs(kVideoFrameSubmitterEventName),
+ &video_frame_submitter_events);
+ for (const auto* event : video_frame_submitter_events) {
+ // kVideoFrameSubmitterEventName is divided into a BEGIN, a PAST and an
+ // END steps. AssociateAsyncBeginEndEvents paired BEGIN with PAST, but we
+ // have to get to the END. Note that if there's no intermediate PAST, it
+ // means this wasn't a remote feed VideoFrame, we should not have those in
+ // this test. If there's no END, then tracing was cut short.
+ if (!event->has_other_event() ||
+ event->other_event->phase != TRACE_EVENT_PHASE_ASYNC_STEP_PAST ||
+ !event->other_event->has_other_event()) {
+ continue;
+ }
+ const auto begin = event->timestamp;
+ const auto end = event->other_event->other_event->timestamp;
+ video_frame_submmitter_latencies_.push_back(end - begin);
+ }
+
+ return true;
+ }
+
+ void PrintResults(const std::string& video_codec) {
+ std::string smoothness_indicator =
+ test_config_.disable_render_smoothness_algorithm ? "_DisableSmoothness"
+ : "";
+ std::string story = base::StringPrintf(
+ "%s_%dp%df%s", video_codec.c_str(), test_config_.height,
+ test_config_.fps, smoothness_indicator.c_str());
+ auto reporter = SetUpReporter(story);
+ reporter.AddResult(kMetricSkippedFramesPercent,
+ base::StringPrintf("%.2lf", skipped_frame_percentage_));
+ // We identify intervals in a way that can help us easily bisect the source
+ // of added latency in case of a regression. From these intervals, "Render
+ // Algorithm" can take random amount of times based on the vsync cycle it is
+ // closest to. Therefore, "Total Controlled Latency" refers to the total
+ // times without that section for semi-consistent results.
+ reporter.AddResultList(kMetricPassingToRenderAlgoLatencyUs,
+ VectorToString(enqueue_frame_durations_));
+ reporter.AddResultList(kMetricRenderAlgoLatencyUs,
+ VectorToString(set_frame_durations_));
+ reporter.AddResultList(kMetricCompositorPickingFrameLatencyUs,
+ VectorToString(get_frame_durations_));
+ reporter.AddResultList(kMetricCompositorResourcePreparationLatencyUs,
+ VectorToString(resource_ready_durations_));
+ reporter.AddResultList(kMetricVsyncLatencyUs,
+ VectorToString(vsync_durations_));
+ reporter.AddResultList(kMetricTotalControlledLatencyUs,
+ VectorToString(total_controlled_durations_));
+ reporter.AddResultList(kMetricTotalLatencyUs,
+ VectorToString(total_durations_));
+
+ reporter.AddResultList(kMetricPostDecodeToRasterLatencyUs,
+ VectorToString(video_frame_submmitter_latencies_));
+ reporter.AddResultList(kMetricWebRtcDecodeLatencyUs,
+ VectorToString(webrtc_decode_latencies_));
+ }
+
+ VideoDisplayPerfTestConfig test_config_;
+ // Containers for test results.
+ double skipped_frame_percentage_ = 0;
+ std::vector<double> enqueue_frame_durations_;
+ std::vector<double> set_frame_durations_;
+ std::vector<double> get_frame_durations_;
+ std::vector<double> resource_ready_durations_;
+ std::vector<double> vsync_durations_;
+ std::vector<double> total_controlled_durations_;
+ std::vector<double> total_durations_;
+
+ // These two put together represent the whole delay from encoded video frames
+ // to OS swap buffers call (or callback, depending on the platform).
+ std::vector<double> video_frame_submmitter_latencies_;
+ std::vector<double> webrtc_decode_latencies_;
+};
+
+// TODO(https://crbug.com/993020): Fix flakes on Windows bots.
+#if defined(OS_WIN)
+#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
+ DISABLED_WebRtcVideoDisplayPerfBrowserTests
+#else
+#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
+ WebRtcVideoDisplayPerfBrowserTests
+#endif
+INSTANTIATE_TEST_SUITE_P(MAYBE_WebRtcVideoDisplayPerfBrowserTests,
+ WebRtcVideoDisplayPerfBrowserTest,
+ testing::Combine(testing::Values(gfx::Size(1280, 720),
+ gfx::Size(1920,
+ 1080)),
+ testing::Values(30, 60),
+ testing::Bool()));
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
+ MANUAL_TestVideoDisplayPerfVP9) {
+ TestVideoDisplayPerf("VP9");
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
+ MANUAL_TestVideoDisplayPerfH264) {
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcVideoDisplayPerfBrowserTest.MANUAL_"
+ "TestVideoDisplayPerfH264 "
+ "(test \"OK\")";
+ return;
+ }
+ TestVideoDisplayPerf("H264");
+}
+#endif // BUILDFLAG(RTC_USE_H264)
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc
new file mode 100644
index 00000000000..a8eebc633b3
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_high_bitrate_browsertest.cc
@@ -0,0 +1,163 @@
+// Copyright 2018 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/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "chrome/browser/media/webrtc/test_stats_dictionary.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/media_switches.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "ui/gl/gl_switches.h"
+
+namespace {
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_video_display_perf_test.html";
+static const char kInboundRtp[] = "inbound-rtp";
+static const char kOutboundRtp[] = "outbound-rtp";
+
+constexpr int kBitsPerByte = 8;
+
+constexpr char kMetricPrefixHighBitrate[] = "WebRtcHighBitrateVideo.";
+constexpr char kMetricSendRateBitsPerS[] = "send_rate";
+constexpr char kMetricReceiveRateBitsPerS[] = "receive_rate";
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixHighBitrate, story);
+ reporter.RegisterFyiMetric(kMetricSendRateBitsPerS, "bits/s");
+ reporter.RegisterFyiMetric(kMetricReceiveRateBitsPerS, "bits/s");
+ return reporter;
+}
+
+// Sums up "RTC[In/Out]boundRTPStreamStats.bytes_[received/sent]" values.
+double GetTotalRTPStreamBytes(content::TestStatsReportDictionary* report,
+ const char* type,
+ const char* media_type) {
+ DCHECK(type == kInboundRtp || type == kOutboundRtp);
+ const char* bytes_name =
+ (type == kInboundRtp) ? "bytesReceived" : "bytesSent";
+ double total_bytes = 0.0;
+ report->ForEach([&type, &bytes_name, &media_type,
+ &total_bytes](const content::TestStatsDictionary& stats) {
+ if (stats.GetString("type") == type &&
+ stats.GetString("mediaType") == media_type) {
+ total_bytes += stats.GetNumber(bytes_name);
+ }
+ });
+ return total_bytes;
+}
+
+double GetVideoBytesSent(content::TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kOutboundRtp, "video");
+}
+
+double GetVideoBytesReceived(content::TestStatsReportDictionary* report) {
+ return GetTotalRTPStreamBytes(report, kInboundRtp, "video");
+}
+
+} // anonymous namespace
+
+namespace content {
+
+// Tests the performance of WebRTC peer connection with high bitrate
+//
+// This test creates a WebRTC peer connection between two tabs and sets a very
+// high target bitrate to observe any perf regressions/improvements for such
+// cases. In order to achieve this, we use a fake codec that creates a dummy
+// output for the given bitrate.
+class WebRtcVideoHighBitrateBrowserTest : public WebRtcTestBase {
+ public:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kUseFakeCodecForPeerConnection);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+ command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ protected:
+ void SetDefaultVideoTargetBitrate(content::WebContents* tab,
+ int bits_per_second) {
+ EXPECT_EQ("ok", ExecuteJavascript(
+ base::StringPrintf("setDefaultVideoTargetBitrate(%d)",
+ bits_per_second),
+ tab));
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebRtcVideoHighBitrateBrowserTest,
+ MANUAL_HighBitrateEncodeDecode) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 30)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 30000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 30)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 30000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: true}");
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ "{audio: true, video: false}");
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+ const int target_bits_per_second = 80000;
+ SetDefaultVideoTargetBitrate(left_tab, target_bits_per_second);
+ SetDefaultVideoTargetBitrate(right_tab, target_bits_per_second);
+ NegotiateCall(left_tab, right_tab);
+
+ // Run the connection a bit to ramp up.
+ test::SleepInJavascript(left_tab, 10000);
+
+ scoped_refptr<TestStatsReportDictionary> sender_report =
+ GetStatsReportDictionary(left_tab);
+ const double video_bytes_sent_before = GetVideoBytesSent(sender_report.get());
+ scoped_refptr<TestStatsReportDictionary> receiver_report =
+ GetStatsReportDictionary(right_tab);
+ const double video_bytes_received_before =
+ GetVideoBytesReceived(receiver_report.get());
+
+ // Collect stats.
+ const double duration_in_seconds = 5.0;
+ test::SleepInJavascript(
+ left_tab, duration_in_seconds * base::Time::kMillisecondsPerSecond);
+
+ sender_report = GetStatsReportDictionary(left_tab);
+ const double video_bytes_sent_after = GetVideoBytesSent(sender_report.get());
+ receiver_report = GetStatsReportDictionary(right_tab);
+ const double video_bytes_received_after =
+ GetVideoBytesReceived(receiver_report.get());
+
+ const double video_send_rate =
+ (video_bytes_sent_after - video_bytes_sent_before) / duration_in_seconds;
+ const double video_receive_rate =
+ (video_bytes_received_after - video_bytes_received_before) /
+ duration_in_seconds;
+
+ auto reporter = SetUpReporter("baseline_story");
+ reporter.AddResult(kMetricSendRateBitsPerS, video_send_rate * kBitsPerByte);
+ reporter.AddResult(kMetricReceiveRateBitsPerS,
+ video_receive_rate * kBitsPerByte);
+
+ HangUp(left_tab);
+ HangUp(right_tab);
+}
+
+} // namespace content
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc
new file mode 100644
index 00000000000..e09ff308c75
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_video_quality_browsertest.cc
@@ -0,0 +1,367 @@
+// 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 <stddef.h>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/infobars/core/infobar.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/python_utils.h"
+#include "testing/perf/perf_test.h"
+#include "third_party/blink/public/common/features.h"
+#include "ui/gl/gl_switches.h"
+
+namespace {
+std::string MakeLabel(const char* test_name, const std::string& video_codec) {
+ std::string codec_label = video_codec.empty() ? "" : "_" + video_codec;
+ return base::StringPrintf("%s%s", test_name, codec_label.c_str());
+}
+} // namespace
+
+static const base::FilePath::CharType kFrameAnalyzerExecutable[] =
+#if defined(OS_WIN)
+ FILE_PATH_LITERAL("frame_analyzer.exe");
+#else
+ FILE_PATH_LITERAL("frame_analyzer");
+#endif
+
+static const base::FilePath::CharType kCapturedYuvFileName[] =
+ FILE_PATH_LITERAL("captured_video.yuv");
+static const base::FilePath::CharType kCapturedWebmFileName[] =
+ FILE_PATH_LITERAL("captured_video.webm");
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+static const char kCapturingWebrtcHtmlPage[] =
+ "/webrtc/webrtc_video_quality_test.html";
+
+static const struct VideoQualityTestConfig {
+ const char* test_name;
+ int width;
+ int height;
+ const base::FilePath::CharType* reference_video;
+ const char* constraints;
+} kVideoConfigurations[] = {
+ { "360p", 640, 360,
+ test::kReferenceFileName360p,
+ WebRtcTestBase::kAudioVideoCallConstraints360p },
+ { "720p", 1280, 720,
+ test::kReferenceFileName720p,
+ WebRtcTestBase::kAudioVideoCallConstraints720p },
+};
+
+// Test the video quality of the WebRTC output.
+//
+// Prerequisites: This test case must run on a machine with a chrome playing
+// the video from the reference files located in GetReferenceFilesDir().
+// The file kReferenceY4mFileName.kY4mFileExtension is played using a
+// FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for
+// comparison.
+//
+// You must also compile the frame_analyzer target before you run this
+// test to get all the tools built.
+//
+// The test runs several custom binaries - rgba_to_i420 converter and
+// frame_analyzer. Both tools can be found under third_party/webrtc/rtc_tools.
+// The test also runs a stand alone Python implementation of a WebSocket server
+// (pywebsocket) and a barcode_decoder script.
+class WebRtcVideoQualityBrowserTest : public WebRtcTestBase,
+ public testing::WithParamInterface<VideoQualityTestConfig> {
+ public:
+ WebRtcVideoQualityBrowserTest()
+ : environment_(base::Environment::Create()) {
+ test_config_ = GetParam();
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+
+ ASSERT_TRUE(temp_working_dir_.CreateUniqueTempDir());
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // Set up the command line option with the expected file name. We will check
+ // its existence in HasAllRequiredResources().
+ webrtc_reference_video_y4m_ = test::GetReferenceFilesDir()
+ .Append(test_config_.reference_video)
+ .AddExtension(test::kY4mFileExtension);
+ command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture,
+ webrtc_reference_video_y4m_);
+ command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
+
+ // The video playback will not work without a GPU, so force its use here.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ // Writes the captured video to a webm file.
+ void WriteCapturedWebmVideo(content::WebContents* capturing_tab,
+ const base::FilePath& webm_video_filename) {
+ std::string base64_encoded_video =
+ ExecuteJavascript("getRecordedVideoAsBase64()", capturing_tab);
+ std::string recorded_video;
+ ASSERT_TRUE(base::Base64Decode(base64_encoded_video, &recorded_video));
+ base::File video_file(webm_video_filename,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ size_t written =
+ video_file.Write(0, recorded_video.c_str(), recorded_video.length());
+ ASSERT_EQ(recorded_video.length(), written);
+ }
+
+ // Runs ffmpeg on the captured webm video and writes it to a yuv video file.
+ bool RunWebmToI420Converter(const base::FilePath& webm_video_filename,
+ const base::FilePath& yuv_video_filename,
+ const int width,
+ const int height) {
+ base::FilePath path_to_ffmpeg = test::GetToolForPlatform("ffmpeg");
+ if (!base::PathExists(path_to_ffmpeg)) {
+ LOG(ERROR) << "Missing ffmpeg: should be in " << path_to_ffmpeg.value();
+ return false;
+ }
+
+ // Set up ffmpeg to output at a certain resolution (-s) and bitrate (-b:v).
+ // This is needed because WebRTC is free to start the call at a lower
+ // resolution before ramping up. Without these flags, ffmpeg would output a
+ // video in the inital lower resolution, causing the SSIM and PSNR results
+ // to become meaningless.
+ base::CommandLine ffmpeg_command(path_to_ffmpeg);
+ ffmpeg_command.AppendArg("-i");
+ ffmpeg_command.AppendArgPath(webm_video_filename);
+ ffmpeg_command.AppendArg("-s");
+ ffmpeg_command.AppendArg(base::StringPrintf("%dx%d", width, height));
+ ffmpeg_command.AppendArg("-b:v");
+ ffmpeg_command.AppendArg(base::StringPrintf("%d", 120 * width * height));
+ ffmpeg_command.AppendArgPath(yuv_video_filename);
+
+ // We produce an output file that will later be used as an input to the
+ // barcode decoder and frame analyzer tools.
+ DVLOG(0) << "Running " << ffmpeg_command.GetCommandLineString();
+ std::string result;
+ bool ok = base::GetAppOutputAndError(ffmpeg_command, &result);
+ DVLOG(0) << "Output was:\n\n" << result;
+ return ok;
+ }
+
+ // Compares the |captured_video_filename| with the |reference_video_filename|.
+ //
+ // The barcode decoder decodes the captured video containing barcodes overlaid
+ // into every frame of the video. It produces a set of PNG images.
+ // The frames should be of size |width| x |height|.
+ // All measurements calculated are printed as perf parsable numbers to stdout.
+ bool CompareVideosAndPrintResult(
+ const std::string& test_label,
+ int width,
+ int height,
+ const base::FilePath& captured_video_filename,
+ const base::FilePath& reference_video_filename) {
+ base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath(
+ GetBrowserDir().Append(kFrameAnalyzerExecutable));
+ base::FilePath path_to_compare_script = GetSourceDir().Append(
+ FILE_PATH_LITERAL("third_party/webrtc/rtc_tools/compare_videos.py"));
+
+ if (!base::PathExists(path_to_analyzer)) {
+ LOG(ERROR) << "Missing frame analyzer: should be in "
+ << path_to_analyzer.value()
+ << ". Try building the frame_analyzer target.";
+ return false;
+ }
+ if (!base::PathExists(path_to_compare_script)) {
+ LOG(ERROR) << "Missing video compare script: should be in "
+ << path_to_compare_script.value();
+ return false;
+ }
+
+ // Note: don't append switches to this command since it will mess up the
+ // -u in the python invocation!
+ base::CommandLine compare_command(base::CommandLine::NO_PROGRAM);
+ EXPECT_TRUE(GetPythonCommand(&compare_command));
+
+ compare_command.AppendArgPath(path_to_compare_script);
+ compare_command.AppendArg("--label=" + test_label);
+ compare_command.AppendArg("--ref_video");
+ compare_command.AppendArgPath(reference_video_filename);
+ compare_command.AppendArg("--test_video");
+ compare_command.AppendArgPath(captured_video_filename);
+ compare_command.AppendArg("--frame_analyzer");
+ compare_command.AppendArgPath(path_to_analyzer);
+ compare_command.AppendArg("--yuv_frame_width");
+ compare_command.AppendArg(base::NumberToString(width));
+ compare_command.AppendArg("--yuv_frame_height");
+ compare_command.AppendArg(base::NumberToString(height));
+
+ DVLOG(0) << "Running " << compare_command.GetCommandLineString();
+ std::string output;
+ bool ok = base::GetAppOutput(compare_command, &output);
+
+ // Print to stdout to ensure the perf numbers are parsed properly by the
+ // buildbot step. The tool should print a handful RESULT lines.
+ printf("Output was:\n\n%s\n", output.c_str());
+ bool has_result_lines = output.find("RESULT") != std::string::npos;
+ if (!ok || !has_result_lines) {
+ LOG(ERROR) << "Failed to compare videos; see output above to see what "
+ << "the error was.";
+ return false;
+ }
+ return true;
+ }
+
+ void TestVideoQuality(const std::string& video_codec,
+ bool prefer_hw_video_codec) {
+ ASSERT_GE(TestTimeouts::test_launcher_timeout().InSeconds(), 150)
+ << "This is a long-running test; you must specify "
+ "--test-launcher-timeout to have a value of at least 150000.";
+ ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150)
+ << "This is a long-running test; you must specify "
+ "--ui-test-action-max-timeout to have a value of at least 150000.";
+ ASSERT_LT(TestTimeouts::action_max_timeout(),
+ TestTimeouts::test_launcher_timeout())
+ << "action_max_timeout needs to be strictly-less-than "
+ "test_launcher_timeout";
+ ASSERT_TRUE(test::HasReferenceFilesInCheckout());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ content::WebContents* left_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
+ test_config_.constraints);
+ content::WebContents* right_tab =
+ OpenPageAndGetUserMediaInNewTabWithConstraints(
+ embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage),
+ test_config_.constraints);
+
+ SetupPeerconnectionWithLocalStream(left_tab);
+ SetupPeerconnectionWithLocalStream(right_tab);
+
+ if (!video_codec.empty()) {
+ SetDefaultVideoCodec(left_tab, video_codec, prefer_hw_video_codec);
+ SetDefaultVideoCodec(right_tab, video_codec, prefer_hw_video_codec);
+ }
+ NegotiateCall(left_tab, right_tab);
+
+ // Poll slower here to avoid flooding the log with messages: capturing and
+ // sending frames take quite a bit of time.
+ int polling_interval_msec = 1000;
+
+ EXPECT_TRUE(test::PollingWaitUntil("doneFrameCapturing()", "done-capturing",
+ right_tab, polling_interval_msec));
+
+ HangUp(left_tab);
+
+ WriteCapturedWebmVideo(right_tab,
+ GetWorkingDir().Append(kCapturedWebmFileName));
+
+ // Shut everything down to avoid having the javascript race with the
+ // analysis tools. For instance, dont have console log printouts interleave
+ // with the RESULT lines from the analysis tools (crbug.com/323200).
+ chrome::CloseWebContents(browser(), left_tab, false);
+ chrome::CloseWebContents(browser(), right_tab, false);
+
+ RunWebmToI420Converter(GetWorkingDir().Append(kCapturedWebmFileName),
+ GetWorkingDir().Append(kCapturedYuvFileName),
+ test_config_.width, test_config_.height);
+
+ ASSERT_TRUE(CompareVideosAndPrintResult(
+ MakeLabel(test_config_.test_name, video_codec), test_config_.width,
+ test_config_.height, GetWorkingDir().Append(kCapturedYuvFileName),
+ test::GetReferenceFilesDir()
+ .Append(test_config_.reference_video)
+ .AddExtension(test::kYuvFileExtension)));
+ }
+
+ protected:
+ VideoQualityTestConfig test_config_;
+
+ base::FilePath GetWorkingDir() { return temp_working_dir_.GetPath(); }
+
+ private:
+ base::FilePath GetSourceDir() {
+ base::FilePath source_dir;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir);
+ return source_dir;
+ }
+
+ base::FilePath GetBrowserDir() {
+ base::FilePath browser_dir;
+ EXPECT_TRUE(base::PathService::Get(base::DIR_MODULE, &browser_dir));
+ return browser_dir;
+ }
+
+ std::unique_ptr<base::Environment> environment_;
+ base::FilePath webrtc_reference_video_y4m_;
+ base::ScopedTempDir temp_working_dir_;
+};
+
+INSTANTIATE_TEST_SUITE_P(WebRtcVideoQualityBrowserTests,
+ WebRtcVideoQualityBrowserTest,
+ testing::ValuesIn(kVideoConfigurations));
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MANUAL_TestVideoQualityVp8) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ TestVideoQuality("VP8", false /* prefer_hw_video_codec */);
+}
+
+// Flaky on windows.
+// TODO(crbug.com/1008766): re-enable when flakiness is investigated, diagnosed
+// and resolved.
+#if defined(OS_WIN)
+#define MAYBE_MANUAL_TestVideoQualityVp9 DISABLED_MANUAL_TestVideoQualityVp9
+#else
+#define MAYBE_MANUAL_TestVideoQualityVp9 MANUAL_TestVideoQualityVp9
+#endif
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MAYBE_MANUAL_TestVideoQualityVp9) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ TestVideoQuality("VP9", true /* prefer_hw_video_codec */);
+}
+
+#if BUILDFLAG(RTC_USE_H264)
+
+// Flaky on mac: crbug.com/754684
+#if defined(OS_MACOSX)
+#define MAYBE_MANUAL_TestVideoQualityH264 DISABLED_MANUAL_TestVideoQualityH264
+#else
+#define MAYBE_MANUAL_TestVideoQualityH264 MANUAL_TestVideoQualityH264
+#endif
+
+IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest,
+ MAYBE_MANUAL_TestVideoQualityH264) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ // Only run test if run-time feature corresponding to |rtc_use_h264| is on.
+ if (!base::FeatureList::IsEnabled(
+ blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
+ LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
+ "Skipping WebRtcVideoQualityBrowserTest.MANUAL_TestVideoQualityH264 "
+ "(test \"OK\")";
+ return;
+ }
+ TestVideoQuality("H264", true /* prefer_hw_video_codec */);
+}
+
+#endif // BUILDFLAG(RTC_USE_H264)
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc b/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc
new file mode 100644
index 00000000000..db5eeb4e8eb
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_webcam_browsertest.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 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 "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+static const char kMainWebrtcTestHtmlPage[] =
+ "/webrtc/webrtc_jsep01_test.html";
+
+enum class TargetVideoCaptureImplementation {
+ DEFAULT,
+#if defined(OS_WIN)
+ WIN_MEDIA_FOUNDATION
+#endif
+};
+
+const TargetVideoCaptureImplementation kTargetVideoCaptureImplementations[] = {
+ TargetVideoCaptureImplementation::DEFAULT,
+#if defined(OS_WIN)
+ TargetVideoCaptureImplementation::WIN_MEDIA_FOUNDATION
+#endif
+};
+
+// These tests runs on real webcams and ensure WebRTC can acquire webcams
+// correctly. They will do nothing if there are no webcams on the system.
+// The webcam on the system must support up to 1080p, or the test will fail.
+// This test is excellent for testing the various capture paths of WebRTC
+// on all desktop platforms.
+class WebRtcWebcamBrowserTest
+ : public WebRtcTestBase,
+ public testing::WithParamInterface<TargetVideoCaptureImplementation> {
+ public:
+ WebRtcWebcamBrowserTest() {
+#if defined(OS_WIN)
+ if (GetParam() == TargetVideoCaptureImplementation::WIN_MEDIA_FOUNDATION) {
+ scoped_feature_list_.InitAndEnableFeature(
+ media::kMediaFoundationVideoCapture);
+ } else {
+ scoped_feature_list_.InitAndDisableFeature(
+ media::kMediaFoundationVideoCapture);
+ }
+#endif
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ EXPECT_FALSE(command_line->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ EXPECT_FALSE(command_line->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+ }
+
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
+ }
+
+ std::string GetUserMediaAndGetStreamSize(content::WebContents* tab,
+ const std::string& constraints) {
+ std::string actual_stream_size;
+ if (GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(tab,
+ constraints)) {
+ StartDetectingVideo(tab, "local-view");
+ if (WaitForVideoToPlay(tab))
+ actual_stream_size = GetStreamSize(tab, "local-view");
+ CloseLastLocalStream(tab);
+ }
+ return actual_stream_size;
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// This test is manual because the test results can vary heavily depending on
+// which webcam or drivers you have on the system.
+IN_PROC_BROWSER_TEST_P(WebRtcWebcamBrowserTest,
+ MANUAL_TestAcquiringAndReacquiringWebcam) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ if (!content::IsWebcamAvailableOnSystem(tab)) {
+ DVLOG(0) << "No webcam found on bot: skipping...";
+ return;
+ }
+
+ EXPECT_EQ("320x240",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraintsQVGA));
+ EXPECT_EQ("640x480",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraintsVGA));
+ EXPECT_EQ("640x360",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints360p));
+ EXPECT_EQ("1280x720",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints720p));
+ EXPECT_EQ("1920x1080",
+ GetUserMediaAndGetStreamSize(tab, kVideoCallConstraints1080p));
+}
+
+INSTANTIATE_TEST_SUITE_P(WebRtcWebcamBrowserTests,
+ WebRtcWebcamBrowserTest,
+ testing::ValuesIn(kTargetVideoCaptureImplementations));
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util.h b/chromium/chrome/browser/media/webrtc/window_icon_util.h
new file mode 100644
index 00000000000..5bbb199bb56
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util.h
@@ -0,0 +1,14 @@
+// Copyright 2016 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 CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
+
+#include "content/public/browser/desktop_media_id.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "ui/gfx/image/image_skia.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id);
+
+#endif // CHROME_BROWSER_MEDIA_WEBRTC_WINDOW_ICON_UTIL_H_
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc
new file mode 100644
index 00000000000..4a826a5b573
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_chromeos.cc
@@ -0,0 +1,21 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK_EQ(content::DesktopMediaID::TYPE_WINDOW, id.type);
+ aura::Window* window = content::DesktopMediaID::GetNativeWindowById(id);
+ if (!window)
+ return gfx::ImageSkia();
+
+ gfx::ImageSkia* image = window->GetProperty(aura::client::kWindowIconKey);
+ if (!image)
+ image = window->GetProperty(aura::client::kAppIconKey);
+ return image ? *image : gfx::ImageSkia();
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm b/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm
new file mode 100644
index 00000000000..0f4b122633b
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_mac.mm
@@ -0,0 +1,78 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/window_icon_util.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/stl_util.h"
+#include "third_party/libyuv/include/libyuv/convert_argb.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ CGWindowID ids[1];
+ ids[0] = id.id;
+ base::ScopedCFTypeRef<CFArrayRef> window_id_array(CFArrayCreate(
+ nullptr, reinterpret_cast<const void**>(&ids), base::size(ids), nullptr));
+ base::ScopedCFTypeRef<CFArrayRef> window_array(
+ CGWindowListCreateDescriptionFromArray(window_id_array));
+ if (!window_array || 0 == CFArrayGetCount(window_array)) {
+ return gfx::ImageSkia();
+ }
+
+ CFDictionaryRef window = base::mac::CFCastStrict<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFNumberRef pid_ref =
+ base::mac::GetValueFromDictionary<CFNumberRef>(window, kCGWindowOwnerPID);
+
+ int pid;
+ CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
+
+ NSImage* icon_image =
+ [[NSRunningApplication runningApplicationWithProcessIdentifier:pid] icon];
+
+ // Icon's NSImage defaults to the smallest which can be only 32x32.
+ NSRect proposed_rect = NSMakeRect(0, 0, 128, 128);
+ CGImageRef cg_icon_image =
+ [icon_image CGImageForProposedRect:&proposed_rect context:nil hints:nil];
+
+ // 4 components of 8 bits each.
+ if (CGImageGetBitsPerPixel(cg_icon_image) != 32 ||
+ CGImageGetBitsPerComponent(cg_icon_image) != 8) {
+ return gfx::ImageSkia();
+ }
+
+ // Premultiplied alpha and last (alpha channel is next to the blue channel)
+ if (CGImageGetAlphaInfo(cg_icon_image) != kCGImageAlphaPremultipliedLast) {
+ return gfx::ImageSkia();
+ }
+
+ // Ensure BGR like.
+ int byte_order = CGImageGetBitmapInfo(cg_icon_image) & kCGBitmapByteOrderMask;
+ if (byte_order != kCGBitmapByteOrderDefault &&
+ byte_order != kCGBitmapByteOrder32Big) {
+ return gfx::ImageSkia();
+ }
+
+ CGDataProviderRef provider = CGImageGetDataProvider(cg_icon_image);
+ base::ScopedCFTypeRef<CFDataRef> cf_data(CGDataProviderCopyData(provider));
+
+ int width = CGImageGetWidth(cg_icon_image);
+ int height = CGImageGetHeight(cg_icon_image);
+ int src_stride = CGImageGetBytesPerRow(cg_icon_image);
+ const uint8_t* src_data = CFDataGetBytePtr(cf_data);
+
+ SkBitmap result;
+ result.allocN32Pixels(width, height, false /* no-premultiplied */);
+
+ uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
+
+ libyuv::ABGRToARGB(src_data, src_stride, pixels_data, result.rowBytes(),
+ width, height);
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc
new file mode 100644
index 00000000000..f519648c489
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_ozone.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "content/public/browser/desktop_media_id.h"
+#include "ui/aura/client/aura_constants.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK_EQ(content::DesktopMediaID::TYPE_WINDOW, id.type);
+ // TODO(tonikitoo): can we make the implementation of
+ // chrome/browser/media/webrtc/window_icon_util_chromeos.cc generic
+ // enough so we can reuse it here?
+ NOTIMPLEMENTED();
+ return gfx::ImageSkia();
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc
new file mode 100644
index 00000000000..0a14a8afc7c
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_win.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "ui/gfx/icon_util.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ HWND hwnd = reinterpret_cast<HWND>(id.id);
+ HICON icon_handle = 0;
+
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ if (!icon_handle)
+ icon_handle = reinterpret_cast<HICON>(GetClassLongPtr(hwnd, GCLP_HICON));
+
+ if (!icon_handle) {
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ }
+ if (!icon_handle) {
+ SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 5,
+ (PDWORD_PTR)&icon_handle);
+ }
+ if (!icon_handle)
+ icon_handle = reinterpret_cast<HICON>(GetClassLongPtr(hwnd, GCLP_HICONSM));
+
+ if (!icon_handle)
+ return gfx::ImageSkia();
+
+ const SkBitmap icon_bitmap = IconUtil::CreateSkBitmapFromHICON(icon_handle);
+
+ if (icon_bitmap.isNull())
+ return gfx::ImageSkia();
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(icon_bitmap);
+}
diff --git a/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc b/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc
new file mode 100644
index 00000000000..272d0109da6
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/window_icon_util_x11.cc
@@ -0,0 +1,74 @@
+// Copyright 2016 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 "chrome/browser/media/webrtc/window_icon_util.h"
+
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/gfx/x/x11_error_tracker.h"
+#include "ui/gfx/x/x11_types.h"
+
+gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
+ DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
+
+ Display* display = gfx::GetXDisplay();
+ Atom property = gfx::GetAtom("_NET_WM_ICON");
+ Atom actual_type;
+ int actual_format;
+ unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
+ unsigned long size;
+ long* data;
+
+ // The |error_tracker| essentially provides an empty X error handler for
+ // the call of XGetWindowProperty. The motivation is to guard against crash
+ // for any reason that XGetWindowProperty fails. For example, at the time that
+ // XGetWindowProperty is called, the window handler (a.k.a |id.id|) may
+ // already be invalid due to the fact that the end user has closed the
+ // corresponding window, etc.
+ std::unique_ptr<gfx::X11ErrorTracker> error_tracker(
+ new gfx::X11ErrorTracker());
+ int status = XGetWindowProperty(display, id.id, property, 0L, ~0L, x11::False,
+ AnyPropertyType, &actual_type, &actual_format,
+ &size, &bytes_after,
+ reinterpret_cast<unsigned char**>(&data));
+ error_tracker.reset();
+
+ if (status != x11::Success) {
+ return gfx::ImageSkia();
+ }
+
+ // The format of |data| is concatenation of sections like
+ // [width, height, pixel data of size width * height], and the total bytes
+ // number of |data| is |size|. And here we are picking the largest icon.
+ int width = 0;
+ int height = 0;
+ int start = 0;
+ int i = 0;
+ while (i + 1 < static_cast<int>(size)) {
+ if ((i == 0 || static_cast<int>(data[i] * data[i + 1]) > width * height) &&
+ (i + 1 + data[i] * data[i + 1] < static_cast<int>(size))) {
+ width = static_cast<int>(data[i]);
+ height = static_cast<int>(data[i + 1]);
+ start = i + 2;
+ }
+ i = i + 2 + static_cast<int>(data[i] * data[i + 1]);
+ }
+
+ SkBitmap result;
+ SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType);
+ result.allocPixels(info);
+
+ uint32_t* pixels_data = reinterpret_cast<uint32_t*>(result.getPixels());
+
+ for (long y = 0; y < height; ++y) {
+ for (long x = 0; x < width; ++x) {
+ pixels_data[result.rowBytesAsPixels() * y + x] =
+ static_cast<uint32_t>(data[start + width * y + x]);
+ }
+ }
+
+ XFree(data);
+ return gfx::ImageSkia::CreateFrom1xBitmap(result);
+}