1 // Copyright (c) 2012 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.
5 #include "content/browser/tracing/tracing_ui.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/debug/trace_event.h"
13 #include "base/file_util.h"
14 #include "base/json/string_escape.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/content_browser_client.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/trace_controller.h"
25 #include "content/public/browser/trace_subscriber.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_view.h"
28 #include "content/public/browser/web_ui.h"
29 #include "content/public/browser/web_ui_data_source.h"
30 #include "content/public/browser/web_ui_message_handler.h"
31 #include "content/public/common/url_constants.h"
32 #include "grit/content_resources.h"
33 #include "ipc/ipc_channel.h"
34 #include "ui/shell_dialogs/select_file_dialog.h"
36 #if defined(OS_CHROMEOS)
37 #include "chromeos/dbus/dbus_thread_manager.h"
38 #include "chromeos/dbus/debug_daemon_client.h"
44 WebUIDataSource
* CreateTracingHTMLSource() {
45 WebUIDataSource
* source
= WebUIDataSource::Create(kChromeUITracingHost
);
47 source
->SetJsonPath("strings.js");
48 source
->SetDefaultResource(IDR_TRACING_HTML
);
49 source
->AddResourcePath("tracing.js", IDR_TRACING_JS
);
53 // This class receives javascript messages from the renderer.
54 // Note that the WebUI infrastructure runs on the UI thread, therefore all of
55 // this class's methods are expected to run on the UI thread.
56 class TracingMessageHandler
57 : public WebUIMessageHandler
,
58 public ui::SelectFileDialog::Listener
,
59 public base::SupportsWeakPtr
<TracingMessageHandler
>,
60 public TraceSubscriber
{
62 TracingMessageHandler();
63 virtual ~TracingMessageHandler();
65 // WebUIMessageHandler implementation.
66 virtual void RegisterMessages() OVERRIDE
;
68 // SelectFileDialog::Listener implementation
69 virtual void FileSelected(const base::FilePath
& path
,
71 void* params
) OVERRIDE
;
72 virtual void FileSelectionCanceled(void* params
) OVERRIDE
;
74 // TraceSubscriber implementation.
75 virtual void OnEndTracingComplete() OVERRIDE
;
76 virtual void OnTraceDataCollected(
77 const scoped_refptr
<base::RefCountedString
>& trace_fragment
) OVERRIDE
;
78 virtual void OnTraceBufferPercentFullReply(float percent_full
) OVERRIDE
;
79 virtual void OnKnownCategoriesCollected(
80 const std::set
<std::string
>& known_categories
) OVERRIDE
;
83 void OnTracingControllerInitialized(const base::ListValue
* list
);
84 void OnBeginTracing(const base::ListValue
* list
);
85 void OnEndTracingAsync(const base::ListValue
* list
);
86 void OnBeginRequestBufferPercentFull(const base::ListValue
* list
);
87 void OnLoadTraceFile(const base::ListValue
* list
);
88 void OnSaveTraceFile(const base::ListValue
* list
);
89 void OnGetKnownCategories(const base::ListValue
* list
);
92 void LoadTraceFileComplete(string16
* file_contents
,
93 const base::FilePath
&path
);
94 void SaveTraceFileComplete();
97 // The file dialog to select a file for loading or saving traces.
98 scoped_refptr
<ui::SelectFileDialog
> select_trace_file_dialog_
;
100 // The type of the file dialog as the same one is used for loading or saving
102 ui::SelectFileDialog::Type select_trace_file_dialog_type_
;
104 // The trace data that is to be written to the file on saving.
105 scoped_ptr
<std::string
> trace_data_to_save_
;
107 // True while tracing is active.
110 // True while system tracing is active.
111 bool system_trace_in_progress_
;
113 void OnEndSystemTracingAck(
114 const scoped_refptr
<base::RefCountedString
>& events_str_ptr
);
116 DISALLOW_COPY_AND_ASSIGN(TracingMessageHandler
);
119 // A proxy passed to the Read and Write tasks used when loading or saving trace
121 class TaskProxy
: public base::RefCountedThreadSafe
<TaskProxy
> {
123 explicit TaskProxy(const base::WeakPtr
<TracingMessageHandler
>& handler
)
124 : handler_(handler
) {}
125 void LoadTraceFileCompleteProxy(string16
* file_contents
,
126 const base::FilePath
& path
) {
128 handler_
->LoadTraceFileComplete(file_contents
, path
);
129 delete file_contents
;
132 void SaveTraceFileCompleteProxy() {
134 handler_
->SaveTraceFileComplete();
138 friend class base::RefCountedThreadSafe
<TaskProxy
>;
141 // The message handler to call callbacks on.
142 base::WeakPtr
<TracingMessageHandler
> handler_
;
144 DISALLOW_COPY_AND_ASSIGN(TaskProxy
);
147 ////////////////////////////////////////////////////////////////////////////////
149 // TracingMessageHandler
151 ////////////////////////////////////////////////////////////////////////////////
153 TracingMessageHandler::TracingMessageHandler()
154 : select_trace_file_dialog_type_(ui::SelectFileDialog::SELECT_NONE
),
155 trace_enabled_(false),
156 system_trace_in_progress_(false) {
159 TracingMessageHandler::~TracingMessageHandler() {
160 if (select_trace_file_dialog_
.get())
161 select_trace_file_dialog_
->ListenerDestroyed();
163 // If we are the current subscriber, this will result in ending tracing.
164 TraceController::GetInstance()->CancelSubscriber(this);
166 // Shutdown any system tracing too.
167 if (system_trace_in_progress_
) {
168 #if defined(OS_CHROMEOS)
169 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
170 RequestStopSystemTracing(
171 chromeos::DebugDaemonClient::EmptyStopSystemTracingCallback());
176 void TracingMessageHandler::RegisterMessages() {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
179 web_ui()->RegisterMessageCallback("tracingControllerInitialized",
180 base::Bind(&TracingMessageHandler::OnTracingControllerInitialized
,
181 base::Unretained(this)));
182 web_ui()->RegisterMessageCallback("beginTracing",
183 base::Bind(&TracingMessageHandler::OnBeginTracing
,
184 base::Unretained(this)));
185 web_ui()->RegisterMessageCallback("endTracingAsync",
186 base::Bind(&TracingMessageHandler::OnEndTracingAsync
,
187 base::Unretained(this)));
188 web_ui()->RegisterMessageCallback("beginRequestBufferPercentFull",
189 base::Bind(&TracingMessageHandler::OnBeginRequestBufferPercentFull
,
190 base::Unretained(this)));
191 web_ui()->RegisterMessageCallback("loadTraceFile",
192 base::Bind(&TracingMessageHandler::OnLoadTraceFile
,
193 base::Unretained(this)));
194 web_ui()->RegisterMessageCallback("saveTraceFile",
195 base::Bind(&TracingMessageHandler::OnSaveTraceFile
,
196 base::Unretained(this)));
197 web_ui()->RegisterMessageCallback("getKnownCategories",
198 base::Bind(&TracingMessageHandler::OnGetKnownCategories
,
199 base::Unretained(this)));
202 void TracingMessageHandler::OnTracingControllerInitialized(
203 const base::ListValue
* args
) {
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
206 // Send the client info to the tracingController
208 scoped_ptr
<base::DictionaryValue
> dict(new base::DictionaryValue());
209 dict
->SetString("version", GetContentClient()->GetProduct());
211 dict
->SetString("command_line",
212 CommandLine::ForCurrentProcess()->GetCommandLineString());
214 web_ui()->CallJavascriptFunction("tracingController.onClientInfoUpdate",
219 void TracingMessageHandler::OnBeginRequestBufferPercentFull(
220 const base::ListValue
* list
) {
221 TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this);
224 // A callback used for asynchronously reading a file to a string. Calls the
225 // TaskProxy callback when reading is complete.
226 void ReadTraceFileCallback(TaskProxy
* proxy
, const base::FilePath
& path
) {
227 std::string file_contents
;
228 if (!file_util::ReadFileToString(path
, &file_contents
))
231 // We need to escape the file contents, because it will go into a javascript
232 // quoted string in TracingMessageHandler::LoadTraceFileComplete. We need to
233 // escape control characters (to have well-formed javascript statements), as
234 // well as \ and ' (the only special characters in a ''-quoted string).
235 // Do the escaping on this thread, it may take a little while for big files
236 // and we don't want to block the UI during that time. Also do the UTF-16
238 // Note: we're using UTF-16 because we'll need to cut the string into slices
239 // to give to Javascript, and it's easier to cut than UTF-8 (since JS strings
240 // are arrays of 16-bit values, UCS-2 really, whereas we can't cut inside of a
241 // multibyte UTF-8 codepoint).
242 size_t size
= file_contents
.size();
243 std::string escaped_contents
;
244 escaped_contents
.reserve(size
);
245 for (size_t i
= 0; i
< size
; ++i
) {
246 char c
= file_contents
[i
];
248 escaped_contents
+= base::StringPrintf("\\u%04x", c
);
251 if (c
== '\\' || c
== '\'')
252 escaped_contents
.push_back('\\');
253 escaped_contents
.push_back(c
);
255 file_contents
.clear();
257 scoped_ptr
<string16
> contents16(new string16
);
258 UTF8ToUTF16(escaped_contents
).swap(*contents16
);
260 BrowserThread::PostTask(
261 BrowserThread::UI
, FROM_HERE
,
262 base::Bind(&TaskProxy::LoadTraceFileCompleteProxy
, proxy
,
263 contents16
.release(),
267 // A callback used for asynchronously writing a file from a string. Calls the
268 // TaskProxy callback when writing is complete.
269 void WriteTraceFileCallback(TaskProxy
* proxy
,
270 const base::FilePath
& path
,
271 std::string
* contents
) {
272 if (!file_util::WriteFile(path
, contents
->c_str(), contents
->size()))
275 BrowserThread::PostTask(
276 BrowserThread::UI
, FROM_HERE
,
277 base::Bind(&TaskProxy::SaveTraceFileCompleteProxy
, proxy
));
280 void TracingMessageHandler::FileSelected(
281 const base::FilePath
& path
, int index
, void* params
) {
282 if (select_trace_file_dialog_type_
==
283 ui::SelectFileDialog::SELECT_OPEN_FILE
) {
284 BrowserThread::PostTask(
285 BrowserThread::FILE, FROM_HERE
,
286 base::Bind(&ReadTraceFileCallback
,
287 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path
));
289 BrowserThread::PostTask(
290 BrowserThread::FILE, FROM_HERE
,
291 base::Bind(&WriteTraceFileCallback
,
292 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path
,
293 trace_data_to_save_
.release()));
296 select_trace_file_dialog_
= NULL
;
299 void TracingMessageHandler::FileSelectionCanceled(void* params
) {
300 select_trace_file_dialog_
= NULL
;
301 if (select_trace_file_dialog_type_
==
302 ui::SelectFileDialog::SELECT_OPEN_FILE
) {
303 web_ui()->CallJavascriptFunction(
304 "tracingController.onLoadTraceFileCanceled");
306 web_ui()->CallJavascriptFunction(
307 "tracingController.onSaveTraceFileCanceled");
311 void TracingMessageHandler::OnLoadTraceFile(const base::ListValue
* list
) {
312 // Only allow a single dialog at a time.
313 if (select_trace_file_dialog_
.get())
315 select_trace_file_dialog_type_
= ui::SelectFileDialog::SELECT_OPEN_FILE
;
316 select_trace_file_dialog_
= ui::SelectFileDialog::Create(
318 GetContentClient()->browser()->CreateSelectFilePolicy(
319 web_ui()->GetWebContents()));
320 select_trace_file_dialog_
->SelectFile(
321 ui::SelectFileDialog::SELECT_OPEN_FILE
,
326 base::FilePath::StringType(),
327 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
331 void TracingMessageHandler::LoadTraceFileComplete(string16
* contents
,
332 const base::FilePath
& path
) {
333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
335 // We need to pass contents to tracingController.onLoadTraceFileComplete, but
336 // that may be arbitrarily big, and IPCs messages are limited in size. So we
337 // need to cut it into pieces and rebuild the string in Javascript.
338 // |contents| has already been escaped in ReadTraceFileCallback.
339 // IPC::Channel::kMaximumMessageSize is in bytes, and we need to account for
341 const size_t kMaxSize
= IPC::Channel::kMaximumMessageSize
/ 2 - 128;
342 string16 first_prefix
= UTF8ToUTF16("window.traceData = '");
343 string16 prefix
= UTF8ToUTF16("window.traceData += '");
344 string16 suffix
= UTF8ToUTF16("';");
346 RenderViewHost
* rvh
= web_ui()->GetWebContents()->GetRenderViewHost();
347 for (size_t i
= 0; i
< contents
->size(); i
+= kMaxSize
) {
348 string16 javascript
= i
== 0 ? first_prefix
: prefix
;
349 javascript
+= contents
->substr(i
, kMaxSize
) + suffix
;
350 rvh
->ExecuteJavascriptInWebFrame(string16(), javascript
);
353 // The CallJavascriptFunction is not used because we need to pass
354 // the first param |window.traceData| through as an un-quoted string.
355 rvh
->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
356 "tracingController.onLoadTraceFileComplete(window.traceData," +
357 base::GetDoubleQuotedJson(path
.value()) + ");" +
358 "delete window.traceData;"));
361 void TracingMessageHandler::OnSaveTraceFile(const base::ListValue
* list
) {
362 // Only allow a single dialog at a time.
363 if (select_trace_file_dialog_
.get())
366 DCHECK(list
->GetSize() == 1);
368 std::string
* trace_data
= new std::string();
369 bool ok
= list
->GetString(0, trace_data
);
371 trace_data_to_save_
.reset(trace_data
);
373 select_trace_file_dialog_type_
= ui::SelectFileDialog::SELECT_SAVEAS_FILE
;
374 select_trace_file_dialog_
= ui::SelectFileDialog::Create(
376 GetContentClient()->browser()->CreateSelectFilePolicy(
377 web_ui()->GetWebContents()));
378 select_trace_file_dialog_
->SelectFile(
379 ui::SelectFileDialog::SELECT_SAVEAS_FILE
,
384 base::FilePath::StringType(),
385 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
389 void TracingMessageHandler::SaveTraceFileComplete() {
390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
391 web_ui()->CallJavascriptFunction("tracingController.onSaveTraceFileComplete");
394 void TracingMessageHandler::OnBeginTracing(const base::ListValue
* args
) {
395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
396 DCHECK_GE(args
->GetSize(), (size_t) 2);
397 DCHECK_LE(args
->GetSize(), (size_t) 3);
399 bool system_tracing_requested
= false;
400 bool ok
= args
->GetBoolean(0, &system_tracing_requested
);
403 std::string chrome_categories
;
404 ok
= args
->GetString(1, &chrome_categories
);
407 base::debug::TraceLog::Options options
=
408 base::debug::TraceLog::RECORD_UNTIL_FULL
;
409 if (args
->GetSize() >= 3) {
410 std::string options_
;
411 ok
= args
->GetString(2, &options_
);
413 options
= base::debug::TraceLog::TraceOptionsFromString(options_
);
416 trace_enabled_
= true;
417 // TODO(jbates) This may fail, but that's OK for current use cases.
418 // Ex: Multiple about:gpu traces can not trace simultaneously.
419 // TODO(nduca) send feedback to javascript about whether or not BeginTracing
421 TraceController::GetInstance()->BeginTracing(this, chrome_categories
,
424 if (system_tracing_requested
) {
425 #if defined(OS_CHROMEOS)
426 DCHECK(!system_trace_in_progress_
);
427 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
428 StartSystemTracing();
429 // TODO(sleffler) async, could wait for completion
430 system_trace_in_progress_
= true;
435 void TracingMessageHandler::OnEndTracingAsync(const base::ListValue
* list
) {
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
438 // This is really us beginning to end tracing, rather than tracing being truly
439 // over. When this function yields, we expect to get some number of
440 // OnTraceDataCollected callbacks, which will append data to window.traceData.
441 // To set up for this, set window.traceData to the empty string.
442 web_ui()->GetWebContents()->GetRenderViewHost()->
443 ExecuteJavascriptInWebFrame(string16(),
444 UTF8ToUTF16("window.traceData = '';"));
446 // TODO(nduca): fix javascript code to make sure trace_enabled_ is always true
447 // here. triggered a false condition by just clicking stop
448 // trace a few times when it was going slow, and maybe switching
450 if (trace_enabled_
&&
451 !TraceController::GetInstance()->EndTracingAsync(this)) {
452 // Set to false now, since it turns out we never were the trace subscriber.
453 OnEndTracingComplete();
457 void TracingMessageHandler::OnEndTracingComplete() {
458 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
459 trace_enabled_
= false;
460 if (system_trace_in_progress_
) {
461 // Disable system tracing now that the local trace has shutdown.
462 // This must be done last because we potentially need to push event
463 // records into the system event log for synchronizing system event
464 // timestamps with chrome event timestamps--and since the system event
465 // log is a ring-buffer (on linux) adding them at the end is the only
466 // way we're confident we'll have them in the final result.
467 system_trace_in_progress_
= false;
468 #if defined(OS_CHROMEOS)
469 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
470 RequestStopSystemTracing(
471 base::Bind(&TracingMessageHandler::OnEndSystemTracingAck
,
472 base::Unretained(this)));
477 RenderViewHost
* rvh
= web_ui()->GetWebContents()->GetRenderViewHost();
478 rvh
->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
479 "tracingController.onEndTracingComplete(window.traceData);"
480 "delete window.traceData;"));
483 void TracingMessageHandler::OnEndSystemTracingAck(
484 const scoped_refptr
<base::RefCountedString
>& events_str_ptr
) {
485 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
487 web_ui()->CallJavascriptFunction(
488 "tracingController.onSystemTraceDataCollected",
489 *scoped_ptr
<base::Value
>(new base::StringValue(events_str_ptr
->data())));
490 DCHECK(!system_trace_in_progress_
);
492 OnEndTracingComplete();
495 void TracingMessageHandler::OnTraceDataCollected(
496 const scoped_refptr
<base::RefCountedString
>& trace_fragment
) {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
499 std::string javascript
;
500 javascript
.reserve(trace_fragment
->size() * 2);
501 javascript
.append("window.traceData += \"");
502 base::JsonDoubleQuote(trace_fragment
->data(), false, &javascript
);
504 // Intentionally append a , to the traceData. This technically causes all
505 // traceData that we pass back to JS to end with a comma, but that is actually
506 // something the JS side strips away anyway
507 javascript
.append(",\";");
509 web_ui()->GetWebContents()->GetRenderViewHost()->
510 ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(javascript
));
513 void TracingMessageHandler::OnTraceBufferPercentFullReply(float percent_full
) {
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
515 web_ui()->CallJavascriptFunction(
516 "tracingController.onRequestBufferPercentFullComplete",
517 *scoped_ptr
<base::Value
>(new base::FundamentalValue(percent_full
)));
520 void TracingMessageHandler::OnGetKnownCategories(const base::ListValue
* list
) {
521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
522 if (!TraceController::GetInstance()->GetKnownCategoryGroupsAsync(this)) {
523 std::set
<std::string
> ret
;
524 OnKnownCategoriesCollected(ret
);
528 void TracingMessageHandler::OnKnownCategoriesCollected(
529 const std::set
<std::string
>& known_categories
) {
530 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
532 scoped_ptr
<base::ListValue
> categories(new base::ListValue());
533 for (std::set
<std::string
>::const_iterator iter
= known_categories
.begin();
534 iter
!= known_categories
.end();
536 categories
->AppendString(*iter
);
539 web_ui()->CallJavascriptFunction(
540 "tracingController.onKnownCategoriesCollected", *categories
);
546 ////////////////////////////////////////////////////////////////////////////////
550 ////////////////////////////////////////////////////////////////////////////////
552 TracingUI::TracingUI(WebUI
* web_ui
) : WebUIController(web_ui
) {
553 web_ui
->AddMessageHandler(new TracingMessageHandler());
555 // Set up the chrome://tracing/ source.
556 BrowserContext
* browser_context
=
557 web_ui
->GetWebContents()->GetBrowserContext();
558 WebUIDataSource::Add(browser_context
, CreateTracingHTMLSource());
561 } // namespace content