blob: 5c83c0d9720a5293765f32121a62c4e02a90c4f6 [file] [log] [blame]
// Copyright (c) 2011 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 "chrome/browser/ui/webui/tracing_ui.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/select_file_dialog.h"
#include "chrome/browser/ui/webui/chrome_web_ui_data_source.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/url_constants.h"
#include "content/browser/gpu/gpu_data_manager.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_view.h"
#include "content/browser/trace_controller.h"
#include "content/public/browser/browser_thread.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
using content::BrowserThread;
namespace {
ChromeWebUIDataSource* CreateTracingHTMLSource() {
ChromeWebUIDataSource* source =
new ChromeWebUIDataSource(chrome::kChromeUITracingHost);
source->set_json_path("strings.js");
source->add_resource_path("tracing.js", IDR_TRACING_JS);
source->set_default_resource(IDR_TRACING_HTML);
source->AddLocalizedString("tracingTitle", IDS_TRACING_TITLE);
return source;
}
// This class receives javascript messages from the renderer.
// Note that the WebUI infrastructure runs on the UI thread, therefore all of
// this class's methods are expected to run on the UI thread.
class TracingMessageHandler
: public WebUIMessageHandler,
public SelectFileDialog::Listener,
public base::SupportsWeakPtr<TracingMessageHandler>,
public TraceSubscriber,
public GpuDataManager::Observer {
public:
TracingMessageHandler();
virtual ~TracingMessageHandler();
// WebUIMessageHandler implementation.
virtual WebUIMessageHandler* Attach(WebUI* web_ui);
virtual void RegisterMessages();
// SelectFileDialog::Listener implementation
virtual void FileSelected(const FilePath& path, int index, void* params);
virtual void FileSelectionCanceled(void* params);
// TraceSubscriber implementation.
virtual void OnEndTracingComplete();
virtual void OnTraceDataCollected(const std::string& trace_fragment);
virtual void OnTraceBufferPercentFullReply(float percent_full);
// GpuDataManager::Observer implementation.
virtual void OnGpuInfoUpdate() OVERRIDE;
// Messages.
void OnTracingControllerInitialized(const ListValue* list);
void OnBeginTracing(const ListValue* list);
void OnEndTracingAsync(const ListValue* list);
void OnBeginRequestBufferPercentFull(const ListValue* list);
void OnLoadTraceFile(const ListValue* list);
void OnSaveTraceFile(const ListValue* list);
// Callbacks.
void LoadTraceFileComplete(std::string* file_contents);
void SaveTraceFileComplete();
private:
// The file dialog to select a file for loading or saving traces.
scoped_refptr<SelectFileDialog> select_trace_file_dialog_;
// The type of the file dialog as the same one is used for loading or saving
// traces.
SelectFileDialog::Type select_trace_file_dialog_type_;
// The trace data that is to be written to the file on saving.
scoped_ptr<std::string> trace_data_to_save_;
// True while tracing is active.
bool trace_enabled_;
// Cache the Singleton for efficiency.
GpuDataManager* gpu_data_manager_;
DISALLOW_COPY_AND_ASSIGN(TracingMessageHandler);
};
// A proxy passed to the Read and Write tasks used when loading or saving trace
// data.
class TaskProxy : public base::RefCountedThreadSafe<TaskProxy> {
public:
explicit TaskProxy(const base::WeakPtr<TracingMessageHandler>& handler)
: handler_(handler) {}
void LoadTraceFileCompleteProxy(std::string* file_contents) {
if (handler_)
handler_->LoadTraceFileComplete(file_contents);
delete file_contents;
}
void SaveTraceFileCompleteProxy() {
if (handler_)
handler_->SaveTraceFileComplete();
}
private:
friend class base::RefCountedThreadSafe<TaskProxy>;
// The message handler to call callbacks on.
base::WeakPtr<TracingMessageHandler> handler_;
DISALLOW_COPY_AND_ASSIGN(TaskProxy);
};
////////////////////////////////////////////////////////////////////////////////
//
// TracingMessageHandler
//
////////////////////////////////////////////////////////////////////////////////
TracingMessageHandler::TracingMessageHandler()
: select_trace_file_dialog_type_(SelectFileDialog::SELECT_NONE),
trace_enabled_(false) {
gpu_data_manager_ = GpuDataManager::GetInstance();
DCHECK(gpu_data_manager_);
}
TracingMessageHandler::~TracingMessageHandler() {
gpu_data_manager_->RemoveObserver(this);
if (select_trace_file_dialog_)
select_trace_file_dialog_->ListenerDestroyed();
// If we are the current subscriber, this will result in ending tracing.
TraceController::GetInstance()->CancelSubscriber(this);
}
WebUIMessageHandler* TracingMessageHandler::Attach(WebUI* web_ui) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui);
return result;
}
void TracingMessageHandler::RegisterMessages() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
web_ui_->RegisterMessageCallback("tracingControllerInitialized",
base::Bind(&TracingMessageHandler::OnTracingControllerInitialized,
base::Unretained(this)));
web_ui_->RegisterMessageCallback("beginTracing",
base::Bind(&TracingMessageHandler::OnBeginTracing,
base::Unretained(this)));
web_ui_->RegisterMessageCallback("endTracingAsync",
base::Bind(&TracingMessageHandler::OnEndTracingAsync,
base::Unretained(this)));
web_ui_->RegisterMessageCallback("beginRequestBufferPercentFull",
base::Bind(&TracingMessageHandler::OnBeginRequestBufferPercentFull,
base::Unretained(this)));
web_ui_->RegisterMessageCallback("loadTraceFile",
base::Bind(&TracingMessageHandler::OnLoadTraceFile,
base::Unretained(this)));
web_ui_->RegisterMessageCallback("saveTraceFile",
base::Bind(&TracingMessageHandler::OnSaveTraceFile,
base::Unretained(this)));
}
void TracingMessageHandler::OnTracingControllerInitialized(
const ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Watch for changes in GPUInfo
gpu_data_manager_->AddObserver(this);
// Tell GpuDataManager it should have full GpuInfo. If the
// Gpu process has not run yet, this will trigger its launch.
gpu_data_manager_->RequestCompleteGpuInfoIfNeeded();
// Run callback immediately in case the info is ready and no update in the
// future.
OnGpuInfoUpdate();
// Send the client info to the tracingController
{
scoped_ptr<DictionaryValue> dict(new DictionaryValue());
chrome::VersionInfo version_info;
if (!version_info.is_valid()) {
DLOG(ERROR) << "Unable to create chrome::VersionInfo";
} else {
// We have everything we need to send the right values.
dict->SetString("version", version_info.Version());
dict->SetString("cl", version_info.LastChange());
dict->SetString("version_mod",
chrome::VersionInfo::GetVersionStringModifier());
dict->SetString("official",
l10n_util::GetStringUTF16(
version_info.IsOfficialBuild() ?
IDS_ABOUT_VERSION_OFFICIAL :
IDS_ABOUT_VERSION_UNOFFICIAL));
dict->SetString("command_line",
CommandLine::ForCurrentProcess()->GetCommandLineString());
}
dict->SetString("blacklist_version",
GpuDataManager::GetInstance()->GetBlacklistVersion());
web_ui_->CallJavascriptFunction("tracingController.onClientInfoUpdate",
*dict);
}
}
void TracingMessageHandler::OnBeginRequestBufferPercentFull(
const ListValue* list) {
TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this);
}
void TracingMessageHandler::OnGpuInfoUpdate() {
// Get GPU Info.
scoped_ptr<base::DictionaryValue> gpu_info_val(
gpu_data_manager_->GpuInfoAsDictionaryValue());
// Add in blacklisting features
Value* feature_status = gpu_data_manager_->GetFeatureStatus();
if (feature_status)
gpu_info_val->Set("featureStatus", feature_status);
// Send GPU Info to javascript.
web_ui_->CallJavascriptFunction("tracingController.onGpuInfoUpdate",
*(gpu_info_val.get()));
}
// A callback used for asynchronously reading a file to a string. Calls the
// TaskProxy callback when reading is complete.
void ReadTraceFileCallback(TaskProxy* proxy, const FilePath& path) {
scoped_ptr<std::string> file_contents(new std::string());
if (!file_util::ReadFileToString(path, file_contents.get()))
return;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&TaskProxy::LoadTraceFileCompleteProxy, proxy,
file_contents.release()));
}
// A callback used for asynchronously writing a file from a string. Calls the
// TaskProxy callback when writing is complete.
void WriteTraceFileCallback(TaskProxy* proxy,
const FilePath& path,
std::string* contents) {
if (!file_util::WriteFile(path, contents->c_str(), contents->size()))
return;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&TaskProxy::SaveTraceFileCompleteProxy, proxy));
}
void TracingMessageHandler::FileSelected(
const FilePath& path, int index, void* params) {
if (select_trace_file_dialog_type_ == SelectFileDialog::SELECT_OPEN_FILE) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ReadTraceFileCallback,
make_scoped_refptr(new TaskProxy(AsWeakPtr())), path));
} else {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&WriteTraceFileCallback,
make_scoped_refptr(new TaskProxy(AsWeakPtr())), path,
trace_data_to_save_.release()));
}
select_trace_file_dialog_.release();
}
void TracingMessageHandler::FileSelectionCanceled(void* params) {
select_trace_file_dialog_.release();
if (select_trace_file_dialog_type_ == SelectFileDialog::SELECT_OPEN_FILE) {
web_ui_->CallJavascriptFunction(
"tracingController.onLoadTraceFileCanceled");
} else {
web_ui_->CallJavascriptFunction(
"tracingController.onSaveTraceFileCanceled");
}
}
void TracingMessageHandler::OnLoadTraceFile(const ListValue* list) {
// Only allow a single dialog at a time.
if (select_trace_file_dialog_.get())
return;
select_trace_file_dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
select_trace_file_dialog_ = SelectFileDialog::Create(this);
select_trace_file_dialog_->SelectFile(
SelectFileDialog::SELECT_OPEN_FILE,
string16(),
FilePath(),
NULL, 0, FILE_PATH_LITERAL(""), web_ui_->tab_contents(),
web_ui_->tab_contents()->GetView()->GetTopLevelNativeWindow(), NULL);
}
void TracingMessageHandler::LoadTraceFileComplete(std::string* file_contents) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
std::string javascript = "tracingController.onLoadTraceFileComplete("
+ *file_contents + ");";
web_ui_->tab_contents()->GetRenderViewHost()->
ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(javascript));
}
void TracingMessageHandler::OnSaveTraceFile(const ListValue* list) {
// Only allow a single dialog at a time.
if (select_trace_file_dialog_.get())
return;
DCHECK(list->GetSize() == 1);
std::string* trace_data = new std::string();
bool ok = list->GetString(0, trace_data);
DCHECK(ok);
trace_data_to_save_.reset(trace_data);
select_trace_file_dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
select_trace_file_dialog_ = SelectFileDialog::Create(this);
select_trace_file_dialog_->SelectFile(
SelectFileDialog::SELECT_SAVEAS_FILE,
string16(),
FilePath(),
NULL, 0, FILE_PATH_LITERAL(""), web_ui_->tab_contents(),
web_ui_->tab_contents()->GetView()->GetTopLevelNativeWindow(), NULL);
}
void TracingMessageHandler::SaveTraceFileComplete() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
web_ui_->CallJavascriptFunction("tracingController.onSaveTraceFileComplete");
}
void TracingMessageHandler::OnBeginTracing(const ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
trace_enabled_ = true;
// TODO(jbates) This may fail, but that's OK for current use cases.
// Ex: Multiple about:gpu traces can not trace simultaneously.
// TODO(nduca) send feedback to javascript about whether or not BeginTracing
// was successful.
TraceController::GetInstance()->BeginTracing(this);
}
void TracingMessageHandler::OnEndTracingAsync(const ListValue* list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// TODO(nduca): fix javascript code to make sure trace_enabled_ is always true
// here. triggered a false condition by just clicking stop
// trace a few times when it was going slow, and maybe switching
// between tabs.
if (trace_enabled_ &&
!TraceController::GetInstance()->EndTracingAsync(this)) {
// Set to false now, since it turns out we never were the trace subscriber.
OnEndTracingComplete();
}
}
void TracingMessageHandler::OnEndTracingComplete() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
trace_enabled_ = false;
web_ui_->CallJavascriptFunction("tracingController.onEndTracingComplete");
}
void TracingMessageHandler::OnTraceDataCollected(
const std::string& trace_fragment) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::debug::TraceResultBuffer::SimpleOutput output;
base::debug::TraceResultBuffer trace_buffer;
trace_buffer.SetOutputCallback(output.GetCallback());
output.Append("tracingController.onTraceDataCollected(");
trace_buffer.Start();
trace_buffer.AddFragment(trace_fragment);
trace_buffer.Finish();
output.Append(");");
web_ui_->tab_contents()->GetRenderViewHost()->
ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(output.json_output));
}
void TracingMessageHandler::OnTraceBufferPercentFullReply(float percent_full) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
web_ui_->CallJavascriptFunction(
"tracingController.onRequestBufferPercentFullComplete",
*scoped_ptr<Value>(Value::CreateDoubleValue(percent_full)));
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
//
// TracingUI
//
////////////////////////////////////////////////////////////////////////////////
TracingUI::TracingUI(TabContents* contents) : ChromeWebUI(contents) {
AddMessageHandler((new TracingMessageHandler())->Attach(this));
// Set up the chrome://tracing/ source.
Profile::FromBrowserContext(contents->browser_context())->
GetChromeURLDataManager()->AddDataSource(CreateTracingHTMLSource());
}