// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/tracing/tracing_ui.h" #include #include #include #include #include #include #include "base/base64.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/format_macros.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" #include "base/values.h" #include "content/browser/tracing/grit/tracing_resources.h" #include "content/browser/tracing/tracing_controller_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/tracing_controller.h" #include "content/public/browser/tracing_delegate.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/common/content_client.h" #include "content/public/common/url_constants.h" namespace content { namespace { void OnGotCategories(WebUIDataSource::GotDataCallback callback, const std::set& categorySet) { base::ListValue category_list; for (auto it = categorySet.begin(); it != categorySet.end(); it++) { category_list.AppendString(*it); } scoped_refptr res(new base::RefCountedString()); base::JSONWriter::Write(category_list, &res->data()); std::move(callback).Run(res); } void OnRecordingEnabledAck(WebUIDataSource::GotDataCallback callback); bool BeginRecording(const std::string& data64, WebUIDataSource::GotDataCallback callback) { base::trace_event::TraceConfig trace_config("", ""); if (!TracingUI::GetTracingOptions(data64, &trace_config)) return false; return TracingController::GetInstance()->StartTracing( trace_config, base::BindOnce(&OnRecordingEnabledAck, std::move(callback))); } void OnRecordingEnabledAck(WebUIDataSource::GotDataCallback callback) { std::move(callback).Run(base::MakeRefCounted()); } void OnTraceBufferUsageResult(WebUIDataSource::GotDataCallback callback, float percent_full, size_t approximate_event_count) { std::string str = base::NumberToString(percent_full); std::move(callback).Run(base::RefCountedString::TakeString(&str)); } void TracingCallbackWrapperBase64(WebUIDataSource::GotDataCallback callback, std::unique_ptr data) { base::RefCountedString* data_base64 = new base::RefCountedString(); base::Base64Encode(*data, &data_base64->data()); std::move(callback).Run(data_base64); } bool OnBeginJSONRequest(const std::string& path, WebUIDataSource::GotDataCallback callback) { if (path == "json/categories") { return TracingController::GetInstance()->GetCategories( base::BindOnce(OnGotCategories, std::move(callback))); } const char kBeginRecordingPath[] = "json/begin_recording?"; if (base::StartsWith(path, kBeginRecordingPath, base::CompareCase::SENSITIVE)) { std::string data = path.substr(strlen(kBeginRecordingPath)); return BeginRecording(data, std::move(callback)); } if (path == "json/get_buffer_percent_full") { return TracingController::GetInstance()->GetTraceBufferUsage( base::BindOnce(OnTraceBufferUsageResult, std::move(callback))); } if (path == "json/end_recording_compressed") { if (!TracingController::GetInstance()->IsTracing()) return false; scoped_refptr data_endpoint = TracingControllerImpl::CreateCompressedStringEndpoint( TracingControllerImpl::CreateCallbackEndpoint(base::BindOnce( TracingCallbackWrapperBase64, std::move(callback))), false /* compress_with_background_priority */); return TracingController::GetInstance()->StopTracing(data_endpoint); } LOG(ERROR) << "Unhandled request to " << path; return false; } bool OnShouldHandleRequest(const std::string& path) { return base::StartsWith(path, "json/", base::CompareCase::SENSITIVE); } void OnTracingRequest(const std::string& path, WebUIDataSource::GotDataCallback callback) { DCHECK(OnShouldHandleRequest(path)); // OnBeginJSONRequest() only runs |callback| if it returns true. But it needs // to take ownership of |callback| even though it won't call |callback| // sometimes, as it binds |callback| into other callbacks before it makes that // decision. So we must give it one copy and keep one ourselves. auto repeating_callback = base::AdaptCallbackForRepeating(std::move(callback)); if (!OnBeginJSONRequest(path, repeating_callback)) { std::string error("##ERROR##"); std::move(repeating_callback) .Run(base::RefCountedString::TakeString(&error)); } } } // namespace //////////////////////////////////////////////////////////////////////////////// // // TracingUI // //////////////////////////////////////////////////////////////////////////////// TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui), delegate_(GetContentClient()->browser()->GetTracingDelegate()) { // Set up the chrome://tracing/ source. BrowserContext* browser_context = web_ui->GetWebContents()->GetBrowserContext(); WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost); source->UseStringsJs(); source->SetDefaultResource(IDR_TRACING_HTML); source->AddResourcePath("tracing.js", IDR_TRACING_JS); source->SetRequestFilter(base::BindRepeating(OnShouldHandleRequest), base::BindRepeating(OnTracingRequest)); WebUIDataSource::Add(browser_context, source); } TracingUI::~TracingUI() = default; // static bool TracingUI::GetTracingOptions( const std::string& data64, base::trace_event::TraceConfig* trace_config) { std::string data; if (!base::Base64Decode(data64, &data)) { LOG(ERROR) << "Options were not base64 encoded."; return false; } std::unique_ptr optionsRaw = base::JSONReader::ReadDeprecated(data); if (!optionsRaw) { LOG(ERROR) << "Options were not valid JSON"; return false; } base::DictionaryValue* options; if (!optionsRaw->GetAsDictionary(&options)) { LOG(ERROR) << "Options must be dict"; return false; } if (!trace_config) { LOG(ERROR) << "trace_config can't be passed as NULL"; return false; } // New style options dictionary. *trace_config = base::trace_event::TraceConfig(*options); return true; } } // namespace content