1 // Copyright 2013 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. 4 5 #include "content/browser/tracing/tracing_controller_impl.h" 6 7 #include "base/bind.h" 8 #include "base/debug/trace_event.h" 9 #include "base/file_util.h" 10 #include "base/json/string_escape.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "content/browser/tracing/trace_message_filter.h" 13 #include "content/common/child_process_messages.h" 14 #include "content/public/browser/browser_message_filter.h" 15 #include "content/public/common/content_switches.h" 16 17 using base::debug::TraceLog; 18 19 namespace content { 20 21 namespace { 22 23 base::LazyInstance<TracingControllerImpl>::Leaky g_controller = 24 LAZY_INSTANCE_INITIALIZER; 25 26 } // namespace 27 28 TracingController* TracingController::GetInstance() { 29 return TracingControllerImpl::GetInstance(); 30 } 31 32 class TracingControllerImpl::ResultFile { 33 public: 34 explicit ResultFile(const base::FilePath& path); 35 void Write(const scoped_refptr<base::RefCountedString>& events_str_ptr) { 36 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 37 base::Bind(&TracingControllerImpl::ResultFile::WriteTask, 38 base::Unretained(this), events_str_ptr)); 39 } 40 void Close(const base::Closure& callback) { 41 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 42 base::Bind(&TracingControllerImpl::ResultFile::CloseTask, 43 base::Unretained(this), callback)); 44 } 45 const base::FilePath& path() const { return path_; } 46 47 private: 48 void OpenTask(); 49 void WriteTask(const scoped_refptr<base::RefCountedString>& events_str_ptr); 50 void CloseTask(const base::Closure& callback); 51 52 FILE* file_; 53 base::FilePath path_; 54 bool has_at_least_one_result_; 55 56 DISALLOW_COPY_AND_ASSIGN(ResultFile); 57 }; 58 59 TracingControllerImpl::ResultFile::ResultFile(const base::FilePath& path) 60 : file_(NULL), 61 path_(path), 62 has_at_least_one_result_(false) { 63 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 64 base::Bind(&TracingControllerImpl::ResultFile::OpenTask, 65 base::Unretained(this))); 66 } 67 68 void TracingControllerImpl::ResultFile::OpenTask() { 69 if (path_.empty()) 70 base::CreateTemporaryFile(&path_); 71 file_ = base::OpenFile(path_, "w"); 72 if (!file_) { 73 LOG(ERROR) << "Failed to open " << path_.value(); 74 return; 75 } 76 const char* preamble = "{\"traceEvents\": ["; 77 size_t written = fwrite(preamble, strlen(preamble), 1, file_); 78 DCHECK(written == 1); 79 } 80 81 void TracingControllerImpl::ResultFile::WriteTask( 82 const scoped_refptr<base::RefCountedString>& events_str_ptr) { 83 if (!file_) 84 return; 85 86 // If there is already a result in the file, then put a commma 87 // before the next batch of results. 88 if (has_at_least_one_result_) { 89 size_t written = fwrite(",", 1, 1, file_); 90 DCHECK(written == 1); 91 } 92 has_at_least_one_result_ = true; 93 size_t written = fwrite(events_str_ptr->data().c_str(), 94 events_str_ptr->data().size(), 1, 95 file_); 96 DCHECK(written == 1); 97 } 98 99 void TracingControllerImpl::ResultFile::CloseTask( 100 const base::Closure& callback) { 101 if (!file_) 102 return; 103 104 const char* trailout = "]}"; 105 size_t written = fwrite(trailout, strlen(trailout), 1, file_); 106 DCHECK(written == 1); 107 base::CloseFile(file_); 108 file_ = NULL; 109 110 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback); 111 } 112 113 114 TracingControllerImpl::TracingControllerImpl() : 115 pending_disable_recording_ack_count_(0), 116 pending_capture_monitoring_snapshot_ack_count_(0), 117 pending_trace_buffer_percent_full_ack_count_(0), 118 maximum_trace_buffer_percent_full_(0), 119 // Tracing may have been enabled by ContentMainRunner if kTraceStartup 120 // is specified in command line. 121 is_recording_(TraceLog::GetInstance()->IsEnabled()), 122 is_monitoring_(false) { 123 } 124 125 TracingControllerImpl::~TracingControllerImpl() { 126 // This is a Leaky instance. 127 NOTREACHED(); 128 } 129 130 TracingControllerImpl* TracingControllerImpl::GetInstance() { 131 return g_controller.Pointer(); 132 } 133 134 bool TracingControllerImpl::GetCategories( 135 const GetCategoriesDoneCallback& callback) { 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 137 138 // Known categories come back from child processes with the EndTracingAck 139 // message. So to get known categories, just begin and end tracing immediately 140 // afterwards. This will ping all the child processes for categories. 141 pending_get_categories_done_callback_ = callback; 142 if (!EnableRecording("*", TracingController::Options(), 143 EnableRecordingDoneCallback())) { 144 pending_get_categories_done_callback_.Reset(); 145 return false; 146 } 147 148 bool ok = DisableRecording(base::FilePath(), TracingFileResultCallback()); 149 DCHECK(ok); 150 return true; 151 } 152 153 bool TracingControllerImpl::EnableRecording( 154 const std::string& category_filter, 155 TracingController::Options options, 156 const EnableRecordingDoneCallback& callback) { 157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 158 159 if (!can_enable_recording()) 160 return false; 161 162 #if defined(OS_ANDROID) 163 if (pending_get_categories_done_callback_.is_null()) 164 TraceLog::GetInstance()->AddClockSyncMetadataEvent(); 165 #endif 166 167 TraceLog::Options trace_options = (options & RECORD_CONTINUOUSLY) ? 168 TraceLog::RECORD_CONTINUOUSLY : TraceLog::RECORD_UNTIL_FULL; 169 if (options & ENABLE_SAMPLING) { 170 trace_options = static_cast<TraceLog::Options>( 171 trace_options | TraceLog::ENABLE_SAMPLING); 172 } 173 // TODO(haraken): How to handle ENABLE_SYSTRACE? 174 175 TraceLog::GetInstance()->SetEnabled( 176 base::debug::CategoryFilter(category_filter), trace_options); 177 is_recording_ = true; 178 179 // Notify all child processes. 180 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 181 it != trace_message_filters_.end(); ++it) { 182 it->get()->SendBeginTracing(category_filter, trace_options); 183 } 184 185 if (!callback.is_null()) 186 callback.Run(); 187 return true; 188 } 189 190 bool TracingControllerImpl::DisableRecording( 191 const base::FilePath& result_file_path, 192 const TracingFileResultCallback& callback) { 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 194 195 if (!can_disable_recording()) 196 return false; 197 198 pending_disable_recording_done_callback_ = callback; 199 200 // Disable local trace early to avoid traces during end-tracing process from 201 // interfering with the process. 202 TraceLog::GetInstance()->SetDisabled(); 203 204 #if defined(OS_ANDROID) 205 if (pending_get_categories_done_callback_.is_null()) 206 TraceLog::GetInstance()->AddClockSyncMetadataEvent(); 207 #endif 208 209 if (!callback.is_null() || !result_file_path.empty()) 210 result_file_.reset(new ResultFile(result_file_path)); 211 212 // Count myself (local trace) in pending_disable_recording_ack_count_, 213 // acked below. 214 pending_disable_recording_ack_count_ = trace_message_filters_.size() + 1; 215 216 // Handle special case of zero child processes by immediately telling the 217 // caller that tracing has ended. Use asynchronous OnDisableRecordingAcked 218 // to avoid recursive call back to the caller. 219 if (pending_disable_recording_ack_count_ == 1) { 220 // Ack asynchronously now, because we don't have any children to wait for. 221 std::vector<std::string> category_groups; 222 TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups); 223 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 224 base::Bind(&TracingControllerImpl::OnDisableRecordingAcked, 225 base::Unretained(this), category_groups)); 226 } 227 228 // Notify all child processes. 229 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 230 it != trace_message_filters_.end(); ++it) { 231 it->get()->SendEndTracing(); 232 } 233 return true; 234 } 235 236 bool TracingControllerImpl::EnableMonitoring( 237 const std::string& category_filter, 238 TracingController::Options options, 239 const EnableMonitoringDoneCallback& callback) { 240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 241 242 if (!can_enable_monitoring()) 243 return false; 244 is_monitoring_ = true; 245 246 #if defined(OS_ANDROID) 247 TraceLog::GetInstance()->AddClockSyncMetadataEvent(); 248 #endif 249 250 int monitoring_tracing_options = 0; 251 if (options & ENABLE_SAMPLING) 252 monitoring_tracing_options |= base::debug::TraceLog::MONITOR_SAMPLING; 253 254 TraceLog::GetInstance()->SetEnabled( 255 base::debug::CategoryFilter(category_filter), 256 static_cast<TraceLog::Options>(monitoring_tracing_options)); 257 258 // Notify all child processes. 259 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 260 it != trace_message_filters_.end(); ++it) { 261 it->get()->SendEnableMonitoring(category_filter, 262 static_cast<TraceLog::Options>(monitoring_tracing_options)); 263 } 264 265 if (!callback.is_null()) 266 callback.Run(); 267 return true; 268 } 269 270 bool TracingControllerImpl::DisableMonitoring( 271 const DisableMonitoringDoneCallback& callback) { 272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 273 274 if (!can_disable_monitoring()) 275 return false; 276 is_monitoring_ = false; 277 278 TraceLog::GetInstance()->SetDisabled(); 279 280 // Notify all child processes. 281 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 282 it != trace_message_filters_.end(); ++it) { 283 it->get()->SendDisableMonitoring(); 284 } 285 286 if (!callback.is_null()) 287 callback.Run(); 288 return true; 289 } 290 291 void TracingControllerImpl::GetMonitoringStatus( 292 bool* out_enabled, 293 std::string* out_category_filter, 294 TracingController::Options* out_options) { 295 NOTIMPLEMENTED(); 296 } 297 298 bool TracingControllerImpl::CaptureMonitoringSnapshot( 299 const base::FilePath& result_file_path, 300 const TracingFileResultCallback& callback) { 301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 302 303 if (!can_disable_monitoring()) 304 return false; 305 306 if (callback.is_null() && result_file_path.empty()) 307 return false; 308 309 pending_capture_monitoring_snapshot_done_callback_ = callback; 310 monitoring_snapshot_file_.reset(new ResultFile(result_file_path)); 311 312 // Count myself in pending_capture_monitoring_snapshot_ack_count_, 313 // acked below. 314 pending_capture_monitoring_snapshot_ack_count_ = 315 trace_message_filters_.size() + 1; 316 317 // Handle special case of zero child processes by immediately telling the 318 // caller that capturing snapshot has ended. Use asynchronous 319 // OnCaptureMonitoringSnapshotAcked to avoid recursive call back to the 320 // caller. 321 if (pending_capture_monitoring_snapshot_ack_count_ == 1) { 322 // Ack asynchronously now, because we don't have any children to wait for. 323 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 324 base::Bind(&TracingControllerImpl::OnCaptureMonitoringSnapshotAcked, 325 base::Unretained(this))); 326 } 327 328 // Notify all child processes. 329 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 330 it != trace_message_filters_.end(); ++it) { 331 it->get()->SendCaptureMonitoringSnapshot(); 332 } 333 334 #if defined(OS_ANDROID) 335 TraceLog::GetInstance()->AddClockSyncMetadataEvent(); 336 #endif 337 338 return true; 339 } 340 341 bool TracingControllerImpl::GetTraceBufferPercentFull( 342 const GetTraceBufferPercentFullCallback& callback) { 343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 344 345 if (!can_get_trace_buffer_percent_full() || callback.is_null()) 346 return false; 347 348 pending_trace_buffer_percent_full_callback_ = callback; 349 350 // Count myself in pending_trace_buffer_percent_full_ack_count_, acked below. 351 pending_trace_buffer_percent_full_ack_count_ = 352 trace_message_filters_.size() + 1; 353 maximum_trace_buffer_percent_full_ = 0; 354 355 // Handle special case of zero child processes. 356 if (pending_trace_buffer_percent_full_ack_count_ == 1) { 357 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 358 base::Bind(&TracingControllerImpl::OnTraceBufferPercentFullReply, 359 base::Unretained(this), 360 TraceLog::GetInstance()->GetBufferPercentFull())); 361 } 362 363 // Notify all child processes. 364 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 365 it != trace_message_filters_.end(); ++it) { 366 it->get()->SendGetTraceBufferPercentFull(); 367 } 368 return true; 369 } 370 371 bool TracingControllerImpl::SetWatchEvent( 372 const std::string& category_name, 373 const std::string& event_name, 374 const WatchEventCallback& callback) { 375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 376 377 if (callback.is_null()) 378 return false; 379 380 watch_category_name_ = category_name; 381 watch_event_name_ = event_name; 382 watch_event_callback_ = callback; 383 384 TraceLog::GetInstance()->SetWatchEvent( 385 category_name, event_name, 386 base::Bind(&TracingControllerImpl::OnWatchEventMatched, 387 base::Unretained(this))); 388 389 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 390 it != trace_message_filters_.end(); ++it) { 391 it->get()->SendSetWatchEvent(category_name, event_name); 392 } 393 return true; 394 } 395 396 bool TracingControllerImpl::CancelWatchEvent() { 397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 398 399 if (!can_cancel_watch_event()) 400 return false; 401 402 for (TraceMessageFilterMap::iterator it = trace_message_filters_.begin(); 403 it != trace_message_filters_.end(); ++it) { 404 it->get()->SendCancelWatchEvent(); 405 } 406 407 watch_event_callback_.Reset(); 408 return true; 409 } 410 411 void TracingControllerImpl::AddTraceMessageFilter( 412 TraceMessageFilter* trace_message_filter) { 413 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 414 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 415 base::Bind(&TracingControllerImpl::AddTraceMessageFilter, 416 base::Unretained(this), 417 make_scoped_refptr(trace_message_filter))); 418 return; 419 } 420 421 trace_message_filters_.insert(trace_message_filter); 422 if (can_cancel_watch_event()) { 423 trace_message_filter->SendSetWatchEvent(watch_category_name_, 424 watch_event_name_); 425 } 426 if (can_disable_recording()) { 427 trace_message_filter->SendBeginTracing( 428 TraceLog::GetInstance()->GetCurrentCategoryFilter().ToString(), 429 TraceLog::GetInstance()->trace_options()); 430 } 431 } 432 433 void TracingControllerImpl::RemoveTraceMessageFilter( 434 TraceMessageFilter* trace_message_filter) { 435 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 436 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 437 base::Bind(&TracingControllerImpl::RemoveTraceMessageFilter, 438 base::Unretained(this), 439 make_scoped_refptr(trace_message_filter))); 440 return; 441 } 442 443 trace_message_filters_.erase(trace_message_filter); 444 } 445 446 void TracingControllerImpl::OnDisableRecordingAcked( 447 const std::vector<std::string>& known_category_groups) { 448 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 449 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 450 base::Bind(&TracingControllerImpl::OnDisableRecordingAcked, 451 base::Unretained(this), known_category_groups)); 452 return; 453 } 454 455 // Merge known_category_groups with known_category_groups_ 456 known_category_groups_.insert(known_category_groups.begin(), 457 known_category_groups.end()); 458 459 if (pending_disable_recording_ack_count_ == 0) 460 return; 461 462 if (--pending_disable_recording_ack_count_ == 1) { 463 // All acks from subprocesses have been received. Now flush the local trace. 464 // During or after this call, our OnLocalTraceDataCollected will be 465 // called with the last of the local trace data. 466 TraceLog::GetInstance()->Flush( 467 base::Bind(&TracingControllerImpl::OnLocalTraceDataCollected, 468 base::Unretained(this))); 469 return; 470 } 471 472 if (pending_disable_recording_ack_count_ != 0) 473 return; 474 475 // All acks (including from the subprocesses and the local trace) have been 476 // received. 477 is_recording_ = false; 478 479 // Trigger callback if one is set. 480 if (!pending_get_categories_done_callback_.is_null()) { 481 pending_get_categories_done_callback_.Run(known_category_groups_); 482 pending_get_categories_done_callback_.Reset(); 483 } else if (result_file_) { 484 result_file_->Close( 485 base::Bind(&TracingControllerImpl::OnResultFileClosed, 486 base::Unretained(this))); 487 } 488 } 489 490 void TracingControllerImpl::OnResultFileClosed() { 491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 492 493 if (!result_file_) 494 return; 495 496 if (!pending_disable_recording_done_callback_.is_null()) { 497 pending_disable_recording_done_callback_.Run(result_file_->path()); 498 pending_disable_recording_done_callback_.Reset(); 499 } 500 result_file_.reset(); 501 } 502 503 void TracingControllerImpl::OnCaptureMonitoringSnapshotAcked() { 504 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 505 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 506 base::Bind(&TracingControllerImpl::OnCaptureMonitoringSnapshotAcked, 507 base::Unretained(this))); 508 return; 509 } 510 511 if (pending_capture_monitoring_snapshot_ack_count_ == 0) 512 return; 513 514 if (--pending_capture_monitoring_snapshot_ack_count_ == 1) { 515 // All acks from subprocesses have been received. Now flush the local trace. 516 // During or after this call, our OnLocalMonitoringTraceDataCollected 517 // will be called with the last of the local trace data. 518 TraceLog::GetInstance()->FlushButLeaveBufferIntact( 519 base::Bind(&TracingControllerImpl::OnLocalMonitoringTraceDataCollected, 520 base::Unretained(this))); 521 return; 522 } 523 524 if (pending_capture_monitoring_snapshot_ack_count_ != 0) 525 return; 526 527 if (monitoring_snapshot_file_) { 528 monitoring_snapshot_file_->Close( 529 base::Bind(&TracingControllerImpl::OnMonitoringSnapshotFileClosed, 530 base::Unretained(this))); 531 } 532 } 533 534 void TracingControllerImpl::OnMonitoringSnapshotFileClosed() { 535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 536 537 if (!monitoring_snapshot_file_) 538 return; 539 540 if (!pending_capture_monitoring_snapshot_done_callback_.is_null()) { 541 pending_capture_monitoring_snapshot_done_callback_.Run( 542 monitoring_snapshot_file_->path()); 543 pending_capture_monitoring_snapshot_done_callback_.Reset(); 544 } 545 monitoring_snapshot_file_.reset(); 546 } 547 548 void TracingControllerImpl::OnTraceDataCollected( 549 const scoped_refptr<base::RefCountedString>& events_str_ptr) { 550 // OnTraceDataCollected may be called from any browser thread, either by the 551 // local event trace system or from child processes via TraceMessageFilter. 552 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 553 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 554 base::Bind(&TracingControllerImpl::OnTraceDataCollected, 555 base::Unretained(this), events_str_ptr)); 556 return; 557 } 558 559 if (result_file_) 560 result_file_->Write(events_str_ptr); 561 } 562 563 void TracingControllerImpl::OnMonitoringTraceDataCollected( 564 const scoped_refptr<base::RefCountedString>& events_str_ptr) { 565 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 566 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 567 base::Bind(&TracingControllerImpl::OnMonitoringTraceDataCollected, 568 base::Unretained(this), events_str_ptr)); 569 return; 570 } 571 572 if (monitoring_snapshot_file_) 573 monitoring_snapshot_file_->Write(events_str_ptr); 574 } 575 576 void TracingControllerImpl::OnLocalTraceDataCollected( 577 const scoped_refptr<base::RefCountedString>& events_str_ptr, 578 bool has_more_events) { 579 if (events_str_ptr->data().size()) 580 OnTraceDataCollected(events_str_ptr); 581 582 if (has_more_events) 583 return; 584 585 // Simulate an DisableRecordingAcked for the local trace. 586 std::vector<std::string> category_groups; 587 TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups); 588 OnDisableRecordingAcked(category_groups); 589 } 590 591 void TracingControllerImpl::OnLocalMonitoringTraceDataCollected( 592 const scoped_refptr<base::RefCountedString>& events_str_ptr, 593 bool has_more_events) { 594 if (events_str_ptr->data().size()) 595 OnMonitoringTraceDataCollected(events_str_ptr); 596 597 if (has_more_events) 598 return; 599 600 // Simulate an CaptureMonitoringSnapshotAcked for the local trace. 601 OnCaptureMonitoringSnapshotAcked(); 602 } 603 604 void TracingControllerImpl::OnTraceBufferPercentFullReply(float percent_full) { 605 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 606 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 607 base::Bind(&TracingControllerImpl::OnTraceBufferPercentFullReply, 608 base::Unretained(this), percent_full)); 609 return; 610 } 611 612 if (pending_trace_buffer_percent_full_ack_count_ == 0) 613 return; 614 615 maximum_trace_buffer_percent_full_ = 616 std::max(maximum_trace_buffer_percent_full_, percent_full); 617 618 if (--pending_trace_buffer_percent_full_ack_count_ == 0) { 619 // Trigger callback if one is set. 620 pending_trace_buffer_percent_full_callback_.Run( 621 maximum_trace_buffer_percent_full_); 622 pending_trace_buffer_percent_full_callback_.Reset(); 623 } 624 625 if (pending_trace_buffer_percent_full_ack_count_ == 1) { 626 // The last ack represents local trace, so we need to ack it now. Note that 627 // this code only executes if there were child processes. 628 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 629 base::Bind(&TracingControllerImpl::OnTraceBufferPercentFullReply, 630 base::Unretained(this), 631 TraceLog::GetInstance()->GetBufferPercentFull())); 632 } 633 } 634 635 void TracingControllerImpl::OnWatchEventMatched() { 636 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 637 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 638 base::Bind(&TracingControllerImpl::OnWatchEventMatched, 639 base::Unretained(this))); 640 return; 641 } 642 643 if (!watch_event_callback_.is_null()) 644 watch_event_callback_.Run(); 645 } 646 647 } // namespace content 648