|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/devtools/protocol/tracing_handler.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cmath> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/format_macros.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/json/json_writer.h" | 
|  | #include "base/memory/ref_counted_memory.h" | 
|  | #include "base/numerics/safe_conversions.h" | 
|  | #include "base/strings/strcat.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/timer/timer.h" | 
|  | #include "base/trace_event/memory_dump_manager.h" | 
|  | #include "base/trace_event/trace_event_impl.h" | 
|  | #include "base/trace_event/traced_value.h" | 
|  | #include "base/trace_event/tracing_agent.h" | 
|  | #include "base/values.h" | 
|  | #include "build/build_config.h" | 
|  | #include "content/browser/devtools/devtools_agent_host_impl.h" | 
|  | #include "content/browser/devtools/devtools_io_context.h" | 
|  | #include "content/browser/devtools/devtools_stream_file.h" | 
|  | #include "content/browser/devtools/devtools_traceable_screenshot.h" | 
|  | #include "content/browser/devtools/devtools_video_consumer.h" | 
|  | #include "content/browser/devtools/tracing_process_set_monitor.h" | 
|  | #include "content/browser/gpu/gpu_process_host.h" | 
|  | #include "content/browser/renderer_host/frame_tree.h" | 
|  | #include "content/browser/renderer_host/frame_tree_node.h" | 
|  | #include "content/browser/renderer_host/navigation_request.h" | 
|  | #include "content/browser/renderer_host/render_frame_host_impl.h" | 
|  | #include "content/browser/renderer_host/render_widget_host_impl.h" | 
|  | #include "content/browser/tracing/tracing_controller_impl.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/tracing_service.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h" | 
|  | #include "services/tracing/public/cpp/perfetto/perfetto_config.h" | 
|  | #include "services/tracing/public/cpp/perfetto/perfetto_session.h" | 
|  | #include "services/tracing/public/cpp/perfetto/trace_packet_tokenizer.h" | 
|  | #include "services/tracing/public/cpp/trace_startup_config.h" | 
|  | #include "services/tracing/public/cpp/tracing_features.h" | 
|  | #include "services/tracing/public/mojom/constants.mojom-forward.h" | 
|  | #include "third_party/abseil-cpp/absl/strings/ascii.h" | 
|  | #include "third_party/inspector_protocol/crdtp/json.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "content/browser/renderer_host/compositor_impl_android.h" | 
|  | #endif | 
|  |  | 
|  | namespace content::protocol { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const double kMinimumReportingInterval = 250.0; | 
|  |  | 
|  | const char kRecordModeParam[] = "record_mode"; | 
|  | const char kTraceBufferSizeInKb[] = "trace_buffer_size_in_kb"; | 
|  |  | 
|  | const char kTrackEventDataSourceName[] = "track_event"; | 
|  |  | 
|  | // Frames need to be at least 1x1, otherwise nothing would be captured. | 
|  | constexpr gfx::Size kMinFrameSize = gfx::Size(1, 1); | 
|  |  | 
|  | // Frames do not need to be greater than 500x500 for tracing. | 
|  | constexpr gfx::Size kMaxFrameSize = gfx::Size(500, 500); | 
|  |  | 
|  | // Convert from camel case to separator + lowercase. | 
|  | std::string ConvertFromCamelCase(const std::string& in_str, char separator) { | 
|  | std::string out_str; | 
|  | out_str.reserve(in_str.size()); | 
|  | for (char c : in_str) { | 
|  | if (absl::ascii_isupper(static_cast<unsigned char>(c))) { | 
|  | out_str.push_back(separator); | 
|  | out_str.push_back(absl::ascii_tolower(static_cast<unsigned char>(c))); | 
|  | } else { | 
|  | out_str.push_back(c); | 
|  | } | 
|  | } | 
|  | return out_str; | 
|  | } | 
|  |  | 
|  | base::Value ConvertDictKeyStyle(const base::Value& value) { | 
|  | const base::Value::Dict* dict = value.GetIfDict(); | 
|  | if (dict) { | 
|  | base::Value::Dict out; | 
|  | for (auto kv : *dict) { | 
|  | out.Set(ConvertFromCamelCase(kv.first, '_'), | 
|  | ConvertDictKeyStyle(kv.second)); | 
|  | } | 
|  | return base::Value(std::move(out)); | 
|  | } | 
|  |  | 
|  | const base::Value::List* list = value.GetIfList(); | 
|  | if (list) { | 
|  | base::Value::List out; | 
|  | for (const auto& v : *list) { | 
|  | out.Append(ConvertDictKeyStyle(v)); | 
|  | } | 
|  | return base::Value(std::move(out)); | 
|  | } | 
|  |  | 
|  | return value.Clone(); | 
|  | } | 
|  |  | 
|  | class DevToolsTraceEndpointProxy : public TracingController::TraceDataEndpoint { | 
|  | public: | 
|  | explicit DevToolsTraceEndpointProxy(base::WeakPtr<TracingHandler> handler) | 
|  | : tracing_handler_(handler) {} | 
|  |  | 
|  | void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override { | 
|  | if (TracingHandler* h = tracing_handler_.get()) | 
|  | h->OnTraceDataCollected(std::move(chunk)); | 
|  | } | 
|  |  | 
|  | void ReceivedTraceFinalContents() override { | 
|  | if (TracingHandler* h = tracing_handler_.get()) | 
|  | h->OnTraceComplete(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~DevToolsTraceEndpointProxy() override = default; | 
|  |  | 
|  | base::WeakPtr<TracingHandler> tracing_handler_; | 
|  | }; | 
|  |  | 
|  | class DevToolsStreamEndpoint : public TracingController::TraceDataEndpoint { | 
|  | public: | 
|  | explicit DevToolsStreamEndpoint( | 
|  | base::WeakPtr<TracingHandler> handler, | 
|  | const scoped_refptr<DevToolsStreamFile>& stream) | 
|  | : stream_(stream), tracing_handler_(handler) {} | 
|  |  | 
|  | // CompressedStringEndpoint calls these methods on a background thread. | 
|  | void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override { | 
|  | if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&DevToolsStreamEndpoint::ReceiveTraceChunk, | 
|  | this, std::move(chunk))); | 
|  | return; | 
|  | } | 
|  | stream_->Append(std::move(chunk)); | 
|  | } | 
|  |  | 
|  | void ReceivedTraceFinalContents() override { | 
|  | if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&DevToolsStreamEndpoint::ReceivedTraceFinalContents, | 
|  | this)); | 
|  | return; | 
|  | } | 
|  | if (TracingHandler* h = tracing_handler_.get()) | 
|  | h->OnTraceToStreamComplete(stream_->handle()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~DevToolsStreamEndpoint() override = default; | 
|  |  | 
|  | scoped_refptr<DevToolsStreamFile> stream_; | 
|  | base::WeakPtr<TracingHandler> tracing_handler_; | 
|  | }; | 
|  |  | 
|  | std::string GetProcessHostHex(RenderProcessHost* host) { | 
|  | return base::StringPrintf("0x%" PRIxPTR, reinterpret_cast<uintptr_t>(host)); | 
|  | } | 
|  |  | 
|  | void SendProcessReadyInBrowserEvent(const base::UnguessableToken& frame_token, | 
|  | RenderProcessHost* host) { | 
|  | auto data = std::make_unique<base::trace_event::TracedValue>(); | 
|  | data->SetString("frame", frame_token.ToString()); | 
|  | data->SetString("processPseudoId", GetProcessHostHex(host)); | 
|  | data->SetInteger("processId", static_cast<int>(host->GetProcess().Pid())); | 
|  | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), | 
|  | "ProcessReadyInBrowser", TRACE_EVENT_SCOPE_THREAD, | 
|  | "data", std::move(data)); | 
|  | } | 
|  |  | 
|  | void FillFrameData(base::trace_event::TracedValue* data, | 
|  | RenderFrameHostImpl* frame_host, | 
|  | const GURL& url) { | 
|  | CHECK(frame_host); | 
|  | GURL::Replacements strip_fragment; | 
|  | strip_fragment.ClearRef(); | 
|  | std::string trimmed_url = url.ReplaceComponents(strip_fragment).spec(); | 
|  | data->SetString("frame", frame_host->devtools_frame_token().ToString()); | 
|  | data->SetString("url", std::move(trimmed_url)); | 
|  | data->SetString("name", frame_host->GetFrameName()); | 
|  | data->SetBoolean("isOutermostMainFrame", frame_host->IsOutermostMainFrame()); | 
|  | // Use FrameTree's primary status since the `frame_host` itself might not be | 
|  | // the primary main RenderFrameHost yet, if this function is called when | 
|  | // `frame_host` is still speculative / pending commit. | 
|  | data->SetBoolean("isInPrimaryMainFrame", | 
|  | frame_host->IsOutermostMainFrame() && | 
|  | frame_host->frame_tree()->is_primary()); | 
|  | if (frame_host->GetParent()) { | 
|  | data->SetString( | 
|  | "parent", frame_host->GetParent()->GetDevToolsFrameToken().ToString()); | 
|  | } | 
|  | RenderProcessHost* process_host = frame_host->GetProcess(); | 
|  | const base::Process& process_handle = process_host->GetProcess(); | 
|  | if (!process_handle.IsValid()) { | 
|  | data->SetString("processPseudoId", GetProcessHostHex(process_host)); | 
|  | frame_host->GetProcess()->PostTaskWhenProcessIsReady( | 
|  | base::BindOnce(&SendProcessReadyInBrowserEvent, | 
|  | frame_host->devtools_frame_token(), process_host)); | 
|  | } else { | 
|  | // Cast process id to int to be compatible with tracing. | 
|  | data->SetInteger("processId", static_cast<int>(process_handle.Pid())); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::optional<base::trace_event::MemoryDumpLevelOfDetail> | 
|  | StringToMemoryDumpLevelOfDetail(const std::string& str) { | 
|  | if (str == Tracing::MemoryDumpLevelOfDetailEnum::Detailed) | 
|  | return {base::trace_event::MemoryDumpLevelOfDetail::kDetailed}; | 
|  | if (str == Tracing::MemoryDumpLevelOfDetailEnum::Background) | 
|  | return {base::trace_event::MemoryDumpLevelOfDetail::kBackground}; | 
|  | if (str == Tracing::MemoryDumpLevelOfDetailEnum::Light) | 
|  | return {base::trace_event::MemoryDumpLevelOfDetail::kLight}; | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | void AddPidsToProcessFilter( | 
|  | const std::unordered_set<base::ProcessId>& included_process_ids, | 
|  | perfetto::TraceConfig& trace_config) { | 
|  | const std::string kDataSourceName = kTrackEventDataSourceName; | 
|  | for (auto& data_source : *(trace_config.mutable_data_sources())) { | 
|  | auto* source_config = data_source.mutable_config(); | 
|  | if (source_config->name() == kDataSourceName) { | 
|  | for (auto& enabled_pid : included_process_ids) { | 
|  | *data_source.add_producer_name_filter() = base::StrCat( | 
|  | {tracing::mojom::kPerfettoProducerNamePrefix, | 
|  | base::NumberToString(static_cast<uint32_t>(enabled_pid))}); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool IsChromeDataSource(const std::string& data_source_name) { | 
|  | return base::StartsWith(data_source_name, "org.chromium.") || | 
|  | data_source_name == "track_event"; | 
|  | } | 
|  |  | 
|  | std::optional<perfetto::BackendType> GetBackendTypeFromParameters( | 
|  | const std::string& tracing_backend, | 
|  | perfetto::TraceConfig& perfetto_config) { | 
|  | if (tracing_backend == Tracing::TracingBackendEnum::Chrome) | 
|  | return perfetto::BackendType::kCustomBackend; | 
|  | if (tracing_backend == Tracing::TracingBackendEnum::System) | 
|  | return perfetto::BackendType::kSystemBackend; | 
|  | if (tracing_backend == Tracing::TracingBackendEnum::Auto) { | 
|  | // Use the Chrome backend by default, unless there are non-Chrome data | 
|  | // sources specified in the config. | 
|  | for (auto& data_source : *(perfetto_config.mutable_data_sources())) { | 
|  | auto* source_config = data_source.mutable_config(); | 
|  | if (!IsChromeDataSource(source_config->name())) | 
|  | return perfetto::BackendType::kSystemBackend; | 
|  | } | 
|  | return perfetto::BackendType::kCustomBackend; | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | // Perfetto SDK build expects track_event data source to be configured via | 
|  | // track_event_config. But some devtools users (e.g. Perfetto UI) send | 
|  | // a chrome_config instead. We build a track_event_config based on the | 
|  | // chrome_config if no other track_event data sources have been configured. | 
|  | void ConvertToTrackEventConfigIfNeeded(perfetto::TraceConfig& trace_config) { | 
|  | for (const auto& data_source : trace_config.data_sources()) { | 
|  | if (!data_source.config().track_event_config_raw().empty()) { | 
|  | return; | 
|  | } | 
|  | } | 
|  | for (auto& data_source : *trace_config.mutable_data_sources()) { | 
|  | if (data_source.config().name() == | 
|  | tracing::mojom::kTraceEventDataSourceName && | 
|  | data_source.config().has_chrome_config()) { | 
|  | data_source.mutable_config()->set_name(kTrackEventDataSourceName); | 
|  | base::trace_event::TraceConfig base_config( | 
|  | data_source.config().chrome_config().trace_config()); | 
|  | bool privacy_filtering_enabled = | 
|  | data_source.config().chrome_config().privacy_filtering_enabled(); | 
|  | data_source.mutable_config()->set_track_event_config_raw( | 
|  | base_config.ToPerfettoTrackEventConfigRaw(privacy_filtering_enabled)); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // We currently don't support concurrent tracing sessions, but are planning to. | 
|  | // For the time being, we're using this flag as a workaround to prevent devtools | 
|  | // users from accidentally starting two concurrent sessions. | 
|  | // TODO(eseckler): Remove once we add support for concurrent sessions to the | 
|  | // perfetto backend. | 
|  | static bool g_any_agent_tracing = false; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class TracingHandler::PerfettoTracingSession { | 
|  | public: | 
|  | PerfettoTracingSession(bool use_proto, perfetto::BackendType backend_type) | 
|  | : use_proto_format_(use_proto), backend_type_(backend_type) {} | 
|  |  | 
|  | ~PerfettoTracingSession() { g_any_agent_tracing = false; } | 
|  |  | 
|  | void EnableTracing(const perfetto::TraceConfig& perfetto_config, | 
|  | base::OnceCallback<void(const std::string& /*error_msg*/)> | 
|  | start_callback) { | 
|  | DCHECK(!tracing_session_); | 
|  | DCHECK(!tracing_active_); | 
|  |  | 
|  | g_any_agent_tracing = true; | 
|  | tracing_active_ = true; | 
|  | start_callback_ = std::move(start_callback); | 
|  |  | 
|  | #if DCHECK_IS_ON() | 
|  | last_perfetto_config_ = perfetto_config; | 
|  | for (auto& data_source : *(last_perfetto_config_.mutable_data_sources())) { | 
|  | data_source.clear_producer_name_filter(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | tracing_session_ = perfetto::Tracing::NewTrace(backend_type_); | 
|  | tracing_session_->Setup(perfetto_config); | 
|  |  | 
|  | auto weak_ptr = weak_factory_.GetWeakPtr(); | 
|  |  | 
|  | tracing_session_->SetOnStartCallback([weak_ptr] { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&PerfettoTracingSession::OnTracingSessionStarted, | 
|  | weak_ptr)); | 
|  | }); | 
|  |  | 
|  | tracing_session_->SetOnErrorCallback( | 
|  | [weak_ptr](perfetto::TracingError error) { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&PerfettoTracingSession::OnTracingSessionFailed, | 
|  | weak_ptr, error.message)); | 
|  | }); | 
|  |  | 
|  | tracing_session_->Start(); | 
|  | } | 
|  |  | 
|  | void AdoptStartupTracingSession( | 
|  | const perfetto::TraceConfig& perfetto_config) { | 
|  | // Start a perfetto tracing session, which will claim startup tracing data. | 
|  | DCHECK(!TracingController::GetInstance()->IsTracing()); | 
|  | waiting_for_startup_tracing_enabled_ = true; | 
|  | EnableTracing( | 
|  | perfetto_config, | 
|  | base::BindOnce(&PerfettoTracingSession::OnStartupTracingEnabled, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ChangeTraceConfig(const perfetto::TraceConfig& perfetto_config) { | 
|  | if (!tracing_session_) | 
|  | return; | 
|  |  | 
|  | #if DCHECK_IS_ON() | 
|  | // Ensure that the process filter is the only thing that gets changed | 
|  | // in a configuration during a tracing session. | 
|  | perfetto::TraceConfig config_without_filters = perfetto_config; | 
|  | for (auto& data_source : *(config_without_filters.mutable_data_sources())) { | 
|  | data_source.clear_producer_name_filter(); | 
|  | } | 
|  | DCHECK(config_without_filters == last_perfetto_config_); | 
|  | last_perfetto_config_ = std::move(config_without_filters); | 
|  | #endif | 
|  |  | 
|  | tracing_session_->ChangeTraceConfig(perfetto_config); | 
|  | } | 
|  |  | 
|  | void DisableTracing( | 
|  | scoped_refptr<TracingController::TraceDataEndpoint> endpoint) { | 
|  | DCHECK(endpoint); | 
|  | if (waiting_for_startup_tracing_enabled_) { | 
|  | pending_disable_tracing_task_ = | 
|  | base::BindOnce(&PerfettoTracingSession::DisableTracing, | 
|  | base::Unretained(this), std::move(endpoint)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | endpoint_ = endpoint; | 
|  | tracing_active_ = false; | 
|  |  | 
|  | if (!tracing_session_) { | 
|  | endpoint_->ReceivedTraceFinalContents(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto weak_ptr = weak_factory_.GetWeakPtr(); | 
|  | tracing_session_->SetOnStopCallback([weak_ptr] { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&PerfettoTracingSession::OnTracingSessionStopped, | 
|  | weak_ptr)); | 
|  | }); | 
|  |  | 
|  | tracing_session_->Stop(); | 
|  | } | 
|  |  | 
|  | void GetBufferUsage(base::OnceCallback<void(bool success, | 
|  | float percent_full, | 
|  | size_t approximate_event_count)> | 
|  | on_buffer_usage_callback) { | 
|  | DCHECK(on_buffer_usage_callback); | 
|  | if (!tracing_session_) { | 
|  | std::move(on_buffer_usage_callback).Run(false, 0.0f, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (on_buffer_usage_callback_) { | 
|  | // We only support one concurrent buffer usage request. | 
|  | std::move(on_buffer_usage_callback).Run(false, 0.0f, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | on_buffer_usage_callback_ = std::move(on_buffer_usage_callback); | 
|  | auto weak_ptr = weak_factory_.GetWeakPtr(); | 
|  |  | 
|  | tracing_session_->GetTraceStats( | 
|  | [weak_ptr](perfetto::TracingSession::GetTraceStatsCallbackArgs args) { | 
|  | tracing::ReadTraceStats( | 
|  | args, | 
|  | base::BindOnce(&PerfettoTracingSession::OnBufferUsage, weak_ptr), | 
|  | GetUIThreadTaskRunner({})); | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool HasTracingFailed() { return tracing_active_ && !tracing_session_; } | 
|  |  | 
|  | bool HasDataLossOccurred() { return data_loss_; } | 
|  |  | 
|  | private: | 
|  | void OnStartupTracingEnabled(const std::string& error_msg) { | 
|  | DCHECK(error_msg.empty()); | 
|  | waiting_for_startup_tracing_enabled_ = false; | 
|  | if (pending_disable_tracing_task_) | 
|  | std::move(pending_disable_tracing_task_).Run(); | 
|  | } | 
|  |  | 
|  | void OnTracingSessionStarted() { | 
|  | if (start_callback_) | 
|  | std::move(start_callback_).Run(/*error_msg=*/std::string()); | 
|  | } | 
|  |  | 
|  | void OnTracingSessionFailed(std::string error_msg) { | 
|  | tracing_session_.reset(); | 
|  |  | 
|  | if (start_callback_) | 
|  | std::move(start_callback_).Run(error_msg); | 
|  |  | 
|  | if (pending_disable_tracing_task_) { | 
|  | // Will call endpoint_->ReceivedTraceFinalContents() and delete |this|. | 
|  | std::move(pending_disable_tracing_task_).Run(); | 
|  | } else if (endpoint_) { | 
|  | // Will delete |this|. | 
|  | endpoint_->ReceivedTraceFinalContents(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnTracingSessionStopped() { | 
|  | DCHECK(tracing_session_); | 
|  |  | 
|  | auto weak_ptr = weak_factory_.GetWeakPtr(); | 
|  |  | 
|  | if (use_proto_format_) { | 
|  | tracing_session_->ReadTrace( | 
|  | [weak_ptr](perfetto::TracingSession::ReadTraceCallbackArgs args) { | 
|  | tracing::ReadTraceAsProtobuf( | 
|  | args, | 
|  | base::BindOnce(&PerfettoTracingSession::OnTraceData, weak_ptr), | 
|  | base::BindOnce(&PerfettoTracingSession::OnTraceDataComplete, | 
|  | weak_ptr), | 
|  | GetUIThreadTaskRunner({})); | 
|  | }); | 
|  | } else { | 
|  | // Ref-counted because it is used within the lambda running on the | 
|  | // perfetto SDK's thread. | 
|  | auto tokenizer = base::MakeRefCounted< | 
|  | base::RefCountedData<std::unique_ptr<tracing::TracePacketTokenizer>>>( | 
|  | std::make_unique<tracing::TracePacketTokenizer>()); | 
|  | tracing_session_->ReadTrace( | 
|  | [weak_ptr, | 
|  | tokenizer](perfetto::TracingSession::ReadTraceCallbackArgs args) { | 
|  | tracing::ReadTraceAsJson( | 
|  | args, tokenizer, | 
|  | base::BindOnce(&PerfettoTracingSession::OnTraceData, weak_ptr), | 
|  | base::BindOnce(&PerfettoTracingSession::OnTraceDataComplete, | 
|  | weak_ptr), | 
|  | GetUIThreadTaskRunner({})); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnBufferUsage(bool success, float percent_full, bool data_loss) { | 
|  | data_loss_ |= data_loss; | 
|  | if (on_buffer_usage_callback_) { | 
|  | std::move(on_buffer_usage_callback_).Run(success, percent_full, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnTraceData(std::unique_ptr<std::string> data) { | 
|  | endpoint_->ReceiveTraceChunk(std::move(data)); | 
|  | } | 
|  |  | 
|  | void OnTraceDataComplete() { | 
|  | // Request stats to check if data loss occurred. | 
|  | GetBufferUsage(base::BindOnce(&PerfettoTracingSession::OnFinalBufferUsage, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void OnFinalBufferUsage(bool success, | 
|  | float percent_full, | 
|  | size_t approximate_event_count) { | 
|  | // Will delete |this|. | 
|  | endpoint_->ReceivedTraceFinalContents(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<perfetto::TracingSession> tracing_session_; | 
|  | const bool use_proto_format_; | 
|  | perfetto::BackendType backend_type_ = | 
|  | perfetto::BackendType::kUnspecifiedBackend; | 
|  | base::OnceCallback<void(const std::string&)> start_callback_; | 
|  | base::OnceCallback< | 
|  | void(bool success, float percent_full, size_t approximate_event_count)> | 
|  | on_buffer_usage_callback_; | 
|  | base::OnceClosure pending_disable_tracing_task_; | 
|  | bool waiting_for_startup_tracing_enabled_ = false; | 
|  | scoped_refptr<TracingController::TraceDataEndpoint> endpoint_; | 
|  | bool tracing_active_ = false; | 
|  | bool data_loss_ = false; | 
|  |  | 
|  | #if DCHECK_IS_ON() | 
|  | perfetto::TraceConfig last_perfetto_config_; | 
|  | #endif | 
|  |  | 
|  | base::WeakPtrFactory<PerfettoTracingSession> weak_factory_{this}; | 
|  | }; | 
|  |  | 
|  | TracingHandler::TracingHandler(DevToolsAgentHostImpl* host, | 
|  | DevToolsIOContext* io_context, | 
|  | DevToolsSession* root_session) | 
|  | : DevToolsDomainHandler(Tracing::Metainfo::domainName), | 
|  | io_context_(io_context), | 
|  | host_(host), | 
|  | session_for_process_filter_(root_session), | 
|  | did_initiate_recording_(false), | 
|  | return_as_stream_(false), | 
|  | gzip_compression_(false), | 
|  | buffer_usage_reporting_interval_(0) { | 
|  | video_consumer_ = std::make_unique<DevToolsVideoConsumer>(base::BindRepeating( | 
|  | &TracingHandler::OnFrameFromVideoConsumer, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | TracingHandler::~TracingHandler() = default; | 
|  |  | 
|  | // static | 
|  | std::vector<TracingHandler*> TracingHandler::ForAgentHost( | 
|  | DevToolsAgentHostImpl* host) { | 
|  | return host->HandlersByName<TracingHandler>(Tracing::Metainfo::domainName); | 
|  | } | 
|  |  | 
|  | void TracingHandler::Wire(UberDispatcher* dispatcher) { | 
|  | frontend_ = std::make_unique<Tracing::Frontend>(dispatcher->channel()); | 
|  | Tracing::Dispatcher::wire(dispatcher, this); | 
|  | } | 
|  |  | 
|  | Response TracingHandler::Disable() { | 
|  | if (session_) | 
|  | StopTracing(nullptr); | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | class TracingNotification : public crdtp::Serializable { | 
|  | public: | 
|  | explicit TracingNotification(std::string json) : json_(std::move(json)) {} | 
|  |  | 
|  | void AppendSerialized(std::vector<uint8_t>* out) const override { | 
|  | crdtp::Status status = | 
|  | crdtp::json::ConvertJSONToCBOR(crdtp::SpanFrom(json_), out); | 
|  | DCHECK(status.ok()) << status.ToASCIIString(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::string json_; | 
|  | }; | 
|  | }  // namespace | 
|  |  | 
|  | void TracingHandler::OnTraceDataCollected( | 
|  | std::unique_ptr<std::string> trace_fragment) { | 
|  | const std::string valid_trace_fragment = | 
|  | UpdateTraceDataBuffer(*trace_fragment); | 
|  | if (valid_trace_fragment.empty()) | 
|  | return; | 
|  |  | 
|  | // Hand-craft protocol notification message so we can substitute JSON | 
|  | // that we already got as string as a bare object, not a quoted string. | 
|  | std::string message( | 
|  | "{ \"method\": \"Tracing.dataCollected\", \"params\": { \"value\": ["); | 
|  | const size_t messageSuffixSize = 10; | 
|  | message.reserve(message.size() + valid_trace_fragment.size() + | 
|  | messageSuffixSize - trace_data_buffer_state_.offset); | 
|  | message.append(valid_trace_fragment, trace_data_buffer_state_.offset); | 
|  | message += "] } }"; | 
|  |  | 
|  | frontend_->sendRawNotification( | 
|  | std::make_unique<TracingNotification>(std::move(message))); | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnTraceComplete() { | 
|  | if (!trace_data_buffer_state_.data.empty()) | 
|  | OnTraceDataCollected(std::make_unique<std::string>("")); | 
|  |  | 
|  | DCHECK(trace_data_buffer_state_.data.empty()); | 
|  | DCHECK_EQ(0u, trace_data_buffer_state_.pos); | 
|  | DCHECK_EQ(0, trace_data_buffer_state_.open_braces); | 
|  | DCHECK(!trace_data_buffer_state_.in_string); | 
|  | DCHECK(!trace_data_buffer_state_.slashed); | 
|  |  | 
|  | bool data_loss = session_->HasDataLossOccurred(); | 
|  | process_set_monitor_.reset(); | 
|  | session_.reset(); | 
|  | frontend_->TracingComplete(data_loss); | 
|  | } | 
|  |  | 
|  | std::string TracingHandler::UpdateTraceDataBuffer( | 
|  | const std::string& trace_fragment) { | 
|  | size_t end = 0; | 
|  | size_t last_open = 0; | 
|  | TraceDataBufferState& state = trace_data_buffer_state_; | 
|  | state.offset = 0; | 
|  | bool update_offset = state.open_braces == 0; | 
|  | for (; state.pos < trace_fragment.size(); ++state.pos) { | 
|  | char c = trace_fragment[state.pos]; | 
|  | switch (c) { | 
|  | case '{': | 
|  | if (!state.in_string && !state.slashed) { | 
|  | state.open_braces++; | 
|  | if (state.open_braces == 1) { | 
|  | last_open = state.data.size() + state.pos; | 
|  | if (update_offset) { | 
|  | state.offset = last_open; | 
|  | update_offset = false; | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | case '}': | 
|  | if (!state.in_string && !state.slashed) { | 
|  | DCHECK_GT(state.open_braces, 0); | 
|  | state.open_braces--; | 
|  | if (state.open_braces == 0) | 
|  | end = state.data.size() + state.pos + 1; | 
|  | } | 
|  | break; | 
|  | case '"': | 
|  | if (!state.slashed) | 
|  | state.in_string = !state.in_string; | 
|  | break; | 
|  | case 'u': | 
|  | if (state.slashed) | 
|  | state.pos += 4; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (state.in_string && c == '\\') { | 
|  | state.slashed = !state.slashed; | 
|  | } else { | 
|  | state.slashed = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Next starting position is usually 0 except when we are in the middle of | 
|  | // processing a unicode character, i.e. \uxxxx. | 
|  | state.pos -= trace_fragment.size(); | 
|  |  | 
|  | std::string complete_str = state.data + trace_fragment; | 
|  | state.data = complete_str.substr(std::max(end, last_open)); | 
|  |  | 
|  | complete_str.resize(end); | 
|  | return complete_str; | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnTraceToStreamComplete(const std::string& stream_handle) { | 
|  | bool data_loss = session_->HasDataLossOccurred(); | 
|  | process_set_monitor_.reset(); | 
|  | session_.reset(); | 
|  | std::string stream_format = (proto_format_ ? Tracing::StreamFormatEnum::Proto | 
|  | : Tracing::StreamFormatEnum::Json); | 
|  | std::string stream_compression = | 
|  | (gzip_compression_ ? Tracing::StreamCompressionEnum::Gzip | 
|  | : Tracing::StreamCompressionEnum::None); | 
|  | frontend_->TracingComplete(data_loss, stream_handle, stream_format, | 
|  | stream_compression); | 
|  | } | 
|  |  | 
|  | void TracingHandler::Start( | 
|  | std::optional<std::string> categories, | 
|  | std::optional<std::string> options, | 
|  | std::optional<double> buffer_usage_reporting_interval, | 
|  | std::optional<std::string> transfer_mode, | 
|  | std::optional<std::string> transfer_format, | 
|  | std::optional<std::string> transfer_compression, | 
|  | std::unique_ptr<Tracing::TraceConfig> config, | 
|  | std::optional<Binary> perfetto_config, | 
|  | std::optional<std::string> tracing_backend, | 
|  | std::unique_ptr<StartCallback> callback) { | 
|  | bool return_as_stream = transfer_mode.value_or("") == | 
|  | Tracing::Start::TransferModeEnum::ReturnAsStream; | 
|  | bool gzip_compression = | 
|  | transfer_compression.value_or("") == Tracing::StreamCompressionEnum::Gzip; | 
|  | bool proto_format = | 
|  | transfer_format.value_or("") == Tracing::StreamFormatEnum::Proto; | 
|  |  | 
|  | perfetto::TraceConfig trace_config; | 
|  | if (perfetto_config.has_value()) { | 
|  | bool parsed = trace_config.ParseFromArray(perfetto_config.value().data(), | 
|  | perfetto_config.value().size()); | 
|  | if (!parsed) { | 
|  | callback->sendFailure(Response::InvalidParams( | 
|  | "Couldn't parse the supplied perfettoConfig.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!trace_config.data_sources_size()) { | 
|  | callback->sendFailure(Response::InvalidParams( | 
|  | "Supplied perfettoConfig doesn't have any data sources specified")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Default to proto format for perfettoConfig, except if it specifies | 
|  | // convert_to_legacy_json in the data source config. | 
|  | proto_format = true; | 
|  | for (const auto& data_source : trace_config.data_sources()) { | 
|  | if (data_source.config().has_chrome_config() && | 
|  | data_source.config().chrome_config().convert_to_legacy_json()) { | 
|  | proto_format = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | ConvertToTrackEventConfigIfNeeded(trace_config); | 
|  | } else { | 
|  | base::trace_event::TraceConfig browser_config = | 
|  | base::trace_event::TraceConfig(); | 
|  | if (config) { | 
|  | base::Value::Dict dict; | 
|  | CHECK(crdtp::ConvertProtocolValue(*config, &dict)); | 
|  | browser_config = | 
|  | GetTraceConfigFromDevToolsConfig(base::Value(std::move(dict))); | 
|  | } else if (categories.has_value() || options.has_value()) { | 
|  | browser_config = base::trace_event::TraceConfig(categories.value_or(""), | 
|  | options.value_or("")); | 
|  | } | 
|  | trace_config = CreatePerfettoConfiguration(browser_config, return_as_stream, | 
|  | proto_format); | 
|  | } | 
|  |  | 
|  | std::optional<perfetto::BackendType> backend = GetBackendTypeFromParameters( | 
|  | tracing_backend.value_or(Tracing::TracingBackendEnum::Auto), | 
|  | trace_config); | 
|  |  | 
|  | if (!backend) { | 
|  | callback->sendFailure(Response::InvalidParams( | 
|  | "Unsupported value for tracing_backend parameter.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Check if we should adopt the startup tracing session. Only the first | 
|  | // Tracing.start() sent to the browser endpoint can adopt it. | 
|  | // TODO(crbug.com/40171330): Add tests for system-controlled startup traces. | 
|  | AttemptAdoptStartupSession(return_as_stream, gzip_compression, proto_format, | 
|  | *backend); | 
|  |  | 
|  | if (IsTracing()) { | 
|  | callback->sendFailure(Response::ServerError( | 
|  | "Tracing has already been started (possibly in another tab).")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (did_initiate_recording_) { | 
|  | callback->sendFailure(Response::ServerError( | 
|  | "Starting trace recording is already in progress")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (config && (categories.has_value() || options.has_value())) { | 
|  | callback->sendFailure(Response::InvalidParams( | 
|  | "Either trace config (preferred), or categories+options should be " | 
|  | "specified, but not both.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (proto_format && !return_as_stream) { | 
|  | callback->sendFailure(Response::InvalidParams( | 
|  | "Proto format is only supported when using stream transfer mode.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | return_as_stream_ = return_as_stream; | 
|  | gzip_compression_ = gzip_compression; | 
|  | proto_format_ = proto_format; | 
|  | buffer_usage_reporting_interval_ = | 
|  | buffer_usage_reporting_interval.value_or(0); | 
|  | did_initiate_recording_ = true; | 
|  | trace_config_ = std::move(trace_config); | 
|  |  | 
|  | if (session_for_process_filter_) { | 
|  | process_set_monitor_ = TracingProcessSetMonitor::Start( | 
|  | *session_for_process_filter_, | 
|  | base::BindRepeating(&TracingHandler::AddProcessToFilter, | 
|  | base::Unretained(this))); | 
|  | std::unordered_set<base::ProcessId> pids = process_set_monitor_->GetPids(); | 
|  |  | 
|  | base::ProcessId browser_pid = base::Process::Current().Pid(); | 
|  | pids.insert(browser_pid); | 
|  | if (auto* gpu_process_host = | 
|  | GpuProcessHost::Get(GPU_PROCESS_KIND_SANDBOXED, | 
|  | /* force_create */ false)) { | 
|  | base::ProcessId gpu_pid = gpu_process_host->process_id(); | 
|  | if (gpu_pid != base::kNullProcessId) { | 
|  | pids.insert(gpu_pid); | 
|  | } | 
|  | } | 
|  | AddPidsToProcessFilter(pids, trace_config_); | 
|  | } | 
|  |  | 
|  | session_ = std::make_unique<PerfettoTracingSession>(proto_format_, *backend); | 
|  | session_->EnableTracing( | 
|  | trace_config_, | 
|  | base::BindOnce(&TracingHandler::OnRecordingEnabled, | 
|  | weak_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  |  | 
|  | perfetto::TraceConfig TracingHandler::CreatePerfettoConfiguration( | 
|  | const base::trace_event::TraceConfig& browser_config, | 
|  | bool return_as_stream, | 
|  | bool proto_format) { | 
|  | return tracing::GetDefaultPerfettoConfig( | 
|  | browser_config, | 
|  | /*privacy_filtering_enabled=*/false, | 
|  | /*convert_to_legacy_json=*/!proto_format, | 
|  | /*json_agent_label_filter*/ | 
|  | (proto_format || return_as_stream) | 
|  | ? "" | 
|  | : tracing::mojom::kChromeTraceEventLabel); | 
|  | } | 
|  |  | 
|  | void TracingHandler::AddProcessToFilter(base::ProcessId pid) { | 
|  | CHECK(did_initiate_recording_); | 
|  | CHECK(session_); | 
|  | AddPidsToProcessFilter({pid}, trace_config_); | 
|  | session_->ChangeTraceConfig(trace_config_); | 
|  | } | 
|  |  | 
|  | void TracingHandler::AttemptAdoptStartupSession( | 
|  | bool return_as_stream, | 
|  | bool gzip_compression, | 
|  | bool proto_format, | 
|  | perfetto::BackendType tracing_backend) { | 
|  | // Only adopt startup session for browser-level sessions. | 
|  | if (session_for_process_filter_) { | 
|  | return; | 
|  | } | 
|  | auto& startup_config = tracing::TraceStartupConfig::GetInstance(); | 
|  | if (!startup_config.AttemptAdoptBySessionOwner( | 
|  | tracing::TraceStartupConfig::SessionOwner::kDevToolsTracingHandler)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | return_as_stream_ = return_as_stream; | 
|  | gzip_compression_ = gzip_compression; | 
|  | proto_format_ = proto_format; | 
|  |  | 
|  | perfetto::TraceConfig perfetto_config = | 
|  | tracing::TraceStartupConfig::GetInstance().GetPerfettoConfig(); | 
|  |  | 
|  | session_ = | 
|  | std::make_unique<PerfettoTracingSession>(proto_format_, tracing_backend); | 
|  | session_->AdoptStartupTracingSession(perfetto_config); | 
|  | } | 
|  |  | 
|  | Response TracingHandler::End() { | 
|  | if (!session_) { | 
|  | did_initiate_recording_ = false; | 
|  | return Response::ServerError("Tracing is not started"); | 
|  | } | 
|  |  | 
|  | if (session_->HasTracingFailed()) | 
|  | return Response::ServerError("Tracing failed"); | 
|  |  | 
|  | scoped_refptr<TracingController::TraceDataEndpoint> endpoint; | 
|  | if (return_as_stream_) { | 
|  | endpoint = new DevToolsStreamEndpoint( | 
|  | weak_factory_.GetWeakPtr(), | 
|  | DevToolsStreamFile::Create( | 
|  | io_context_, gzip_compression_ || proto_format_ /* binary */)); | 
|  | if (gzip_compression_) { | 
|  | endpoint = TracingControllerImpl::CreateCompressedStringEndpoint( | 
|  | endpoint, true /* compress_with_background_priority */); | 
|  | } | 
|  | } else { | 
|  | // Reset the trace data buffer state. | 
|  | trace_data_buffer_state_ = TracingHandler::TraceDataBufferState(); | 
|  | endpoint = new DevToolsTraceEndpointProxy(weak_factory_.GetWeakPtr()); | 
|  | } | 
|  |  | 
|  | StopTracing(endpoint); | 
|  |  | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | void TracingHandler::GetCategories( | 
|  | std::unique_ptr<GetCategoriesCallback> callback) { | 
|  | // TODO(eseckler): Support this via the perfetto service too. | 
|  | TracingController::GetInstance()->GetCategories( | 
|  | base::BindOnce(&TracingHandler::OnCategoriesReceived, | 
|  | weak_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnRecordingEnabled(std::unique_ptr<StartCallback> callback, | 
|  | const std::string& error_msg) { | 
|  | if (!error_msg.empty()) { | 
|  | callback->sendFailure(Response::ServerError(error_msg)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!did_initiate_recording_) { | 
|  | callback->sendFailure(Response::ServerError( | 
|  | "Tracing was stopped before start has been completed.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | EmitFrameTree(); | 
|  | callback->sendSuccess(); | 
|  |  | 
|  | SetupTimer(buffer_usage_reporting_interval_); | 
|  |  | 
|  | bool screenshot_enabled; | 
|  | TRACE_EVENT_CATEGORY_GROUP_ENABLED( | 
|  | TRACE_DISABLED_BY_DEFAULT("devtools.screenshot"), &screenshot_enabled); | 
|  | if (screenshot_enabled) { | 
|  | // Reset number of screenshots received, each time tracing begins. | 
|  | number_of_screenshots_from_video_consumer_ = 0; | 
|  | if (WebContents* wc = host_ ? host_->GetWebContents() : nullptr) { | 
|  | auto* frame_host = | 
|  | static_cast<RenderFrameHostImpl*>(wc->GetPrimaryMainFrame()); | 
|  | video_consumer_->SetFrameSinkId( | 
|  | frame_host->GetRenderWidgetHost()->GetFrameSinkId()); | 
|  | } | 
|  | video_consumer_->SetMinAndMaxFrameSize(kMinFrameSize, kMaxFrameSize); | 
|  | video_consumer_->StartCapture(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnBufferUsage(bool success, | 
|  | float percent_full, | 
|  | size_t approximate_event_count) { | 
|  | if (!did_initiate_recording_) | 
|  | return; | 
|  |  | 
|  | if (!success) | 
|  | return; | 
|  |  | 
|  | // TODO(crbug426117): remove set_value once all clients have switched to | 
|  | // the new interface of the event. | 
|  | frontend_->BufferUsage(percent_full, approximate_event_count, percent_full); | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnCategoriesReceived( | 
|  | std::unique_ptr<GetCategoriesCallback> callback, | 
|  | const std::set<std::string>& category_set) { | 
|  | auto categories = std::make_unique<protocol::Array<std::string>>( | 
|  | category_set.begin(), category_set.end()); | 
|  | callback->sendSuccess(std::move(categories)); | 
|  | } | 
|  |  | 
|  | void TracingHandler::RequestMemoryDump( | 
|  | std::optional<bool> deterministic, | 
|  | std::optional<std::string> level_of_detail, | 
|  | std::unique_ptr<RequestMemoryDumpCallback> callback) { | 
|  | if (!IsTracing()) { | 
|  | callback->sendFailure(Response::ServerError("Tracing is not started")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::optional<base::trace_event::MemoryDumpLevelOfDetail> memory_detail = | 
|  | StringToMemoryDumpLevelOfDetail(level_of_detail.value_or( | 
|  | Tracing::MemoryDumpLevelOfDetailEnum::Detailed)); | 
|  |  | 
|  | if (!memory_detail) { | 
|  | callback->sendFailure( | 
|  | Response::ServerError("Invalid levelOfDetail specified.")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto determinism = deterministic.value_or(false) | 
|  | ? base::trace_event::MemoryDumpDeterminism::kForceGc | 
|  | : base::trace_event::MemoryDumpDeterminism::kNone; | 
|  |  | 
|  | auto on_memory_dump_finished = | 
|  | base::BindOnce(&TracingHandler::OnMemoryDumpFinished, | 
|  | weak_factory_.GetWeakPtr(), std::move(callback)); | 
|  |  | 
|  | memory_instrumentation::MemoryInstrumentation::GetInstance() | 
|  | ->RequestGlobalDumpAndAppendToTrace( | 
|  | base::trace_event::MemoryDumpType::kExplicitlyTriggered, | 
|  | *memory_detail, determinism, std::move(on_memory_dump_finished)); | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnMemoryDumpFinished( | 
|  | std::unique_ptr<RequestMemoryDumpCallback> callback, | 
|  | bool success, | 
|  | uint64_t dump_id) { | 
|  | callback->sendSuccess(base::StringPrintf("0x%" PRIx64, dump_id), success); | 
|  | } | 
|  |  | 
|  | void TracingHandler::OnFrameFromVideoConsumer( | 
|  | scoped_refptr<media::VideoFrame> frame) { | 
|  | if (!IsTracing()) { | 
|  | return; | 
|  | } | 
|  | const SkBitmap skbitmap = DevToolsVideoConsumer::GetSkBitmapFromFrame(frame); | 
|  | // This reference_time is an ESTIMATE. It is set by the compositor frame sink | 
|  | // from the `expected_display_time`, which is based on a previously known | 
|  | // frame start PLUS the vsync interval (eg 16.6ms) | 
|  | base::TimeTicks expected_display_time = *frame->metadata().reference_time; | 
|  |  | 
|  | uint64_t frame_sequence = *frame->metadata().frame_sequence; | 
|  | uint64_t source_id = *frame->metadata().source_id; | 
|  |  | 
|  | TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("devtools.screenshot"), | 
|  | "Screenshot", "expected_display_time", | 
|  | expected_display_time, "frame_sequence", frame_sequence, | 
|  | "source_id", source_id, "snapshot", | 
|  | std::make_unique<DevToolsTraceableScreenshot>(skbitmap)); | 
|  |  | 
|  | ++number_of_screenshots_from_video_consumer_; | 
|  | DCHECK(video_consumer_); | 
|  | if (number_of_screenshots_from_video_consumer_ >= | 
|  | DevToolsTraceableScreenshot::kMaximumNumberOfScreenshots) { | 
|  | video_consumer_->StopCapture(); | 
|  | } | 
|  | } | 
|  |  | 
|  | Response TracingHandler::RecordClockSyncMarker(const std::string& sync_id) { | 
|  | if (!IsTracing()) | 
|  | return Response::ServerError("Tracing is not started"); | 
|  | TRACE_EVENT_CLOCK_SYNC_RECEIVER(sync_id); | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | void TracingHandler::SetupTimer(double usage_reporting_interval) { | 
|  | if (usage_reporting_interval == 0) | 
|  | return; | 
|  |  | 
|  | if (usage_reporting_interval < kMinimumReportingInterval) | 
|  | usage_reporting_interval = kMinimumReportingInterval; | 
|  |  | 
|  | base::TimeDelta interval = | 
|  | base::Milliseconds(std::ceil(usage_reporting_interval)); | 
|  | buffer_usage_poll_timer_ = std::make_unique<base::RepeatingTimer>(); | 
|  | buffer_usage_poll_timer_->Start( | 
|  | FROM_HERE, interval, | 
|  | base::BindRepeating(&TracingHandler::UpdateBufferUsage, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void TracingHandler::UpdateBufferUsage() { | 
|  | session_->GetBufferUsage(base::BindOnce(&TracingHandler::OnBufferUsage, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void TracingHandler::StopTracing( | 
|  | const scoped_refptr<TracingController::TraceDataEndpoint>& endpoint) { | 
|  | DCHECK(session_); | 
|  | buffer_usage_poll_timer_.reset(); | 
|  | process_set_monitor_.reset(); | 
|  | if (endpoint) { | 
|  | // Will delete |session_|. | 
|  | session_->DisableTracing(std::move(endpoint)); | 
|  | } else { | 
|  | session_.reset(); | 
|  | } | 
|  | did_initiate_recording_ = false; | 
|  | video_consumer_->StopCapture(); | 
|  | } | 
|  |  | 
|  | bool TracingHandler::IsTracing() const { | 
|  | return TracingController::GetInstance()->IsTracing() || g_any_agent_tracing; | 
|  | } | 
|  |  | 
|  | void TracingHandler::EmitFrameTree() { | 
|  | auto data = std::make_unique<base::trace_event::TracedValue>(); | 
|  | if (WebContents* wc = host_ ? host_->GetWebContents() : nullptr) { | 
|  | auto* frame_host = | 
|  | static_cast<RenderFrameHostImpl*>(wc->GetPrimaryMainFrame()); | 
|  | CHECK(frame_host); | 
|  | data->SetInteger( | 
|  | "frameTreeNodeId", | 
|  | frame_host->frame_tree_node()->frame_tree_node_id().value()); | 
|  | data->SetBoolean("persistentIds", true); | 
|  | data->BeginArray("frames"); | 
|  | wc->ForEachRenderFrameHost([&data](RenderFrameHost* rfh) { | 
|  | data->BeginDictionary(); | 
|  | FillFrameData(data.get(), static_cast<RenderFrameHostImpl*>(rfh), | 
|  | rfh->GetLastCommittedURL()); | 
|  | data->EndDictionary(); | 
|  | }); | 
|  | data->EndArray(); | 
|  | } | 
|  | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), | 
|  | "TracingStartedInBrowser", TRACE_EVENT_SCOPE_THREAD, | 
|  | "data", std::move(data)); | 
|  | } | 
|  |  | 
|  | void TracingHandler::WillInitiatePrerender(FrameTreeNode* frame_tree_node) { | 
|  | if (!did_initiate_recording_) { | 
|  | return; | 
|  | } | 
|  | auto data = std::make_unique<base::trace_event::TracedValue>(); | 
|  | FillFrameData(data.get(), frame_tree_node->current_frame_host(), | 
|  | frame_tree_node->current_url()); | 
|  | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), | 
|  | "FrameCommittedInBrowser", TRACE_EVENT_SCOPE_THREAD, | 
|  | "data", std::move(data)); | 
|  | } | 
|  |  | 
|  | void TracingHandler::ReadyToCommitNavigation( | 
|  | NavigationRequest* navigation_request) { | 
|  | if (!did_initiate_recording_) | 
|  | return; | 
|  | auto data = std::make_unique<base::trace_event::TracedValue>(); | 
|  | RenderFrameHostImpl* frame_host = navigation_request->GetRenderFrameHost(); | 
|  | FillFrameData(data.get(), frame_host, navigation_request->GetURL()); | 
|  | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), | 
|  | "FrameCommittedInBrowser", TRACE_EVENT_SCOPE_THREAD, | 
|  | "data", std::move(data)); | 
|  | if (frame_host->IsOutermostMainFrame()) { | 
|  | video_consumer_->SetFrameSinkId(navigation_request->GetRenderFrameHost() | 
|  | ->GetRenderWidgetHost() | 
|  | ->GetFrameSinkId()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TracingHandler::FrameDeleted(FrameTreeNodeId frame_tree_node_id) { | 
|  | if (!did_initiate_recording_) | 
|  | return; | 
|  | FrameTreeNode* node = FrameTreeNode::GloballyFindByID(frame_tree_node_id); | 
|  |  | 
|  | if (!node->current_frame_host()) { | 
|  | // This might happen on prerendering activation when the prerender tree is | 
|  | // shutting how and the RFH is migrated to a different frame tree. | 
|  | return; | 
|  | } | 
|  | auto data = std::make_unique<base::trace_event::TracedValue>(); | 
|  | data->SetString( | 
|  | "frame", node->current_frame_host()->devtools_frame_token().ToString()); | 
|  | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), | 
|  | "FrameDeletedInBrowser", TRACE_EVENT_SCOPE_THREAD, | 
|  | "data", std::move(data)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool TracingHandler::IsStartupTracingActive() { | 
|  | return ::tracing::TraceStartupConfig::GetInstance().IsEnabled(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | base::trace_event::TraceConfig TracingHandler::GetTraceConfigFromDevToolsConfig( | 
|  | const base::Value& devtools_config) { | 
|  | base::Value config = ConvertDictKeyStyle(devtools_config); | 
|  | base::Value::Dict& config_dict = config.GetDict(); | 
|  | if (std::string* mode = config_dict.FindString(kRecordModeParam)) { | 
|  | config_dict.Set(kRecordModeParam, ConvertFromCamelCase(*mode, '-')); | 
|  | } | 
|  | if (std::optional<double> buffer_size = | 
|  | config_dict.FindDouble(kTraceBufferSizeInKb)) { | 
|  | config_dict.Set( | 
|  | kTraceBufferSizeInKb, | 
|  | static_cast<int>(base::saturated_cast<size_t>(buffer_size.value()))); | 
|  | } | 
|  | return base::trace_event::TraceConfig(config_dict); | 
|  | } | 
|  |  | 
|  | }  // namespace content::protocol |