// 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/gpu_internals_ui.h" #include #include #include #include #include "base/command_line.h" #include "base/file_util.h" #include "base/memory/singleton.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/scoped_ptr.h" #include "base/stringprintf.h" #include "base/string_number_conversions.h" #include "base/string_piece.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/io_thread.h" #include "chrome/browser/net/chrome_net_log.h" #include "chrome/browser/net/connection_tester.h" #include "chrome/browser/net/passive_log_collector.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/shell_dialogs.h" #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/url_constants.h" #include "content/browser/browser_thread.h" #include "content/browser/gpu/gpu_data_manager.h" #include "content/browser/gpu/gpu_process_host.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 "grit/browser_resources.h" #include "grit/generated_resources.h" #include "net/base/escape.h" #include "net/url_request/url_request_context_getter.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" namespace { class GpuHTMLSource : public ChromeURLDataManager::DataSource { public: GpuHTMLSource(); // Called when the network layer has requested a resource underneath // the path we registered. virtual void StartDataRequest(const std::string& path, bool is_incognito, int request_id); virtual std::string GetMimeType(const std::string&) const; private: ~GpuHTMLSource() {} DISALLOW_COPY_AND_ASSIGN(GpuHTMLSource); }; // 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 GpuMessageHandler : public WebUIMessageHandler, public SelectFileDialog::Listener, public base::SupportsWeakPtr, public TraceSubscriber { public: GpuMessageHandler(); virtual ~GpuMessageHandler(); // WebUIMessageHandler implementation. virtual WebUIMessageHandler* Attach(WebUI* web_ui); virtual void RegisterMessages(); // Mesages void OnBeginTracing(const ListValue* list); void OnEndTracingAsync(const ListValue* list); void OnBrowserBridgeInitialized(const ListValue* list); void OnCallAsync(const ListValue* list); void OnBeginRequestBufferPercentFull(const ListValue* list); void OnLoadTraceFile(const ListValue* list); void OnSaveTraceFile(const ListValue* list); // Submessages dispatched from OnCallAsync Value* OnRequestClientInfo(const ListValue* list); Value* OnRequestLogMessages(const ListValue* list); // SelectFileDialog::Listener implementation virtual void FileSelected(const FilePath& path, int index, void* params); virtual void FileSelectionCanceled(void* params); // Callbacks. void OnGpuInfoUpdate(); void LoadTraceFileComplete(std::string* file_contents); void SaveTraceFileComplete(); // TraceSubscriber implementation. virtual void OnEndTracingComplete(); virtual void OnTraceDataCollected(const std::string& json_events); virtual void OnTraceBufferPercentFullReply(float percent_full); // Executes the javascript function |function_name| in the renderer, passing // it the argument |value|. void CallJavascriptFunction(const std::wstring& function_name, const Value* value); private: DISALLOW_COPY_AND_ASSIGN(GpuMessageHandler); // Cache the Singleton for efficiency. GpuDataManager* gpu_data_manager_; Callback0::Type* gpu_info_update_callback_; scoped_refptr select_trace_file_dialog_; SelectFileDialog::Type select_trace_file_dialog_type_; scoped_ptr trace_data_to_save_; bool trace_enabled_; }; class TaskProxy : public base::RefCountedThreadSafe { public: explicit TaskProxy(const base::WeakPtr& 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: base::WeakPtr handler_; friend class base::RefCountedThreadSafe; DISALLOW_COPY_AND_ASSIGN(TaskProxy); }; //////////////////////////////////////////////////////////////////////////////// // // GpuHTMLSource // //////////////////////////////////////////////////////////////////////////////// GpuHTMLSource::GpuHTMLSource() : DataSource(chrome::kChromeUIGpuInternalsHost, MessageLoop::current()) { } void GpuHTMLSource::StartDataRequest(const std::string& path, bool is_incognito, int request_id) { DictionaryValue localized_strings; SetFontAndTextDirection(&localized_strings); base::StringPiece gpu_html( ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_GPU_INTERNALS_HTML)); std::string full_html(gpu_html.data(), gpu_html.size()); jstemplate_builder::AppendJsonHtml(&localized_strings, &full_html); jstemplate_builder::AppendI18nTemplateSourceHtml(&full_html); jstemplate_builder::AppendI18nTemplateProcessHtml(&full_html); jstemplate_builder::AppendJsTemplateSourceHtml(&full_html); scoped_refptr html_bytes(new RefCountedBytes); html_bytes->data.resize(full_html.size()); std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); SendResponse(request_id, html_bytes); } std::string GpuHTMLSource::GetMimeType(const std::string&) const { return "text/html"; } //////////////////////////////////////////////////////////////////////////////// // // GpuMessageHandler // //////////////////////////////////////////////////////////////////////////////// GpuMessageHandler::GpuMessageHandler() : gpu_info_update_callback_(NULL) , trace_enabled_(false) { gpu_data_manager_ = GpuDataManager::GetInstance(); DCHECK(gpu_data_manager_); } GpuMessageHandler::~GpuMessageHandler() { if (gpu_info_update_callback_) { gpu_data_manager_->RemoveGpuInfoUpdateCallback(gpu_info_update_callback_); delete gpu_info_update_callback_; } 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* GpuMessageHandler::Attach(WebUI* web_ui) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui); return result; } /* BrowserBridge.callAsync prepends a requestID to these messages. */ void GpuMessageHandler::RegisterMessages() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); web_ui_->RegisterMessageCallback( "beginTracing", NewCallback(this, &GpuMessageHandler::OnBeginTracing)); web_ui_->RegisterMessageCallback( "endTracingAsync", NewCallback(this, &GpuMessageHandler::OnEndTracingAsync)); web_ui_->RegisterMessageCallback( "browserBridgeInitialized", NewCallback(this, &GpuMessageHandler::OnBrowserBridgeInitialized)); web_ui_->RegisterMessageCallback( "callAsync", NewCallback(this, &GpuMessageHandler::OnCallAsync)); web_ui_->RegisterMessageCallback( "beginRequestBufferPercentFull", NewCallback(this, &GpuMessageHandler::OnBeginRequestBufferPercentFull)); web_ui_->RegisterMessageCallback( "loadTraceFile", NewCallback(this, &GpuMessageHandler::OnLoadTraceFile)); web_ui_->RegisterMessageCallback( "saveTraceFile", NewCallback(this, &GpuMessageHandler::OnSaveTraceFile)); } void GpuMessageHandler::OnCallAsync(const ListValue* args) { DCHECK_GE(args->GetSize(), static_cast(2)); // unpack args into requestId, submessage and submessageArgs bool ok; Value* requestId; ok = args->Get(0, &requestId); DCHECK(ok); std::string submessage; ok = args->GetString(1, &submessage); DCHECK(ok); ListValue* submessageArgs = new ListValue(); for (size_t i = 2; i < args->GetSize(); ++i) { Value* arg; ok = args->Get(i, &arg); DCHECK(ok); Value* argCopy = arg->DeepCopy(); submessageArgs->Append(argCopy); } // call the submessage handler Value* ret = NULL; if (submessage == "requestClientInfo") { ret = OnRequestClientInfo(submessageArgs); } else if (submessage == "requestLogMessages") { ret = OnRequestLogMessages(submessageArgs); } else { // unrecognized submessage NOTREACHED(); delete submessageArgs; return; } delete submessageArgs; // call BrowserBridge.onCallAsyncReply with result if (ret) { web_ui_->CallJavascriptFunction("browserBridge.onCallAsyncReply", *requestId, *ret); delete ret; } else { web_ui_->CallJavascriptFunction("browserBridge.onCallAsyncReply", *requestId); } } void GpuMessageHandler::OnBeginRequestBufferPercentFull(const ListValue* list) { TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this); } class ReadTraceFileTask : public Task { public: ReadTraceFileTask(TaskProxy* proxy, const FilePath& path) : proxy_(proxy) , path_(path) {} virtual void Run() { std::string* file_contents = new std::string(); if (!file_util::ReadFileToString(path_, file_contents)) return; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(proxy_.get(), &TaskProxy::LoadTraceFileCompleteProxy, file_contents)); } private: scoped_refptr proxy_; // Path of the file to open. const FilePath path_; }; class WriteTraceFileTask : public Task { public: WriteTraceFileTask(TaskProxy* proxy, const FilePath& path, std::string* contents) : proxy_(proxy) , path_(path) , contents_(contents) {} virtual void Run() { if (!file_util::WriteFile(path_, contents_->c_str(), contents_->size())) return; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(proxy_.get(), &TaskProxy::SaveTraceFileCompleteProxy)); } private: scoped_refptr proxy_; // Path of the file to save. const FilePath path_; // What to save scoped_ptr contents_; }; void GpuMessageHandler::FileSelected( const FilePath& path, int index, void* params) { if (select_trace_file_dialog_type_ == SelectFileDialog::SELECT_OPEN_FILE) BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, new ReadTraceFileTask(new TaskProxy(AsWeakPtr()), path)); else BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, new WriteTraceFileTask(new TaskProxy(AsWeakPtr()), path, trace_data_to_save_.release())); select_trace_file_dialog_.release(); } void GpuMessageHandler::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 GpuMessageHandler::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()->view()->GetTopLevelNativeWindow(), NULL); } void GpuMessageHandler::LoadTraceFileComplete(std::string* file_contents) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); std::wstring javascript; javascript += L"tracingController.onLoadTraceFileComplete("; javascript += UTF8ToWide(*file_contents); javascript += L");"; web_ui_->GetRenderViewHost()->ExecuteJavascriptInWebFrame(string16(), WideToUTF16Hack(javascript)); } void GpuMessageHandler::OnSaveTraceFile(const ListValue* list) { // Only allow a single dialog at a time. if (select_trace_file_dialog_.get()) return; DCHECK(list->GetSize() == 1); Value* tmp; list->Get(0, &tmp); 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()->view()->GetTopLevelNativeWindow(), NULL); } void GpuMessageHandler::SaveTraceFileComplete() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); std::wstring javascript; web_ui_->CallJavascriptFunction("tracingController.onSaveTraceFileComplete"); } void GpuMessageHandler::OnBrowserBridgeInitialized(const ListValue* args) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(!gpu_info_update_callback_); // Watch for changes in GPUInfo gpu_info_update_callback_ = NewCallback(this, &GpuMessageHandler::OnGpuInfoUpdate); gpu_data_manager_->AddGpuInfoUpdateCallback(gpu_info_update_callback_); // 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(); } Value* GpuMessageHandler::OnRequestClientInfo(const ListValue* list) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 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", platform_util::GetVersionStringModifier()); dict->SetString("official", l10n_util::GetStringUTF16( version_info.IsOfficialBuild() ? IDS_ABOUT_VERSION_OFFICIAL : IDS_ABOUT_VERSION_UNOFFICIAL)); dict->SetString("command_line", CommandLine::ForCurrentProcess()->command_line_string()); } dict->SetString("blacklist_version", GpuDataManager::GetInstance()->GetBlacklistVersion()); return dict; } DictionaryValue* NewDescriptionValuePair(const std::string& desc, const std::string& value) { DictionaryValue* dict = new DictionaryValue(); dict->SetString("description", desc); dict->SetString("value", value); return dict; } DictionaryValue* NewDescriptionValuePair(const std::string& desc, Value* value) { DictionaryValue* dict = new DictionaryValue(); dict->SetString("description", desc); dict->Set("value", value); return dict; } #if defined(OS_WIN) // Output DxDiagNode tree as nested array of {description,value} pairs ListValue* DxDiagNodeToList(const DxDiagNode& node) { ListValue* list = new ListValue(); for (std::map::const_iterator it = node.values.begin(); it != node.values.end(); ++it) { list->Append(NewDescriptionValuePair(it->first, it->second)); } for (std::map::const_iterator it = node.children.begin(); it != node.children.end(); ++it) { ListValue* sublist = DxDiagNodeToList(it->second); list->Append(NewDescriptionValuePair(it->first, sublist)); } return list; } #endif // OS_WIN DictionaryValue* GpuInfoToDict(const GPUInfo& gpu_info) { ListValue* basic_info = new ListValue(); basic_info->Append(NewDescriptionValuePair("Initialization time", base::Int64ToString(gpu_info.initialization_time.InMilliseconds()))); basic_info->Append(NewDescriptionValuePair("Vendor Id", base::StringPrintf("0x%04x", gpu_info.vendor_id))); basic_info->Append(NewDescriptionValuePair("Device Id", base::StringPrintf("0x%04x", gpu_info.device_id))); basic_info->Append(NewDescriptionValuePair("Driver vendor", gpu_info.driver_vendor)); basic_info->Append(NewDescriptionValuePair("Driver version", gpu_info.driver_version)); basic_info->Append(NewDescriptionValuePair("Driver date", gpu_info.driver_date)); basic_info->Append(NewDescriptionValuePair("Pixel shader version", gpu_info.pixel_shader_version)); basic_info->Append(NewDescriptionValuePair("Vertex shader version", gpu_info.vertex_shader_version)); basic_info->Append(NewDescriptionValuePair("GL version", gpu_info.gl_version)); basic_info->Append(NewDescriptionValuePair("GL_VENDOR", gpu_info.gl_vendor)); basic_info->Append(NewDescriptionValuePair("GL_RENDERER", gpu_info.gl_renderer)); basic_info->Append(NewDescriptionValuePair("GL_VERSION", gpu_info.gl_version_string)); basic_info->Append(NewDescriptionValuePair("GL_EXTENSIONS", gpu_info.gl_extensions)); DictionaryValue* info = new DictionaryValue(); info->Set("basic_info", basic_info); #if defined(OS_WIN) Value* dx_info; if (gpu_info.dx_diagnostics.children.size()) dx_info = DxDiagNodeToList(gpu_info.dx_diagnostics); else dx_info = Value::CreateNullValue(); info->Set("diagnostics", dx_info); #endif return info; } Value* GpuMessageHandler::OnRequestLogMessages(const ListValue*) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); return gpu_data_manager_->log_messages().DeepCopy(); } void GpuMessageHandler::OnGpuInfoUpdate() { const GPUInfo& gpu_info = gpu_data_manager_->gpu_info(); // Get GPU Info. DictionaryValue* gpu_info_val = GpuInfoToDict(gpu_info); // 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("browserBridge.onGpuInfoUpdate", *gpu_info_val); delete gpu_info_val; } void GpuMessageHandler::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 GpuMessageHandler::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 GpuMessageHandler::OnEndTracingComplete() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); trace_enabled_ = false; web_ui_->CallJavascriptFunction("tracingController.onEndTracingComplete"); } void GpuMessageHandler::OnTraceDataCollected(const std::string& json_events) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); std::wstring javascript; javascript += L"tracingController.onTraceDataCollected("; javascript += UTF8ToWide(json_events); javascript += L");"; web_ui_->GetRenderViewHost()->ExecuteJavascriptInWebFrame(string16(), WideToUTF16Hack(javascript)); } void GpuMessageHandler::OnTraceBufferPercentFullReply(float percent_full) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); web_ui_->CallJavascriptFunction( "tracingController.onRequestBufferPercentFullComplete", *scoped_ptr(Value::CreateDoubleValue(percent_full))); } } // namespace //////////////////////////////////////////////////////////////////////////////// // // GpuInternalsUI // //////////////////////////////////////////////////////////////////////////////// GpuInternalsUI::GpuInternalsUI(TabContents* contents) : ChromeWebUI(contents) { AddMessageHandler((new GpuMessageHandler())->Attach(this)); GpuHTMLSource* html_source = new GpuHTMLSource(); // Set up the chrome://gpu/ source. contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); }