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/path_service.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "content/browser/media/webrtc_internals_ui_observer.h"
10 #include "content/browser/web_contents/web_contents_view.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/content_browser_client.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_types.h"
15 #include "content/public/browser/power_save_blocker.h"
16 #include "content/public/browser/render_process_host.h"
17 #include "content/public/browser/web_contents.h"
19 using base::ProcessId
;
26 static base::LazyInstance
<WebRTCInternals
>::Leaky g_webrtc_internals
=
27 LAZY_INSTANCE_INITIALIZER
;
29 // Makes sure that |dict| has a ListValue under path "log".
30 static base::ListValue
* EnsureLogList(base::DictionaryValue
* dict
) {
31 base::ListValue
* log
= NULL
;
32 if (!dict
->GetList("log", &log
)) {
33 log
= new base::ListValue();
35 dict
->Set("log", log
);
42 WebRTCInternals::WebRTCInternals()
43 : aec_dump_enabled_(false) {
44 registrar_
.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED
,
45 NotificationService::AllBrowserContextsAndSources());
46 // TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the
47 // build if WebRTC is disabled?
48 #if defined(ENABLE_WEBRTC)
50 GetContentClient()->browser()->GetDefaultDownloadDirectory();
51 if (aec_dump_file_path_
.empty()) {
52 // In this case the default path (|aec_dump_file_path_|) will be empty and
53 // the platform default path will be used in the file dialog (with no
54 // default file name). See SelectFileDialog::SelectFile. On Android where
55 // there's no dialog we'll fail to open the file.
56 VLOG(1) << "Could not get the download directory.";
59 aec_dump_file_path_
.Append(FILE_PATH_LITERAL("audio.aecdump"));
61 #endif // defined(ENABLE_WEBRTC)
64 WebRTCInternals::~WebRTCInternals() {
67 WebRTCInternals
* WebRTCInternals::GetInstance() {
68 return g_webrtc_internals
.Pointer();
71 void WebRTCInternals::OnAddPeerConnection(int render_process_id
,
75 const string
& rtc_configuration
,
76 const string
& constraints
) {
77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
79 base::DictionaryValue
* dict
= new base::DictionaryValue();
83 dict
->SetInteger("rid", render_process_id
);
84 dict
->SetInteger("pid", static_cast<int>(pid
));
85 dict
->SetInteger("lid", lid
);
86 dict
->SetString("rtcConfiguration", rtc_configuration
);
87 dict
->SetString("constraints", constraints
);
88 dict
->SetString("url", url
);
89 peer_connection_data_
.Append(dict
);
90 CreateOrReleasePowerSaveBlocker();
92 if (observers_
.might_have_observers())
93 SendUpdate("addPeerConnection", dict
);
96 void WebRTCInternals::OnRemovePeerConnection(ProcessId pid
, int lid
) {
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
98 for (size_t i
= 0; i
< peer_connection_data_
.GetSize(); ++i
) {
99 base::DictionaryValue
* dict
= NULL
;
100 peer_connection_data_
.GetDictionary(i
, &dict
);
104 dict
->GetInteger("pid", &this_pid
);
105 dict
->GetInteger("lid", &this_lid
);
107 if (this_pid
!= static_cast<int>(pid
) || this_lid
!= lid
)
110 peer_connection_data_
.Remove(i
, NULL
);
111 CreateOrReleasePowerSaveBlocker();
113 if (observers_
.might_have_observers()) {
114 base::DictionaryValue id
;
115 id
.SetInteger("pid", static_cast<int>(pid
));
116 id
.SetInteger("lid", lid
);
117 SendUpdate("removePeerConnection", &id
);
123 void WebRTCInternals::OnUpdatePeerConnection(
124 ProcessId pid
, int lid
, const string
& type
, const string
& value
) {
125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
127 for (size_t i
= 0; i
< peer_connection_data_
.GetSize(); ++i
) {
128 base::DictionaryValue
* record
= NULL
;
129 peer_connection_data_
.GetDictionary(i
, &record
);
131 int this_pid
= 0, this_lid
= 0;
132 record
->GetInteger("pid", &this_pid
);
133 record
->GetInteger("lid", &this_lid
);
135 if (this_pid
!= static_cast<int>(pid
) || this_lid
!= lid
)
138 // Append the update to the end of the log.
139 base::ListValue
* log
= EnsureLogList(record
);
143 base::DictionaryValue
* log_entry
= new base::DictionaryValue();
147 double epoch_time
= base::Time::Now().ToJsTime();
148 string time
= base::DoubleToString(epoch_time
);
149 log_entry
->SetString("time", time
);
150 log_entry
->SetString("type", type
);
151 log_entry
->SetString("value", value
);
152 log
->Append(log_entry
);
154 if (observers_
.might_have_observers()) {
155 base::DictionaryValue update
;
156 update
.SetInteger("pid", static_cast<int>(pid
));
157 update
.SetInteger("lid", lid
);
158 update
.MergeDictionary(log_entry
);
160 SendUpdate("updatePeerConnection", &update
);
166 void WebRTCInternals::OnAddStats(base::ProcessId pid
, int lid
,
167 const base::ListValue
& value
) {
168 if (!observers_
.might_have_observers())
171 base::DictionaryValue dict
;
172 dict
.SetInteger("pid", static_cast<int>(pid
));
173 dict
.SetInteger("lid", lid
);
175 base::ListValue
* list
= value
.DeepCopy();
179 dict
.Set("reports", list
);
181 SendUpdate("addStats", &dict
);
184 void WebRTCInternals::OnGetUserMedia(int rid
,
186 const std::string
& origin
,
189 const std::string
& audio_constraints
,
190 const std::string
& video_constraints
) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
193 base::DictionaryValue
* dict
= new base::DictionaryValue();
194 dict
->SetInteger("rid", rid
);
195 dict
->SetInteger("pid", static_cast<int>(pid
));
196 dict
->SetString("origin", origin
);
198 dict
->SetString("audio", audio_constraints
);
200 dict
->SetString("video", video_constraints
);
202 get_user_media_requests_
.Append(dict
);
204 if (observers_
.might_have_observers())
205 SendUpdate("addGetUserMedia", dict
);
208 void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver
*observer
) {
209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
210 observers_
.AddObserver(observer
);
213 void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver
*observer
) {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
215 observers_
.RemoveObserver(observer
);
217 // Disables the AEC recording if it is enabled and the last webrtc-internals
218 // page is going away.
219 if (aec_dump_enabled_
&& !observers_
.might_have_observers())
223 void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver
* observer
) {
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
225 if (peer_connection_data_
.GetSize() > 0)
226 observer
->OnUpdate("updateAllPeerConnections", &peer_connection_data_
);
228 for (base::ListValue::iterator it
= get_user_media_requests_
.begin();
229 it
!= get_user_media_requests_
.end();
231 observer
->OnUpdate("addGetUserMedia", *it
);
235 void WebRTCInternals::EnableAecDump(content::WebContents
* web_contents
) {
236 #if defined(ENABLE_WEBRTC)
237 #if defined(OS_ANDROID)
238 EnableAecDumpOnAllRenderProcessHosts();
240 select_file_dialog_
= ui::SelectFileDialog::Create(this, NULL
);
241 select_file_dialog_
->SelectFile(
242 ui::SelectFileDialog::SELECT_SAVEAS_FILE
,
247 FILE_PATH_LITERAL(""),
248 web_contents
->GetTopLevelNativeWindow(),
254 void WebRTCInternals::DisableAecDump() {
255 #if defined(ENABLE_WEBRTC)
256 aec_dump_enabled_
= false;
258 // Tear down the dialog since the user has unchecked the AEC dump box.
259 select_file_dialog_
= NULL
;
261 for (RenderProcessHost::iterator
i(
262 content::RenderProcessHost::AllHostsIterator());
263 !i
.IsAtEnd(); i
.Advance()) {
264 i
.GetCurrentValue()->DisableAecDump();
269 void WebRTCInternals::ResetForTesting() {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
272 peer_connection_data_
.Clear();
273 CreateOrReleasePowerSaveBlocker();
274 get_user_media_requests_
.Clear();
275 aec_dump_enabled_
= false;
278 void WebRTCInternals::SendUpdate(const string
& command
, base::Value
* value
) {
279 DCHECK(observers_
.might_have_observers());
281 FOR_EACH_OBSERVER(WebRTCInternalsUIObserver
,
283 OnUpdate(command
, value
));
286 void WebRTCInternals::Observe(int type
,
287 const NotificationSource
& source
,
288 const NotificationDetails
& details
) {
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
290 DCHECK_EQ(type
, NOTIFICATION_RENDERER_PROCESS_TERMINATED
);
291 OnRendererExit(Source
<RenderProcessHost
>(source
)->GetID());
294 void WebRTCInternals::FileSelected(const base::FilePath
& path
,
295 int /* unused_index */,
296 void* /*unused_params */) {
297 #if defined(ENABLE_WEBRTC)
298 aec_dump_file_path_
= path
;
299 EnableAecDumpOnAllRenderProcessHosts();
303 void WebRTCInternals::FileSelectionCanceled(void* params
) {
304 #if defined(ENABLE_WEBRTC)
305 SendUpdate("aecRecordingFileSelectionCancelled", NULL
);
309 void WebRTCInternals::OnRendererExit(int render_process_id
) {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
312 // Iterates from the end of the list to remove the PeerConnections created
313 // by the exitting renderer.
314 for (int i
= peer_connection_data_
.GetSize() - 1; i
>= 0; --i
) {
315 base::DictionaryValue
* record
= NULL
;
316 peer_connection_data_
.GetDictionary(i
, &record
);
319 record
->GetInteger("rid", &this_rid
);
321 if (this_rid
== render_process_id
) {
322 if (observers_
.might_have_observers()) {
323 int lid
= 0, pid
= 0;
324 record
->GetInteger("lid", &lid
);
325 record
->GetInteger("pid", &pid
);
327 base::DictionaryValue update
;
328 update
.SetInteger("lid", lid
);
329 update
.SetInteger("pid", pid
);
330 SendUpdate("removePeerConnection", &update
);
332 peer_connection_data_
.Remove(i
, NULL
);
335 CreateOrReleasePowerSaveBlocker();
337 bool found_any
= false;
338 // Iterates from the end of the list to remove the getUserMedia requests
339 // created by the exiting renderer.
340 for (int i
= get_user_media_requests_
.GetSize() - 1; i
>= 0; --i
) {
341 base::DictionaryValue
* record
= NULL
;
342 get_user_media_requests_
.GetDictionary(i
, &record
);
345 record
->GetInteger("rid", &this_rid
);
347 if (this_rid
== render_process_id
) {
348 get_user_media_requests_
.Remove(i
, NULL
);
353 if (found_any
&& observers_
.might_have_observers()) {
354 base::DictionaryValue update
;
355 update
.SetInteger("rid", render_process_id
);
356 SendUpdate("removeGetUserMediaForRenderer", &update
);
360 #if defined(ENABLE_WEBRTC)
361 void WebRTCInternals::EnableAecDumpOnAllRenderProcessHosts() {
362 aec_dump_enabled_
= true;
363 for (RenderProcessHost::iterator
i(
364 content::RenderProcessHost::AllHostsIterator());
365 !i
.IsAtEnd(); i
.Advance()) {
366 i
.GetCurrentValue()->EnableAecDump(aec_dump_file_path_
);
371 void WebRTCInternals::CreateOrReleasePowerSaveBlocker() {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
374 if (peer_connection_data_
.empty() && power_save_blocker_
) {
375 DVLOG(1) << ("Releasing the block on application suspension since no "
376 "PeerConnections are active anymore.");
377 power_save_blocker_
.reset();
378 } else if (!peer_connection_data_
.empty() && !power_save_blocker_
) {
379 DVLOG(1) << ("Preventing the application from being suspended while one or "
380 "more PeerConnections are active.");
381 power_save_blocker_
= content::PowerSaveBlocker::Create(
382 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
383 "WebRTC has active PeerConnections.").Pass();
387 } // namespace content