910e62b5创建于 1月15日历史提交
// 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.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/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom-data-view.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,
    memory_instrumentation::mojom::RequestOutcome outcome,
    uint64_t dump_id) {
  callback->sendSuccess(
      base::StringPrintf("0x%" PRIx64, dump_id),
      outcome == memory_instrumentation::mojom::RequestOutcome::kSuccess);
}

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