12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/browser/media/webrtc_internals.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 7010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)#include "base/path_service.h" 8116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "base/strings/string_number_conversions.h" 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/browser/media/webrtc_internals_ui_observer.h" 10010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)#include "content/browser/web_contents/web_contents_view.h" 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/browser_thread.h" 12010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)#include "content/public/browser/content_browser_client.h" 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/notification_service.h" 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/notification_types.h" 156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)#include "content/public/browser/power_save_blocker.h" 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/render_process_host.h" 175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "content/public/browser/web_contents.h" 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)using base::ProcessId; 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)using std::string; 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace content { 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace { 251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccistatic base::LazyInstance<WebRTCInternals>::Leaky g_webrtc_internals = 271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci LAZY_INSTANCE_INITIALIZER; 281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Makes sure that |dict| has a ListValue under path "log". 307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)static base::ListValue* EnsureLogList(base::DictionaryValue* dict) { 317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::ListValue* log = NULL; 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!dict->GetList("log", &log)) { 337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) log = new base::ListValue(); 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (log) 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->Set("log", log); 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return log; 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} // namespace 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)WebRTCInternals::WebRTCInternals() 435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : aec_dump_enabled_(false) { 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) NotificationService::AllBrowserContextsAndSources()); 465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the 475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// build if WebRTC is disabled? 485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(ENABLE_WEBRTC) 49010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) aec_dump_file_path_ = 50010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) GetContentClient()->browser()->GetDefaultDownloadDirectory(); 51010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) if (aec_dump_file_path_.empty()) { 52010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // In this case the default path (|aec_dump_file_path_|) will be empty and 53010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // the platform default path will be used in the file dialog (with no 54010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // default file name). See SelectFileDialog::SelectFile. On Android where 55010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // there's no dialog we'll fail to open the file. 56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) VLOG(1) << "Could not get the download directory."; 57010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) } else { 585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_file_path_ = 59010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) aec_dump_file_path_.Append(FILE_PATH_LITERAL("audio.aecdump")); 60010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) } 615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif // defined(ENABLE_WEBRTC) 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)WebRTCInternals::~WebRTCInternals() { 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)WebRTCInternals* WebRTCInternals::GetInstance() { 681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return g_webrtc_internals.Pointer(); 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebRTCInternals::OnAddPeerConnection(int render_process_id, 72c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) ProcessId pid, 73c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) int lid, 74c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const string& url, 755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) const string& rtc_configuration, 76c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const string& constraints) { 772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 797d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue* dict = new base::DictionaryValue(); 802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!dict) 812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->SetInteger("rid", render_process_id); 842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->SetInteger("pid", static_cast<int>(pid)); 852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->SetInteger("lid", lid); 865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) dict->SetString("rtcConfiguration", rtc_configuration); 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->SetString("constraints", constraints); 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->SetString("url", url); 892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.Append(dict); 906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) CreateOrReleasePowerSaveBlocker(); 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 92424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (observers_.might_have_observers()) 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SendUpdate("addPeerConnection", dict); 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebRTCInternals::OnRemovePeerConnection(ProcessId pid, int lid) { 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) { 997d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue* dict = NULL; 1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.GetDictionary(i, &dict); 1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) int this_pid = 0; 1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) int this_lid = 0; 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->GetInteger("pid", &this_pid); 1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict->GetInteger("lid", &this_lid); 1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (this_pid != static_cast<int>(pid) || this_lid != lid) 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) continue; 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.Remove(i, NULL); 1116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) CreateOrReleasePowerSaveBlocker(); 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 113424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (observers_.might_have_observers()) { 1147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue id; 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) id.SetInteger("pid", static_cast<int>(pid)); 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) id.SetInteger("lid", lid); 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SendUpdate("removePeerConnection", &id); 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break; 1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebRTCInternals::OnUpdatePeerConnection( 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ProcessId pid, int lid, const string& type, const string& value) { 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) { 1287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue* record = NULL; 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.GetDictionary(i, &record); 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) int this_pid = 0, this_lid = 0; 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) record->GetInteger("pid", &this_pid); 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) record->GetInteger("lid", &this_lid); 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (this_pid != static_cast<int>(pid) || this_lid != lid) 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) continue; 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Append the update to the end of the log. 1397d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::ListValue* log = EnsureLogList(record); 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!log) 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue* log_entry = new base::DictionaryValue(); 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!log_entry) 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) double epoch_time = base::Time::Now().ToJsTime(); 1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) string time = base::DoubleToString(epoch_time); 149116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch log_entry->SetString("time", time); 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) log_entry->SetString("type", type); 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) log_entry->SetString("value", value); 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) log->Append(log_entry); 1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 154424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (observers_.might_have_observers()) { 1557d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue update; 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) update.SetInteger("pid", static_cast<int>(pid)); 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) update.SetInteger("lid", lid); 158116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch update.MergeDictionary(log_entry); 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SendUpdate("updatePeerConnection", &update); 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid, 167c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const base::ListValue& value) { 168424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (!observers_.might_have_observers()) 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1717d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue dict; 1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict.SetInteger("pid", static_cast<int>(pid)); 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict.SetInteger("lid", lid); 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::ListValue* list = value.DeepCopy(); 1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!list) 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) dict.Set("reports", list); 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SendUpdate("addStats", &dict); 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::OnGetUserMedia(int rid, 1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::ProcessId pid, 1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const std::string& origin, 1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) bool audio, 1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) bool video, 1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const std::string& audio_constraints, 1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const std::string& video_constraints) { 1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = new base::DictionaryValue(); 1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict->SetInteger("rid", rid); 1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict->SetInteger("pid", static_cast<int>(pid)); 1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict->SetString("origin", origin); 1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (audio) 1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict->SetString("audio", audio_constraints); 1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (video) 2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict->SetString("video", video_constraints); 2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) get_user_media_requests_.Append(dict); 2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (observers_.might_have_observers()) 2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) SendUpdate("addGetUserMedia", dict); 2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) { 2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) observers_.AddObserver(observer); 2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) { 2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 2152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) observers_.RemoveObserver(observer); 2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Disables the AEC recording if it is enabled and the last webrtc-internals 2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // page is going away. 2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (aec_dump_enabled_ && !observers_.might_have_observers()) 2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DisableAecDump(); 2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver* observer) { 2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (peer_connection_data_.GetSize() > 0) 2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) observer->OnUpdate("updateAllPeerConnections", &peer_connection_data_); 2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::ListValue::iterator it = get_user_media_requests_.begin(); 2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) it != get_user_media_requests_.end(); 2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ++it) { 2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) observer->OnUpdate("addGetUserMedia", *it); 232c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 233c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 234c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::EnableAecDump(content::WebContents* web_contents) { 2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(ENABLE_WEBRTC) 2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(OS_ANDROID) 2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EnableAecDumpOnAllRenderProcessHosts(); 2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#else 2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL); 2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) select_file_dialog_->SelectFile( 2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ui::SelectFileDialog::SELECT_SAVEAS_FILE, 2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::string16(), 2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_file_path_, 2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) NULL, 2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 0, 2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) FILE_PATH_LITERAL(""), 248010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) web_contents->GetTopLevelNativeWindow(), 2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) NULL); 2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif 2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif 2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::DisableAecDump() { 2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(ENABLE_WEBRTC) 2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_enabled_ = false; 25746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) 25846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) // Tear down the dialog since the user has unchecked the AEC dump box. 25946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) select_file_dialog_ = NULL; 26046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) 2615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (RenderProcessHost::iterator i( 2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) content::RenderProcessHost::AllHostsIterator()); 2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) !i.IsAtEnd(); i.Advance()) { 2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) i.GetCurrentValue()->DisableAecDump(); 265c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 2665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif 2675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 2685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::ResetForTesting() { 2705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 2715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) observers_.Clear(); 2725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) peer_connection_data_.Clear(); 2736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) CreateOrReleasePowerSaveBlocker(); 2745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) get_user_media_requests_.Clear(); 2755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_enabled_ = false; 276c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 277c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 2787d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)void WebRTCInternals::SendUpdate(const string& command, base::Value* value) { 279424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) DCHECK(observers_.might_have_observers()); 2802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) FOR_EACH_OBSERVER(WebRTCInternalsUIObserver, 2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) observers_, 2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) OnUpdate(command, value)); 2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void WebRTCInternals::Observe(int type, 2872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const NotificationSource& source, 2882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const NotificationDetails& details) { 2892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 2902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED); 2912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) OnRendererExit(Source<RenderProcessHost>(source)->GetID()); 2922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 2932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::FileSelected(const base::FilePath& path, 2955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int /* unused_index */, 2965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) void* /*unused_params */) { 2975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(ENABLE_WEBRTC) 2985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_file_path_ = path; 2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EnableAecDumpOnAllRenderProcessHosts(); 3005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif 3015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 3025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 303effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid WebRTCInternals::FileSelectionCanceled(void* params) { 304effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#if defined(ENABLE_WEBRTC) 305effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch SendUpdate("aecRecordingFileSelectionCancelled", NULL); 306effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#endif 307effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch} 308effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 3092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void WebRTCInternals::OnRendererExit(int render_process_id) { 3106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 3116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 3122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Iterates from the end of the list to remove the PeerConnections created 3132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // by the exitting renderer. 3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for (int i = peer_connection_data_.GetSize() - 1; i >= 0; --i) { 3157d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue* record = NULL; 3162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.GetDictionary(i, &record); 3172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) int this_rid = 0; 3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) record->GetInteger("rid", &this_rid); 3202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (this_rid == render_process_id) { 322424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (observers_.might_have_observers()) { 3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) int lid = 0, pid = 0; 3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) record->GetInteger("lid", &lid); 3252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) record->GetInteger("pid", &pid); 3262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) base::DictionaryValue update; 3282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) update.SetInteger("lid", lid); 3292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) update.SetInteger("pid", pid); 3302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) SendUpdate("removePeerConnection", &update); 3312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 3322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) peer_connection_data_.Remove(i, NULL); 3332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 3342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 3356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) CreateOrReleasePowerSaveBlocker(); 3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) bool found_any = false; 3385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Iterates from the end of the list to remove the getUserMedia requests 3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // created by the exiting renderer. 3405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (int i = get_user_media_requests_.GetSize() - 1; i >= 0; --i) { 3415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* record = NULL; 3425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) get_user_media_requests_.GetDictionary(i, &record); 3435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int this_rid = 0; 3455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) record->GetInteger("rid", &this_rid); 3465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (this_rid == render_process_id) { 3485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) get_user_media_requests_.Remove(i, NULL); 3495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) found_any = true; 3505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (found_any && observers_.might_have_observers()) { 3545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue update; 3555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) update.SetInteger("rid", render_process_id); 3565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) SendUpdate("removeGetUserMediaForRenderer", &update); 3575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 3592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(ENABLE_WEBRTC) 3615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void WebRTCInternals::EnableAecDumpOnAllRenderProcessHosts() { 3625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aec_dump_enabled_ = true; 3635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (RenderProcessHost::iterator i( 3645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) content::RenderProcessHost::AllHostsIterator()); 3655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) !i.IsAtEnd(); i.Advance()) { 3665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) i.GetCurrentValue()->EnableAecDump(aec_dump_file_path_); 3675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 368c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 3695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif 370c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 3716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)void WebRTCInternals::CreateOrReleasePowerSaveBlocker() { 3726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 3736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 3746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) if (peer_connection_data_.empty() && power_save_blocker_) { 3756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DVLOG(1) << ("Releasing the block on application suspension since no " 3766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) "PeerConnections are active anymore."); 3776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) power_save_blocker_.reset(); 3786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) } else if (!peer_connection_data_.empty() && !power_save_blocker_) { 3796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DVLOG(1) << ("Preventing the application from being suspended while one or " 3806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) "more PeerConnections are active."); 3816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) power_save_blocker_ = content::PowerSaveBlocker::Create( 3826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, 3836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) "WebRTC has active PeerConnections.").Pass(); 3846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) } 3856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)} 3866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 3872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} // namespace content 388