// 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 #include #include #include "base/bind.h" #include "base/command_line.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" using base::ProcessId; using std::string; namespace content { namespace { const base::FilePath::CharType kEventLogFilename[] = FILE_PATH_LITERAL("event_log"); // This is intended to limit DoS attacks against the browser process consisting // of many getUserMedia() calls. See https://crbug.com/804440. const size_t kMaxGetUserMediaEntries = 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); // TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the // build if WebRTC is disabled? // https://crbug.com/817446 #if BUILDFLAG(ENABLE_WEBRTC) 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(); } #endif // BUILDFLAG(ENABLE_WEBRTC) g_webrtc_internals = this; } WebRTCInternals::~WebRTCInternals() { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(g_webrtc_internals); g_webrtc_internals = nullptr; } 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/1322082): 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, const string& constraints) { 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(pid)); dict.Set("rtcConfiguration", rtc_configuration); dict.Set("constraints", constraints); 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().ToJsTime(); 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::OnAddLegacyStats(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-legacy-stats", std::move(dict)); } 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) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (get_user_media_requests_.size() >= kMaxGetUserMediaEntries) { LOG(WARNING) << "Maximum number of tracked getUserMedia() 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(pid)); dict.Set("request_id", request_id); dict.Set("origin", origin); dict.Set("timestamp", base::Time::Now().ToJsTime()); if (audio) dict.Set("audio", audio_constraints); if (video) dict.Set("video", video_constraints); if (!observers_.empty()) SendUpdate("add-get-user-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::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) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (get_user_media_requests_.size() >= kMaxGetUserMediaEntries) { LOG(WARNING) << "Maximum number of tracked getUserMedia() requests reached " "in webrtc-internals."; return; } base::Value::Dict dict; dict.Set("rid", frame_id.child_id); dict.Set("pid", static_cast(pid)); dict.Set("request_id", request_id); dict.Set("timestamp", base::Time::Now().ToJsTime()); 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-get-user-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::OnGetUserMediaFailure(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() >= kMaxGetUserMediaEntries) { LOG(WARNING) << "Maximum number of tracked getUserMedia() requests reached " "in webrtc-internals."; return; } base::Value::Dict dict; dict.Set("rid", frame_id.child_id); dict.Set("pid", static_cast(pid)); dict.Set("request_id", request_id); dict.Set("timestamp", base::Time::Now().ToJsTime()); dict.Set("error", error); dict.Set("error_message", error_message); if (!observers_.empty()) SendUpdate("update-get-user-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::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-get-user-media", &request); } else { observer->OnUpdate("add-get-user-media", &request); } } } void WebRTCInternals::EnableAudioDebugRecordings( content::WebContents* web_contents) { DCHECK_CURRENTLY_ON(BrowserThread::UI); #if BUILDFLAG(ENABLE_WEBRTC) #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(), nullptr); #endif #endif } void WebRTCInternals::DisableAudioDebugRecordings() { DCHECK_CURRENTLY_ON(BrowserThread::UI); #if BUILDFLAG(ENABLE_WEBRTC) 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(); } #endif } 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(ENABLE_WEBRTC) #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(), nullptr); #endif #endif } void WebRTCInternals::DisableLocalEventLogRecordings() { #if BUILDFLAG(ENABLE_WEBRTC) 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(); } #endif } 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->GetID()); render_process_id_set_.erase(host->GetID()); host->RemoveObserver(this); } void WebRTCInternals::FileSelected(const base::FilePath& path, int /* unused_index */, void* /*unused_params */) { #if BUILDFLAG(ENABLE_WEBRTC) DCHECK_CURRENTLY_ON(BrowserThread::UI); switch (selection_type_) { case SelectionType::kRtcEventLogs: { event_log_recordings_file_path_ = path; event_log_recordings_ = true; WebRtcEventLogger* const logger = WebRtcEventLogger::Get(); if (logger) { logger->EnableLocalLogging(path); } break; } case SelectionType::kAudioDebugRecordings: { audio_debug_recordings_file_path_ = path; EnableAudioDebugRecordingsOnAllRenderProcessHosts(); break; } default: { NOTREACHED(); } } #endif } void WebRTCInternals::FileSelectionCanceled(void* params) { #if BUILDFLAG(ENABLE_WEBRTC) 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; #endif } 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()); absl::optional 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()); absl::optional 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-get-user-media-for-renderer", std::move(update)); } } #if BUILDFLAG(ENABLE_WEBRTC) void WebRTCInternals::EnableAudioDebugRecordingsOnAllRenderProcessHosts() { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!audio_debug_recording_session_); mojo::PendingRemote 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_); } } #endif void WebRTCInternals::MaybeClosePeerConnection(base::Value& record) { DCHECK_CURRENTLY_ON(BrowserThread::UI); absl::optional 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 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