Add SQL tables for new v8 data source

Initial parsing code mostly to deal with interned data.

Bug: b/283794416
Change-Id: I3765a5eb044469a833ad4b7d20fea513f99a52eb
diff --git a/Android.bp b/Android.bp
index 25225b3..4ed7036 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11230,6 +11230,9 @@
         "src/trace_processor/importers/proto/system_probes_module.cc",
         "src/trace_processor/importers/proto/system_probes_parser.cc",
         "src/trace_processor/importers/proto/translation_table_module.cc",
+        "src/trace_processor/importers/proto/v8_module.cc",
+        "src/trace_processor/importers/proto/v8_sequence_state.cc",
+        "src/trace_processor/importers/proto/v8_tracker.cc",
         "src/trace_processor/importers/proto/vulkan_memory_tracker.cc",
     ],
 }
@@ -11839,6 +11842,7 @@
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
+        "src/trace_processor/tables/v8_tables.py",
         "src/trace_processor/tables/winscope_tables.py",
         "tools/gen_tp_table_headers.py",
     ],
@@ -12083,6 +12087,7 @@
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
+        "src/trace_processor/tables/v8_tables.py",
         "src/trace_processor/tables/winscope_tables.py",
     ],
     tools: [
@@ -12100,6 +12105,7 @@
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
+        "src/trace_processor/tables/v8_tables_py.h",
         "src/trace_processor/tables/winscope_tables_py.h",
     ],
 }
@@ -12121,6 +12127,7 @@
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
+        "src/trace_processor/tables/v8_tables.py",
         "src/trace_processor/tables/winscope_tables.py",
         "tools/gen_tp_table_headers.py",
     ],
diff --git a/BUILD b/BUILD
index 7737bec..c8e521b 100644
--- a/BUILD
+++ b/BUILD
@@ -1639,6 +1639,12 @@
         "src/trace_processor/importers/proto/system_probes_parser.h",
         "src/trace_processor/importers/proto/translation_table_module.cc",
         "src/trace_processor/importers/proto/translation_table_module.h",
+        "src/trace_processor/importers/proto/v8_module.cc",
+        "src/trace_processor/importers/proto/v8_module.h",
+        "src/trace_processor/importers/proto/v8_sequence_state.cc",
+        "src/trace_processor/importers/proto/v8_sequence_state.h",
+        "src/trace_processor/importers/proto/v8_tracker.cc",
+        "src/trace_processor/importers/proto/v8_tracker.h",
         "src/trace_processor/importers/proto/vulkan_memory_tracker.cc",
         "src/trace_processor/importers/proto/vulkan_memory_tracker.h",
     ],
@@ -2458,6 +2464,7 @@
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
+        "src/trace_processor/tables/v8_tables.py",
         "src/trace_processor/tables/winscope_tables.py",
     ],
     outs = [
@@ -2471,6 +2478,7 @@
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
+        "src/trace_processor/tables/v8_tables_py.h",
         "src/trace_processor/tables/winscope_tables_py.h",
     ],
 )
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index f5808fd..7f8d3e0 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -73,7 +73,6 @@
     "../../../../protos/perfetto/common:zero",
     "../../../../protos/perfetto/config:zero",
     "../../../../protos/perfetto/trace:zero",
-    "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace/android:zero",
     "../../../../protos/perfetto/trace/chrome:zero",
     "../../../../protos/perfetto/trace/ftrace:zero",
@@ -141,6 +140,12 @@
     "system_probes_parser.h",
     "translation_table_module.cc",
     "translation_table_module.h",
+    "v8_module.cc",
+    "v8_module.h",
+    "v8_sequence_state.cc",
+    "v8_sequence_state.h",
+    "v8_tracker.cc",
+    "v8_tracker.h",
     "vulkan_memory_tracker.cc",
     "vulkan_memory_tracker.h",
   ]
@@ -153,15 +158,13 @@
     "../../../../include/perfetto/ext/traced:sys_stats_counters",
     "../../../../protos/perfetto/common:zero",
     "../../../../protos/perfetto/config:zero",
-    "../../../../protos/perfetto/config:zero",
-    "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace/android:zero",
+    "../../../../protos/perfetto/trace/chrome:zero",
     "../../../../protos/perfetto/trace/gpu:zero",
     "../../../../protos/perfetto/trace/interned_data:zero",
     "../../../../protos/perfetto/trace/power:zero",
     "../../../../protos/perfetto/trace/profiling:zero",
-    "../../../../protos/perfetto/trace/profiling:zero",
     "../../../../protos/perfetto/trace/ps:zero",
     "../../../../protos/perfetto/trace/statsd:zero",
     "../../../../protos/perfetto/trace/sys_stats:zero",
diff --git a/src/trace_processor/importers/proto/additional_modules.cc b/src/trace_processor/importers/proto/additional_modules.cc
index 2d3e95f..341d6d7 100644
--- a/src/trace_processor/importers/proto/additional_modules.cc
+++ b/src/trace_processor/importers/proto/additional_modules.cc
@@ -25,6 +25,7 @@
 #include "src/trace_processor/importers/proto/statsd_module.h"
 #include "src/trace_processor/importers/proto/system_probes_module.h"
 #include "src/trace_processor/importers/proto/translation_table_module.h"
+#include "src/trace_processor/importers/proto/v8_module.h"
 #include "src/trace_processor/importers/proto/winscope/winscope_module.h"
 
 namespace perfetto {
@@ -40,6 +41,7 @@
   context->modules.emplace_back(new StatsdModule(context));
   context->modules.emplace_back(new AndroidCameraEventModule(context));
   context->modules.emplace_back(new MetadataModule(context));
+  context->modules.emplace_back(new V8Module(context));
   context->modules.emplace_back(new WinscopeModule(context));
 
   // Ftrace module is special, because it has one extra method for parsing
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index d95fccc..8c29222 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -34,6 +34,17 @@
 
 class PacketSequenceState {
  public:
+  // Helper to keep per sequence state. These are not reset when the generation
+  // changes.
+  // Trackers or parsers can add their custom per sequence state here instead of
+  // keeping a map from seq_id to some internal state.
+  // TODO(carlscab): We should come up with a nicer API that allows extensions
+  // to be notified of generation changes.
+  // TODO(carlscab): There is some existing code that could use this. Migrate.
+  struct ExtensibleSequenceState {
+    std::unique_ptr<Destructible> v8_sequence_state;
+  };
+
   explicit PacketSequenceState(TraceProcessorContext* context)
       : context_(context), sequence_stack_profile_tracker_(context) {
     current_generation_.reset(
@@ -112,6 +123,10 @@
     return sequence_stack_profile_tracker_;
   }
 
+  ExtensibleSequenceState& extensible_sequence_state() {
+    return extensible_sequence_state_;
+  }
+
   // Returns a ref-counted ptr to the current generation.
   RefPtr<PacketSequenceStateGeneration> current_generation() const {
     return current_generation_;
@@ -160,6 +175,7 @@
 
   RefPtr<PacketSequenceStateGeneration> current_generation_;
   SequenceStackProfileTracker sequence_stack_profile_tracker_;
+  ExtensibleSequenceState extensible_sequence_state_;
 };
 
 template <uint32_t FieldId, typename MessageType>
diff --git a/src/trace_processor/importers/proto/v8_module.cc b/src/trace_processor/importers/proto/v8_module.cc
new file mode 100644
index 0000000..ac2086a
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_module.cc
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/v8_module.h"
+
+#include <optional>
+
+#include "protos/perfetto/trace/chrome/v8.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/v8_sequence_state.h"
+#include "src/trace_processor/importers/proto/v8_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::perfetto::protos::pbzero::TracePacket;
+using ::perfetto::protos::pbzero::V8CodeMove;
+using ::perfetto::protos::pbzero::V8InternalCode;
+using ::perfetto::protos::pbzero::V8JsCode;
+using ::perfetto::protos::pbzero::V8RegExpCode;
+using ::perfetto::protos::pbzero::V8WasmCode;
+
+}  // namespace
+
+V8Module::V8Module(TraceProcessorContext* context)
+    : context_(context), v8_tracker_(V8Tracker::GetOrCreate(context_)) {
+  RegisterForField(TracePacket::kV8JsCodeFieldNumber, context_);
+  RegisterForField(TracePacket::kV8InternalCodeFieldNumber, context_);
+  RegisterForField(TracePacket::kV8WasmCodeFieldNumber, context_);
+  RegisterForField(TracePacket::kV8RegExpCodeFieldNumber, context_);
+  RegisterForField(TracePacket::kV8CodeMoveFieldNumber, context_);
+}
+
+V8Module::~V8Module() = default;
+
+ModuleResult V8Module::TokenizePacket(const TracePacket::Decoder&,
+                                      TraceBlobView* /*packet*/,
+                                      int64_t /*packet_timestamp*/,
+                                      PacketSequenceState* /*state*/,
+                                      uint32_t /*field_id*/) {
+  return ModuleResult::Ignored();
+}
+
+void V8Module::ParseTracePacketData(const TracePacket::Decoder& decoder,
+                                    int64_t ts,
+                                    const TracePacketData& data,
+                                    uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kV8JsCodeFieldNumber:
+      ParseV8JsCode(decoder.v8_js_code(), ts, data);
+      break;
+    case TracePacket::kV8InternalCodeFieldNumber:
+      ParseV8InternalCode(decoder.v8_internal_code(), ts, data);
+      break;
+    case TracePacket::kV8WasmCodeFieldNumber:
+      ParseV8WasmCode(decoder.v8_wasm_code(), ts, data);
+      break;
+    case TracePacket::kV8RegExpCodeFieldNumber:
+      ParseV8RegExpCode(decoder.v8_reg_exp_code(), ts, data);
+      break;
+    case TracePacket::kV8CodeMoveFieldNumber:
+      ParseV8CodeMove(decoder.v8_code_move(), ts, data);
+      break;
+    default:
+      break;
+  }
+}
+
+void V8Module::ParseV8JsCode(protozero::ConstBytes bytes,
+                             int64_t ts,
+                             const TracePacketData& data) {
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
+
+  V8JsCode::Decoder code(bytes);
+
+  auto v8_isolate_id = state.GetOrInsertIsolate(code.v8_isolate_iid());
+  if (!v8_isolate_id) {
+    return;
+  }
+
+  auto v8_function_id =
+      state.GetOrInsertJsFunction(code.v8_js_function_iid(), *v8_isolate_id);
+  if (!v8_function_id) {
+    return;
+  }
+
+  v8_tracker_->AddJsCode(ts, *v8_isolate_id, *v8_function_id, code);
+}
+
+void V8Module::ParseV8InternalCode(protozero::ConstBytes bytes,
+                                   int64_t ts,
+                                   const TracePacketData& data) {
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
+
+  V8InternalCode::Decoder code(bytes);
+
+  auto v8_isolate_id = state.GetOrInsertIsolate(code.v8_isolate_iid());
+  if (!v8_isolate_id) {
+    return;
+  }
+
+  v8_tracker_->AddInternalCode(ts, *v8_isolate_id, code);
+}
+
+void V8Module::ParseV8WasmCode(protozero::ConstBytes bytes,
+                               int64_t ts,
+                               const TracePacketData& data) {
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
+
+  V8WasmCode::Decoder code(bytes);
+
+  auto v8_isolate_id = state.GetOrInsertIsolate(code.v8_isolate_iid());
+  if (!v8_isolate_id) {
+    return;
+  }
+
+  auto v8_wasm_script_id =
+      state.GetOrInsertWasmScript(code.v8_wasm_script_iid(), *v8_isolate_id);
+  if (!v8_wasm_script_id) {
+    return;
+  }
+
+  v8_tracker_->AddWasmCode(ts, *v8_isolate_id, *v8_wasm_script_id, code);
+}
+
+void V8Module::ParseV8RegExpCode(protozero::ConstBytes bytes,
+                                 int64_t ts,
+                                 const TracePacketData& data) {
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
+
+  V8RegExpCode::Decoder code(bytes);
+
+  auto v8_isolate_id = state.GetOrInsertIsolate(code.v8_isolate_iid());
+  if (!v8_isolate_id) {
+    return;
+  }
+
+  v8_tracker_->AddRegExpCode(ts, *v8_isolate_id, code);
+}
+
+void V8Module::ParseV8CodeMove(protozero::ConstBytes bytes,
+                               int64_t,
+                               const TracePacketData& data) {
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
+  protos::pbzero::V8CodeMove::Decoder v8_code_move(bytes);
+
+  std::optional<tables::V8IsolateTable::Id> isolate_id =
+      state.GetOrInsertIsolate(v8_code_move.isolate_iid());
+  if (!isolate_id) {
+    return;
+  }
+
+  // TODO(carlscab): Implement
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/v8_module.h b/src/trace_processor/importers/proto/v8_module.h
new file mode 100644
index 0000000..de3f896
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_module.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_MODULE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_MODULE_H_
+
+#include <cstdint>
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+
+namespace perfetto {
+namespace protos {
+namespace pbzero {
+class TracePacket_Decoder;
+}
+}  // namespace protos
+namespace trace_processor {
+
+class PacketSequenceState;
+struct TracePacketData;
+class V8Tracker;
+
+// Populates v8 related tables.
+//
+// This class processes v8 related trace packets and populates the various
+// tables. In particular it keeps track of v8 Isolates and what code and
+// associated debug information has been loaded in each of the isolates.
+class V8Module : public ProtoImporterModule {
+ public:
+  explicit V8Module(TraceProcessorContext* context);
+
+  ~V8Module() override;
+
+  ModuleResult TokenizePacket(
+      const protos::pbzero::TracePacket_Decoder& decoder,
+      TraceBlobView* packet,
+      int64_t packet_timestamp,
+      PacketSequenceState* state,
+      uint32_t field_id) override;
+
+  void ParseTracePacketData(const protos::pbzero::TracePacket_Decoder&,
+                            int64_t ts,
+                            const TracePacketData& packet_data,
+                            uint32_t field_id) override;
+
+ private:
+  void ParseV8JsCode(protozero::ConstBytes bytes,
+                     int64_t ts,
+                     const TracePacketData& data);
+  void ParseV8InternalCode(protozero::ConstBytes bytes,
+                           int64_t ts,
+                           const TracePacketData& data);
+  void ParseV8WasmCode(protozero::ConstBytes bytes,
+                       int64_t ts,
+                       const TracePacketData& data);
+  void ParseV8RegExpCode(protozero::ConstBytes bytes,
+                         int64_t ts,
+                         const TracePacketData& data);
+  void ParseV8CodeMove(protozero::ConstBytes bytes,
+                       int64_t ts,
+                       const TracePacketData& data);
+
+  TraceProcessorContext* const context_;
+  V8Tracker* const v8_tracker_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_MODULE_H_
diff --git a/src/trace_processor/importers/proto/v8_sequence_state.cc b/src/trace_processor/importers/proto/v8_sequence_state.cc
new file mode 100644
index 0000000..946ab22
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_sequence_state.cc
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/v8_sequence_state.h"
+#include <optional>
+
+#include "perfetto/ext/base/string_utils.h"
+#include "protos/perfetto/trace/chrome/v8.pbzero.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/string_encoding_utils.h"
+#include "src/trace_processor/importers/proto/v8_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::perfetto::protos::pbzero::InternedData;
+using ::perfetto::protos::pbzero::InternedV8JsFunction;
+using ::perfetto::protos::pbzero::InternedV8String;
+
+protozero::ConstBytes ToConstBytes(const TraceBlobView& view) {
+  return {view.data(), view.size()};
+}
+
+}  // namespace
+
+V8SequenceState::V8SequenceState(PacketSequenceState* sequence_state)
+    : sequence_state_(sequence_state),
+      v8_tracker_(V8Tracker::GetOrCreate(sequence_state->context())) {}
+
+V8SequenceState::~V8SequenceState() = default;
+
+std::optional<tables::V8IsolateTable::Id> V8SequenceState::GetOrInsertIsolate(
+    uint64_t iid) {
+  if (auto* id = isolates_.Find(iid); id != nullptr) {
+    return *id;
+  }
+
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8IsolateFieldNumber, iid);
+  if (!view) {
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
+    return std::nullopt;
+  }
+
+  auto isolate_id = v8_tracker_->InternIsolate(ToConstBytes(view->message()));
+  isolates_.Insert(iid, isolate_id);
+  return isolate_id;
+}
+
+std::optional<tables::V8JsFunctionTable::Id>
+V8SequenceState::GetOrInsertJsFunction(uint64_t iid,
+                                       tables::V8IsolateTable::Id isolate_id) {
+  if (auto* id = js_functions_.Find(iid); id != nullptr) {
+    return *id;
+  }
+
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsFunctionFieldNumber, iid);
+  if (!view) {
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
+    return std::nullopt;
+  }
+
+  InternedV8JsFunction::Decoder function(ToConstBytes(view->message()));
+
+  std::optional<tables::V8JsScriptTable::Id> script_id =
+      GetOrInsertJsScript(function.v8_js_script_iid(), isolate_id);
+  if (!script_id) {
+    return std::nullopt;
+  }
+
+  auto name = GetOrInsertJsFunctionName(function.v8_js_function_name_iid());
+  if (!name) {
+    return std::nullopt;
+  }
+
+  auto function_id = v8_tracker_->InternJsFunction(
+      ToConstBytes(view->message()), *name, *script_id);
+
+  js_functions_.Insert(iid, function_id);
+  return function_id;
+}
+
+std::optional<tables::V8WasmScriptTable::Id>
+V8SequenceState::GetOrInsertWasmScript(uint64_t iid,
+                                       tables::V8IsolateTable::Id isolate_id) {
+  if (auto* id = wasm_scripts_.Find(iid); id != nullptr) {
+    return *id;
+  }
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8WasmScriptFieldNumber, iid);
+  if (!view) {
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
+    return std::nullopt;
+  }
+
+  tables::V8WasmScriptTable::Id script_id =
+      v8_tracker_->InternWasmScript(ToConstBytes(view->message()), isolate_id);
+  wasm_scripts_.Insert(iid, script_id);
+  return script_id;
+}
+
+std::optional<tables::V8JsScriptTable::Id> V8SequenceState::GetOrInsertJsScript(
+    uint64_t iid,
+    tables::V8IsolateTable::Id v8_isolate_id) {
+  if (auto* id = js_scripts_.Find(iid); id != nullptr) {
+    return *id;
+  }
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsScriptFieldNumber, iid);
+  if (!view) {
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
+    return std::nullopt;
+  }
+
+  tables::V8JsScriptTable::Id script_id =
+      v8_tracker_->InternJsScript(ToConstBytes(view->message()), v8_isolate_id);
+  js_scripts_.Insert(iid, script_id);
+  return script_id;
+}
+
+std::optional<StringId> V8SequenceState::GetOrInsertJsFunctionName(
+    uint64_t iid) {
+  if (auto* id = js_function_names_.Find(iid); id != nullptr) {
+    return *id;
+  }
+
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsFunctionNameFieldNumber, iid);
+
+  if (!view) {
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
+    return std::nullopt;
+  }
+
+  InternedV8String::Decoder function_name(ToConstBytes(view->message()));
+  auto& storage = *sequence_state_->context()->storage;
+  StringId id;
+  if (function_name.has_latin1()) {
+    id = storage.InternString(
+        base::StringView(ConvertLatin1ToUtf8(function_name.latin1())));
+  } else if (function_name.has_utf16_le()) {
+    id = storage.InternString(
+        base::StringView(ConvertUtf16LeToUtf8(function_name.latin1())));
+  } else if (function_name.has_utf16_be()) {
+    id = storage.InternString(
+        base::StringView(ConvertUtf16BeToUtf8(function_name.latin1())));
+  } else {
+    id = storage.InternString("");
+  }
+
+  js_function_names_.Insert(iid, id);
+  return id;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/v8_sequence_state.h b/src/trace_processor/importers/proto/v8_sequence_state.h
new file mode 100644
index 0000000..781960d
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_sequence_state.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_SEQUENCE_STATE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_SEQUENCE_STATE_H_
+
+#include <cstdint>
+#include <optional>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
+#include "src/trace_processor/types/destructible.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class V8Tracker;
+
+// Helper class to deal with V8 related interned data.
+class V8SequenceState : public Destructible {
+ public:
+  static V8SequenceState* GetOrCreate(PacketSequenceState* sequence_state) {
+    auto& v8_sequence_state =
+        sequence_state->extensible_sequence_state().v8_sequence_state;
+    if (!v8_sequence_state) {
+      v8_sequence_state.reset(new V8SequenceState(sequence_state));
+    }
+    return static_cast<V8SequenceState*>(v8_sequence_state.get());
+  }
+
+  ~V8SequenceState() override;
+
+  std::optional<tables::V8IsolateTable::Id> GetOrInsertIsolate(uint64_t iid);
+  std::optional<tables::V8JsFunctionTable::Id> GetOrInsertJsFunction(
+      uint64_t iid,
+      tables::V8IsolateTable::Id isolate_id);
+  std::optional<tables::V8WasmScriptTable::Id> GetOrInsertWasmScript(
+      uint64_t iid,
+      tables::V8IsolateTable::Id isolate_id);
+
+ private:
+  explicit V8SequenceState(PacketSequenceState* sequence_state);
+  std::optional<tables::V8JsScriptTable::Id> GetOrInsertJsScript(
+      uint64_t iid,
+      tables::V8IsolateTable::Id isolate_id);
+  std::optional<StringId> GetOrInsertJsFunctionName(uint64_t iid);
+
+  PacketSequenceState* const sequence_state_;
+  V8Tracker* const v8_tracker_;
+
+  using InterningId = uint64_t;
+  base::FlatHashMap<InterningId, tables::V8IsolateTable::Id> isolates_;
+  base::FlatHashMap<InterningId, tables::V8JsScriptTable::Id> js_scripts_;
+  base::FlatHashMap<InterningId, tables::V8WasmScriptTable::Id> wasm_scripts_;
+  base::FlatHashMap<InterningId, tables::V8JsFunctionTable::Id> js_functions_;
+  base::FlatHashMap<InterningId, StringId> js_function_names_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_SEQUENCE_STATE_H_
diff --git a/src/trace_processor/importers/proto/v8_tracker.cc b/src/trace_processor/importers/proto/v8_tracker.cc
new file mode 100644
index 0000000..963ad5b
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_tracker.cc
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/v8_tracker.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/field.h"
+#include "protos/perfetto/trace/chrome/v8.pbzero.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/proto/string_encoding_utils.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::perfetto::protos::pbzero::InternedV8Isolate;
+using ::perfetto::protos::pbzero::InternedV8JsFunction;
+using ::perfetto::protos::pbzero::InternedV8JsScript;
+using ::perfetto::protos::pbzero::InternedV8WasmScript;
+using ::perfetto::protos::pbzero::V8InternalCode;
+using ::perfetto::protos::pbzero::V8JsCode;
+using ::perfetto::protos::pbzero::V8String;
+using ::perfetto::protos::pbzero::V8WasmCode;
+
+base::StringView JsScriptTypeToString(int32_t type) {
+  if (type < protos::pbzero::InternedV8JsScript_Type_MIN ||
+      type > protos::pbzero::InternedV8JsScript_Type_MAX) {
+    return "UNKNOWN";
+  }
+  base::StringView name =
+      InternedV8JsScript::Type_Name(InternedV8JsScript::Type(type));
+  // Remove the "TYPE_" prefix
+  return name.substr(5);
+}
+
+base::StringView JsFunctionKindToString(int32_t kind) {
+  if (kind < protos::pbzero::InternedV8JsFunction_Kind_MIN ||
+      kind > protos::pbzero::InternedV8JsFunction_Kind_MAX) {
+    return "UNKNOWN";
+  }
+  base::StringView name =
+      InternedV8JsFunction::Kind_Name(InternedV8JsFunction::Kind(kind));
+  // Remove the "KIND_" prefix
+  return name.substr(5);
+}
+
+}  // namespace
+
+V8Tracker::V8Tracker(TraceProcessorContext* context) : context_(context) {}
+
+V8Tracker::~V8Tracker() = default;
+
+tables::V8IsolateTable::Id V8Tracker::InternIsolate(
+    protozero::ConstBytes bytes) {
+  InternedV8Isolate::Decoder isolate(bytes);
+  const UniquePid upid =
+      context_->process_tracker->GetOrCreateProcess(isolate.pid());
+
+  if (auto* id =
+          isolate_index_.Find(std::make_pair(upid, isolate.isolate_id()));
+      id) {
+    return *id;
+  }
+
+  // TODO(carlscab): Implement support for no code range
+  PERFETTO_CHECK(isolate.has_code_range());
+
+  InternedV8Isolate::CodeRange::Decoder code_range(isolate.code_range());
+
+  auto v8_isolate_id =
+      context_->storage->mutable_v8_isolate_table()
+          ->Insert(
+              {upid, isolate.isolate_id(),
+               static_cast<int64_t>(isolate.embedded_blob_code_start_address()),
+               static_cast<int64_t>(isolate.embedded_blob_code_size()),
+               static_cast<int64_t>(code_range.base_address()),
+               static_cast<int64_t>(code_range.size()),
+               code_range.is_process_wide(),
+               code_range.has_embedded_blob_code_copy_start_address()
+                   ? std::make_optional(static_cast<int64_t>(
+                         code_range.embedded_blob_code_copy_start_address()))
+                   : std::nullopt
+
+              })
+          .id;
+  isolate_index_.Insert(std::make_pair(upid, isolate.isolate_id()),
+                        v8_isolate_id);
+  return v8_isolate_id;
+}
+
+tables::V8JsScriptTable::Id V8Tracker::InternJsScript(
+    protozero::ConstBytes bytes,
+    tables::V8IsolateTable::Id isolate_id) {
+  InternedV8JsScript::Decoder script(bytes);
+
+  if (auto* id =
+          js_script_index_.Find(std::make_pair(isolate_id, script.script_id()));
+      id) {
+    return *id;
+  }
+
+  tables::V8JsScriptTable::Row row;
+  row.v8_isolate_id = isolate_id;
+  row.internal_script_id = script.script_id();
+  row.script_type =
+      context_->storage->InternString(JsScriptTypeToString(script.type()));
+  row.name = InternV8String(V8String::Decoder(script.name()));
+  row.source = InternV8String(V8String::Decoder(script.source()));
+
+  tables::V8JsScriptTable::Id script_id =
+      context_->storage->mutable_v8_js_script_table()->Insert(row).id;
+  js_script_index_.Insert(std::make_pair(isolate_id, script.script_id()),
+                          script_id);
+  return script_id;
+}
+
+tables::V8WasmScriptTable::Id V8Tracker::InternWasmScript(
+    protozero::ConstBytes bytes,
+    tables::V8IsolateTable::Id isolate_id) {
+  InternedV8WasmScript::Decoder script(bytes);
+
+  if (auto* id = wasm_script_index_.Find(
+          std::make_pair(isolate_id, script.script_id()));
+      id) {
+    return *id;
+  }
+
+  tables::V8WasmScriptTable::Row row;
+  row.v8_isolate_id = isolate_id;
+  row.internal_script_id = script.script_id();
+  row.url = context_->storage->InternString(script.url());
+
+  tables::V8WasmScriptTable::Id script_id =
+      context_->storage->mutable_v8_wasm_script_table()->Insert(row).id;
+  wasm_script_index_.Insert(std::make_pair(isolate_id, script.script_id()),
+                            script_id);
+  return script_id;
+}
+
+tables::V8JsFunctionTable::Id V8Tracker::InternJsFunction(
+    protozero::ConstBytes bytes,
+    StringId name,
+    tables::V8JsScriptTable::Id script_id) {
+  InternedV8JsFunction::Decoder function(bytes);
+
+  tables::V8JsFunctionTable::Row row;
+  row.name = name;
+  row.v8_js_script_id = script_id;
+  row.is_toplevel = function.is_toplevel();
+  row.kind =
+      context_->storage->InternString(JsFunctionKindToString(function.kind()));
+  // TODO(carlscab): Row and line are hard. Offset is in bytes, row and line are
+  // in characters and we potentially have a multi byte encoding (UTF16). Good
+  // luck!
+
+  if (auto* id = js_function_index_.Find(row); id) {
+    return *id;
+  }
+
+  tables::V8JsFunctionTable::Id function_id =
+      context_->storage->mutable_v8_js_function_table()->Insert(row).id;
+  js_function_index_.Insert(row, function_id);
+  return function_id;
+}
+
+void V8Tracker::AddJsCode(int64_t,
+                          tables::V8IsolateTable::Id,
+                          tables::V8JsFunctionTable::Id,
+                          const protos::pbzero::V8JsCode::Decoder&) {
+  // TODO(carlscab): Implement
+}
+
+void V8Tracker::AddInternalCode(
+    int64_t,
+    tables::V8IsolateTable::Id,
+    const protos::pbzero::V8InternalCode::Decoder&) {
+  // TODO(carlscab): Implement
+}
+
+void V8Tracker::AddWasmCode(int64_t,
+                            tables::V8IsolateTable::Id,
+                            tables::V8WasmScriptTable::Id,
+                            const protos::pbzero::V8WasmCode::Decoder&) {
+  // TODO(carlscab): Implement
+}
+
+void V8Tracker::AddRegExpCode(int64_t,
+                              tables::V8IsolateTable::Id,
+                              const protos::pbzero::V8RegExpCode::Decoder&) {
+  // TODO(carlscab): Implement
+}
+
+StringId V8Tracker::InternV8String(
+    const protos::pbzero::V8String::Decoder& v8_string) {
+  auto& storage = *context_->storage;
+  if (v8_string.has_latin1()) {
+    return storage.InternString(
+        base::StringView(ConvertLatin1ToUtf8(v8_string.latin1())));
+  }
+
+  if (v8_string.has_utf16_le()) {
+    return storage.InternString(
+        base::StringView(ConvertUtf16LeToUtf8(v8_string.latin1())));
+  }
+
+  if (v8_string.has_utf16_be()) {
+    return storage.InternString(
+        base::StringView(ConvertUtf16BeToUtf8(v8_string.latin1())));
+  }
+  return storage.InternString("");
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/v8_tracker.h b/src/trace_processor/importers/proto/v8_tracker.h
new file mode 100644
index 0000000..6da4d12
--- /dev/null
+++ b/src/trace_processor/importers/proto/v8_tracker.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_TRACKER_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/protozero/field.h"
+#include "protos/perfetto/trace/chrome/v8.pbzero.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Keeps track of V8 related objects.
+class V8Tracker : public Destructible {
+ public:
+  static V8Tracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->v8_tracker) {
+      context->v8_tracker.reset(new V8Tracker(context));
+    }
+    return static_cast<V8Tracker*>(context->v8_tracker.get());
+  }
+
+  ~V8Tracker() override;
+
+  tables::V8IsolateTable::Id InternIsolate(protozero::ConstBytes bytes);
+  tables::V8JsScriptTable::Id InternJsScript(
+      protozero::ConstBytes bytes,
+      tables::V8IsolateTable::Id isolate_id);
+  tables::V8WasmScriptTable::Id InternWasmScript(
+      protozero::ConstBytes bytes,
+      tables::V8IsolateTable::Id isolate_id);
+  tables::V8JsFunctionTable::Id InternJsFunction(
+      protozero::ConstBytes bytes,
+      StringId name,
+      tables::V8JsScriptTable::Id script_id);
+
+  void AddJsCode(int64_t timestamp,
+                 tables::V8IsolateTable::Id isolate_id,
+                 tables::V8JsFunctionTable::Id function_id,
+                 const protos::pbzero::V8JsCode::Decoder& code);
+
+  void AddInternalCode(int64_t timestamp,
+                       tables::V8IsolateTable::Id v8_isolate_id,
+                       const protos::pbzero::V8InternalCode::Decoder& code);
+
+  void AddWasmCode(int64_t timestamp,
+                   tables::V8IsolateTable::Id isolate_id,
+                   tables::V8WasmScriptTable::Id script_id,
+                   const protos::pbzero::V8WasmCode::Decoder& code);
+
+  void AddRegExpCode(int64_t timestamp,
+                     tables::V8IsolateTable::Id v8_isolate_id,
+                     const protos::pbzero::V8RegExpCode::Decoder& code);
+
+ private:
+  explicit V8Tracker(TraceProcessorContext* context);
+
+  StringId InternV8String(const protos::pbzero::V8String::Decoder& v8_string);
+
+  TraceProcessorContext* const context_;
+
+  struct IsolateIndexHash {
+    size_t operator()(const std::pair<UniquePid, int32_t>& v) const {
+      return static_cast<size_t>(base::Hasher::Combine(v.first, v.second));
+    }
+  };
+  base::FlatHashMap<std::pair<UniquePid, int32_t>,
+                    tables::V8IsolateTable::Id,
+                    IsolateIndexHash>
+      isolate_index_;
+
+  struct ScriptIndexHash {
+    size_t operator()(
+        const std::pair<tables::V8IsolateTable::Id, int32_t>& v) const {
+      return static_cast<size_t>(
+          base::Hasher::Combine(v.first.value, v.second));
+    }
+  };
+  base::FlatHashMap<std::pair<tables::V8IsolateTable::Id, int32_t>,
+                    tables::V8JsScriptTable::Id,
+                    ScriptIndexHash>
+      js_script_index_;
+  base::FlatHashMap<std::pair<tables::V8IsolateTable::Id, int32_t>,
+                    tables::V8WasmScriptTable::Id,
+                    ScriptIndexHash>
+      wasm_script_index_;
+
+  struct JsFunctionHash {
+    size_t operator()(const tables::V8JsFunctionTable::Row& v) const {
+      return static_cast<size_t>(base::Hasher::Combine(
+          v.name.raw_id(), v.v8_js_script_id.value, v.is_toplevel,
+          v.kind.raw_id(), v.line.value_or(0), v.column.value_or(0)));
+    }
+  };
+  base::FlatHashMap<tables::V8JsFunctionTable::Row,
+                    tables::V8JsFunctionTable::Id,
+                    JsFunctionHash>
+      js_function_index_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_TRACKER_H_
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index abd7d24..1d572a7 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -255,6 +255,9 @@
       "missing. Defaulted to inaccurate packet timestamp."),                   \
   F(atom_unknown,                         kSingle,  kInfo,     kAnalysis,      \
       "Unknown statsd atom. Atom descriptor may need to be updated"),          \
+  F(v8_intern_errors,                                                          \
+                                          kSingle,  kDataLoss, kAnalysis,      \
+      "Failed to resolve V8 interned data."),                                  \
   F(winscope_sf_layers_parse_errors,      kSingle,  kInfo,     kAnalysis,      \
       "SurfaceFlinger layers snapshot has unknown fields, which results in "   \
       "some arguments missing. You may need a newer version of trace "         \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 4eb9253..51ec58c 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -46,6 +46,7 @@
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
 #include "src/trace_processor/tables/winscope_tables_py.h"
 #include "src/trace_processor/types/variadic.h"
 #include "src/trace_processor/views/slice_views.h"
@@ -725,6 +726,31 @@
     return &actual_frame_timeline_slice_table_;
   }
 
+  const tables::V8IsolateTable& v8_isolate_table() const {
+    return v8_isolate_table_;
+  }
+  tables::V8IsolateTable* mutable_v8_isolate_table() {
+    return &v8_isolate_table_;
+  }
+  const tables::V8JsScriptTable& v8_js_script_table() const {
+    return v8_js_script_table_;
+  }
+  tables::V8JsScriptTable* mutable_v8_js_script_table() {
+    return &v8_js_script_table_;
+  }
+  const tables::V8WasmScriptTable& v8_wasm_script_table() const {
+    return v8_wasm_script_table_;
+  }
+  tables::V8WasmScriptTable* mutable_v8_wasm_script_table() {
+    return &v8_wasm_script_table_;
+  }
+  const tables::V8JsFunctionTable& v8_js_function_table() const {
+    return v8_js_function_table_;
+  }
+  tables::V8JsFunctionTable* mutable_v8_js_function_table() {
+    return &v8_js_function_table_;
+  }
+
   const tables::SurfaceFlingerLayersSnapshotTable&
   surfaceflinger_layers_snapshot_table() const {
     return surfaceflinger_layers_snapshot_table_;
@@ -1022,6 +1048,12 @@
   tables::ActualFrameTimelineSliceTable actual_frame_timeline_slice_table_{
       &string_pool_, &slice_table_};
 
+  // V8 tables
+  tables::V8IsolateTable v8_isolate_table_{&string_pool_};
+  tables::V8JsScriptTable v8_js_script_table_{&string_pool_};
+  tables::V8WasmScriptTable v8_wasm_script_table_{&string_pool_};
+  tables::V8JsFunctionTable v8_js_function_table_{&string_pool_};
+
   // Winscope tables
   tables::SurfaceFlingerLayersSnapshotTable
       surfaceflinger_layers_snapshot_table_{&string_pool_};
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 7d0ec0f..1bbce44 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -27,6 +27,7 @@
     "slice_tables.py",
     "trace_proto_tables.py",
     "track_tables.py",
+    "v8_tables.py",
     "winscope_tables.py",
   ]
   generate_docs = true
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 553f5cd..4975f81 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -24,6 +24,7 @@
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
 #include "src/trace_processor/tables/winscope_tables_py.h"
 
 namespace perfetto {
@@ -117,6 +118,12 @@
 MemorySnapshotNodeTable::~MemorySnapshotNodeTable() = default;
 MemorySnapshotEdgeTable::~MemorySnapshotEdgeTable() = default;
 
+// v8_tables_py.h
+V8IsolateTable::~V8IsolateTable() = default;
+V8JsScriptTable::~V8JsScriptTable() = default;
+V8WasmScriptTable::~V8WasmScriptTable() = default;
+V8JsFunctionTable::~V8JsFunctionTable() = default;
+
 // winscope_tables_py.h
 SurfaceFlingerLayersSnapshotTable::~SurfaceFlingerLayersSnapshotTable() =
     default;
diff --git a/src/trace_processor/tables/v8_tables.py b/src/trace_processor/tables/v8_tables.py
new file mode 100644
index 0000000..8fb7c01
--- /dev/null
+++ b/src/trace_processor/tables/v8_tables.py
@@ -0,0 +1,175 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tables related to the v8 (Javasrcript Engine) Datasource.
+
+These tables are WIP, the schema is not stable and you should not rely on them
+for any serious business just yet""
+"""
+
+from python.generators.trace_processor_table.public import Alias
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import CppInt32
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import CppUint32 as CppBool
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import TableDoc
+from .profiler_tables import STACK_PROFILE_FRAME_TABLE
+
+V8_ISOLATE = Table(
+    python_module=__file__,
+    class_name='V8IsolateTable',
+    sql_name='v8_isolate',
+    columns=[
+        C('upid', CppUint32()),
+        C('internal_isolate_id', CppInt32()),
+        C('embedded_blob_code_start_address', CppInt64()),
+        C('embedded_blob_code_size', CppInt64()),
+        C('code_range_base_address', CppOptional(CppInt64())),
+        C('code_range_size', CppOptional(CppInt64())),
+        C('shared_code_range', CppOptional(CppBool())),
+        C('embedded_blob_code_copy_start_address', CppOptional(CppInt64())),
+        C('v8_isolate_id', Alias('id')),
+    ],
+    tabledoc=TableDoc(
+        doc='Represents one Isolate instance',
+        group='v8',
+        columns={
+            'upid':
+                'Process the isolate was created in.',
+            'internal_isolate_id':
+                'Internal id used by the v8 engine. Unique in a process.',
+            'embedded_blob_code_start_address':
+                'Absolute start address of the embedded code blob.',
+            'embedded_blob_code_size':
+                'Size in bytes of the embedded code blob.',
+            'code_range_base_address':
+                'If this Isolate defines a CodeRange its base address is stored'
+                ' here',
+            'code_range_size':
+                'If this Isolate defines a CodeRange its size is stored here',
+            'shared_code_range':
+                'Whether the code range for this Isolate is shared with others'
+                ' in the same process. There is at max one such shared code'
+                ' range per process.',
+            'embedded_blob_code_copy_start_address':
+                'Used when short builtin calls are enabled, where embedded'
+                ' builtins are copied into the CodeRange so calls can be'
+                ' nearer.',
+            'v8_isolate_id':
+                'Alias for id. Makes joins easier',
+        },
+    ),
+)
+
+V8_JS_SCRIPT = Table(
+    python_module=__file__,
+    class_name='V8JsScriptTable',
+    sql_name='v8_js_script',
+    columns=[
+        C('v8_isolate_id', CppTableId(V8_ISOLATE)),
+        C('internal_script_id', CppInt32()),
+        C('script_type', CppString()),
+        C('name', CppString()),
+        C('source', CppOptional(CppString())),
+        C('v8_js_script_id', Alias('id')),
+    ],
+    tabledoc=TableDoc(
+        doc='Represents one Javascript script',
+        group='v8',
+        columns={
+            'v8_isolate_id': 'V8 Isolate',
+            'internal_script_id': 'Script id used by the V8 engine',
+            'script_type': '',
+            'name': '',
+            'source': 'Actual contents of the script.',
+            'v8_js_script_id': 'Alias for id. Makes joins easier',
+        },
+    ),
+)
+
+V8_WASM_SCRIPT = Table(
+    python_module=__file__,
+    class_name='V8WasmScriptTable',
+    sql_name='v8_wasm_script',
+    columns=[
+        C('v8_isolate_id', CppTableId(V8_ISOLATE)),
+        C('internal_script_id', CppInt32()),
+        C('url', CppString()),
+        C('source', CppOptional(CppString())),
+        C('v8_wasm_script_id', Alias('id')),
+    ],
+    tabledoc=TableDoc(
+        doc='Represents one WASM script',
+        group='v8',
+        columns={
+            'v8_isolate_id': 'V8 Isolate',
+            'internal_script_id': 'Script id used by the V8 engine',
+            'url': 'URL of the source',
+            'source': 'Actual contents of the script.',
+            'v8_wasm_script_id': 'Alias for id. Makes joins easier',
+        },
+    ),
+)
+
+V8_JS_FUNCTION = Table(
+    python_module=__file__,
+    class_name='V8JsFunctionTable',
+    sql_name='v8_js_function',
+    columns=[
+        C('name', CppString()),
+        C('v8_js_script_id', CppTableId(V8_JS_SCRIPT)),
+        C('is_toplevel', CppBool()),
+        C('kind', CppString()),
+        C('line', CppOptional(CppUint32())),
+        C('column', CppOptional(CppUint32())),
+        C('v8_js_function_id', Alias('id')),
+    ],
+    tabledoc=TableDoc(
+        doc='Represents a v8 Javascript function',
+        group='v8',
+        columns={
+            'name':
+                '',
+            'v8_js_script_id':
+                ColumnDoc(
+                    doc='Script where the function is defined.',
+                    joinable='v8_js_script.id',
+                ),
+            'is_toplevel':
+                'Whether this function represents the top level script',
+            'kind':
+                'Function kind (e.g. regular function or constructor)',
+            'line':
+                'Line in script where function is defined. Starts at 1',
+            'column':
+                'Column in script where function is defined. Starts at 1',
+            'v8_js_function_id':
+                'Alias for id. Makes joins easier',
+        },
+    ),
+)
+
+# Keep this list sorted.
+ALL_TABLES = [
+    V8_ISOLATE,
+    V8_JS_SCRIPT,
+    V8_WASM_SCRIPT,
+    V8_JS_FUNCTION,
+]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index e777d6e..86a6111 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -832,6 +832,11 @@
   RegisterStaticTable(storage->expected_frame_timeline_slice_table());
   RegisterStaticTable(storage->actual_frame_timeline_slice_table());
 
+  RegisterStaticTable(storage->v8_isolate_table());
+  RegisterStaticTable(storage->v8_js_script_table());
+  RegisterStaticTable(storage->v8_wasm_script_table());
+  RegisterStaticTable(storage->v8_js_function_table());
+
   RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table());
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 1eb16e4..8d770d0 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -122,8 +122,10 @@
   std::unique_ptr<Destructible> thread_state_tracker;    // ThreadStateTracker
   std::unique_ptr<Destructible> i2c_tracker;             // I2CTracker
   std::unique_ptr<Destructible> perf_data_tracker;       // PerfDataTracker
-  std::unique_ptr<Destructible> content_analyzer;
-  std::unique_ptr<Destructible> shell_transitions_tracker;
+  std::unique_ptr<Destructible> content_analyzer;        // ProtoContentAnalyzer
+  std::unique_ptr<Destructible>
+      shell_transitions_tracker;             // ShellTransitionsTracker
+  std::unique_ptr<Destructible> v8_tracker;  // V8Tracker
 
   // These fields are trace readers which will be called by |forwarding_parser|
   // once the format of the trace is discovered. They are placed here as they
diff --git a/test/data/chrome/v8.code.trace.pb.gz.sha256 b/test/data/chrome/v8.code.trace.pb.gz.sha256
new file mode 100644
index 0000000..e1ac49b
--- /dev/null
+++ b/test/data/chrome/v8.code.trace.pb.gz.sha256
@@ -0,0 +1 @@
+cb21932d5a83cc63b4a4bc49059541075bc2da8865f5dfd0dff3f2bea4b21740
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 3ba2591..0dba5fa 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -56,6 +56,7 @@
 from diff_tests.parser.atrace.tests import Atrace
 from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
 from diff_tests.parser.chrome.tests import ChromeParser
+from diff_tests.parser.chrome.tests_v8 import ChromeV8Parser
 from diff_tests.parser.chrome.tests_memory_snapshots import ChromeMemorySnapshots
 from diff_tests.parser.cros.tests import Cros
 from diff_tests.parser.fs.tests import Fs
@@ -135,6 +136,7 @@
       *ChromeMemorySnapshots(index_path, 'parser/chrome',
                              'ChromeMemorySnapshots').fetch(),
       *ChromeParser(index_path, 'parser/chrome', 'ChromeParser').fetch(),
+      *ChromeV8Parser(index_path, 'parser/chrome', 'ChromeV8Parser').fetch(),
       *Cros(index_path, 'parser/cros', 'Cros').fetch(),
       *Fs(index_path, 'parser/fs', 'Fs').fetch(),
       *Fuchsia(index_path, 'parser/fuchsia', 'Fuchsia').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
new file mode 100644
index 0000000..798077a
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import DataPath, Metric, Path
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.trace_processor_table.public import Alias
+from src.trace_processor.tables.v8_tables import V8_ISOLATE, V8_JS_SCRIPT, V8_JS_FUNCTION, V8_WASM_SCRIPT
+
+
+def _no_duplicates_query(table):
+  group_by_columns = [
+      c.name for c in table.columns if not isinstance(c.type, Alias)
+  ]
+  return f"""
+  SELECT DISTINCT COUNT(*) AS count
+  FROM {table.sql_name}
+  GROUP BY {', '.join( group_by_columns)}"""
+
+
+class ChromeV8Parser(TestSuite):
+
+  def test_no_duplicates_in_v8_js_function(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome/v8.code.trace.pb.gz'),
+        query=_no_duplicates_query(V8_JS_FUNCTION),
+        out=Csv(""""count"\n1\n"""),
+    )
+
+  def test_no_duplicates_in_v8_js_script(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome/v8.code.trace.pb.gz'),
+        query=_no_duplicates_query(V8_JS_SCRIPT),
+        out=Csv(""""count"\n1\n"""),
+    )
+
+  def test_no_duplicates_in_v8_isolate(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome/v8.code.trace.pb.gz'),
+        query=_no_duplicates_query(V8_ISOLATE),
+        out=Csv(""""count"\n1\n"""),
+    )
+
+  def test_no_duplicates_in_v8_wasm_script(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome/v8.code.trace.pb.gz'),
+        query=_no_duplicates_query(V8_WASM_SCRIPT),
+        out=Csv(""""count"\n1\n"""),
+    )