blob: 4290a583d63ba7db426025be80ee02df9daa783d [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/media/webrtc_logs_ui.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/media_resources.h"
#include "components/prefs/pref_service.h"
#include "components/upload_list/upload_list.h"
#include "components/version_info/version_info.h"
#include "components/webrtc_logging/browser/text_log_list.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "ui/webui/webui_util.h"
using content::WebContents;
using content::WebUIMessageHandler;
namespace {
void CreateAndAddWebRtcLogsUIHTMLSource(Profile* profile) {
content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
profile, chrome::kChromeUIWebRtcLogsHost);
static constexpr webui::LocalizedString kStrings[] = {
{"webrtcLogsTitle", IDS_WEBRTC_LOGS_TITLE},
{"webrtcTextLogCountFormat",
IDS_WEBRTC_TEXT_LOGS_LOG_COUNT_BANNER_FORMAT},
{"webrtcEventLogCountFormat",
IDS_WEBRTC_EVENT_LOGS_LOG_COUNT_BANNER_FORMAT},
{"webrtcLogHeaderFormat", IDS_WEBRTC_LOGS_LOG_HEADER_FORMAT},
{"webrtcLogLocalFileLabelFormat",
IDS_WEBRTC_LOGS_LOG_LOCAL_FILE_LABEL_FORMAT},
{"noLocalLogFileMessage", IDS_WEBRTC_LOGS_NO_LOCAL_LOG_FILE_MESSAGE},
{"webrtcLogUploadTimeFormat", IDS_WEBRTC_LOGS_LOG_UPLOAD_TIME_FORMAT},
{"webrtcLogFailedUploadTimeFormat",
IDS_WEBRTC_LOGS_LOG_FAILED_UPLOAD_TIME_FORMAT},
{"webrtcLogReportIdFormat", IDS_WEBRTC_LOGS_LOG_REPORT_ID_FORMAT},
{"webrtcEventLogLocalLogIdFormat",
IDS_WEBRTC_LOGS_EVENT_LOG_LOCAL_LOG_ID},
{"bugLinkText", IDS_WEBRTC_LOGS_BUG_LINK_LABEL},
{"webrtcLogNotUploadedMessage", IDS_WEBRTC_LOGS_LOG_NOT_UPLOADED_MESSAGE},
{"webrtcLogPendingMessage", IDS_WEBRTC_LOGS_LOG_PENDING_MESSAGE},
{"webrtcLogActivelyUploadedMessage",
IDS_WEBRTC_LOGS_LOG_ACTIVELY_UPLOADED_MESSAGE},
{"noTextLogsMessage", IDS_WEBRTC_LOGS_NO_TEXT_LOGS_MESSAGE},
{"noEventLogsMessage", IDS_WEBRTC_LOGS_NO_EVENT_LOGS_MESSAGE},
};
source->AddLocalizedStrings(kStrings);
source->UseStringsJs();
source->AddResourcePath("webrtc_logs.css", IDR_MEDIA_WEBRTC_LOGS_CSS);
source->AddResourcePath("webrtc_logs.js", IDR_MEDIA_WEBRTC_LOGS_JS);
source->SetDefaultResource(IDR_MEDIA_WEBRTC_LOGS_HTML);
}
////////////////////////////////////////////////////////////////////////////////
//
// WebRtcLogsDOMHandler
//
////////////////////////////////////////////////////////////////////////////////
// The handler for Javascript messages for the chrome://webrtc-logs/ page.
class WebRtcLogsDOMHandler final : public WebUIMessageHandler {
public:
explicit WebRtcLogsDOMHandler(Profile* profile);
WebRtcLogsDOMHandler(const WebRtcLogsDOMHandler&) = delete;
WebRtcLogsDOMHandler& operator=(const WebRtcLogsDOMHandler&) = delete;
~WebRtcLogsDOMHandler() override;
// WebUIMessageHandler implementation.
void OnJavascriptDisallowed() override;
void RegisterMessages() override;
private:
using WebRtcEventLogManager = webrtc_event_logging::WebRtcEventLogManager;
// Asynchronously fetches the list of upload WebRTC logs. Called from JS.
void HandleRequestWebRtcLogs(const base::Value::List& args);
// Asynchronously load WebRTC text logs.
void LoadWebRtcTextLogs(const std::string& callback_id);
// Callback for when WebRTC text logs have been asynchronously loaded.
void OnWebRtcTextLogsLoaded(const std::string& callback_id);
// Asynchronously load WebRTC event logs.
void LoadWebRtcEventLogs(const std::string& callback_id);
// Callback for when WebRTC event logs have been asynchronously loaded.
void OnWebRtcEventLogsLoaded(
const std::string& callback_id,
const std::vector<UploadList::UploadInfo>& event_logs);
// Update the chrome://webrtc-logs/ page.
void UpdateUI(const std::string& callback_id);
// Update the text/event logs part of the forementioned page.
base::Value::List UpdateUIWithTextLogs() const;
base::Value::List UpdateUIWithEventLogs() const;
// Convert a history entry about a captured WebRTC event log into a
// Value of the type expected by updateWebRtcLogsList().
base::Value EventLogUploadInfoToValue(
const UploadList::UploadInfo& info) const;
// Helpers for `EventLogUploadInfoToValue()`.
base::Value FromPendingLog(const UploadList::UploadInfo& info) const;
base::Value FromActivelyUploadedLog(const UploadList::UploadInfo& info) const;
base::Value FromNotUploadedLog(const UploadList::UploadInfo& info) const;
base::Value FromUploadUnsuccessfulLog(
const UploadList::UploadInfo& info) const;
base::Value FromUploadSuccessfulLog(const UploadList::UploadInfo& info) const;
bool SanityCheckOnUploadInfo(const UploadList::UploadInfo& info) const;
// The directories where the (text/event) logs are stored.
const base::FilePath text_log_dir_;
const base::FilePath event_log_dir_;
// Identifies to WebRtcEventLogManager the profile with which |this| is
// associated. Technically, we should be able to just keep Profile*,
// but avoiding it makes less lifetime assumptions.
// Note that the profile->GetOriginalProfile() is used, to make sure that
// for incognito profiles, the parent profile's event logs are shown,
// as is the behavior for text logs.
const WebRtcEventLogManager::BrowserContextId original_browser_context_id_;
// Loads, parses and stores the list of uploaded text WebRTC logs.
scoped_refptr<UploadList> text_log_upload_list_;
// List of WebRTC logs captured and possibly uploaded to Crash.
std::vector<UploadList::UploadInfo> event_logs_;
// Factory for creating weak references to instances of this class.
base::WeakPtrFactory<WebRtcLogsDOMHandler> weak_ptr_factory_{this};
};
WebRtcLogsDOMHandler::WebRtcLogsDOMHandler(Profile* profile)
: text_log_dir_(
webrtc_logging::TextLogList::
GetWebRtcLogDirectoryForBrowserContextPath(profile->GetPath())),
event_log_dir_(
WebRtcEventLogManager::GetRemoteBoundWebRtcEventLogsDir(profile)),
original_browser_context_id_(webrtc_event_logging::GetBrowserContextId(
profile->GetOriginalProfile())),
text_log_upload_list_(
webrtc_logging::TextLogList::CreateWebRtcLogList(profile)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
WebRtcLogsDOMHandler::~WebRtcLogsDOMHandler() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
text_log_upload_list_->CancelLoadCallback();
}
void WebRtcLogsDOMHandler::RegisterMessages() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
web_ui()->RegisterMessageCallback(
"requestWebRtcLogsList",
base::BindRepeating(&WebRtcLogsDOMHandler::HandleRequestWebRtcLogs,
base::Unretained(this)));
}
void WebRtcLogsDOMHandler::HandleRequestWebRtcLogs(
const base::Value::List& args) {
std::string callback_id = args[0].GetString();
AllowJavascript();
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
LoadWebRtcTextLogs(callback_id);
}
void WebRtcLogsDOMHandler::OnJavascriptDisallowed() {
weak_ptr_factory_.InvalidateWeakPtrs();
}
void WebRtcLogsDOMHandler::LoadWebRtcTextLogs(const std::string& callback_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
text_log_upload_list_->Load(
base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded,
weak_ptr_factory_.GetWeakPtr(), callback_id));
}
void WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded(
const std::string& callback_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
LoadWebRtcEventLogs(callback_id); // Text logs loaded; on to the event logs.
}
void WebRtcLogsDOMHandler::LoadWebRtcEventLogs(const std::string& callback_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
if (manager) {
manager->GetHistory(
original_browser_context_id_,
base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded,
weak_ptr_factory_.GetWeakPtr(), callback_id));
} else {
OnWebRtcEventLogsLoaded(callback_id, std::vector<UploadList::UploadInfo>());
}
}
void WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded(
const std::string& callback_id,
const std::vector<UploadList::UploadInfo>& event_logs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
event_logs_ = event_logs;
// All log histories loaded asynchronously; time to display.
UpdateUI(callback_id);
}
void WebRtcLogsDOMHandler::UpdateUI(const std::string& callback_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Value::Dict result;
result.Set("textLogs", UpdateUIWithTextLogs());
result.Set("eventLogs", UpdateUIWithEventLogs());
result.Set("version", version_info::GetVersionNumber());
ResolveJavascriptCallback(base::Value(callback_id), result);
}
base::Value::List WebRtcLogsDOMHandler::UpdateUIWithTextLogs() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Value::List result;
const std::vector<const UploadList::UploadInfo*> uploads =
text_log_upload_list_->GetUploads(50);
for (const auto* upload : uploads) {
base::Value::Dict upload_value;
upload_value.Set("id", upload->upload_id);
std::u16string value_w;
if (!upload->upload_time.is_null()) {
value_w = base::TimeFormatFriendlyDateAndTime(upload->upload_time);
}
upload_value.Set("upload_time", value_w);
std::string value;
if (!upload->local_id.empty()) {
value = text_log_dir_.AppendASCII(upload->local_id)
.AddExtension(FILE_PATH_LITERAL(".gz"))
.AsUTF8Unsafe();
}
upload_value.Set("local_file", value);
// In october 2015, capture time was added to the log list, previously the
// local ID was used as capture time. The local ID has however changed so
// that it might not be a time. We fall back on the local ID if it traslates
// to a time within reasonable bounds, otherwise we fall back on the upload
// time.
// TODO(grunell): Use |capture_time| only.
if (!upload->capture_time.is_null()) {
value_w = base::TimeFormatFriendlyDateAndTime(upload->capture_time);
} else {
// Fall back on local ID as time. We need to check that it's within
// resonable bounds, since the ID may not represent time. Check between
// 2012 when the feature was introduced and now.
double seconds_since_epoch;
if (base::StringToDouble(upload->local_id, &seconds_since_epoch)) {
base::Time capture_time =
base::Time::FromSecondsSinceUnixEpoch(seconds_since_epoch);
static constexpr base::Time::Exploded kLowerLimit = {
.year = 2012, .month = 1, .day_of_month = 1};
base::Time out_time;
bool conversion_success =
base::Time::FromUTCExploded(kLowerLimit, &out_time);
DCHECK(conversion_success);
if (capture_time > out_time && capture_time < base::Time::Now()) {
value_w = base::TimeFormatFriendlyDateAndTime(capture_time);
}
}
}
// If we haven't set |value_w| above, we fall back on the upload time, which
// was already in the variable. In case it's empty set the string to
// inform that the time is unknown.
if (value_w.empty()) {
value_w = std::u16string(u"(unknown time)");
}
upload_value.Set("capture_time", value_w);
result.Append(std::move(upload_value));
}
return result;
}
base::Value::List WebRtcLogsDOMHandler::UpdateUIWithEventLogs() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Value::List result;
for (const auto& log : event_logs_) {
result.Append(EventLogUploadInfoToValue(log));
}
return result;
}
base::Value WebRtcLogsDOMHandler::EventLogUploadInfoToValue(
const UploadList::UploadInfo& info) const {
switch (info.state) {
case UploadList::UploadInfo::State::Pending:
// TODO(crbug.com/40545136): Display actively-written logs differently
// than fully captured pending logs.
return info.upload_time.is_null() ? FromPendingLog(info)
: FromActivelyUploadedLog(info);
case UploadList::UploadInfo::State::NotUploaded:
return info.upload_time.is_null() ? FromNotUploadedLog(info)
: FromUploadUnsuccessfulLog(info);
case UploadList::UploadInfo::State::Uploaded:
return FromUploadSuccessfulLog(info);
case UploadList::UploadInfo::State::Pending_UserRequested:
NOTREACHED();
}
LOG(ERROR) << "Unrecognized state (" << static_cast<int>(info.state) << ").";
return base::Value();
}
base::Value WebRtcLogsDOMHandler::FromPendingLog(
const UploadList::UploadInfo& info) const {
DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
DCHECK(info.upload_time.is_null());
if (!SanityCheckOnUploadInfo(info)) {
return base::Value();
}
base::Value::Dict log;
log.Set("state", "pending");
log.Set("capture_time",
base::TimeFormatFriendlyDateAndTime(info.capture_time));
log.Set("local_file",
event_log_dir_.AppendASCII(info.local_id).AsUTF8Unsafe());
return base::Value(std::move(log));
}
base::Value WebRtcLogsDOMHandler::FromActivelyUploadedLog(
const UploadList::UploadInfo& info) const {
DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
DCHECK(!info.upload_time.is_null());
if (!SanityCheckOnUploadInfo(info)) {
return base::Value();
}
base::Value::Dict log;
log.Set("state", "actively_uploaded");
log.Set("capture_time",
base::TimeFormatFriendlyDateAndTime(info.capture_time));
log.Set("local_file",
event_log_dir_.AppendASCII(info.local_id).AsUTF8Unsafe());
return base::Value(std::move(log));
}
base::Value WebRtcLogsDOMHandler::FromNotUploadedLog(
const UploadList::UploadInfo& info) const {
DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
DCHECK(info.upload_time.is_null());
if (!SanityCheckOnUploadInfo(info)) {
return base::Value();
}
base::Value::Dict log;
log.Set("state", "not_uploaded");
log.Set("capture_time",
base::TimeFormatFriendlyDateAndTime(info.capture_time));
log.Set("local_id", info.local_id);
return base::Value(std::move(log));
}
base::Value WebRtcLogsDOMHandler::FromUploadUnsuccessfulLog(
const UploadList::UploadInfo& info) const {
DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
DCHECK(!info.upload_time.is_null());
if (!SanityCheckOnUploadInfo(info)) {
return base::Value();
}
if (!info.upload_id.empty()) {
LOG(ERROR) << "Unexpected upload ID.";
return base::Value();
}
base::Value::Dict log;
log.Set("state", "upload_unsuccessful");
log.Set("capture_time",
base::TimeFormatFriendlyDateAndTime(info.capture_time));
log.Set("local_id", info.local_id);
log.Set("upload_time", base::TimeFormatFriendlyDateAndTime(info.upload_time));
return base::Value(std::move(log));
}
base::Value WebRtcLogsDOMHandler::FromUploadSuccessfulLog(
const UploadList::UploadInfo& info) const {
DCHECK_EQ(info.state, UploadList::UploadInfo::State::Uploaded);
DCHECK(!info.upload_time.is_null());
if (!SanityCheckOnUploadInfo(info)) {
return base::Value();
}
if (info.upload_id.empty()) {
LOG(ERROR) << "Unknown upload ID.";
return base::Value();
}
base::Value::Dict log;
log.Set("state", "upload_successful");
log.Set("capture_time",
base::TimeFormatFriendlyDateAndTime(info.capture_time));
log.Set("local_id", info.local_id);
log.Set("upload_id", info.upload_id);
log.Set("upload_time", base::TimeFormatFriendlyDateAndTime(info.upload_time));
return base::Value(std::move(log));
}
bool WebRtcLogsDOMHandler::SanityCheckOnUploadInfo(
const UploadList::UploadInfo& info) const {
if (info.capture_time.is_null()) {
LOG(ERROR) << "Unknown capture time.";
return false;
}
if (info.local_id.empty()) {
LOG(ERROR) << "Unknown local ID.";
return false;
}
return true;
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
//
// WebRtcLogsUI
//
///////////////////////////////////////////////////////////////////////////////
WebRtcLogsUI::WebRtcLogsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
Profile* profile = Profile::FromWebUI(web_ui);
web_ui->AddMessageHandler(std::make_unique<WebRtcLogsDOMHandler>(profile));
// Set up the chrome://webrtc-logs/ source.
CreateAndAddWebRtcLogsUIHTMLSource(profile);
}