| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/tracing/tracing_ui.h" | 5 #include "content/browser/tracing/tracing_ui.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/base64.h" |
| 9 #include "base/bind.h" | 10 #include "base/bind.h" |
| 10 #include "base/bind_helpers.h" | 11 #include "base/bind_helpers.h" |
| 11 #include "base/command_line.h" | |
| 12 #include "base/debug/trace_event.h" | |
| 13 #include "base/file_util.h" | 12 #include "base/file_util.h" |
| 14 #include "base/json/string_escape.h" | 13 #include "base/json/json_reader.h" |
| 14 #include "base/json/json_writer.h" |
| 15 #include "base/memory/scoped_ptr.h" | 15 #include "base/memory/scoped_ptr.h" |
| 16 #include "base/safe_numerics.h" | |
| 17 #include "base/strings/string_number_conversions.h" | 16 #include "base/strings/string_number_conversions.h" |
| 18 #include "base/strings/string_util.h" | 17 #include "base/strings/string_util.h" |
| 19 #include "base/strings/stringprintf.h" | |
| 20 #include "base/strings/utf_string_conversions.h" | |
| 21 #include "base/values.h" | 18 #include "base/values.h" |
| 22 #include "content/public/browser/browser_thread.h" | 19 #include "content/public/browser/browser_thread.h" |
| 23 #include "content/public/browser/content_browser_client.h" | 20 #include "content/public/browser/tracing_controller.h" |
| 24 #include "content/public/browser/render_view_host.h" | |
| 25 #include "content/public/browser/trace_controller.h" | |
| 26 #include "content/public/browser/trace_subscriber.h" | |
| 27 #include "content/public/browser/web_contents.h" | 21 #include "content/public/browser/web_contents.h" |
| 28 #include "content/public/browser/web_contents_view.h" | |
| 29 #include "content/public/browser/web_ui.h" | 22 #include "content/public/browser/web_ui.h" |
| 30 #include "content/public/browser/web_ui_data_source.h" | 23 #include "content/public/browser/web_ui_data_source.h" |
| 31 #include "content/public/browser/web_ui_message_handler.h" | |
| 32 #include "content/public/common/url_constants.h" | 24 #include "content/public/common/url_constants.h" |
| 33 #include "grit/tracing_resources.h" | 25 #include "grit/tracing_resources.h" |
| 34 #include "ipc/ipc_channel.h" | |
| 35 #include "ui/shell_dialogs/select_file_dialog.h" | |
| 36 | |
| 37 #if defined(OS_CHROMEOS) | |
| 38 #include "chromeos/dbus/dbus_thread_manager.h" | |
| 39 #include "chromeos/dbus/debug_daemon_client.h" | |
| 40 #endif | |
| 41 | 26 |
| 42 namespace content { | 27 namespace content { |
| 43 namespace { | 28 namespace { |
| 44 | 29 |
| 45 WebUIDataSource* CreateTracingHTMLSource() { | 30 void OnGotCategories(const WebUIDataSource::GotDataCallback& callback, |
| 46 WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost); | 31 const std::set<std::string>& categorySet) { |
| 47 | 32 |
| 48 source->SetJsonPath("strings.js"); | 33 scoped_ptr<base::ListValue> category_list(new base::ListValue()); |
| 49 source->SetDefaultResource(IDR_TRACING_HTML); | 34 for (std::set<std::string>::const_iterator it = categorySet.begin(); |
| 50 source->AddResourcePath("tracing.js", IDR_TRACING_JS); | 35 it != categorySet.end(); it++) { |
| 51 return source; | 36 category_list->AppendString(*it); |
| 37 } |
| 38 |
| 39 base::RefCountedString* res = new base::RefCountedString(); |
| 40 base::JSONWriter::Write(category_list.get(), &res->data()); |
| 41 callback.Run(res); |
| 52 } | 42 } |
| 53 | 43 |
| 54 // This class receives javascript messages from the renderer. | 44 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback); |
| 55 // Note that the WebUI infrastructure runs on the UI thread, therefore all of | |
| 56 // this class's methods are expected to run on the UI thread. | |
| 57 class TracingMessageHandler | |
| 58 : public WebUIMessageHandler, | |
| 59 public ui::SelectFileDialog::Listener, | |
| 60 public base::SupportsWeakPtr<TracingMessageHandler>, | |
| 61 public TraceSubscriber { | |
| 62 public: | |
| 63 TracingMessageHandler(); | |
| 64 virtual ~TracingMessageHandler(); | |
| 65 | 45 |
| 66 // WebUIMessageHandler implementation. | 46 bool OnBeginRecording(const std::string& data64, |
| 67 virtual void RegisterMessages() OVERRIDE; | 47 const WebUIDataSource::GotDataCallback& callback) { |
| 68 | 48 std::string data; |
| 69 // SelectFileDialog::Listener implementation | 49 if (!base::Base64Decode(data64, &data)) { |
| 70 virtual void FileSelected(const base::FilePath& path, | 50 LOG(ERROR) << "Options were not base64 encoded."; |
| 71 int index, | 51 return false; |
| 72 void* params) OVERRIDE; | |
| 73 virtual void FileSelectionCanceled(void* params) OVERRIDE; | |
| 74 | |
| 75 // TraceSubscriber implementation. | |
| 76 virtual void OnEndTracingComplete() OVERRIDE; | |
| 77 virtual void OnTraceDataCollected( | |
| 78 const scoped_refptr<base::RefCountedString>& trace_fragment) OVERRIDE; | |
| 79 virtual void OnTraceBufferPercentFullReply(float percent_full) OVERRIDE; | |
| 80 virtual void OnKnownCategoriesCollected( | |
| 81 const std::set<std::string>& known_categories) OVERRIDE; | |
| 82 | |
| 83 // Messages. | |
| 84 void OnTracingControllerInitialized(const base::ListValue* list); | |
| 85 void OnBeginTracing(const base::ListValue* list); | |
| 86 void OnEndTracingAsync(const base::ListValue* list); | |
| 87 void OnBeginRequestBufferPercentFull(const base::ListValue* list); | |
| 88 void OnLoadTraceFile(const base::ListValue* list); | |
| 89 void OnSaveTraceFile(const base::ListValue* list); | |
| 90 void OnGetKnownCategories(const base::ListValue* list); | |
| 91 | |
| 92 // Callbacks. | |
| 93 void LoadTraceFileComplete(string16* file_contents, | |
| 94 const base::FilePath &path); | |
| 95 void SaveTraceFileComplete(); | |
| 96 | |
| 97 private: | |
| 98 // The file dialog to select a file for loading or saving traces. | |
| 99 scoped_refptr<ui::SelectFileDialog> select_trace_file_dialog_; | |
| 100 | |
| 101 // The type of the file dialog as the same one is used for loading or saving | |
| 102 // traces. | |
| 103 ui::SelectFileDialog::Type select_trace_file_dialog_type_; | |
| 104 | |
| 105 // The trace data that is to be written to the file on saving. | |
| 106 scoped_ptr<std::string> trace_data_to_save_; | |
| 107 | |
| 108 // True while tracing is active. | |
| 109 bool trace_enabled_; | |
| 110 | |
| 111 // True while system tracing is active. | |
| 112 bool system_trace_in_progress_; | |
| 113 | |
| 114 void OnEndSystemTracingAck( | |
| 115 const scoped_refptr<base::RefCountedString>& events_str_ptr); | |
| 116 | |
| 117 DISALLOW_COPY_AND_ASSIGN(TracingMessageHandler); | |
| 118 }; | |
| 119 | |
| 120 // A proxy passed to the Read and Write tasks used when loading or saving trace | |
| 121 // data. | |
| 122 class TaskProxy : public base::RefCountedThreadSafe<TaskProxy> { | |
| 123 public: | |
| 124 explicit TaskProxy(const base::WeakPtr<TracingMessageHandler>& handler) | |
| 125 : handler_(handler) {} | |
| 126 void LoadTraceFileCompleteProxy(string16* file_contents, | |
| 127 const base::FilePath& path) { | |
| 128 if (handler_.get()) | |
| 129 handler_->LoadTraceFileComplete(file_contents, path); | |
| 130 delete file_contents; | |
| 131 } | 52 } |
| 132 | 53 |
| 133 void SaveTraceFileCompleteProxy() { | 54 scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data)); |
| 134 if (handler_.get()) | 55 if (!optionsRaw) { |
| 135 handler_->SaveTraceFileComplete(); | 56 LOG(ERROR) << "Options were not valid JSON"; |
| 57 return false; |
| 58 } |
| 59 base::DictionaryValue* options; |
| 60 if (!optionsRaw->GetAsDictionary(&options)) { |
| 61 LOG(ERROR) << "Options must be dict"; |
| 62 return false; |
| 136 } | 63 } |
| 137 | 64 |
| 138 private: | 65 std::string category_filter_string; |
| 139 friend class base::RefCountedThreadSafe<TaskProxy>; | 66 bool use_system_tracing; |
| 140 ~TaskProxy() {} | 67 bool use_continuous_tracing; |
| 68 bool use_sampling; |
| 141 | 69 |
| 142 // The message handler to call callbacks on. | 70 bool options_ok = true; |
| 143 base::WeakPtr<TracingMessageHandler> handler_; | 71 options_ok &= options->GetString("categoryFilter", &category_filter_string); |
| 72 options_ok &= options->GetBoolean("useSystemTracing", &use_system_tracing); |
| 73 options_ok &= options->GetBoolean("useContinuousTracing", |
| 74 &use_continuous_tracing); |
| 75 options_ok &= options->GetBoolean("useSampling", &use_sampling); |
| 76 if (!options_ok) { |
| 77 LOG(ERROR) << "Malformed options"; |
| 78 return false; |
| 79 } |
| 144 | 80 |
| 145 DISALLOW_COPY_AND_ASSIGN(TaskProxy); | 81 int tracing_options = 0; |
| 146 }; | 82 if (use_system_tracing) |
| 83 tracing_options |= TracingController::ENABLE_SYSTRACE; |
| 84 if (use_sampling) |
| 85 tracing_options |= TracingController::ENABLE_SAMPLING; |
| 86 if (use_continuous_tracing) |
| 87 tracing_options |= TracingController::RECORD_CONTINUOUSLY; |
| 147 | 88 |
| 148 //////////////////////////////////////////////////////////////////////////////// | 89 base::debug::CategoryFilter category_filter(category_filter_string); |
| 149 // | 90 return TracingController::GetInstance()->EnableRecording( |
| 150 // TracingMessageHandler | 91 category_filter, |
| 151 // | 92 static_cast<TracingController::Options>(tracing_options), |
| 152 //////////////////////////////////////////////////////////////////////////////// | 93 base::Bind(OnRecordingEnabledAck, callback)); |
| 153 | |
| 154 TracingMessageHandler::TracingMessageHandler() | |
| 155 : select_trace_file_dialog_type_(ui::SelectFileDialog::SELECT_NONE), | |
| 156 trace_enabled_(false), | |
| 157 system_trace_in_progress_(false) { | |
| 158 } | 94 } |
| 159 | 95 |
| 160 TracingMessageHandler::~TracingMessageHandler() { | 96 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) { |
| 161 if (select_trace_file_dialog_.get()) | 97 base::RefCountedString* res = new base::RefCountedString(); |
| 162 select_trace_file_dialog_->ListenerDestroyed(); | 98 callback.Run(res); |
| 163 | |
| 164 // If we are the current subscriber, this will result in ending tracing. | |
| 165 TraceController::GetInstance()->CancelSubscriber(this); | |
| 166 | |
| 167 // Shutdown any system tracing too. | |
| 168 if (system_trace_in_progress_) { | |
| 169 #if defined(OS_CHROMEOS) | |
| 170 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()-> | |
| 171 RequestStopSystemTracing( | |
| 172 chromeos::DebugDaemonClient::EmptyStopSystemTracingCallback()); | |
| 173 #endif | |
| 174 } | |
| 175 } | 99 } |
| 176 | 100 |
| 177 void TracingMessageHandler::RegisterMessages() { | 101 void OnTraceBufferPercentFullResult( |
| 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 102 const WebUIDataSource::GotDataCallback& callback, float result) { |
| 179 | 103 std::string str = base::DoubleToString(result); |
| 180 web_ui()->RegisterMessageCallback("tracingControllerInitialized", | 104 callback.Run(base::RefCountedString::TakeString(&str)); |
| 181 base::Bind(&TracingMessageHandler::OnTracingControllerInitialized, | |
| 182 base::Unretained(this))); | |
| 183 web_ui()->RegisterMessageCallback("beginTracing", | |
| 184 base::Bind(&TracingMessageHandler::OnBeginTracing, | |
| 185 base::Unretained(this))); | |
| 186 web_ui()->RegisterMessageCallback("endTracingAsync", | |
| 187 base::Bind(&TracingMessageHandler::OnEndTracingAsync, | |
| 188 base::Unretained(this))); | |
| 189 web_ui()->RegisterMessageCallback("beginRequestBufferPercentFull", | |
| 190 base::Bind(&TracingMessageHandler::OnBeginRequestBufferPercentFull, | |
| 191 base::Unretained(this))); | |
| 192 web_ui()->RegisterMessageCallback("loadTraceFile", | |
| 193 base::Bind(&TracingMessageHandler::OnLoadTraceFile, | |
| 194 base::Unretained(this))); | |
| 195 web_ui()->RegisterMessageCallback("saveTraceFile", | |
| 196 base::Bind(&TracingMessageHandler::OnSaveTraceFile, | |
| 197 base::Unretained(this))); | |
| 198 web_ui()->RegisterMessageCallback("getKnownCategories", | |
| 199 base::Bind(&TracingMessageHandler::OnGetKnownCategories, | |
| 200 base::Unretained(this))); | |
| 201 } | 105 } |
| 202 | 106 |
| 203 void TracingMessageHandler::OnTracingControllerInitialized( | 107 void ReadRecordingResult(const WebUIDataSource::GotDataCallback& callback, |
| 204 const base::ListValue* args) { | 108 const base::FilePath& path) { |
| 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 109 std::string tmp; |
| 206 | 110 if (!base::ReadFileToString(path, &tmp)) |
| 207 // Send the client info to the tracingController | 111 LOG(ERROR) << "Failed to read file " << path.value(); |
| 208 { | 112 base::DeleteFile(path, false); |
| 209 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | 113 callback.Run(base::RefCountedString::TakeString(&tmp)); |
| 210 dict->SetString("version", GetContentClient()->GetProduct()); | |
| 211 | |
| 212 dict->SetString("command_line", | |
| 213 CommandLine::ForCurrentProcess()->GetCommandLineString()); | |
| 214 | |
| 215 web_ui()->CallJavascriptFunction("tracingController.onClientInfoUpdate", | |
| 216 *dict); | |
| 217 } | |
| 218 } | 114 } |
| 219 | 115 |
| 220 void TracingMessageHandler::OnBeginRequestBufferPercentFull( | 116 void BeginReadingRecordingResult( |
| 221 const base::ListValue* list) { | 117 const WebUIDataSource::GotDataCallback& callback, |
| 222 TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this); | 118 const base::FilePath& path) { |
| 119 BrowserThread::PostTask( |
| 120 BrowserThread::FILE, FROM_HERE, |
| 121 base::Bind(ReadRecordingResult, callback, path)); |
| 223 } | 122 } |
| 224 | 123 |
| 225 // A callback used for asynchronously reading a file to a string. Calls the | 124 bool OnBeginRequest(const std::string& path, |
| 226 // TaskProxy callback when reading is complete. | 125 const WebUIDataSource::GotDataCallback& callback) { |
| 227 void ReadTraceFileCallback(TaskProxy* proxy, const base::FilePath& path) { | 126 if (path == "json/categories") { |
| 228 std::string file_contents; | 127 TracingController::GetInstance()->GetCategories( |
| 229 if (!base::ReadFileToString(path, &file_contents)) | 128 base::Bind(OnGotCategories, callback)); |
| 230 return; | 129 return true; |
| 231 | |
| 232 // We need to escape the file contents, because it will go into a javascript | |
| 233 // quoted string in TracingMessageHandler::LoadTraceFileComplete. We need to | |
| 234 // escape control characters (to have well-formed javascript statements), as | |
| 235 // well as \ and ' (the only special characters in a ''-quoted string). | |
| 236 // Do the escaping on this thread, it may take a little while for big files | |
| 237 // and we don't want to block the UI during that time. Also do the UTF-16 | |
| 238 // conversion here. | |
| 239 // Note: we're using UTF-16 because we'll need to cut the string into slices | |
| 240 // to give to Javascript, and it's easier to cut than UTF-8 (since JS strings | |
| 241 // are arrays of 16-bit values, UCS-2 really, whereas we can't cut inside of a | |
| 242 // multibyte UTF-8 codepoint). | |
| 243 size_t size = file_contents.size(); | |
| 244 std::string escaped_contents; | |
| 245 escaped_contents.reserve(size); | |
| 246 for (size_t i = 0; i < size; ++i) { | |
| 247 char c = file_contents[i]; | |
| 248 if (c < ' ' || c == 0x7f) { | |
| 249 escaped_contents += base::StringPrintf("\\u%04x", | |
| 250 static_cast<uint8_t>(c)); | |
| 251 continue; | |
| 252 } | |
| 253 if (c == '\\' || c == '\'') | |
| 254 escaped_contents.push_back('\\'); | |
| 255 escaped_contents.push_back(c); | |
| 256 } | 130 } |
| 257 file_contents.clear(); | 131 const char* beginRecordingPath = "json/begin_recording?"; |
| 258 | 132 if (path.find(beginRecordingPath) == 0) { |
| 259 scoped_ptr<string16> contents16(new string16); | 133 std::string data = path.substr(strlen(beginRecordingPath)); |
| 260 UTF8ToUTF16(escaped_contents).swap(*contents16); | 134 return OnBeginRecording(data, callback); |
| 261 | |
| 262 BrowserThread::PostTask( | |
| 263 BrowserThread::UI, FROM_HERE, | |
| 264 base::Bind(&TaskProxy::LoadTraceFileCompleteProxy, proxy, | |
| 265 contents16.release(), | |
| 266 path)); | |
| 267 } | |
| 268 | |
| 269 // A callback used for asynchronously writing a file from a string. Calls the | |
| 270 // TaskProxy callback when writing is complete. | |
| 271 void WriteTraceFileCallback(TaskProxy* proxy, | |
| 272 const base::FilePath& path, | |
| 273 std::string* contents) { | |
| 274 int size = base::checked_numeric_cast<int>(contents->size()); | |
| 275 if (file_util::WriteFile(path, contents->c_str(), size) != size) | |
| 276 return; | |
| 277 | |
| 278 BrowserThread::PostTask( | |
| 279 BrowserThread::UI, FROM_HERE, | |
| 280 base::Bind(&TaskProxy::SaveTraceFileCompleteProxy, proxy)); | |
| 281 } | |
| 282 | |
| 283 void TracingMessageHandler::FileSelected( | |
| 284 const base::FilePath& path, int index, void* params) { | |
| 285 if (select_trace_file_dialog_type_ == | |
| 286 ui::SelectFileDialog::SELECT_OPEN_FILE) { | |
| 287 BrowserThread::PostTask( | |
| 288 BrowserThread::FILE, FROM_HERE, | |
| 289 base::Bind(&ReadTraceFileCallback, | |
| 290 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path)); | |
| 291 } else { | |
| 292 BrowserThread::PostTask( | |
| 293 BrowserThread::FILE, FROM_HERE, | |
| 294 base::Bind(&WriteTraceFileCallback, | |
| 295 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path, | |
| 296 trace_data_to_save_.release())); | |
| 297 } | 135 } |
| 298 | 136 if (path == "json/get_buffer_percent_full") { |
| 299 select_trace_file_dialog_ = NULL; | 137 return TracingController::GetInstance()->GetTraceBufferPercentFull( |
| 300 } | 138 base::Bind(OnTraceBufferPercentFullResult, callback)); |
| 301 | |
| 302 void TracingMessageHandler::FileSelectionCanceled(void* params) { | |
| 303 select_trace_file_dialog_ = NULL; | |
| 304 if (select_trace_file_dialog_type_ == | |
| 305 ui::SelectFileDialog::SELECT_OPEN_FILE) { | |
| 306 web_ui()->CallJavascriptFunction( | |
| 307 "tracingController.onLoadTraceFileCanceled"); | |
| 308 } else { | |
| 309 web_ui()->CallJavascriptFunction( | |
| 310 "tracingController.onSaveTraceFileCanceled"); | |
| 311 } | 139 } |
| 312 } | 140 if (path == "json/end_recording") { |
| 313 | 141 return TracingController::GetInstance()->DisableRecording( |
| 314 void TracingMessageHandler::OnLoadTraceFile(const base::ListValue* list) { | 142 base::FilePath(), base::Bind(BeginReadingRecordingResult, callback)); |
| 315 // Only allow a single dialog at a time. | |
| 316 if (select_trace_file_dialog_.get()) | |
| 317 return; | |
| 318 select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE; | |
| 319 select_trace_file_dialog_ = ui::SelectFileDialog::Create( | |
| 320 this, | |
| 321 GetContentClient()->browser()->CreateSelectFilePolicy( | |
| 322 web_ui()->GetWebContents())); | |
| 323 select_trace_file_dialog_->SelectFile( | |
| 324 ui::SelectFileDialog::SELECT_OPEN_FILE, | |
| 325 string16(), | |
| 326 base::FilePath(), | |
| 327 NULL, | |
| 328 0, | |
| 329 base::FilePath::StringType(), | |
| 330 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(), | |
| 331 NULL); | |
| 332 } | |
| 333 | |
| 334 void TracingMessageHandler::LoadTraceFileComplete(string16* contents, | |
| 335 const base::FilePath& path) { | |
| 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 337 | |
| 338 // We need to pass contents to tracingController.onLoadTraceFileComplete, but | |
| 339 // that may be arbitrarily big, and IPCs messages are limited in size. So we | |
| 340 // need to cut it into pieces and rebuild the string in Javascript. | |
| 341 // |contents| has already been escaped in ReadTraceFileCallback, so we need to | |
| 342 // avoid splitting escape sequences (e.g., \' or \u1234) to keep the data | |
| 343 // intact. IPC::Channel::kMaximumMessageSize is in bytes, and we need to | |
| 344 // account for overhead. | |
| 345 const size_t kMaxSize = IPC::Channel::kMaximumMessageSize / 2 - 128; | |
| 346 const size_t kControlCharEscapeSequenceSize = 5; | |
| 347 string16 first_prefix = UTF8ToUTF16("window.traceData = '"); | |
| 348 string16 prefix = UTF8ToUTF16("window.traceData += '"); | |
| 349 string16 suffix = UTF8ToUTF16("';"); | |
| 350 | |
| 351 RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost(); | |
| 352 | |
| 353 size_t flush_offset = 0; | |
| 354 size_t token_start = 0; | |
| 355 while (flush_offset < contents->size()) { | |
| 356 size_t token_end = token_start; | |
| 357 if ((*contents)[token_end] == '\\') { | |
| 358 token_end++; | |
| 359 DCHECK(token_end < contents->size()); | |
| 360 if (token_end < contents->size() && (*contents)[token_end] == 'u') { | |
| 361 token_end += kControlCharEscapeSequenceSize; | |
| 362 } else { | |
| 363 token_end++; | |
| 364 } | |
| 365 } else { | |
| 366 token_end++; | |
| 367 } | |
| 368 token_end = std::min(contents->size(), token_end); | |
| 369 size_t token_size = token_end - token_start; | |
| 370 size_t flush_size = token_start - flush_offset; | |
| 371 if (token_end == contents->size()) { | |
| 372 flush_size = contents->size() - flush_offset; | |
| 373 } else if (flush_size + token_size < kMaxSize) { | |
| 374 token_start += token_size; | |
| 375 continue; | |
| 376 } | |
| 377 string16 javascript = flush_offset == 0 ? first_prefix : prefix; | |
| 378 javascript += contents->substr(flush_offset, flush_size) + suffix; | |
| 379 rvh->ExecuteJavascriptInWebFrame(string16(), javascript); | |
| 380 flush_offset += flush_size; | |
| 381 } | 143 } |
| 382 | 144 if (StartsWithASCII(path, "json/", true)) |
| 383 // The CallJavascriptFunction is not used because we need to pass | 145 LOG(ERROR) << "Unhandled request to " << path; |
| 384 // the first param |window.traceData| through as an un-quoted string. | 146 return false; |
| 385 rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16( | |
| 386 "tracingController.onLoadTraceFileComplete(window.traceData," + | |
| 387 base::GetDoubleQuotedJson(path.value()) + ");" + | |
| 388 "delete window.traceData;")); | |
| 389 } | |
| 390 | |
| 391 void TracingMessageHandler::OnSaveTraceFile(const base::ListValue* list) { | |
| 392 // Only allow a single dialog at a time. | |
| 393 if (select_trace_file_dialog_.get()) | |
| 394 return; | |
| 395 | |
| 396 DCHECK_EQ(1U, list->GetSize()); | |
| 397 | |
| 398 std::string* trace_data = new std::string(); | |
| 399 bool ok = list->GetString(0, trace_data); | |
| 400 DCHECK(ok); | |
| 401 trace_data_to_save_.reset(trace_data); | |
| 402 | |
| 403 select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE; | |
| 404 select_trace_file_dialog_ = ui::SelectFileDialog::Create( | |
| 405 this, | |
| 406 GetContentClient()->browser()->CreateSelectFilePolicy( | |
| 407 web_ui()->GetWebContents())); | |
| 408 select_trace_file_dialog_->SelectFile( | |
| 409 ui::SelectFileDialog::SELECT_SAVEAS_FILE, | |
| 410 string16(), | |
| 411 base::FilePath(), | |
| 412 NULL, | |
| 413 0, | |
| 414 base::FilePath::StringType(), | |
| 415 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(), | |
| 416 NULL); | |
| 417 } | |
| 418 | |
| 419 void TracingMessageHandler::SaveTraceFileComplete() { | |
| 420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 421 web_ui()->CallJavascriptFunction("tracingController.onSaveTraceFileComplete"); | |
| 422 } | |
| 423 | |
| 424 void TracingMessageHandler::OnBeginTracing(const base::ListValue* args) { | |
| 425 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 426 DCHECK_GE(args->GetSize(), (size_t) 2); | |
| 427 DCHECK_LE(args->GetSize(), (size_t) 3); | |
| 428 | |
| 429 bool system_tracing_requested = false; | |
| 430 bool ok = args->GetBoolean(0, &system_tracing_requested); | |
| 431 DCHECK(ok); | |
| 432 | |
| 433 std::string chrome_categories; | |
| 434 ok = args->GetString(1, &chrome_categories); | |
| 435 DCHECK(ok); | |
| 436 | |
| 437 base::debug::TraceLog::Options options = | |
| 438 base::debug::TraceLog::RECORD_UNTIL_FULL; | |
| 439 if (args->GetSize() >= 3) { | |
| 440 std::string options_; | |
| 441 ok = args->GetString(2, &options_); | |
| 442 DCHECK(ok); | |
| 443 options = base::debug::TraceLog::TraceOptionsFromString(options_); | |
| 444 } | |
| 445 | |
| 446 trace_enabled_ = true; | |
| 447 // TODO(jbates) This may fail, but that's OK for current use cases. | |
| 448 // Ex: Multiple about:gpu traces can not trace simultaneously. | |
| 449 // TODO(nduca) send feedback to javascript about whether or not BeginTracing | |
| 450 // was successful. | |
| 451 TraceController::GetInstance()->BeginTracing(this, chrome_categories, | |
| 452 options); | |
| 453 | |
| 454 if (system_tracing_requested) { | |
| 455 #if defined(OS_CHROMEOS) | |
| 456 DCHECK(!system_trace_in_progress_); | |
| 457 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()-> | |
| 458 StartSystemTracing(); | |
| 459 // TODO(sleffler) async, could wait for completion | |
| 460 system_trace_in_progress_ = true; | |
| 461 #endif | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 void TracingMessageHandler::OnEndTracingAsync(const base::ListValue* list) { | |
| 466 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 467 | |
| 468 // This is really us beginning to end tracing, rather than tracing being truly | |
| 469 // over. When this function yields, we expect to get some number of | |
| 470 // OnTraceDataCollected callbacks, which will append data to window.traceData. | |
| 471 // To set up for this, set window.traceData to the empty string. | |
| 472 web_ui()->GetWebContents()->GetRenderViewHost()-> | |
| 473 ExecuteJavascriptInWebFrame(string16(), | |
| 474 UTF8ToUTF16("window.traceData = '';")); | |
| 475 | |
| 476 // TODO(nduca): fix javascript code to make sure trace_enabled_ is always true | |
| 477 // here. triggered a false condition by just clicking stop | |
| 478 // trace a few times when it was going slow, and maybe switching | |
| 479 // between tabs. | |
| 480 if (trace_enabled_ && | |
| 481 !TraceController::GetInstance()->EndTracingAsync(this)) { | |
| 482 // Set to false now, since it turns out we never were the trace subscriber. | |
| 483 OnEndTracingComplete(); | |
| 484 } | |
| 485 } | |
| 486 | |
| 487 void TracingMessageHandler::OnEndTracingComplete() { | |
| 488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 489 trace_enabled_ = false; | |
| 490 if (system_trace_in_progress_) { | |
| 491 // Disable system tracing now that the local trace has shutdown. | |
| 492 // This must be done last because we potentially need to push event | |
| 493 // records into the system event log for synchronizing system event | |
| 494 // timestamps with chrome event timestamps--and since the system event | |
| 495 // log is a ring-buffer (on linux) adding them at the end is the only | |
| 496 // way we're confident we'll have them in the final result. | |
| 497 system_trace_in_progress_ = false; | |
| 498 #if defined(OS_CHROMEOS) | |
| 499 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()-> | |
| 500 RequestStopSystemTracing( | |
| 501 base::Bind(&TracingMessageHandler::OnEndSystemTracingAck, | |
| 502 base::Unretained(this))); | |
| 503 return; | |
| 504 #endif | |
| 505 } | |
| 506 | |
| 507 RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost(); | |
| 508 rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16( | |
| 509 "tracingController.onEndTracingComplete(window.traceData);" | |
| 510 "delete window.traceData;")); | |
| 511 } | |
| 512 | |
| 513 void TracingMessageHandler::OnEndSystemTracingAck( | |
| 514 const scoped_refptr<base::RefCountedString>& events_str_ptr) { | |
| 515 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 516 | |
| 517 web_ui()->CallJavascriptFunction( | |
| 518 "tracingController.onSystemTraceDataCollected", | |
| 519 *scoped_ptr<base::Value>(new base::StringValue(events_str_ptr->data()))); | |
| 520 DCHECK(!system_trace_in_progress_); | |
| 521 | |
| 522 OnEndTracingComplete(); | |
| 523 } | |
| 524 | |
| 525 void TracingMessageHandler::OnTraceDataCollected( | |
| 526 const scoped_refptr<base::RefCountedString>& trace_fragment) { | |
| 527 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 528 | |
| 529 std::string javascript; | |
| 530 javascript.reserve(trace_fragment->size() * 2); | |
| 531 javascript.append("window.traceData += \""); | |
| 532 base::JsonDoubleQuote(trace_fragment->data(), false, &javascript); | |
| 533 | |
| 534 // Intentionally append a , to the traceData. This technically causes all | |
| 535 // traceData that we pass back to JS to end with a comma, but that is actually | |
| 536 // something the JS side strips away anyway | |
| 537 javascript.append(",\";"); | |
| 538 | |
| 539 web_ui()->GetWebContents()->GetRenderViewHost()-> | |
| 540 ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(javascript)); | |
| 541 } | |
| 542 | |
| 543 void TracingMessageHandler::OnTraceBufferPercentFullReply(float percent_full) { | |
| 544 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 545 web_ui()->CallJavascriptFunction( | |
| 546 "tracingController.onRequestBufferPercentFullComplete", | |
| 547 *scoped_ptr<base::Value>(new base::FundamentalValue(percent_full))); | |
| 548 } | |
| 549 | |
| 550 void TracingMessageHandler::OnGetKnownCategories(const base::ListValue* list) { | |
| 551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 552 if (!TraceController::GetInstance()->GetKnownCategoryGroupsAsync(this)) { | |
| 553 std::set<std::string> ret; | |
| 554 OnKnownCategoriesCollected(ret); | |
| 555 } | |
| 556 } | |
| 557 | |
| 558 void TracingMessageHandler::OnKnownCategoriesCollected( | |
| 559 const std::set<std::string>& known_categories) { | |
| 560 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 561 | |
| 562 scoped_ptr<base::ListValue> categories(new base::ListValue()); | |
| 563 for (std::set<std::string>::const_iterator iter = known_categories.begin(); | |
| 564 iter != known_categories.end(); | |
| 565 ++iter) { | |
| 566 categories->AppendString(*iter); | |
| 567 } | |
| 568 | |
| 569 web_ui()->CallJavascriptFunction( | |
| 570 "tracingController.onKnownCategoriesCollected", *categories); | |
| 571 } | 147 } |
| 572 | 148 |
| 573 } // namespace | 149 } // namespace |
| 574 | 150 |
| 575 | 151 |
| 576 //////////////////////////////////////////////////////////////////////////////// | 152 //////////////////////////////////////////////////////////////////////////////// |
| 577 // | 153 // |
| 578 // TracingUI | 154 // TracingUI |
| 579 // | 155 // |
| 580 //////////////////////////////////////////////////////////////////////////////// | 156 //////////////////////////////////////////////////////////////////////////////// |
| 581 | 157 |
| 582 TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) { | 158 TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) { |
| 583 web_ui->AddMessageHandler(new TracingMessageHandler()); | |
| 584 | |
| 585 // Set up the chrome://tracing/ source. | 159 // Set up the chrome://tracing/ source. |
| 586 BrowserContext* browser_context = | 160 BrowserContext* browser_context = |
| 587 web_ui->GetWebContents()->GetBrowserContext(); | 161 web_ui->GetWebContents()->GetBrowserContext(); |
| 588 WebUIDataSource::Add(browser_context, CreateTracingHTMLSource()); | 162 |
| 163 WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost); |
| 164 source->SetJsonPath("strings.js"); |
| 165 source->SetDefaultResource(IDR_TRACING_HTML); |
| 166 source->AddResourcePath("tracing.js", IDR_TRACING_JS); |
| 167 source->SetRequestFilter(base::Bind(OnBeginRequest)); |
| 168 WebUIDataSource::Add(browser_context, source); |
| 589 } | 169 } |
| 590 | 170 |
| 591 } // namespace content | 171 } // namespace content |
| OLD | NEW |