1 // Copyright 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 "chrome/browser/ui/webui/media/webrtc_logs_ui.h"
6
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/macros.h"
15 #include "base/memory/ref_counted_memory.h"
16 #include "base/memory/weak_ptr.h"
17 #include "base/strings/string16.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "build/build_config.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/webui/webui_util.h"
26 #include "chrome/common/url_constants.h"
27 #include "chrome/grit/generated_resources.h"
28 #include "chrome/grit/webrtc_logs_resources.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/upload_list/upload_list.h"
31 #include "components/version_info/version_info.h"
32 #include "components/webrtc_logging/browser/text_log_list.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/browser/web_ui.h"
36 #include "content/public/browser/web_ui_data_source.h"
37 #include "content/public/browser/web_ui_message_handler.h"
38 #include "ui/base/webui/web_ui_util.h"
39
40 #if defined(OS_CHROMEOS)
41 #include "chrome/browser/chromeos/settings/cros_settings.h"
42 #endif
43
44 using content::WebContents;
45 using content::WebUIMessageHandler;
46
47 namespace {
48
CreateWebRtcLogsUIHTMLSource()49 content::WebUIDataSource* CreateWebRtcLogsUIHTMLSource() {
50 content::WebUIDataSource* source =
51 content::WebUIDataSource::Create(chrome::kChromeUIWebRtcLogsHost);
52
53 static constexpr webui::LocalizedString kStrings[] = {
54 {"webrtcLogsTitle", IDS_WEBRTC_LOGS_TITLE},
55 {"webrtcTextLogCountFormat",
56 IDS_WEBRTC_TEXT_LOGS_LOG_COUNT_BANNER_FORMAT},
57 {"webrtcEventLogCountFormat",
58 IDS_WEBRTC_EVENT_LOGS_LOG_COUNT_BANNER_FORMAT},
59 {"webrtcLogHeaderFormat", IDS_WEBRTC_LOGS_LOG_HEADER_FORMAT},
60 {"webrtcLogLocalFileLabelFormat",
61 IDS_WEBRTC_LOGS_LOG_LOCAL_FILE_LABEL_FORMAT},
62 {"noLocalLogFileMessage", IDS_WEBRTC_LOGS_NO_LOCAL_LOG_FILE_MESSAGE},
63 {"webrtcLogUploadTimeFormat", IDS_WEBRTC_LOGS_LOG_UPLOAD_TIME_FORMAT},
64 {"webrtcLogFailedUploadTimeFormat",
65 IDS_WEBRTC_LOGS_LOG_FAILED_UPLOAD_TIME_FORMAT},
66 {"webrtcLogReportIdFormat", IDS_WEBRTC_LOGS_LOG_REPORT_ID_FORMAT},
67 {"webrtcEventLogLocalLogIdFormat",
68 IDS_WEBRTC_LOGS_EVENT_LOG_LOCAL_LOG_ID},
69 {"bugLinkText", IDS_WEBRTC_LOGS_BUG_LINK_LABEL},
70 {"webrtcLogNotUploadedMessage", IDS_WEBRTC_LOGS_LOG_NOT_UPLOADED_MESSAGE},
71 {"webrtcLogPendingMessage", IDS_WEBRTC_LOGS_LOG_PENDING_MESSAGE},
72 {"webrtcLogActivelyUploadedMessage",
73 IDS_WEBRTC_LOGS_LOG_ACTIVELY_UPLOADED_MESSAGE},
74 {"noTextLogsMessage", IDS_WEBRTC_LOGS_NO_TEXT_LOGS_MESSAGE},
75 {"noEventLogsMessage", IDS_WEBRTC_LOGS_NO_EVENT_LOGS_MESSAGE},
76 };
77 AddLocalizedStringsBulk(source, kStrings);
78
79 source->UseStringsJs();
80 source->AddResourcePath("webrtc_logs.js", IDR_WEBRTC_LOGS_JS);
81 source->SetDefaultResource(IDR_WEBRTC_LOGS_HTML);
82 return source;
83 }
84
85 ////////////////////////////////////////////////////////////////////////////////
86 //
87 // WebRtcLogsDOMHandler
88 //
89 ////////////////////////////////////////////////////////////////////////////////
90
91 // The handler for Javascript messages for the chrome://webrtc-logs/ page.
92 class WebRtcLogsDOMHandler final : public WebUIMessageHandler {
93 public:
94 explicit WebRtcLogsDOMHandler(Profile* profile);
95 ~WebRtcLogsDOMHandler() override;
96
97 // WebUIMessageHandler implementation.
98 void RegisterMessages() override;
99
100 private:
101 using WebRtcEventLogManager = webrtc_event_logging::WebRtcEventLogManager;
102
103 // Asynchronously fetches the list of upload WebRTC logs. Called from JS.
104 void HandleRequestWebRtcLogs(const base::ListValue* args);
105
106 // Asynchronously load WebRTC text logs.
107 void LoadWebRtcTextLogs();
108
109 // Callback for when WebRTC text logs have been asynchronously loaded.
110 void OnWebRtcTextLogsLoaded();
111
112 // Asynchronously load WebRTC event logs.
113 void LoadWebRtcEventLogs();
114
115 // Callback for when WebRTC event logs have been asynchronously loaded.
116 void OnWebRtcEventLogsLoaded(
117 const std::vector<UploadList::UploadInfo>& event_logs);
118
119 // Update the chrome://webrtc-logs/ page.
120 void UpdateUI();
121
122 // Update the text/event logs part of the forementioned page.
123 void UpdateUIWithTextLogs(base::ListValue* text_logs_list) const;
124 void UpdateUIWithEventLogs(base::ListValue* event_logs_list) const;
125
126 // Convert a history entry about a captured WebRTC event log into a
127 // DictionaryValue of the type expected by updateWebRtcLogsList().
128 std::unique_ptr<base::DictionaryValue> EventLogUploadInfoToDictionaryValue(
129 const UploadList::UploadInfo& info) const;
130
131 // Helpers for EventLogUploadInfoToDictionaryValue().
132 std::unique_ptr<base::DictionaryValue> FromPendingLog(
133 const UploadList::UploadInfo& info) const;
134 std::unique_ptr<base::DictionaryValue> FromActivelyUploadedLog(
135 const UploadList::UploadInfo& info) const;
136 std::unique_ptr<base::DictionaryValue> FromNotUploadedLog(
137 const UploadList::UploadInfo& info) const;
138 std::unique_ptr<base::DictionaryValue> FromUploadUnsuccessfulLog(
139 const UploadList::UploadInfo& info) const;
140 std::unique_ptr<base::DictionaryValue> FromUploadSuccessfulLog(
141 const UploadList::UploadInfo& info) const;
142
143 bool SanityCheckOnUploadInfo(const UploadList::UploadInfo& info) const;
144
145 // The directories where the (text/event) logs are stored.
146 const base::FilePath text_log_dir_;
147 const base::FilePath event_log_dir_;
148
149 // Identifies to WebRtcEventLogManager the profile with which |this| is
150 // associated. Technically, we should be able to just keep Profile*,
151 // but avoiding it makes less lifetime assumptions.
152 // Note that the profile->GetOriginalProfile() is used, to make sure that
153 // for incognito profiles, the parent profile's event logs are shown,
154 // as is the behavior for text logs.
155 const WebRtcEventLogManager::BrowserContextId original_browser_context_id_;
156
157 // Loads, parses and stores the list of uploaded text WebRTC logs.
158 scoped_refptr<UploadList> text_log_upload_list_;
159
160 // List of WebRTC logs captured and possibly uploaded to Crash.
161 std::vector<UploadList::UploadInfo> event_logs_;
162
163 // Factory for creating weak references to instances of this class.
164 base::WeakPtrFactory<WebRtcLogsDOMHandler> weak_ptr_factory_{this};
165
166 DISALLOW_COPY_AND_ASSIGN(WebRtcLogsDOMHandler);
167 };
168
WebRtcLogsDOMHandler(Profile * profile)169 WebRtcLogsDOMHandler::WebRtcLogsDOMHandler(Profile* profile)
170 : text_log_dir_(
171 webrtc_logging::TextLogList::
172 GetWebRtcLogDirectoryForBrowserContextPath(profile->GetPath())),
173 event_log_dir_(
174 WebRtcEventLogManager::GetRemoteBoundWebRtcEventLogsDir(profile)),
175 original_browser_context_id_(webrtc_event_logging::GetBrowserContextId(
176 profile->GetOriginalProfile())),
177 text_log_upload_list_(
178 webrtc_logging::TextLogList::CreateWebRtcLogList(profile)) {
179 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
180 }
181
~WebRtcLogsDOMHandler()182 WebRtcLogsDOMHandler::~WebRtcLogsDOMHandler() {
183 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
184 text_log_upload_list_->CancelLoadCallback();
185 }
186
RegisterMessages()187 void WebRtcLogsDOMHandler::RegisterMessages() {
188 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
189
190 web_ui()->RegisterMessageCallback(
191 "requestWebRtcLogsList",
192 base::BindRepeating(&WebRtcLogsDOMHandler::HandleRequestWebRtcLogs,
193 weak_ptr_factory_.GetWeakPtr()));
194 }
195
HandleRequestWebRtcLogs(const base::ListValue * args)196 void WebRtcLogsDOMHandler::HandleRequestWebRtcLogs(
197 const base::ListValue* args) {
198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
199 LoadWebRtcTextLogs();
200 }
201
LoadWebRtcTextLogs()202 void WebRtcLogsDOMHandler::LoadWebRtcTextLogs() {
203 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
204 text_log_upload_list_->Load(
205 base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded,
206 weak_ptr_factory_.GetWeakPtr()));
207 }
208
OnWebRtcTextLogsLoaded()209 void WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded() {
210 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
211 LoadWebRtcEventLogs(); // Text logs loaded; on to the event logs.
212 }
213
LoadWebRtcEventLogs()214 void WebRtcLogsDOMHandler::LoadWebRtcEventLogs() {
215 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
216
217 WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
218 if (manager) {
219 manager->GetHistory(
220 original_browser_context_id_,
221 base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded,
222 weak_ptr_factory_.GetWeakPtr()));
223 } else {
224 OnWebRtcEventLogsLoaded(std::vector<UploadList::UploadInfo>());
225 }
226 }
227
OnWebRtcEventLogsLoaded(const std::vector<UploadList::UploadInfo> & event_logs)228 void WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded(
229 const std::vector<UploadList::UploadInfo>& event_logs) {
230 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
231
232 event_logs_ = event_logs;
233
234 UpdateUI(); // All log histories loaded asynchronously; time to display.
235 }
236
UpdateUI()237 void WebRtcLogsDOMHandler::UpdateUI() {
238 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
239
240 base::ListValue text_logs_list;
241 UpdateUIWithTextLogs(&text_logs_list);
242
243 base::ListValue event_logs_list;
244 UpdateUIWithEventLogs(&event_logs_list);
245
246 base::Value version(version_info::GetVersionNumber());
247
248 web_ui()->CallJavascriptFunctionUnsafe("updateWebRtcLogsList", text_logs_list,
249 event_logs_list, version);
250 }
251
UpdateUIWithTextLogs(base::ListValue * upload_list) const252 void WebRtcLogsDOMHandler::UpdateUIWithTextLogs(
253 base::ListValue* upload_list) const {
254 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
255
256 std::vector<UploadList::UploadInfo> uploads;
257 text_log_upload_list_->GetUploads(50, &uploads);
258
259 for (auto i = uploads.begin(); i != uploads.end(); ++i) {
260 std::unique_ptr<base::DictionaryValue> upload(new base::DictionaryValue());
261 upload->SetString("id", i->upload_id);
262
263 base::string16 value_w;
264 if (!i->upload_time.is_null())
265 value_w = base::TimeFormatFriendlyDateAndTime(i->upload_time);
266 upload->SetString("upload_time", value_w);
267
268 base::FilePath::StringType value;
269 if (!i->local_id.empty())
270 value = text_log_dir_.AppendASCII(i->local_id)
271 .AddExtension(FILE_PATH_LITERAL(".gz"))
272 .value();
273 upload->SetString("local_file", value);
274
275 // In october 2015, capture time was added to the log list, previously the
276 // local ID was used as capture time. The local ID has however changed so
277 // that it might not be a time. We fall back on the local ID if it traslates
278 // to a time within reasonable bounds, otherwise we fall back on the upload
279 // time.
280 // TODO(grunell): Use |capture_time| only.
281 if (!i->capture_time.is_null()) {
282 value_w = base::TimeFormatFriendlyDateAndTime(i->capture_time);
283 } else {
284 // Fall back on local ID as time. We need to check that it's within
285 // resonable bounds, since the ID may not represent time. Check between
286 // 2012 when the feature was introduced and now.
287 double seconds_since_epoch;
288 if (base::StringToDouble(i->local_id, &seconds_since_epoch)) {
289 base::Time capture_time = base::Time::FromDoubleT(seconds_since_epoch);
290 const base::Time::Exploded lower_limit = {2012, 1, 0, 1, 0, 0, 0, 0};
291 base::Time out_time;
292 bool conversion_success =
293 base::Time::FromUTCExploded(lower_limit, &out_time);
294 DCHECK(conversion_success);
295 if (capture_time > out_time && capture_time < base::Time::Now()) {
296 value_w = base::TimeFormatFriendlyDateAndTime(capture_time);
297 }
298 }
299 }
300 // If we haven't set |value_w| above, we fall back on the upload time, which
301 // was already in the variable. In case it's empty set the string to
302 // inform that the time is unknown.
303 if (value_w.empty())
304 value_w = base::string16(base::ASCIIToUTF16("(unknown time)"));
305 upload->SetString("capture_time", value_w);
306
307 upload_list->Append(std::move(upload));
308 }
309 }
310
UpdateUIWithEventLogs(base::ListValue * event_logs_list) const311 void WebRtcLogsDOMHandler::UpdateUIWithEventLogs(
312 base::ListValue* event_logs_list) const {
313 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
314 for (auto it = event_logs_.crbegin(); it != event_logs_.crend(); ++it) {
315 event_logs_list->Append(EventLogUploadInfoToDictionaryValue(*it));
316 }
317 }
318
319 std::unique_ptr<base::DictionaryValue>
EventLogUploadInfoToDictionaryValue(const UploadList::UploadInfo & info) const320 WebRtcLogsDOMHandler::EventLogUploadInfoToDictionaryValue(
321 const UploadList::UploadInfo& info) const {
322 switch (info.state) {
323 case UploadList::UploadInfo::State::Pending:
324 // TODO(crbug.com/775415): Display actively-written logs differently
325 // than fully captured pending logs.
326 return info.upload_time.is_null() ? FromPendingLog(info)
327 : FromActivelyUploadedLog(info);
328 case UploadList::UploadInfo::State::NotUploaded:
329 return info.upload_time.is_null() ? FromNotUploadedLog(info)
330 : FromUploadUnsuccessfulLog(info);
331 case UploadList::UploadInfo::State::Uploaded:
332 return FromUploadSuccessfulLog(info);
333 case UploadList::UploadInfo::State::Pending_UserRequested:
334 NOTREACHED();
335 }
336
337 LOG(ERROR) << "Unrecognized state (" << static_cast<int>(info.state) << ").";
338 return nullptr;
339 }
340
FromPendingLog(const UploadList::UploadInfo & info) const341 std::unique_ptr<base::DictionaryValue> WebRtcLogsDOMHandler::FromPendingLog(
342 const UploadList::UploadInfo& info) const {
343 DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
344 DCHECK(info.upload_time.is_null());
345
346 if (!SanityCheckOnUploadInfo(info)) {
347 return nullptr;
348 }
349
350 std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
351 log->SetString("state", "pending");
352 log->SetString("capture_time",
353 base::TimeFormatFriendlyDateAndTime(info.capture_time));
354 log->SetString("local_file",
355 event_log_dir_.AppendASCII(info.local_id).value());
356 return log;
357 }
358
359 std::unique_ptr<base::DictionaryValue>
FromActivelyUploadedLog(const UploadList::UploadInfo & info) const360 WebRtcLogsDOMHandler::FromActivelyUploadedLog(
361 const UploadList::UploadInfo& info) const {
362 DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
363 DCHECK(!info.upload_time.is_null());
364
365 if (!SanityCheckOnUploadInfo(info)) {
366 return nullptr;
367 }
368
369 std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
370 log->SetString("state", "actively_uploaded");
371 log->SetString("capture_time",
372 base::TimeFormatFriendlyDateAndTime(info.capture_time));
373 log->SetString("local_file",
374 event_log_dir_.AppendASCII(info.local_id).value());
375 return log;
376 }
377
FromNotUploadedLog(const UploadList::UploadInfo & info) const378 std::unique_ptr<base::DictionaryValue> WebRtcLogsDOMHandler::FromNotUploadedLog(
379 const UploadList::UploadInfo& info) const {
380 DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
381 DCHECK(info.upload_time.is_null());
382
383 if (!SanityCheckOnUploadInfo(info)) {
384 return nullptr;
385 }
386
387 std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
388 log->SetString("state", "not_uploaded");
389 log->SetString("capture_time",
390 base::TimeFormatFriendlyDateAndTime(info.capture_time));
391 log->SetString("local_id", info.local_id);
392 return log;
393 }
394
395 std::unique_ptr<base::DictionaryValue>
FromUploadUnsuccessfulLog(const UploadList::UploadInfo & info) const396 WebRtcLogsDOMHandler::FromUploadUnsuccessfulLog(
397 const UploadList::UploadInfo& info) const {
398 DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
399 DCHECK(!info.upload_time.is_null());
400
401 if (!SanityCheckOnUploadInfo(info)) {
402 return nullptr;
403 }
404
405 if (!info.upload_id.empty()) {
406 LOG(ERROR) << "Unexpected upload ID.";
407 return nullptr;
408 }
409
410 std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
411 log->SetString("state", "upload_unsuccessful");
412 log->SetString("capture_time",
413 base::TimeFormatFriendlyDateAndTime(info.capture_time));
414 log->SetString("local_id", info.local_id);
415 log->SetString("upload_time",
416 base::TimeFormatFriendlyDateAndTime(info.upload_time));
417 return log;
418 }
419
420 std::unique_ptr<base::DictionaryValue>
FromUploadSuccessfulLog(const UploadList::UploadInfo & info) const421 WebRtcLogsDOMHandler::FromUploadSuccessfulLog(
422 const UploadList::UploadInfo& info) const {
423 DCHECK_EQ(info.state, UploadList::UploadInfo::State::Uploaded);
424 DCHECK(!info.upload_time.is_null());
425
426 if (!SanityCheckOnUploadInfo(info)) {
427 return nullptr;
428 }
429
430 if (info.upload_id.empty()) {
431 LOG(ERROR) << "Unknown upload ID.";
432 return nullptr;
433 }
434
435 std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
436 log->SetString("state", "upload_successful");
437 log->SetString("capture_time",
438 base::TimeFormatFriendlyDateAndTime(info.capture_time));
439 log->SetString("local_id", info.local_id);
440 log->SetString("upload_id", info.upload_id);
441 log->SetString("upload_time",
442 base::TimeFormatFriendlyDateAndTime(info.upload_time));
443 return log;
444 }
445
SanityCheckOnUploadInfo(const UploadList::UploadInfo & info) const446 bool WebRtcLogsDOMHandler::SanityCheckOnUploadInfo(
447 const UploadList::UploadInfo& info) const {
448 if (info.capture_time.is_null()) {
449 LOG(ERROR) << "Unknown capture time.";
450 return false;
451 }
452
453 if (info.local_id.empty()) {
454 LOG(ERROR) << "Unknown local ID.";
455 return false;
456 }
457
458 return true;
459 }
460
461 } // namespace
462
463 ///////////////////////////////////////////////////////////////////////////////
464 //
465 // WebRtcLogsUI
466 //
467 ///////////////////////////////////////////////////////////////////////////////
468
WebRtcLogsUI(content::WebUI * web_ui)469 WebRtcLogsUI::WebRtcLogsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
470 Profile* profile = Profile::FromWebUI(web_ui);
471 web_ui->AddMessageHandler(std::make_unique<WebRtcLogsDOMHandler>(profile));
472
473 // Set up the chrome://webrtc-logs/ source.
474 content::WebUIDataSource::Add(profile, CreateWebRtcLogsUIHTMLSource());
475 }
476