| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/webrtc/webrtc_internals.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/observer_list.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "build/build_config.h" |
| #include "content/browser/web_contents/web_contents_view.h" |
| #include "content/browser/webrtc/webrtc_internals_connections_observer.h" |
| #include "content/browser/webrtc/webrtc_internals_ui_observer.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/device_service.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/webrtc_event_logger.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "ipc/ipc_platform_file.h" |
| #include "media/audio/audio_debug_recording_session.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/media_buildflags.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "services/audio/public/cpp/debug_recording_session_factory.h" |
| #include "services/device/public/mojom/wake_lock_provider.mojom.h" |
| #include "ui/shell_dialogs/select_file_policy.h" |
| #include "ui/shell_dialogs/selected_file_info.h" |
| |
| using base::ProcessId; |
| using std::string; |
| |
| namespace content { |
| |
| namespace { |
| |
| const base::FilePath::CharType kEventLogFilename[] = |
| FILE_PATH_LITERAL("event_log"); |
| |
| constexpr char kGetUserMedia[] = "getUserMedia"; |
| constexpr char kGetDisplayMedia[] = "getDisplayMedia"; |
| |
| // This is intended to limit DoS attacks against the browser process consisting |
| // of many getUserMedia()/getDisplayMedia() calls. See https://crbug.com/804440. |
| const size_t kMaxMediaEntries = 1000; |
| |
| // Makes sure that |dict| has a List under path "log". |
| base::Value::List& EnsureLogList(base::Value::Dict& dict) { |
| base::Value::List* log = dict.FindList("log"); |
| if (log) |
| return *log; |
| return dict.Set("log", base::Value::List())->GetList(); |
| } |
| |
| // Removes the log entry associated with a given record. |
| void FreeLogList(base::Value* value) { |
| DCHECK(value->is_dict()); |
| value->GetDict().Remove("log"); |
| } |
| |
| } // namespace |
| |
| WebRTCInternals* WebRTCInternals::g_webrtc_internals = nullptr; |
| |
| WebRTCInternals::PendingUpdate::PendingUpdate(const std::string& event_name, |
| base::Value event_data) |
| : event_name_(event_name), event_data_(std::move(event_data)) {} |
| |
| WebRTCInternals::PendingUpdate::PendingUpdate(PendingUpdate&& other) |
| : event_name_(other.event_name_), |
| event_data_(std::move(other.event_data_)) {} |
| |
| WebRTCInternals::PendingUpdate::~PendingUpdate() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| } |
| |
| const std::string& WebRTCInternals::PendingUpdate::event_name() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return event_name_; |
| } |
| |
| const base::Value* WebRTCInternals::PendingUpdate::event_data() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return event_data_.is_none() ? nullptr : &event_data_; |
| } |
| |
| WebRTCInternals::WebRTCInternals() : WebRTCInternals(500, true) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| WebRTCInternals::WebRTCInternals(int aggregate_updates_ms, |
| bool should_block_power_saving) |
| : peer_connection_data_(base::Value::List()), |
| selection_type_(SelectionType::kAudioDebugRecordings), |
| command_line_derived_logging_path_( |
| base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( |
| switches::kWebRtcLocalEventLogging)), |
| event_log_recordings_(false), |
| num_connected_connections_(0), |
| should_block_power_saving_(should_block_power_saving), |
| aggregate_updates_ms_(aggregate_updates_ms) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!g_webrtc_internals); |
| |
| audio_debug_recordings_file_path_ = |
| GetContentClient()->browser()->GetDefaultDownloadDirectory(); |
| event_log_recordings_file_path_ = audio_debug_recordings_file_path_; |
| |
| if (audio_debug_recordings_file_path_.empty()) { |
| // In this case the default path (|audio_debug_recordings_file_path_|) will |
| // be empty and the platform default path will be used in the file dialog |
| // (with no default file name). See SelectFileDialog::SelectFile. On Android |
| // where there's no dialog we'll fail to open the file. |
| VLOG(1) << "Could not get the download directory."; |
| } else { |
| audio_debug_recordings_file_path_ = |
| audio_debug_recordings_file_path_.Append( |
| FILE_PATH_LITERAL("audio_debug")); |
| event_log_recordings_file_path_ = |
| event_log_recordings_file_path_.Append(kEventLogFilename); |
| } |
| |
| // Allow command-line based setting of (local) WebRTC event logging. |
| if (!command_line_derived_logging_path_.empty()) { |
| const base::FilePath local_logs_path = |
| command_line_derived_logging_path_.Append(kEventLogFilename); |
| WebRtcEventLogger* const logger = WebRtcEventLogger::Get(); |
| if (logger) { |
| logger->EnableLocalLogging(local_logs_path); |
| } |
| // For clarity's sake, though these aren't supposed to be regarded now: |
| event_log_recordings_ = true; |
| event_log_recordings_file_path_.clear(); |
| } |
| |
| g_webrtc_internals = this; |
| } |
| |
| WebRTCInternals::~WebRTCInternals() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(g_webrtc_internals); |
| g_webrtc_internals = nullptr; |
| |
| if (select_file_dialog_) { |
| select_file_dialog_->ListenerDestroyed(); |
| } |
| } |
| |
| WebRTCInternals* WebRTCInternals::CreateSingletonInstance() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!g_webrtc_internals); |
| g_webrtc_internals = new WebRTCInternals; |
| return g_webrtc_internals; |
| } |
| |
| WebRTCInternals* WebRTCInternals::GetInstance() { |
| // TODO(crbug.com/40837773): DCHECK calling from UI thread. |
| // Currently, some unit tests call this from outside of the UI thread, |
| // but that's not a real issue as these tests neglect setting |
| // `g_webrtc_internals` to begin with, and therefore just ignore it. |
| DCHECK(!g_webrtc_internals || BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return g_webrtc_internals; |
| } |
| |
| void WebRTCInternals::OnPeerConnectionAdded(GlobalRenderFrameHostId frame_id, |
| int lid, |
| ProcessId pid, |
| const string& url, |
| const string& rtc_configuration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // TODO(tommi): Consider changing this design so that webrtc-internals has |
| // minimal impact if chrome://webrtc-internals isn't open. |
| |
| base::Value::Dict dict; |
| dict.Set("rid", frame_id.child_id); |
| dict.Set("lid", lid); |
| dict.Set("pid", static_cast<int>(pid)); |
| dict.Set("rtcConfiguration", rtc_configuration); |
| dict.Set("url", url); |
| dict.Set("isOpen", true); |
| dict.Set("connected", false); |
| |
| if (!observers_.empty()) |
| SendUpdate("add-peer-connection", dict.Clone()); |
| |
| peer_connection_data().Append(std::move(dict)); |
| |
| if (render_process_id_set_.insert(frame_id.child_id).second) { |
| RenderProcessHost* host = RenderProcessHost::FromID(frame_id.child_id); |
| if (host) |
| host->AddObserver(this); |
| } |
| } |
| |
| void WebRTCInternals::OnPeerConnectionRemoved(GlobalRenderFrameHostId frame_id, |
| int lid) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto it = FindRecord(frame_id, lid); |
| if (it != peer_connection_data().end()) { |
| MaybeClosePeerConnection(*it); |
| peer_connection_data().erase(it); |
| } |
| |
| if (!observers_.empty()) { |
| base::Value::Dict id; |
| id.Set("rid", frame_id.child_id); |
| id.Set("lid", lid); |
| SendUpdate("remove-peer-connection", std::move(id)); |
| } |
| } |
| |
| void WebRTCInternals::OnPeerConnectionUpdated(GlobalRenderFrameHostId frame_id, |
| int lid, |
| const string& type, |
| const string& value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto it = FindRecord(frame_id, lid); |
| if (it == peer_connection_data().end()) |
| return; |
| |
| if (type == "iceconnectionstatechange") { |
| if (value == "connected" || value == "checking" || value == "completed") { |
| MaybeMarkPeerConnectionAsConnected(*it); |
| } else if (value == "failed" || value == "disconnected" || |
| value == "closed" || value == "new") { |
| MaybeMarkPeerConnectionAsNotConnected(*it); |
| } |
| } else if (type == "close") { |
| MaybeClosePeerConnection(*it); |
| } else if (type == "setConfiguration") { |
| // Update the configuration we have for this connection. |
| it->GetDict().Set("rtcConfiguration", value); |
| } |
| |
| // Don't update entries if there aren't any observers. |
| if (observers_.empty()) |
| return; |
| |
| base::Value::Dict log_entry; |
| |
| double epoch_time = base::Time::Now().InMillisecondsFSinceUnixEpoch(); |
| string time = base::NumberToString(epoch_time); |
| log_entry.Set("time", time); |
| log_entry.Set("type", type); |
| log_entry.Set("value", value); |
| |
| base::Value::Dict update; |
| update.Set("rid", frame_id.child_id); |
| update.Set("lid", lid); |
| update.Merge(log_entry.Clone()); |
| |
| SendUpdate("update-peer-connection", std::move(update)); |
| |
| // Append the update to the end of the log. |
| EnsureLogList(it->GetDict()).Append(std::move(log_entry)); |
| } |
| |
| void WebRTCInternals::OnAddStandardStats(GlobalRenderFrameHostId frame_id, |
| int lid, |
| base::Value::List value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (observers_.empty()) |
| return; |
| |
| base::Value::Dict dict; |
| dict.Set("rid", frame_id.child_id); |
| dict.Set("lid", lid); |
| |
| dict.Set("reports", std::move(value)); |
| |
| SendUpdate("add-standard-stats", std::move(dict)); |
| } |
| |
| void WebRTCInternals::OnGetMedia(const std::string& request_type, |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| bool audio, |
| bool video, |
| const std::string& audio_constraints, |
| const std::string& video_constraints) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (get_user_media_requests_.size() >= kMaxMediaEntries) { |
| LOG(WARNING) << "Maximum number of tracked getUserMedia/getDisplayMedia " |
| "requests reached in webrtc-internals."; |
| return; |
| } |
| |
| RenderFrameHost* rfh = RenderFrameHost::FromID(frame_id); |
| // Frame may be gone (and does not exist in tests). |
| std::string origin = rfh ? rfh->GetLastCommittedOrigin().Serialize() : ""; |
| |
| base::Value::Dict dict; |
| dict.Set("rid", frame_id.child_id); |
| dict.Set("pid", static_cast<int>(pid)); |
| dict.Set("request_id", request_id); |
| dict.Set("request_type", request_type); |
| dict.Set("origin", origin); |
| dict.Set("timestamp", base::Time::Now().InMillisecondsFSinceUnixEpoch()); |
| if (audio) |
| dict.Set("audio", audio_constraints); |
| if (video) |
| dict.Set("video", video_constraints); |
| |
| if (!observers_.empty()) |
| SendUpdate("add-media", dict.Clone()); |
| |
| get_user_media_requests_.Append(std::move(dict)); |
| |
| if (render_process_id_set_.insert(frame_id.child_id).second) { |
| RenderProcessHost* rph = RenderProcessHost::FromID(frame_id.child_id); |
| if (rph) |
| rph->AddObserver(this); |
| } |
| } |
| |
| void WebRTCInternals::OnGetMediaSuccess(const std::string& request_type, |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& stream_id, |
| const std::string& audio_track_info, |
| const std::string& video_track_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (get_user_media_requests_.size() >= kMaxMediaEntries) { |
| LOG(WARNING) << "Maximum number of tracked getUserMedia/getDisplayMedia " |
| "requests reached in webrtc-internals."; |
| return; |
| } |
| |
| base::Value::Dict dict; |
| dict.Set("rid", frame_id.child_id); |
| dict.Set("pid", static_cast<int>(pid)); |
| dict.Set("request_id", request_id); |
| dict.Set("request_type", request_type); |
| dict.Set("timestamp", base::Time::Now().InMillisecondsFSinceUnixEpoch()); |
| dict.Set("stream_id", stream_id); |
| if (!audio_track_info.empty()) |
| dict.Set("audio_track_info", audio_track_info); |
| if (!video_track_info.empty()) |
| dict.Set("video_track_info", video_track_info); |
| |
| if (!observers_.empty()) |
| SendUpdate("update-media", dict.Clone()); |
| |
| get_user_media_requests_.Append(std::move(dict)); |
| |
| if (render_process_id_set_.insert(frame_id.child_id).second) { |
| RenderProcessHost* host = RenderProcessHost::FromID(frame_id.child_id); |
| if (host) |
| host->AddObserver(this); |
| } |
| } |
| |
| void WebRTCInternals::OnGetMediaFailure(const std::string& request_type, |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& error, |
| const std::string& error_message) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (get_user_media_requests_.size() >= kMaxMediaEntries) { |
| LOG(WARNING) << "Maximum number of tracked /getDisplayMedia " |
| "requests reached in webrtc-internals."; |
| return; |
| } |
| |
| base::Value::Dict dict; |
| dict.Set("rid", frame_id.child_id); |
| dict.Set("pid", static_cast<int>(pid)); |
| dict.Set("request_id", request_id); |
| dict.Set("request_type", request_type); |
| dict.Set("timestamp", base::Time::Now().InMillisecondsFSinceUnixEpoch()); |
| dict.Set("error", error); |
| dict.Set("error_message", error_message); |
| |
| if (!observers_.empty()) |
| SendUpdate("update-media", dict.Clone()); |
| |
| get_user_media_requests_.Append(std::move(dict)); |
| |
| if (render_process_id_set_.insert(frame_id.child_id).second) { |
| RenderProcessHost* host = RenderProcessHost::FromID(frame_id.child_id); |
| if (host) |
| host->AddObserver(this); |
| } |
| } |
| |
| void WebRTCInternals::OnGetUserMedia(GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| bool audio, |
| bool video, |
| const std::string& audio_constraints, |
| const std::string& video_constraints) { |
| OnGetMedia(kGetUserMedia, frame_id, pid, request_id, audio, video, |
| audio_constraints, video_constraints); |
| } |
| |
| void WebRTCInternals::OnGetUserMediaSuccess( |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& stream_id, |
| const std::string& audio_track_info, |
| const std::string& video_track_info) { |
| OnGetMediaSuccess(kGetUserMedia, frame_id, pid, request_id, stream_id, |
| audio_track_info, video_track_info); |
| } |
| |
| void WebRTCInternals::OnGetUserMediaFailure(GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& error, |
| const std::string& error_message) { |
| OnGetMediaFailure(kGetUserMedia, frame_id, pid, request_id, error, |
| error_message); |
| } |
| |
| void WebRTCInternals::OnGetDisplayMedia(GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| bool audio, |
| bool video, |
| const std::string& audio_constraints, |
| const std::string& video_constraints) { |
| OnGetMedia(kGetDisplayMedia, frame_id, pid, request_id, audio, video, |
| audio_constraints, video_constraints); |
| } |
| |
| void WebRTCInternals::OnGetDisplayMediaSuccess( |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& stream_id, |
| const std::string& audio_track_info, |
| const std::string& video_track_info) { |
| OnGetMediaSuccess(kGetDisplayMedia, frame_id, pid, request_id, stream_id, |
| audio_track_info, video_track_info); |
| } |
| |
| void WebRTCInternals::OnGetDisplayMediaFailure( |
| GlobalRenderFrameHostId frame_id, |
| base::ProcessId pid, |
| int request_id, |
| const std::string& error, |
| const std::string& error_message) { |
| OnGetMediaFailure(kGetDisplayMedia, frame_id, pid, request_id, error, |
| error_message); |
| } |
| |
| void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| observers_.AddObserver(observer); |
| } |
| |
| void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| observers_.RemoveObserver(observer); |
| if (!observers_.empty()) |
| return; |
| |
| // Disables event log and audio debug recordings if enabled and the last |
| // webrtc-internals page is going away. |
| DisableAudioDebugRecordings(); |
| if (CanToggleEventLogRecordings()) { |
| // Do not disable event log recording when the browser was started |
| // with the flag to enable the recordings on the command line. |
| DisableLocalEventLogRecordings(); |
| } |
| |
| // TODO(tommi): Consider removing all the peer_connection_data(). |
| for (auto& dictionary : peer_connection_data()) |
| FreeLogList(&dictionary); |
| } |
| |
| void WebRTCInternals::AddConnectionsObserver( |
| WebRtcInternalsConnectionsObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| connections_observers_.AddObserver(observer); |
| } |
| |
| void WebRTCInternals::RemoveConnectionsObserver( |
| WebRtcInternalsConnectionsObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| connections_observers_.RemoveObserver(observer); |
| } |
| |
| void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (peer_connection_data().size() > 0) |
| observer->OnUpdate("update-all-peer-connections", &peer_connection_data_); |
| |
| for (const auto& request : get_user_media_requests_) { |
| // If there is a stream_id key or an error key this is an update. |
| if (request.GetDict().FindString("stream_id") || |
| request.GetDict().FindString("error")) { |
| observer->OnUpdate("update-media", &request); |
| } else { |
| observer->OnUpdate("add-media", &request); |
| } |
| } |
| } |
| |
| void WebRTCInternals::EnableAudioDebugRecordings( |
| content::WebContents* web_contents) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| #if BUILDFLAG(IS_ANDROID) |
| EnableAudioDebugRecordingsOnAllRenderProcessHosts(); |
| #else |
| if (select_file_dialog_) { |
| return; |
| } |
| selection_type_ = SelectionType::kAudioDebugRecordings; |
| select_file_dialog_ = ui::SelectFileDialog::Create( |
| this, |
| GetContentClient()->browser()->CreateSelectFilePolicy(web_contents)); |
| select_file_dialog_->SelectFile( |
| ui::SelectFileDialog::SELECT_SAVEAS_FILE, std::u16string(), |
| audio_debug_recordings_file_path_, nullptr, 0, |
| base::FilePath::StringType(), web_contents->GetTopLevelNativeWindow()); |
| #endif |
| } |
| |
| void WebRTCInternals::DisableAudioDebugRecordings() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!audio_debug_recording_session_) |
| return; |
| audio_debug_recording_session_.reset(); |
| |
| // Tear down the dialog since the user has unchecked the audio debug |
| // recordings box. |
| select_file_dialog_ = nullptr; |
| |
| for (RenderProcessHost::iterator i( |
| content::RenderProcessHost::AllHostsIterator()); |
| !i.IsAtEnd(); i.Advance()) { |
| i.GetCurrentValue()->DisableAudioDebugRecordings(); |
| } |
| } |
| |
| bool WebRTCInternals::IsAudioDebugRecordingsEnabled() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return !!audio_debug_recording_session_; |
| } |
| |
| const base::FilePath& WebRTCInternals::GetAudioDebugRecordingsFilePath() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return audio_debug_recordings_file_path_; |
| } |
| |
| void WebRTCInternals::EnableLocalEventLogRecordings( |
| content::WebContents* web_contents) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(web_contents); |
| DCHECK(CanToggleEventLogRecordings()); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| WebRtcEventLogger* const logger = WebRtcEventLogger::Get(); |
| if (logger) { |
| logger->EnableLocalLogging(event_log_recordings_file_path_); |
| } |
| #else |
| if (select_file_dialog_) { |
| return; |
| } |
| |
| selection_type_ = SelectionType::kRtcEventLogs; |
| select_file_dialog_ = ui::SelectFileDialog::Create( |
| this, |
| GetContentClient()->browser()->CreateSelectFilePolicy(web_contents)); |
| select_file_dialog_->SelectFile( |
| ui::SelectFileDialog::SELECT_SAVEAS_FILE, std::u16string(), |
| event_log_recordings_file_path_, nullptr, 0, FILE_PATH_LITERAL(""), |
| web_contents->GetTopLevelNativeWindow()); |
| #endif |
| } |
| |
| void WebRTCInternals::DisableLocalEventLogRecordings() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| event_log_recordings_ = false; |
| // Tear down the dialog since the user has unchecked the event log checkbox. |
| select_file_dialog_ = nullptr; |
| DCHECK(CanToggleEventLogRecordings()); |
| WebRtcEventLogger* const logger = WebRtcEventLogger::Get(); |
| if (logger) { |
| logger->DisableLocalLogging(); |
| } |
| } |
| |
| bool WebRTCInternals::IsEventLogRecordingsEnabled() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return event_log_recordings_; |
| } |
| |
| bool WebRTCInternals::CanToggleEventLogRecordings() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return command_line_derived_logging_path_.empty(); |
| } |
| |
| void WebRTCInternals::SendUpdate(const std::string& event_name, |
| base::Value event_data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!observers_.empty()); |
| |
| bool queue_was_empty = pending_updates_.empty(); |
| pending_updates_.push(PendingUpdate(event_name, std::move(event_data))); |
| |
| if (queue_was_empty) { |
| GetUIThreadTaskRunner({})->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&WebRTCInternals::ProcessPendingUpdates, |
| weak_factory_.GetWeakPtr()), |
| base::Milliseconds(aggregate_updates_ms_)); |
| } |
| } |
| |
| void WebRTCInternals::SendUpdate(const std::string& event_name, |
| base::Value::Dict event_data) { |
| SendUpdate(event_name, base::Value(std::move(event_data))); |
| } |
| |
| void WebRTCInternals::RenderProcessExited( |
| RenderProcessHost* host, |
| const ChildProcessTerminationInfo& info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| OnRendererExit(host->GetDeprecatedID()); |
| render_process_id_set_.erase(host->GetDeprecatedID()); |
| host->RemoveObserver(this); |
| } |
| |
| void WebRTCInternals::FileSelected(const ui::SelectedFileInfo& file, |
| int /* unused_index */) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| switch (selection_type_) { |
| case SelectionType::kRtcEventLogs: { |
| event_log_recordings_file_path_ = file.path(); |
| event_log_recordings_ = true; |
| WebRtcEventLogger* const logger = WebRtcEventLogger::Get(); |
| if (logger) { |
| logger->EnableLocalLogging(file.path()); |
| } |
| break; |
| } |
| case SelectionType::kAudioDebugRecordings: { |
| audio_debug_recordings_file_path_ = file.path(); |
| EnableAudioDebugRecordingsOnAllRenderProcessHosts(); |
| break; |
| } |
| default: { |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| void WebRTCInternals::FileSelectionCanceled() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| switch (selection_type_) { |
| case SelectionType::kRtcEventLogs: |
| SendUpdate("event-log-recordings-file-selection-cancelled", |
| base::Value()); |
| break; |
| case SelectionType::kAudioDebugRecordings: |
| SendUpdate("audio-debug-recordings-file-selection-cancelled", |
| base::Value()); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| select_file_dialog_ = nullptr; |
| } |
| |
| void WebRTCInternals::OnRendererExit(int render_process_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Iterates from the end of the list to remove the PeerConnections created |
| // by the exiting renderer. |
| for (int i = peer_connection_data().size() - 1; i >= 0; --i) { |
| DCHECK(peer_connection_data()[i].is_dict()); |
| |
| std::optional<int> this_rid, this_lid; |
| this_rid = peer_connection_data()[i].GetDict().FindInt("rid"); |
| this_lid = peer_connection_data()[i].GetDict().FindInt("lid"); |
| |
| if (this_rid.value_or(0) == render_process_id) { |
| if (!observers_.empty()) { |
| base::Value::Dict update; |
| update.Set("rid", this_rid.value_or(0)); |
| update.Set("lid", this_lid.value_or(0)); |
| SendUpdate("remove-peer-connection", std::move(update)); |
| } |
| MaybeClosePeerConnection(peer_connection_data()[i]); |
| peer_connection_data().erase(peer_connection_data().begin() + i); |
| } |
| } |
| UpdateWakeLock(); |
| |
| bool found_any = false; |
| // Iterates from the end of the list to remove the getUserMedia requests |
| // created by the exiting renderer. |
| for (int i = get_user_media_requests_.size() - 1; i >= 0; --i) { |
| DCHECK(get_user_media_requests_[i].is_dict()); |
| |
| std::optional<int> this_rid = |
| get_user_media_requests_[i].GetDict().FindInt("rid"); |
| |
| if (this_rid.value_or(0) == render_process_id) { |
| get_user_media_requests_.erase(get_user_media_requests_.begin() + i); |
| found_any = true; |
| } |
| } |
| |
| if (found_any && !observers_.empty()) { |
| base::Value::Dict update; |
| update.Set("rid", render_process_id); |
| SendUpdate("remove-media-for-renderer", std::move(update)); |
| } |
| } |
| |
| void WebRTCInternals::EnableAudioDebugRecordingsOnAllRenderProcessHosts() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!audio_debug_recording_session_); |
| mojo::PendingRemote<audio::mojom::DebugRecording> debug_recording; |
| content::GetAudioService().BindDebugRecording( |
| debug_recording.InitWithNewPipeAndPassReceiver()); |
| audio_debug_recording_session_ = audio::CreateAudioDebugRecordingSession( |
| audio_debug_recordings_file_path_, std::move(debug_recording)); |
| |
| for (RenderProcessHost::iterator i( |
| content::RenderProcessHost::AllHostsIterator()); |
| !i.IsAtEnd(); i.Advance()) { |
| i.GetCurrentValue()->EnableAudioDebugRecordings( |
| audio_debug_recordings_file_path_); |
| } |
| } |
| |
| void WebRTCInternals::MaybeClosePeerConnection(base::Value& record) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::optional<bool> is_open = record.GetDict().FindBool("isOpen"); |
| DCHECK(is_open.has_value()); |
| if (!*is_open) |
| return; |
| |
| record.GetDict().Set("isOpen", false); |
| MaybeMarkPeerConnectionAsNotConnected(record); |
| } |
| |
| void WebRTCInternals::MaybeMarkPeerConnectionAsConnected(base::Value& record) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| bool was_connected = record.GetDict().FindBool("connected").value_or(true); |
| if (!was_connected) { |
| ++num_connected_connections_; |
| record.GetDict().Set("connected", true); |
| UpdateWakeLock(); |
| for (auto& observer : connections_observers_) |
| observer.OnConnectionsCountChange(num_connected_connections_); |
| } |
| } |
| |
| void WebRTCInternals::MaybeMarkPeerConnectionAsNotConnected( |
| base::Value& record) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| bool was_connected = record.GetDict().FindBool("connected").value_or(false); |
| if (was_connected) { |
| record.GetDict().Set("connected", false); |
| --num_connected_connections_; |
| DCHECK_GE(num_connected_connections_, 0); |
| UpdateWakeLock(); |
| for (auto& observer : connections_observers_) |
| observer.OnConnectionsCountChange(num_connected_connections_); |
| } |
| } |
| |
| void WebRTCInternals::UpdateWakeLock() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!should_block_power_saving_) |
| return; |
| |
| if (num_connected_connections_ == 0) { |
| DVLOG(1) |
| << ("Cancel the wake lock on application suspension since no " |
| "PeerConnections are active anymore."); |
| GetWakeLock()->CancelWakeLock(); |
| } else { |
| DCHECK_GT(num_connected_connections_, 0); |
| DVLOG(1) << ("Preventing the application from being suspended while one or " |
| "more PeerConnections are active."); |
| GetWakeLock()->RequestWakeLock(); |
| } |
| } |
| |
| device::mojom::WakeLock* WebRTCInternals::GetWakeLock() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Here is a lazy binding, and will not reconnect after connection error. |
| if (!wake_lock_) { |
| mojo::Remote<device::mojom::WakeLockProvider> wake_lock_provider; |
| GetDeviceService().BindWakeLockProvider( |
| wake_lock_provider.BindNewPipeAndPassReceiver()); |
| wake_lock_provider->GetWakeLockWithoutContext( |
| device::mojom::WakeLockType::kPreventAppSuspension, |
| device::mojom::WakeLockReason::kOther, |
| "WebRTC has active PeerConnections", |
| wake_lock_.BindNewPipeAndPassReceiver()); |
| } |
| return wake_lock_.get(); |
| } |
| |
| void WebRTCInternals::ProcessPendingUpdates() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| while (!pending_updates_.empty()) { |
| const auto& update = pending_updates_.front(); |
| for (auto& observer : observers_) |
| observer.OnUpdate(update.event_name(), update.event_data()); |
| pending_updates_.pop(); |
| } |
| } |
| |
| base::Value::List::iterator WebRTCInternals::FindRecord( |
| GlobalRenderFrameHostId frame_id, |
| int lid) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| for (auto it = peer_connection_data().begin(); |
| it != peer_connection_data().end(); ++it) { |
| DCHECK(it->is_dict()); |
| |
| int this_rid = it->GetDict().FindInt("rid").value_or(0); |
| int this_lid = it->GetDict().FindInt("lid").value_or(0); |
| |
| if (this_rid == frame_id.child_id && this_lid == lid) |
| return it; |
| } |
| return peer_connection_data().end(); |
| } |
| } // namespace content |