1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/media/webrtc_internals.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "content/browser/media/webrtc_internals_ui_observer.h"
9 #include "content/browser/web_contents/web_contents_view.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "content/public/browser/content_browser_client.h"
12 #include "content/public/browser/power_save_blocker.h"
13 #include "content/public/browser/render_process_host.h"
14 #include "content/public/browser/web_contents.h"
16 using base::ProcessId
;
23 static base::LazyInstance
<WebRTCInternals
>::Leaky g_webrtc_internals
=
24 LAZY_INSTANCE_INITIALIZER
;
26 // Makes sure that |dict| has a ListValue under path "log".
27 static base::ListValue
* EnsureLogList(base::DictionaryValue
* dict
) {
28 base::ListValue
* log
= NULL
;
29 if (!dict
->GetList("log", &log
)) {
30 log
= new base::ListValue();
32 dict
->Set("log", log
);
39 WebRTCInternals::WebRTCInternals()
40 : audio_debug_recordings_(false) {
41 // TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the
42 // build if WebRTC is disabled?
43 #if defined(ENABLE_WEBRTC)
44 audio_debug_recordings_file_path_
=
45 GetContentClient()->browser()->GetDefaultDownloadDirectory();
46 if (audio_debug_recordings_file_path_
.empty()) {
47 // In this case the default path (|audio_debug_recordings_file_path_|) will
48 // be empty and the platform default path will be used in the file dialog
49 // (with no default file name). See SelectFileDialog::SelectFile. On Android
50 // where there's no dialog we'll fail to open the file.
51 VLOG(1) << "Could not get the download directory.";
53 audio_debug_recordings_file_path_
=
54 audio_debug_recordings_file_path_
.Append(
55 FILE_PATH_LITERAL("audio_debug"));
57 #endif // defined(ENABLE_WEBRTC)
60 WebRTCInternals::~WebRTCInternals() {
63 WebRTCInternals
* WebRTCInternals::GetInstance() {
64 return g_webrtc_internals
.Pointer();
67 void WebRTCInternals::OnAddPeerConnection(int render_process_id
,
71 const string
& rtc_configuration
,
72 const string
& constraints
) {
73 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
75 base::DictionaryValue
* dict
= new base::DictionaryValue();
79 dict
->SetInteger("rid", render_process_id
);
80 dict
->SetInteger("pid", static_cast<int>(pid
));
81 dict
->SetInteger("lid", lid
);
82 dict
->SetString("rtcConfiguration", rtc_configuration
);
83 dict
->SetString("constraints", constraints
);
84 dict
->SetString("url", url
);
85 peer_connection_data_
.Append(dict
);
86 CreateOrReleasePowerSaveBlocker();
88 if (observers_
.might_have_observers())
89 SendUpdate("addPeerConnection", dict
);
91 if (render_process_id_set_
.insert(render_process_id
).second
) {
92 RenderProcessHost
* host
= RenderProcessHost::FromID(render_process_id
);
94 host
->AddObserver(this);
98 void WebRTCInternals::OnRemovePeerConnection(ProcessId pid
, int lid
) {
99 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
100 for (size_t i
= 0; i
< peer_connection_data_
.GetSize(); ++i
) {
101 base::DictionaryValue
* dict
= NULL
;
102 peer_connection_data_
.GetDictionary(i
, &dict
);
106 dict
->GetInteger("pid", &this_pid
);
107 dict
->GetInteger("lid", &this_lid
);
109 if (this_pid
!= static_cast<int>(pid
) || this_lid
!= lid
)
112 peer_connection_data_
.Remove(i
, NULL
);
113 CreateOrReleasePowerSaveBlocker();
115 if (observers_
.might_have_observers()) {
116 base::DictionaryValue id
;
117 id
.SetInteger("pid", static_cast<int>(pid
));
118 id
.SetInteger("lid", lid
);
119 SendUpdate("removePeerConnection", &id
);
125 void WebRTCInternals::OnUpdatePeerConnection(
126 ProcessId pid
, int lid
, const string
& type
, const string
& value
) {
127 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
129 for (size_t i
= 0; i
< peer_connection_data_
.GetSize(); ++i
) {
130 base::DictionaryValue
* record
= NULL
;
131 peer_connection_data_
.GetDictionary(i
, &record
);
133 int this_pid
= 0, this_lid
= 0;
134 record
->GetInteger("pid", &this_pid
);
135 record
->GetInteger("lid", &this_lid
);
137 if (this_pid
!= static_cast<int>(pid
) || this_lid
!= lid
)
140 // Append the update to the end of the log.
141 base::ListValue
* log
= EnsureLogList(record
);
145 base::DictionaryValue
* log_entry
= new base::DictionaryValue();
149 double epoch_time
= base::Time::Now().ToJsTime();
150 string time
= base::DoubleToString(epoch_time
);
151 log_entry
->SetString("time", time
);
152 log_entry
->SetString("type", type
);
153 log_entry
->SetString("value", value
);
154 log
->Append(log_entry
);
156 if (observers_
.might_have_observers()) {
157 base::DictionaryValue update
;
158 update
.SetInteger("pid", static_cast<int>(pid
));
159 update
.SetInteger("lid", lid
);
160 update
.MergeDictionary(log_entry
);
162 SendUpdate("updatePeerConnection", &update
);
168 void WebRTCInternals::OnAddStats(base::ProcessId pid
, int lid
,
169 const base::ListValue
& value
) {
170 if (!observers_
.might_have_observers())
173 base::DictionaryValue dict
;
174 dict
.SetInteger("pid", static_cast<int>(pid
));
175 dict
.SetInteger("lid", lid
);
177 base::ListValue
* list
= value
.DeepCopy();
181 dict
.Set("reports", list
);
183 SendUpdate("addStats", &dict
);
186 void WebRTCInternals::OnGetUserMedia(int rid
,
188 const std::string
& origin
,
191 const std::string
& audio_constraints
,
192 const std::string
& video_constraints
) {
193 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
195 base::DictionaryValue
* dict
= new base::DictionaryValue();
196 dict
->SetInteger("rid", rid
);
197 dict
->SetInteger("pid", static_cast<int>(pid
));
198 dict
->SetString("origin", origin
);
200 dict
->SetString("audio", audio_constraints
);
202 dict
->SetString("video", video_constraints
);
204 get_user_media_requests_
.Append(dict
);
206 if (observers_
.might_have_observers())
207 SendUpdate("addGetUserMedia", dict
);
209 if (render_process_id_set_
.insert(rid
).second
) {
210 RenderProcessHost
* host
= RenderProcessHost::FromID(rid
);
212 host
->AddObserver(this);
216 void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver
*observer
) {
217 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
218 observers_
.AddObserver(observer
);
221 void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver
*observer
) {
222 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
223 observers_
.RemoveObserver(observer
);
225 // Disables audio debug recordings if it is enabled and the last
226 // webrtc-internals page is going away.
227 if (audio_debug_recordings_
&& !observers_
.might_have_observers())
228 DisableAudioDebugRecordings();
231 void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver
* observer
) {
232 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
233 if (peer_connection_data_
.GetSize() > 0)
234 observer
->OnUpdate("updateAllPeerConnections", &peer_connection_data_
);
236 for (base::ListValue::iterator it
= get_user_media_requests_
.begin();
237 it
!= get_user_media_requests_
.end();
239 observer
->OnUpdate("addGetUserMedia", *it
);
243 void WebRTCInternals::EnableAudioDebugRecordings(
244 content::WebContents
* web_contents
) {
245 #if defined(ENABLE_WEBRTC)
246 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
247 #if defined(OS_ANDROID)
248 EnableAudioDebugRecordingsOnAllRenderProcessHosts();
250 select_file_dialog_
= ui::SelectFileDialog::Create(this, NULL
);
251 select_file_dialog_
->SelectFile(
252 ui::SelectFileDialog::SELECT_SAVEAS_FILE
,
254 audio_debug_recordings_file_path_
,
257 FILE_PATH_LITERAL(""),
258 web_contents
->GetTopLevelNativeWindow(),
264 void WebRTCInternals::DisableAudioDebugRecordings() {
265 #if defined(ENABLE_WEBRTC)
266 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
267 audio_debug_recordings_
= false;
269 // Tear down the dialog since the user has unchecked the audio debug
271 select_file_dialog_
= NULL
;
273 for (RenderProcessHost::iterator
i(
274 content::RenderProcessHost::AllHostsIterator());
275 !i
.IsAtEnd(); i
.Advance()) {
276 i
.GetCurrentValue()->DisableAudioDebugRecordings();
281 bool WebRTCInternals::IsAudioDebugRecordingsEnabled() const {
282 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
283 return audio_debug_recordings_
;
286 const base::FilePath
& WebRTCInternals::GetAudioDebugRecordingsFilePath() const {
287 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
288 return audio_debug_recordings_file_path_
;
291 void WebRTCInternals::ResetForTesting() {
292 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
294 peer_connection_data_
.Clear();
295 CreateOrReleasePowerSaveBlocker();
296 get_user_media_requests_
.Clear();
297 audio_debug_recordings_
= false;
300 void WebRTCInternals::SendUpdate(const string
& command
, base::Value
* value
) {
301 DCHECK(observers_
.might_have_observers());
303 FOR_EACH_OBSERVER(WebRTCInternalsUIObserver
,
305 OnUpdate(command
, value
));
308 void WebRTCInternals::RenderProcessHostDestroyed(RenderProcessHost
* host
) {
309 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
310 OnRendererExit(host
->GetID());
312 render_process_id_set_
.erase(host
->GetID());
313 host
->RemoveObserver(this);
316 void WebRTCInternals::FileSelected(const base::FilePath
& path
,
317 int /* unused_index */,
318 void* /*unused_params */) {
319 #if defined(ENABLE_WEBRTC)
320 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
321 audio_debug_recordings_file_path_
= path
;
322 EnableAudioDebugRecordingsOnAllRenderProcessHosts();
326 void WebRTCInternals::FileSelectionCanceled(void* params
) {
327 #if defined(ENABLE_WEBRTC)
328 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
329 SendUpdate("audioDebugRecordingsFileSelectionCancelled", NULL
);
333 void WebRTCInternals::OnRendererExit(int render_process_id
) {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
336 // Iterates from the end of the list to remove the PeerConnections created
337 // by the exitting renderer.
338 for (int i
= peer_connection_data_
.GetSize() - 1; i
>= 0; --i
) {
339 base::DictionaryValue
* record
= NULL
;
340 peer_connection_data_
.GetDictionary(i
, &record
);
343 record
->GetInteger("rid", &this_rid
);
345 if (this_rid
== render_process_id
) {
346 if (observers_
.might_have_observers()) {
347 int lid
= 0, pid
= 0;
348 record
->GetInteger("lid", &lid
);
349 record
->GetInteger("pid", &pid
);
351 base::DictionaryValue update
;
352 update
.SetInteger("lid", lid
);
353 update
.SetInteger("pid", pid
);
354 SendUpdate("removePeerConnection", &update
);
356 peer_connection_data_
.Remove(i
, NULL
);
359 CreateOrReleasePowerSaveBlocker();
361 bool found_any
= false;
362 // Iterates from the end of the list to remove the getUserMedia requests
363 // created by the exiting renderer.
364 for (int i
= get_user_media_requests_
.GetSize() - 1; i
>= 0; --i
) {
365 base::DictionaryValue
* record
= NULL
;
366 get_user_media_requests_
.GetDictionary(i
, &record
);
369 record
->GetInteger("rid", &this_rid
);
371 if (this_rid
== render_process_id
) {
372 get_user_media_requests_
.Remove(i
, NULL
);
377 if (found_any
&& observers_
.might_have_observers()) {
378 base::DictionaryValue update
;
379 update
.SetInteger("rid", render_process_id
);
380 SendUpdate("removeGetUserMediaForRenderer", &update
);
384 #if defined(ENABLE_WEBRTC)
385 void WebRTCInternals::EnableAudioDebugRecordingsOnAllRenderProcessHosts() {
386 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
388 audio_debug_recordings_
= true;
389 for (RenderProcessHost::iterator
i(
390 content::RenderProcessHost::AllHostsIterator());
391 !i
.IsAtEnd(); i
.Advance()) {
392 i
.GetCurrentValue()->EnableAudioDebugRecordings(
393 audio_debug_recordings_file_path_
);
398 void WebRTCInternals::CreateOrReleasePowerSaveBlocker() {
399 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
401 if (peer_connection_data_
.empty() && power_save_blocker_
) {
402 DVLOG(1) << ("Releasing the block on application suspension since no "
403 "PeerConnections are active anymore.");
404 power_save_blocker_
.reset();
405 } else if (!peer_connection_data_
.empty() && !power_save_blocker_
) {
406 DVLOG(1) << ("Preventing the application from being suspended while one or "
407 "more PeerConnections are active.");
408 power_save_blocker_
=
409 content::PowerSaveBlocker::Create(
410 PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
411 PowerSaveBlocker::kReasonOther
,
412 "WebRTC has active PeerConnections").Pass();
416 } // namespace content