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. 4 5 #include "content/browser/media/webrtc_internals.h" 6 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" 18 19 using base::ProcessId; 20 using std::string; 21 22 namespace content { 23 24 namespace { 25 26 static base::LazyInstance<WebRTCInternals>::Leaky g_webrtc_internals = 27 LAZY_INSTANCE_INITIALIZER; 28 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(); 34 if (log) 35 dict->Set("log", log); 36 } 37 return log; 38 } 39 40 } // namespace 41 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) 49 aec_dump_file_path_ = 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."; 57 } else { 58 aec_dump_file_path_ = 59 aec_dump_file_path_.Append(FILE_PATH_LITERAL("audio.aecdump")); 60 } 61 #endif // defined(ENABLE_WEBRTC) 62 } 63 64 WebRTCInternals::~WebRTCInternals() { 65 } 66 67 WebRTCInternals* WebRTCInternals::GetInstance() { 68 return g_webrtc_internals.Pointer(); 69 } 70 71 void WebRTCInternals::OnAddPeerConnection(int render_process_id, 72 ProcessId pid, 73 int lid, 74 const string& url, 75 const string& rtc_configuration, 76 const string& constraints) { 77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 78 79 base::DictionaryValue* dict = new base::DictionaryValue(); 80 if (!dict) 81 return; 82 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(); 91 92 if (observers_.might_have_observers()) 93 SendUpdate("addPeerConnection", dict); 94 } 95 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); 101 102 int this_pid = 0; 103 int this_lid = 0; 104 dict->GetInteger("pid", &this_pid); 105 dict->GetInteger("lid", &this_lid); 106 107 if (this_pid != static_cast<int>(pid) || this_lid != lid) 108 continue; 109 110 peer_connection_data_.Remove(i, NULL); 111 CreateOrReleasePowerSaveBlocker(); 112 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); 118 } 119 break; 120 } 121 } 122 123 void WebRTCInternals::OnUpdatePeerConnection( 124 ProcessId pid, int lid, const string& type, const string& value) { 125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 126 127 for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) { 128 base::DictionaryValue* record = NULL; 129 peer_connection_data_.GetDictionary(i, &record); 130 131 int this_pid = 0, this_lid = 0; 132 record->GetInteger("pid", &this_pid); 133 record->GetInteger("lid", &this_lid); 134 135 if (this_pid != static_cast<int>(pid) || this_lid != lid) 136 continue; 137 138 // Append the update to the end of the log. 139 base::ListValue* log = EnsureLogList(record); 140 if (!log) 141 return; 142 143 base::DictionaryValue* log_entry = new base::DictionaryValue(); 144 if (!log_entry) 145 return; 146 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); 153 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); 159 160 SendUpdate("updatePeerConnection", &update); 161 } 162 return; 163 } 164 } 165 166 void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid, 167 const base::ListValue& value) { 168 if (!observers_.might_have_observers()) 169 return; 170 171 base::DictionaryValue dict; 172 dict.SetInteger("pid", static_cast<int>(pid)); 173 dict.SetInteger("lid", lid); 174 175 base::ListValue* list = value.DeepCopy(); 176 if (!list) 177 return; 178 179 dict.Set("reports", list); 180 181 SendUpdate("addStats", &dict); 182 } 183 184 void WebRTCInternals::OnGetUserMedia(int rid, 185 base::ProcessId pid, 186 const std::string& origin, 187 bool audio, 188 bool video, 189 const std::string& audio_constraints, 190 const std::string& video_constraints) { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 192 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); 197 if (audio) 198 dict->SetString("audio", audio_constraints); 199 if (video) 200 dict->SetString("video", video_constraints); 201 202 get_user_media_requests_.Append(dict); 203 204 if (observers_.might_have_observers()) 205 SendUpdate("addGetUserMedia", dict); 206 } 207 208 void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) { 209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 210 observers_.AddObserver(observer); 211 } 212 213 void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 215 observers_.RemoveObserver(observer); 216 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()) 220 DisableAecDump(); 221 } 222 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_); 227 228 for (base::ListValue::iterator it = get_user_media_requests_.begin(); 229 it != get_user_media_requests_.end(); 230 ++it) { 231 observer->OnUpdate("addGetUserMedia", *it); 232 } 233 } 234 235 void WebRTCInternals::EnableAecDump(content::WebContents* web_contents) { 236 #if defined(ENABLE_WEBRTC) 237 #if defined(OS_ANDROID) 238 EnableAecDumpOnAllRenderProcessHosts(); 239 #else 240 select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL); 241 select_file_dialog_->SelectFile( 242 ui::SelectFileDialog::SELECT_SAVEAS_FILE, 243 base::string16(), 244 aec_dump_file_path_, 245 NULL, 246 0, 247 FILE_PATH_LITERAL(""), 248 web_contents->GetTopLevelNativeWindow(), 249 NULL); 250 #endif 251 #endif 252 } 253 254 void WebRTCInternals::DisableAecDump() { 255 #if defined(ENABLE_WEBRTC) 256 aec_dump_enabled_ = false; 257 258 // Tear down the dialog since the user has unchecked the AEC dump box. 259 select_file_dialog_ = NULL; 260 261 for (RenderProcessHost::iterator i( 262 content::RenderProcessHost::AllHostsIterator()); 263 !i.IsAtEnd(); i.Advance()) { 264 i.GetCurrentValue()->DisableAecDump(); 265 } 266 #endif 267 } 268 269 void WebRTCInternals::ResetForTesting() { 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 271 observers_.Clear(); 272 peer_connection_data_.Clear(); 273 CreateOrReleasePowerSaveBlocker(); 274 get_user_media_requests_.Clear(); 275 aec_dump_enabled_ = false; 276 } 277 278 void WebRTCInternals::SendUpdate(const string& command, base::Value* value) { 279 DCHECK(observers_.might_have_observers()); 280 281 FOR_EACH_OBSERVER(WebRTCInternalsUIObserver, 282 observers_, 283 OnUpdate(command, value)); 284 } 285 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()); 292 } 293 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(); 300 #endif 301 } 302 303 void WebRTCInternals::FileSelectionCanceled(void* params) { 304 #if defined(ENABLE_WEBRTC) 305 SendUpdate("aecRecordingFileSelectionCancelled", NULL); 306 #endif 307 } 308 309 void WebRTCInternals::OnRendererExit(int render_process_id) { 310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 311 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); 317 318 int this_rid = 0; 319 record->GetInteger("rid", &this_rid); 320 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); 326 327 base::DictionaryValue update; 328 update.SetInteger("lid", lid); 329 update.SetInteger("pid", pid); 330 SendUpdate("removePeerConnection", &update); 331 } 332 peer_connection_data_.Remove(i, NULL); 333 } 334 } 335 CreateOrReleasePowerSaveBlocker(); 336 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); 343 344 int this_rid = 0; 345 record->GetInteger("rid", &this_rid); 346 347 if (this_rid == render_process_id) { 348 get_user_media_requests_.Remove(i, NULL); 349 found_any = true; 350 } 351 } 352 353 if (found_any && observers_.might_have_observers()) { 354 base::DictionaryValue update; 355 update.SetInteger("rid", render_process_id); 356 SendUpdate("removeGetUserMediaForRenderer", &update); 357 } 358 } 359 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_); 367 } 368 } 369 #endif 370 371 void WebRTCInternals::CreateOrReleasePowerSaveBlocker() { 372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 373 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(); 384 } 385 } 386 387 } // namespace content 388