Commit 12b4c387fe4 for nodejs

commit 12b4c387fe4c73bb6379cea58c2eb12d1fef5eea
Author: Node.js GitHub Bot <github-bot@iojs.org>
Date:   Sun Nov 8 00:36:00 2026 +0000

    deps: update inspector_protocol to c0045057ce801744c5e88596a2f1bab1802e

    PR-URL: https://github.com/nodejs/node/pull/66653
    Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
    Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
    Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>

diff --git a/deps/inspector_protocol/README.node b/deps/inspector_protocol/README.node
index 6216d7a8ea0..ca6864b0508 100644
--- a/deps/inspector_protocol/README.node
+++ b/deps/inspector_protocol/README.node
@@ -2,7 +2,7 @@ Name: inspector protocol
 Short Name: inspector_protocol
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: 0
-Revision: 1b1bcbbe060e8c8cd8704f00f78978c50991b307
+Revision: c0045057ce801744c5e88596a2f1bab1802e39f0
 License: BSD
 License File: LICENSE
 Security Critical: no
diff --git a/deps/inspector_protocol/code_generator.py b/deps/inspector_protocol/code_generator.py
index b25ffc29b0a..2ad9465a6b2 100755
--- a/deps/inspector_protocol/code_generator.py
+++ b/deps/inspector_protocol/code_generator.py
@@ -101,35 +101,35 @@ def read_config():
                                     config_base)
     config_json_file.close()
     defaults = {
-      ".use_snake_file_names": False,
-      ".use_title_case_methods": False,
-      ".use_embedder_types": False,
-      ".imported": False,
-      ".imported.export_macro": "",
-      ".imported.export_header": False,
-      ".imported.header": False,
-      ".imported.package": False,
-      ".imported.options": False,
-      ".protocol.export_macro": "",
-      ".protocol.export_header": False,
-      ".protocol.options": False,
-      ".protocol.file_name_prefix": "",
-      ".exported": False,
-      ".exported.export_macro": "",
-      ".exported.export_header": False,
-      ".lib": False,
-      ".lib.export_macro": "",
-      ".lib.export_header": False,
-      ".crdtp": False,
-      ".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"),
-      ".crdtp.namespace": "crdtp",
+        ".use_snake_file_names": False,
+        ".use_title_case_methods": False,
+        ".use_embedder_types": False,
+        ".imported": False,
+        ".imported.export_macro": "",
+        ".imported.export_header": False,
+        ".imported.header": False,
+        ".imported.package": False,
+        ".imported.options": False,
+        ".protocol.export_macro": "",
+        ".protocol.export_header": False,
+        ".protocol.options": False,
+        ".protocol.file_name_prefix": "",
+        ".exported": False,
+        ".exported.export_macro": "",
+        ".exported.export_header": False,
+        ".lib": False,
+        ".lib.export_macro": "",
+        ".lib.export_header": False,
+        ".crdtp": False,
+        ".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"),
+        ".crdtp.namespace": "crdtp",
     }
     for key_value in config_values:
       parts = key_value.split("=")
       if len(parts) == 2:
         defaults["." + parts[0]] = parts[1]
-    return (jinja_dir, config_file, init_defaults(config_partial, "", defaults),
-            depfile, stamp)
+    return (jinja_dir, config_file, init_defaults(config_partial, "",
+                                                  defaults), depfile, stamp)
   except Exception:
     # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
     exc = sys.exc_info()[1]
@@ -286,32 +286,32 @@ def create_any_type_definition():
 def create_string_type_definition():
   # pylint: disable=W0622
   return {
-    "return_type": "String",
-    "pass_type": "const String&",
-    "to_pass_type": "%s",
-    "to_raw_type": "%s",
-    "to_rvalue": "%s",
-    "type": "String",
-    "raw_type": "String",
-    "raw_pass_type": "const String&",
-    "raw_return_type": "String",
-    "is_primitive": True
+      "return_type": "String",
+      "pass_type": "const String&",
+      "to_pass_type": "%s",
+      "to_raw_type": "%s",
+      "to_rvalue": "%s",
+      "type": "String",
+      "raw_type": "String",
+      "raw_pass_type": "const String&",
+      "raw_return_type": "String",
+      "is_primitive": True
   }


 def create_binary_type_definition():
   # pylint: disable=W0622
   return {
-    "return_type": "Binary",
-    "pass_type": "const Binary&",
-    "to_pass_type": "%s",
-    "to_raw_type": "%s",
-    "to_rvalue": "%s",
-    "type": "Binary",
-    "raw_type": "Binary",
-    "raw_pass_type": "const Binary&",
-    "raw_return_type": "Binary",
-    "is_primitive": True
+      "return_type": "Binary",
+      "pass_type": "const Binary&",
+      "to_pass_type": "%s",
+      "to_raw_type": "%s",
+      "to_rvalue": "%s",
+      "type": "Binary",
+      "raw_type": "Binary",
+      "raw_pass_type": "const Binary&",
+      "raw_return_type": "Binary",
+      "is_primitive": True
   }


@@ -333,17 +333,17 @@ def create_primitive_type_definition(type):
     "boolean": "TypeBoolean",
   }
   return {
-    "return_type": typedefs[type],
-    "pass_type": typedefs[type],
-    "to_pass_type": "%s",
-    "to_raw_type": "%s",
-    "to_rvalue": "%s",
-    "type": typedefs[type],
-    "raw_type": typedefs[type],
-    "raw_pass_type": typedefs[type],
-    "raw_return_type": typedefs[type],
-    "default_value": defaults[type],
-    "is_primitive": True
+      "return_type": typedefs[type],
+      "pass_type": typedefs[type],
+      "to_pass_type": "%s",
+      "to_raw_type": "%s",
+      "to_rvalue": "%s",
+      "type": typedefs[type],
+      "raw_type": typedefs[type],
+      "raw_pass_type": typedefs[type],
+      "raw_return_type": typedefs[type],
+      "default_value": defaults[type],
+      "is_primitive": True
   }

 def wrap_array_definition(type):
@@ -444,10 +444,12 @@ class Protocol(object):
       domain_match = re.match(r'^(.*)[.]', ref)
       if domain_match:
         referenced_domain_name = domain_match.group(1)
-        if referenced_domain_name != domain['domain'] and not referenced_domain_name in dependencies:
-            sys.stderr.write(("Domains [%s] uses type [%s] from domain [%s], but did not declare the dependency\n\n"
-                    ) % (domain["domain"], ref, referenced_domain_name))
-            exit(1)
+        if referenced_domain_name != domain[
+            'domain'] and not referenced_domain_name in dependencies:
+          sys.stderr.write((
+              "Domains [%s] uses type [%s] from domain [%s], but did not declare the dependency\n\n"
+          ) % (domain["domain"], ref, referenced_domain_name))
+          exit(1)

   def generate_used_types(self):
     all_refs = set()
@@ -552,10 +554,10 @@ class Protocol(object):
     return self.type_definitions[prop["type"]]

   def optional_type(self, prop):
-      type = self.resolve_type(prop)
-      template = ("std::optional<{}>" if type.get('is_primitive', False)
-                  else "std::unique_ptr<{}>")
-      return template.format(type.get("raw_type"))
+    type = self.resolve_type(prop)
+    template = ("std::optional<{}>"
+                if type.get('is_primitive', False) else "std::unique_ptr<{}>")
+    return template.format(type.get("raw_type"))

   def generate_command(self, domain, command):
     if not self.config.protocol.options:
@@ -572,6 +574,12 @@ class Protocol(object):
   def generate_type(self, domain, typename):
     return domain + "." + typename in self.used_types

+  def should_pass_associated_data(self, domain, command):
+    if not self.config.protocol.options:
+      return False
+    return self.check_options(self.config.protocol.options, domain, command,
+                              "pass_associated_data", None, False)
+
   def is_async_command(self, domain, command):
     if not self.config.protocol.options:
       return False
@@ -658,25 +666,23 @@ def main():
     }

     if domain["domain"] in protocol.generate_domains:
-      output_h = os.path.join(config.protocol.output, to_file_name(
-          config, file_name + ".h"))
+      output_h = os.path.join(config.protocol.output,
+                              to_file_name(config, file_name + ".h"))
       outputs[output_h] = h_template.render(template_context)
       source_set.add(h_template.filename)
-      output_cpp = os.path.join(config.protocol.output, to_file_name(
-          config, file_name + ".cpp"))
+      output_cpp = os.path.join(config.protocol.output,
+                                to_file_name(config, file_name + ".cpp"))
       outputs[output_cpp] = cpp_template.render(template_context)
       source_set.add(cpp_template.filename)
       if domain["domain"] in protocol.exported_domains:
-        output_export_h = os.path.join(config.exported.output, to_file_name(
-            config, file_name + ".h"))
-        outputs[output_export_h] = exported_template.render(
-                template_context)
+        output_export_h = os.path.join(config.exported.output,
+                                       to_file_name(config, file_name + ".h"))
+        outputs[output_export_h] = exported_template.render(template_context)
         source_set.add(exported_template.filename)
     if domain["domain"] in protocol.imported_domains:
-      output_import_h = os.path.join(config.protocol.output, to_file_name(
-          config, file_name + ".h"))
-      outputs[output_import_h] = imported_template.render(
-              template_context)
+      output_import_h = os.path.join(config.protocol.output,
+                                     to_file_name(config, file_name + ".h"))
+      outputs[output_import_h] = imported_template.render(template_context)
       source_set.add(imported_template.filename)

   if config.lib:
@@ -699,19 +705,19 @@ def main():

     if not config.use_embedder_types:
       protocol_h_templates += [
-        "Values_h.template",
-        "Object_h.template",
-        "ValueConversions_h.template",
+          "Values_h.template",
+          "Object_h.template",
+          "ValueConversions_h.template",
       ]
       protocol_cpp_templates += [
-        "Protocol_cpp.template",
-        "Values_cpp.template",
-        "Object_cpp.template",
-        "ValueConversions_cpp.template",
+          "Protocol_cpp.template",
+          "Values_cpp.template",
+          "Object_cpp.template",
+          "ValueConversions_cpp.template",
       ]
     else:
       protocol_h_templates += [
-        "Forward_h.template",
+          "Forward_h.template",
       ]

     def generate_lib_file(file_name, template_files):
@@ -729,8 +735,9 @@ def main():
         config, "Protocol.h")), protocol_h_templates)

     if not config.use_embedder_types:
-      generate_lib_file(os.path.join(config.lib.output, to_file_name(
-          config, "Protocol.cpp")), protocol_cpp_templates)
+      generate_lib_file(
+          os.path.join(config.lib.output, to_file_name(config, "Protocol.cpp")),
+          protocol_cpp_templates)

   if stamp_filename:
     with open(stamp_filename, "w"):
@@ -748,8 +755,8 @@ def main():
   if deps_filename:
     assert stamp_filename
     with open(deps_filename, "w") as deps_file:
-      deps_file.write("%s: %s\n" % (
-        stamp_filename, " ".join(sorted(source_set))))
+      deps_file.write("%s: %s\n" %
+                      (stamp_filename, " ".join(sorted(source_set))))


 if __name__ == "__main__":
diff --git a/deps/inspector_protocol/concatenate_protocols.py b/deps/inspector_protocol/concatenate_protocols.py
index 8ad4f6de712..14bb366497c 100755
--- a/deps/inspector_protocol/concatenate_protocols.py
+++ b/deps/inspector_protocol/concatenate_protocols.py
@@ -58,8 +58,8 @@ def main(argv):
   if deps_filename:
     assert stamp_filename
     with open(deps_filename, "w") as deps_file:
-      deps_file.write("%s: %s\n" % (
-        stamp_filename, " ".join(sorted(source_set))))
+      deps_file.write("%s: %s\n" %
+                      (stamp_filename, " ".join(sorted(source_set))))


 if __name__ == '__main__':
diff --git a/deps/inspector_protocol/crdtp/cbor.cc b/deps/inspector_protocol/crdtp/cbor.cc
index dc52549c55a..7e62d765141 100644
--- a/deps/inspector_protocol/crdtp/cbor.cc
+++ b/deps/inspector_protocol/crdtp/cbor.cc
@@ -1074,5 +1074,97 @@ Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
   *(out) = new_envelope_size & 0xff;
   return Status();
 }
+
+span<uint8_t> GetString8ValueFromMap(span<uint8_t> message,
+                                     span<uint8_t> string8_key) {
+  CBORTokenizer tokenizer(message);
+  if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) {
+    return {};
+  }
+  tokenizer.EnterEnvelope();
+  if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) {
+    return {};
+  }
+  tokenizer.Next();
+  span<uint8_t> result;
+  bool found_key = false;
+  bool found_value = false;
+  bool is_key = true;
+  while (tokenizer.TokenTag() != CBORTokenTag::STOP &&
+         tokenizer.TokenTag() != CBORTokenTag::DONE &&
+         tokenizer.TokenTag() != CBORTokenTag::ERROR_VALUE) {
+    if (tokenizer.TokenTag() == CBORTokenTag::MAP_START ||
+        tokenizer.TokenTag() == CBORTokenTag::ARRAY_START) {
+      return {};
+    }
+    if (is_key) {
+      if (tokenizer.TokenTag() == CBORTokenTag::STRING8 &&
+          SpanEquals(tokenizer.GetString8(), string8_key)) {
+        if (found_key) {
+          return {};  // Duplicate key
+        }
+        found_key = true;
+      }
+    } else if (found_key && !found_value) {
+      if (tokenizer.TokenTag() == CBORTokenTag::STRING8) {
+        result = tokenizer.GetString8();
+      }
+      found_value = true;
+    }
+    tokenizer.Next();
+    is_key = !is_key;
+  }
+  if (tokenizer.TokenTag() != CBORTokenTag::STOP) {
+    return {};
+  }
+  return result;
+}
+
+bool HasKeyInMap(span<uint8_t> message, span<uint8_t> key) {
+  CBORTokenizer tokenizer(message);
+  if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) {
+    return false;
+  }
+  tokenizer.EnterEnvelope();
+  if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) {
+    return false;
+  }
+  tokenizer.Next();
+  bool is_key = true;
+  size_t nested_depth = 0;
+  while (tokenizer.TokenTag() != CBORTokenTag::DONE &&
+         tokenizer.TokenTag() != CBORTokenTag::ERROR_VALUE) {
+    if (tokenizer.TokenTag() == CBORTokenTag::STOP) {
+      if (nested_depth == 0) {
+        break;
+      }
+      nested_depth--;
+      if (nested_depth == 0) {
+        is_key = !is_key;
+      }
+      tokenizer.Next();
+      continue;
+    }
+    if (tokenizer.TokenTag() == CBORTokenTag::MAP_START ||
+        tokenizer.TokenTag() == CBORTokenTag::ARRAY_START) {
+      nested_depth++;
+      tokenizer.Next();
+      continue;
+    }
+    if (nested_depth > 0) {
+      tokenizer.Next();
+      continue;
+    }
+    if (is_key) {
+      if (tokenizer.TokenTag() == CBORTokenTag::STRING8 &&
+          SpanEquals(tokenizer.GetString8(), key)) {
+        return true;
+      }
+    }
+    tokenizer.Next();
+    is_key = !is_key;
+  }
+  return false;
+}
 }  // namespace cbor
 }  // namespace crdtp
diff --git a/deps/inspector_protocol/crdtp/cbor.h b/deps/inspector_protocol/crdtp/cbor.h
index 2d925fad5cc..d7bcfa2652f 100644
--- a/deps/inspector_protocol/crdtp/cbor.h
+++ b/deps/inspector_protocol/crdtp/cbor.h
@@ -315,6 +315,18 @@ CRDTP_EXPORT Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
                                                 span<uint8_t> string8_value,
                                                 std::vector<uint8_t>* cbor);

+// Safely extracts the value for |string8_key| from a CBOR encoded map wrapped
+// in an envelope. This is a shallow parser that skips unknown keys and
+// complex values. If the key is not found, or if it is found more than once,
+// or if it contains nested maps/arrays, returns an empty span.
+// Explicitly rejects duplicate keys and non-STRING8 values.
+CRDTP_EXPORT span<uint8_t> GetString8ValueFromMap(span<uint8_t> message,
+                                                  span<uint8_t> string8_key);
+// Safely checks if |key| exists in the top-level of a CBOR encoded map wrapped
+// in an envelope. Shallow parser that skips nested structures.
+// Returns true as soon as the key is found at the top level.
+CRDTP_EXPORT bool HasKeyInMap(span<uint8_t> message, span<uint8_t> key);
+
 namespace internals {  // Exposed only for writing tests.
 CRDTP_EXPORT size_t ReadTokenStart(span<uint8_t> bytes,
                                    cbor::MajorType* type,
diff --git a/deps/inspector_protocol/crdtp/cbor_test.cc b/deps/inspector_protocol/crdtp/cbor_test.cc
index 2d3689e7300..4b23d3629d8 100644
--- a/deps/inspector_protocol/crdtp/cbor_test.cc
+++ b/deps/inspector_protocol/crdtp/cbor_test.cc
@@ -1445,5 +1445,362 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
     EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0));
   }
 }
+
+// =============================================================================
+// cbor::GetString8ValueFromMap
+// =============================================================================
+
+TEST(GetString8ValueFromMapTest, HandlesEmptyStringKey) {
+  std::vector<uint8_t> encoded = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+  EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom(""), SpanFrom("value1"),
+                                          &encoded),
+              StatusIsOk());
+  EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key2"), SpanFrom("value2"),
+                                          &encoded),
+              StatusIsOk());
+
+  span<uint8_t> result1 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom(""));
+  EXPECT_EQ("value1", std::string(result1.begin(), result1.end()));
+
+  span<uint8_t> result2 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key2"));
+  EXPECT_EQ("value2", std::string(result2.begin(), result2.end()));
+}
+
+TEST(GetString8ValueFromMapTest, ExtractsValueSuccessfully) {
+  std::vector<uint8_t> encoded = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+  EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key1"), SpanFrom("value1"),
+                                          &encoded),
+              StatusIsOk());
+  EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key2"), SpanFrom("value2"),
+                                          &encoded),
+              StatusIsOk());
+
+  span<uint8_t> result1 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key1"));
+  EXPECT_EQ("value1", std::string(result1.begin(), result1.end()));
+
+  span<uint8_t> result2 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key2"));
+  EXPECT_EQ("value2", std::string(result2.begin(), result2.end()));
+}
+
+TEST(GetString8ValueFromMapTest, HandlesMissingKey) {
+  std::vector<uint8_t> encoded = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+  EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key1"), SpanFrom("value1"),
+                                          &encoded),
+              StatusIsOk());
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("missing_key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, HandlesNonStringValuesGracefully) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("int_key"), &encoded);
+  EncodeInt32(42, &encoded);
+
+  EncodeString8(SpanFrom("string_key"), &encoded);
+  EncodeString8(SpanFrom("string_value"), &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result1 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("int_key"));
+  EXPECT_TRUE(result1.empty());
+
+  span<uint8_t> result2 =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("string_key"));
+  EXPECT_EQ("string_value", std::string(result2.begin(), result2.end()));
+}
+
+TEST(GetString8ValueFromMapTest, HandlesErrorValue) {
+  // Envelope with 2 bytes of content:
+  // [Map Start (0xbf), String Start length 1 (0x61)].
+  // The string key is truncated (missing its data byte), which triggers
+  // CBORTokenTag::ERROR_VALUE.
+  std::vector<uint8_t> encoded = {
+      0xd8, 0x5a, 0, 0, 0, 2,
+      EncodeIndefiniteLengthMapStart(),
+      0x61 /* major type 3 (string), additional info 1 (length 1) */
+  };
+
+  // Without the fix for the infinite loop, this call would never return.
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, RejectsDuplicateKeys) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeString8(SpanFrom("value1"), &encoded);
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeString8(SpanFrom("value2"), &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, RejectsNestedStructures) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("fake"), &encoded);
+  // Nested map: {"key": "spoofed"}
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeString8(SpanFrom("spoofed"), &encoded);
+  encoded.push_back(EncodeStop());
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, RejectsString16Values) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  // "value16" in UTF-16LE
+  std::vector<uint16_t> value16 = {'v', 'a', 'l', 'u', 'e', '1', '6'};
+  EncodeString16(SpanFrom(value16), &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, HandlesEmptyValuesWithDuplicates) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeString8(SpanFrom(""), &encoded);
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeString8(SpanFrom("value2"), &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+
+TEST(GetString8ValueFromMapTest, RejectsTruncatedMap) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  // Missing value here.
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(encoded), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(GetString8ValueFromMapTest, InvalidMessage) {
+  std::vector<uint8_t> msg = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), 42};
+  span<uint8_t> result =
+      GetString8ValueFromMap(SpanFrom(msg), SpanFrom("key"));
+  EXPECT_TRUE(result.empty());
+}
+
+TEST(HasKeyInMapTest, FindsKeySuccessfully) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key1"), &encoded);
+  EncodeString8(SpanFrom("value1"), &encoded);
+
+  EncodeString8(SpanFrom("key2"), &encoded);
+  EncodeInt32(42, &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key1")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key2")));
+}
+
+TEST(HasKeyInMapTest, HandlesMissingKey) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key1"), &encoded);
+  EncodeString8(SpanFrom("value1"), &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  EXPECT_FALSE(HasKeyInMap(SpanFrom(encoded), SpanFrom("missing_key")));
+}
+
+TEST(HasKeyInMapTest, WorksWithVariousValueTypes) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("string_key"), &encoded);
+  EncodeString8(SpanFrom("value"), &encoded);
+
+  EncodeString8(SpanFrom("int_key"), &encoded);
+  EncodeInt32(42, &encoded);
+
+  EncodeString8(SpanFrom("double_key"), &encoded);
+  EncodeDouble(3.14, &encoded);
+
+  EncodeString8(SpanFrom("bool_true_key"), &encoded);
+  encoded.push_back(EncodeTrue());
+
+  EncodeString8(SpanFrom("bool_false_key"), &encoded);
+  encoded.push_back(EncodeFalse());
+
+  EncodeString8(SpanFrom("null_key"), &encoded);
+  encoded.push_back(EncodeNull());
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("string_key")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("int_key")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("double_key")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("bool_true_key")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("bool_false_key")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("null_key")));
+}
+
+TEST(HasKeyInMapTest, HandlesErrorValue) {
+  // Envelope with 2 bytes of content:
+  // [Map Start (0xbf), String Start length 1 (0x61)].
+  // The string key is truncated (missing its data byte), which triggers
+  // CBORTokenTag::ERROR_VALUE.
+  std::vector<uint8_t> encoded = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(),
+      0x61 /* major type 3 (string), additional info 1 (length 1) */
+  };
+
+  EXPECT_FALSE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key")));
+}
+
+TEST(HasKeyInMapTest, AllowsDuplicateKeys) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeInt32(42, &encoded);
+
+  EncodeString8(SpanFrom("key"), &encoded);
+  EncodeInt32(24, &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key")));
+}
+
+TEST(HasKeyInMapTest, AllowsNestedStructures) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key1"), &encoded);
+  // Start a nested map as value.
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+  EncodeString8(SpanFrom("inner_key"), &encoded);
+  EncodeInt32(42, &encoded);
+  encoded.push_back(EncodeStop());
+
+  EncodeString8(SpanFrom("key2"), &encoded);
+  EncodeInt32(24, &encoded);
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  // We should find top-level keys even with nested structures.
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key1")));
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key2")));
+
+  // We should NOT find keys that are inside nested structures (they are not
+  // top-level).
+  EXPECT_FALSE(HasKeyInMap(SpanFrom(encoded), SpanFrom("inner_key")));
+}
+
+TEST(HasKeyInMapTest, HandlesTruncation) {
+  std::vector<uint8_t> encoded;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&encoded);
+  encoded.push_back(EncodeIndefiniteLengthMapStart());
+
+  EncodeString8(SpanFrom("key1"), &encoded);
+  // Missing value here.
+  // Truncated before we can write key2.
+
+  encoded.push_back(EncodeStop());
+  envelope.EncodeStop(&encoded);
+
+  // "key1" is found because we return early on seeing the key.
+  EXPECT_TRUE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key1")));
+
+  // "key2" is not found because it is not in the map (and map is truncated).
+  EXPECT_FALSE(HasKeyInMap(SpanFrom(encoded), SpanFrom("key2")));
+}
+
+TEST(HasKeyInMapTest, InvalidMessage) {
+  std::vector<uint8_t> msg = {
+      0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), 42};
+  EXPECT_FALSE(HasKeyInMap(SpanFrom(msg), SpanFrom("key")));
+}
+
 }  // namespace cbor
 }  // namespace crdtp
diff --git a/deps/inspector_protocol/crdtp/dispatch.cc b/deps/inspector_protocol/crdtp/dispatch.cc
index b36b91ad0e3..9f94cf2793c 100644
--- a/deps/inspector_protocol/crdtp/dispatch.cc
+++ b/deps/inspector_protocol/crdtp/dispatch.cc
@@ -30,6 +30,14 @@ DispatchResponse DispatchResponse::FallThrough() {
   return result;
 }

+// static
+DispatchResponse DispatchResponse::FallThrough(std::string associated_data) {
+  DispatchResponse result;
+  result.code_ = DispatchCode::FALL_THROUGH;
+  result.message_ = std::move(associated_data);
+  return result;
+}
+
 // static
 DispatchResponse DispatchResponse::ParseError(std::string message) {
   DispatchResponse result;
@@ -89,7 +97,12 @@ DispatchResponse DispatchResponse::SessionNotFound(std::string message) {
 // =============================================================================
 // Dispatchable - a shallow parser for CBOR encoded DevTools messages
 // =============================================================================
-Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
+Dispatchable::Dispatchable(span<uint8_t> serialized,
+                           std::string_view associated_data,
+                           FallthroughCallback fallthrough_callback)
+    : serialized_(serialized),
+      associated_data_(associated_data),
+      fallthrough_callback_(std::move(fallthrough_callback)) {
   Status s = cbor::CheckCBORMessage(serialized);
   if (!s.ok()) {
     status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, s.pos};
@@ -264,6 +277,17 @@ bool Dispatchable::MaybeParseSessionId(cbor::CBORTokenizer* tokenizer) {
   return true;
 }

+FallthroughCallback Dispatchable::TakeFallthroughCallback() {
+  FallthroughCallback result;
+  std::swap(result, fallthrough_callback_);
+  return result;
+}
+
+void Dispatchable::DispatchFallThrough(const std::string& associated_data) {
+  assert(fallthrough_callback_);
+  TakeFallthroughCallback()(call_id_, method_, serialized_, associated_data);
+}
+
 namespace {
 class ProtocolError : public Serializable {
  public:
@@ -424,13 +448,13 @@ void DomainDispatcher::Callback::dispose() {

 DomainDispatcher::Callback::Callback(
     std::unique_ptr<DomainDispatcher::WeakPtr> backend_impl,
-    int call_id,
-    span<uint8_t> method,
-    span<uint8_t> message)
+    Dispatchable& dispatchable)
     : backend_impl_(std::move(backend_impl)),
-      call_id_(call_id),
-      method_(method),
-      message_(message.begin(), message.end()) {}
+      call_id_(dispatchable.CallId()),
+      method_(dispatchable.Method()),
+      message_(dispatchable.Serialized().begin(),
+               dispatchable.Serialized().end()),
+      fallthrough_callback_(dispatchable.TakeFallthroughCallback()) {}

 void DomainDispatcher::Callback::sendIfActive(
     std::unique_ptr<Serializable> partialMessage,
@@ -445,8 +469,11 @@ void DomainDispatcher::Callback::sendIfActive(
 void DomainDispatcher::Callback::fallThroughIfActive() {
   if (!backend_impl_ || !backend_impl_->get())
     return;
-  backend_impl_->get()->channel()->FallThrough(call_id_, method_,
-                                               SpanFrom(message_));
+  // Fallthrough callback may be retaining session which outlives backend.
+  // TODO(caseq): handle fall-through associated data for async callbacks.
+  fallthrough_callback_(call_id_, method_, SpanFrom(message_),
+                        std::string_view());
+  fallthrough_callback_ = nullptr;
   backend_impl_ = nullptr;
 }

@@ -499,17 +526,6 @@ std::unique_ptr<DomainDispatcher::WeakPtr> DomainDispatcher::weakPtr() {
 // =============================================================================
 // UberDispatcher - dispatches between domains (backends).
 // =============================================================================
-UberDispatcher::DispatchResult::DispatchResult(bool method_found,
-                                               std::function<void()> runnable)
-    : method_found_(method_found), runnable_(runnable) {}
-
-void UberDispatcher::DispatchResult::Run() {
-  if (!runnable_)
-    return;
-  runnable_();
-  runnable_ = nullptr;
-}
-
 UberDispatcher::UberDispatcher(FrontendChannel* frontend_channel)
     : frontend_channel_(frontend_channel) {
   assert(frontend_channel);
@@ -526,36 +542,34 @@ size_t DotIdx(span<uint8_t> method) {
 }
 }  // namespace

-UberDispatcher::DispatchResult UberDispatcher::Dispatch(
-    const Dispatchable& dispatchable) const {
+void UberDispatcher::Dispatch(Dispatchable& dispatchable) {
   span<uint8_t> method = FindByFirst(redirects_, dispatchable.Method(),
                                      /*default_value=*/dispatchable.Method());
   size_t dot_idx = DotIdx(method);
   if (dot_idx != kNotFound) {
     span<uint8_t> domain = method.subspan(0, dot_idx);
     span<uint8_t> command = method.subspan(dot_idx + 1);
-    DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain);
-    if (dispatcher) {
-      std::function<void(const Dispatchable&)> dispatched =
-          dispatcher->Dispatch(command);
-      if (dispatched) {
-        return DispatchResult(
-            true, [dispatchable, dispatched = std::move(dispatched)]() {
-              dispatched(dispatchable);
-            });
+    if (DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain)) {
+      if (dispatcher->Dispatch(command, dispatchable)) {
+        return;
       }
     }
   }
-  return DispatchResult(false, [this, dispatchable]() {
-    frontend_channel_->SendProtocolResponse(
-        dispatchable.CallId(),
-        CreateErrorResponse(dispatchable.CallId(),
-                            DispatchResponse::MethodNotFound(
-                                "'" +
-                                std::string(dispatchable.Method().begin(),
-                                            dispatchable.Method().end()) +
-                                "' wasn't found")));
-  });
+  if (auto fallthrough = dispatchable.TakeFallthroughCallback()) {
+    fallthrough(dispatchable.CallId(), dispatchable.Method(),
+                dispatchable.Serialized(), std::string_view());
+  } else {
+    SendMethodNotFound(dispatchable.CallId(), method);
+  }
+}
+
+void UberDispatcher::SendMethodNotFound(int call_id, span<uint8_t> method) {
+  frontend_channel_->SendProtocolResponse(
+      call_id,
+      CreateErrorResponse(call_id,
+                          DispatchResponse::MethodNotFound(
+                              "'" + std::string(method.begin(), method.end()) +
+                              "' wasn't found")));
 }

 template <typename T>
diff --git a/deps/inspector_protocol/crdtp/dispatch.h b/deps/inspector_protocol/crdtp/dispatch.h
index 8da8255c6fe..4f5d33990cf 100644
--- a/deps/inspector_protocol/crdtp/dispatch.h
+++ b/deps/inspector_protocol/crdtp/dispatch.h
@@ -9,7 +9,9 @@
 #include <cstdint>
 #include <functional>
 #include <string>
+#include <string_view>
 #include <unordered_set>
+
 #include "export.h"
 #include "serializable.h"
 #include "span.h"
@@ -55,6 +57,7 @@ class CRDTP_EXPORT DispatchResponse {

   static DispatchResponse Success();
   static DispatchResponse FallThrough();
+  static DispatchResponse FallThrough(std::string associated_data);

   // Indicates that a message could not be parsed. E.g., malformed JSON.
   static DispatchResponse ParseError(std::string message);
@@ -84,9 +87,20 @@ class CRDTP_EXPORT DispatchResponse {
  private:
   DispatchResponse() = default;
   DispatchCode code_;
+  // For error responses, a message describing the error.
+  // For fall-through responses, fall-through associated message.
   std::string message_;
 };

+// This callback is invoked if the command was not dispatched due to handler
+// not being found. The caller has an opportunity to dispatch the command
+// to a different layer then.
+using FallthroughCallback =
+    std::function<void(int call_id,
+                       span<uint8_t> method,
+                       span<uint8_t> serialized_message,
+                       std::string_view associated_data)>;
+
 // =============================================================================
 // Dispatchable - a shallow parser for CBOR encoded DevTools messages
 // =============================================================================
@@ -100,7 +114,16 @@ class CRDTP_EXPORT Dispatchable {
   // |Params()| can be used to access, the extracted contents. Otherwise,
   // |ok()| will yield |false|, and |DispatchError()| can be
   // used to send a response or notification to the client.
-  explicit Dispatchable(span<uint8_t> serialized);
+  // |associated_data| would be passed as is to handler methods that were
+  // configured as those willing to receive it.
+  // |fallthrough_callback|, if non-empty, will be invoked by the dispatcher
+  // if no handler was found or if the handler chose to fall through,
+  // and can be used to pass command to be handled elsewhere.
+  // Note that in case of an async command, fallthrough_callback may be invoked
+  // asynchronously.
+  Dispatchable(span<uint8_t> serialized,
+               std::string_view associated_data,
+               FallthroughCallback fallthrough_callback);

   // The serialized message that we just parsed.
   span<uint8_t> Serialized() const { return serialized_; }
@@ -127,6 +150,17 @@ class CRDTP_EXPORT Dispatchable {
   // not parse into this; it only provides access to its raw contents here.
   span<uint8_t> Params() const { return params_; }

+  std::string_view AssociatedData() const { return associated_data_; }
+
+  // Takes the fallthrough callback, if any.
+  FallthroughCallback TakeFallthroughCallback();
+  // Takes the fallthrough callback and dispatches it with stored
+  // call/method/message. The associated_data is passed through to
+  // the other handling layer (not to be confused with the member
+  // associated_data which is the one to be passed to handlers
+  // during current dispatch).
+  void DispatchFallThrough(const std::string& associated_data);
+
  private:
   bool MaybeParseProperty(cbor::CBORTokenizer* tokenizer);
   bool MaybeParseCallId(cbor::CBORTokenizer* tokenizer);
@@ -144,6 +178,8 @@ class CRDTP_EXPORT Dispatchable {
   bool params_seen_ = false;
   span<uint8_t> params_;
   span<uint8_t> session_id_;
+  std::string_view associated_data_;
+  FallthroughCallback fallthrough_callback_;
 };

 // =============================================================================
@@ -197,10 +233,7 @@ class CRDTP_EXPORT DomainDispatcher {

    protected:
     // |method| must point at static storage (a C++ string literal in practice).
-    Callback(std::unique_ptr<WeakPtr> backend_impl,
-             int call_id,
-             span<uint8_t> method,
-             span<uint8_t> message);
+    Callback(std::unique_ptr<WeakPtr> backend_impl, Dispatchable& dispatchable);

     void sendIfActive(std::unique_ptr<Serializable> partialMessage,
                       const DispatchResponse& response);
@@ -214,20 +247,19 @@ class CRDTP_EXPORT DomainDispatcher {
     // storage for |method| is the binary of the running process.
     span<uint8_t> method_;
     std::vector<uint8_t> message_;
+    FallthroughCallback fallthrough_callback_;
   };

   explicit DomainDispatcher(FrontendChannel*);
   virtual ~DomainDispatcher();

   // Given a |command_name| without domain qualification, looks up the
-  // corresponding method. If the method is not found, returns nullptr.
-  // Otherwise, Returns a closure that will parse the provided
-  // Dispatchable.params() to a protocol object and execute the
-  // apprpropriate method. If the parsing fails it will issue an
-  // error response on the frontend channel, otherwise it will execute the
-  // command.
-  virtual std::function<void(const Dispatchable&)> Dispatch(
-      span<uint8_t> command_name) = 0;
+  // corresponding method and attempeds to parse and execute it.
+  // If the command is not found, returns false. Otherwise and possible
+  // result, whether a response or error, is send using underlying
+  // frontend_channel_.
+  virtual bool Dispatch(span<uint8_t> command_name,
+                        Dispatchable& dispatchable) = 0;

   // Sends a response to the client via the channel.
   void sendResponse(int call_id,
@@ -253,25 +285,6 @@ class CRDTP_EXPORT DomainDispatcher {
 // =============================================================================
 class CRDTP_EXPORT UberDispatcher {
  public:
-  // Return type for ::Dispatch.
-  class CRDTP_EXPORT DispatchResult {
-   public:
-    DispatchResult(bool method_found, std::function<void()> runnable);
-
-    // Indicates whether the method was found, that is, it could be dispatched
-    // to a backend registered with this dispatcher.
-    bool MethodFound() const { return method_found_; }
-
-    // Runs the dispatched result. This will send the appropriate error
-    // responses if the method wasn't found or if something went wrong during
-    // parameter parsing.
-    void Run();
-
-   private:
-    bool method_found_;
-    std::function<void()> runnable_;
-  };
-
   // |frontend_hannel| can't be nullptr.
   explicit UberDispatcher(FrontendChannel* frontend_channel);
   virtual ~UberDispatcher();
@@ -280,7 +293,9 @@ class CRDTP_EXPORT UberDispatcher {
   // handlers registered with this uber dispatcher. Also see |DispatchResult|.
   // |dispatchable.ok()| must hold - callers must check this separately and
   // deal with errors.
-  DispatchResult Dispatch(const Dispatchable& dispatchable) const;
+  void Dispatch(Dispatchable& dispatchable);
+
+  void SendMethodNotFound(int call_id, span<uint8_t> method);

   // Invoked from generated code for wiring domain backends; that is,
   // connecting domain handlers to an uber dispatcher.
@@ -308,6 +323,7 @@ class CRDTP_EXPORT UberDispatcher {
   std::vector<std::pair<span<uint8_t>, std::unique_ptr<DomainDispatcher>>>
       dispatchers_;
 };
+
 }  // namespace crdtp

 #endif  // CRDTP_DISPATCH_H_
diff --git a/deps/inspector_protocol/crdtp/dispatch_test.cc b/deps/inspector_protocol/crdtp/dispatch_test.cc
index c7290f92961..e21b9bb309d 100644
--- a/deps/inspector_protocol/crdtp/dispatch_test.cc
+++ b/deps/inspector_protocol/crdtp/dispatch_test.cc
@@ -55,13 +55,21 @@ TEST(DispatchResponseTest, FallThrough) {
   EXPECT_EQ(DispatchCode::FALL_THROUGH, error.Code());
 }

+TEST(DispatchResponseTest, FallThroughWithAssociatedData) {
+  DispatchResponse fall_through = DispatchResponse::FallThrough("some details");
+  EXPECT_FALSE(fall_through.IsSuccess());
+  EXPECT_TRUE(fall_through.IsFallThrough());
+  EXPECT_EQ(DispatchCode::FALL_THROUGH, fall_through.Code());
+  EXPECT_EQ("some details", fall_through.Message());
+}
+
 // =============================================================================
 // Dispatchable - a shallow parser for CBOR encoded DevTools messages
 // =============================================================================
 TEST(DispatchableTest, MessageMustBeAnObject) {
   // Provide no input whatsoever.
   span<uint8_t> empty_span;
-  Dispatchable empty(empty_span);
+  Dispatchable empty(empty_span, std::string_view(), nullptr);
   EXPECT_FALSE(empty.ok());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, empty.DispatchError().Code());
   EXPECT_EQ("Message must be an object", empty.DispatchError().Message());
@@ -71,7 +79,7 @@ TEST(DispatchableTest, MessageMustHaveIntegerIdProperty) {
   // Construct an empty map inside of an envelope.
   std::vector<uint8_t> cbor;
   ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{}"), &cbor).ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_FALSE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -84,7 +92,7 @@ TEST(DispatchableTest, MessageMustHaveIntegerIdProperty_IncorrectType) {
   std::vector<uint8_t> cbor;
   ASSERT_TRUE(
       json::ConvertJSONToCBOR(SpanFrom("{\"id\":\"foo\"}"), &cbor).ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_FALSE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -96,7 +104,7 @@ TEST(DispatchableTest, MessageMustHaveStringMethodProperty) {
   // This time we set the id property, but not the method property.
   std::vector<uint8_t> cbor;
   ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{\"id\":42}"), &cbor).ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -110,7 +118,7 @@ TEST(DispatchableTest, MessageMustHaveStringMethodProperty_IncorrectType) {
   ASSERT_TRUE(
       json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":42}"), &cbor)
           .ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -128,7 +136,7 @@ TEST(DispatchableTest, MessageMayHaveStringSessionIdProperty) {
                            "}"),
                   &cbor)
                   .ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -145,7 +153,7 @@ TEST(DispatchableTest, MessageMayHaveObjectParamsProperty) {
                            "}"),
                   &cbor)
                   .ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -159,7 +167,7 @@ TEST(DispatchableTest, MessageWithUnknownProperty) {
   ASSERT_TRUE(
       json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"unknown\":42}"), &cbor)
           .ok());
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
@@ -177,7 +185,7 @@ TEST(DispatchableTest, DuplicateMapKey) {
     SCOPED_TRACE("json = " + json);
     std::vector<uint8_t> cbor;
     ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
-    Dispatchable dispatchable(SpanFrom(cbor));
+    Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
     EXPECT_FALSE(dispatchable.ok());
     EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
     EXPECT_THAT(dispatchable.DispatchError().Message(),
@@ -195,7 +203,7 @@ TEST(DispatchableTest, ValidMessageParsesOK_NoParams) {
     SCOPED_TRACE("json = " + json);
     std::vector<uint8_t> cbor;
     ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
-    Dispatchable dispatchable(SpanFrom(cbor));
+    Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
     EXPECT_TRUE(dispatchable.ok());
     EXPECT_TRUE(dispatchable.HasCallId());
     EXPECT_EQ(42, dispatchable.CallId());
@@ -228,7 +236,7 @@ TEST(DispatchableTest, ValidMessageParsesOK_WithParams) {
   cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor);
   cbor.push_back(cbor::EncodeStop());
   envelope.EncodeStop(&cbor);
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), "associated data", nullptr);
   EXPECT_TRUE(dispatchable.ok());
   EXPECT_TRUE(dispatchable.HasCallId());
   EXPECT_EQ(42, dispatchable.CallId());
@@ -242,6 +250,7 @@ TEST(DispatchableTest, ValidMessageParsesOK_WithParams) {
   ASSERT_EQ(cbor::CBORTokenTag::STRING8, params_tokenizer.TokenTag());
   EXPECT_EQ("params payload", std::string(params_tokenizer.GetString8().begin(),
                                           params_tokenizer.GetString8().end()));
+  EXPECT_EQ(dispatchable.AssociatedData(), "associated data");
 }

 TEST(DispatchableTest, FaultyCBORTrailingJunk) {
@@ -266,7 +275,7 @@ TEST(DispatchableTest, FaultyCBORTrailingJunk) {
   cbor.push_back('a');
   cbor.push_back('i');
   cbor.push_back('l');
-  Dispatchable dispatchable(SpanFrom(cbor));
+  Dispatchable dispatchable(SpanFrom(cbor), std::string_view(), nullptr);
   EXPECT_FALSE(dispatchable.ok());
   EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
   EXPECT_EQ(57u, trailing_junk_pos);
@@ -341,9 +350,7 @@ class TestChannel : public FrontendChannel {
     cbor_ = message->Serialize();
   }

-  void FallThrough(int call_id,
-                   span<uint8_t> method,
-                   span<uint8_t> message) override {}
+

   void FlushProtocolNotifications() override {}

@@ -359,11 +366,9 @@ TEST(UberDispatcherTest, MethodNotFound) {
   std::vector<uint8_t> message;
   json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.bar\"}"),
                           &message);
-  Dispatchable dispatchable(SpanFrom(message));
+  Dispatchable dispatchable(SpanFrom(message), std::string_view(), nullptr);
   ASSERT_TRUE(dispatchable.ok());
-  UberDispatcher::DispatchResult dispatched = dispatcher.Dispatch(dispatchable);
-  EXPECT_FALSE(dispatched.MethodFound());
-  dispatched.Run();
+  dispatcher.Dispatch(dispatchable);
   EXPECT_EQ(
       "{\"id\":42,\"error\":"
       "{\"code\":-32601,\"message\":\"'Foo.bar' wasn't found\"}}",
@@ -376,13 +381,12 @@ class TestDomain : public DomainDispatcher {
  public:
   explicit TestDomain(FrontendChannel* channel) : DomainDispatcher(channel) {}

-  std::function<void(const Dispatchable&)> Dispatch(
-      span<uint8_t> command_name) override {
+  bool Dispatch(span<uint8_t> command_name,
+                Dispatchable& dispatchable) override {
     dispatched_commands_.push_back(
         std::string(command_name.begin(), command_name.end()));
-    return [this](const Dispatchable& dispatchable) {
-      executed_commands_.push_back(dispatchable.CallId());
-    };
+    executed_commands_.push_back(dispatchable.CallId());
+    return true;
   }

   // Command names of the dispatched commands.
@@ -418,23 +422,17 @@ TEST(UberDispatcherTest, DispatchingToDomainWithRedirects) {
     std::vector<uint8_t> message;
     json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.execute\"}"),
                             &message);
-    Dispatchable dispatchable(SpanFrom(message));
+    Dispatchable dispatchable(SpanFrom(message), std::string_view(), nullptr);
     ASSERT_TRUE(dispatchable.ok());
-    UberDispatcher::DispatchResult dispatched =
-        dispatcher.Dispatch(dispatchable);
-    EXPECT_TRUE(dispatched.MethodFound());
-    dispatched.Run();
+    dispatcher.Dispatch(dispatchable);
   }
   {
     std::vector<uint8_t> message;
     json::ConvertJSONToCBOR(SpanFrom("{\"id\":43,\"method\":\"Foo.redirect\"}"),
                             &message);
-    Dispatchable dispatchable(SpanFrom(message));
+    Dispatchable dispatchable(SpanFrom(message), std::string_view(), nullptr);
     ASSERT_TRUE(dispatchable.ok());
-    UberDispatcher::DispatchResult dispatched =
-        dispatcher.Dispatch(dispatchable);
-    EXPECT_TRUE(dispatched.MethodFound());
-    dispatched.Run();
+    dispatcher.Dispatch(dispatchable);
   }
   EXPECT_THAT(foo->DispatchedCommands(), testing::ElementsAre("execute"));
   EXPECT_THAT(foo->ExecutedCommands(), testing::ElementsAre(42));
diff --git a/deps/inspector_protocol/crdtp/frontend_channel.h b/deps/inspector_protocol/crdtp/frontend_channel.h
index 652c65219d4..fddb52e51e3 100644
--- a/deps/inspector_protocol/crdtp/frontend_channel.h
+++ b/deps/inspector_protocol/crdtp/frontend_channel.h
@@ -30,14 +30,6 @@ class CRDTP_EXPORT FrontendChannel {
   virtual void SendProtocolNotification(
       std::unique_ptr<Serializable> message) = 0;

-  // FallThrough indicates that |message| should be handled in another layer.
-  // Usually this means the layer responding to the message didn't handle it,
-  // but in some cases messages are handled by multiple layers (e.g. both
-  // the embedder and the content layer in Chromium).
-  virtual void FallThrough(int call_id,
-                           span<uint8_t> method,
-                           span<uint8_t> message) = 0;
-
   // Session implementations may queue notifications for performance or
   // other considerations; this is a hook for domain handlers to manually flush.
   virtual void FlushProtocolNotifications() = 0;
diff --git a/deps/inspector_protocol/inspector_protocol.gni b/deps/inspector_protocol/inspector_protocol.gni
index 6b278c9fa8e..b9948c08a04 100644
--- a/deps/inspector_protocol/inspector_protocol.gni
+++ b/deps/inspector_protocol/inspector_protocol.gni
@@ -144,8 +144,8 @@ template("inspector_protocol_concatenate") {
     inputs += invoker.inputs
     args += rebase_path(invoker.inputs, root_build_dir)

-    outputs += [invoker.output_file]
-    args += [rebase_path(invoker.output_file, root_build_dir)]
+    outputs += [ invoker.output_file ]
+    args += [ rebase_path(invoker.output_file, root_build_dir) ]

     forward_variables_from(invoker,
                            [
diff --git a/deps/inspector_protocol/lib/Values_cpp.template b/deps/inspector_protocol/lib/Values_cpp.template
index abeee79e5c3..2d34fb3de4f 100644
--- a/deps/inspector_protocol/lib/Values_cpp.template
+++ b/deps/inspector_protocol/lib/Values_cpp.template
@@ -461,7 +461,7 @@ double DictionaryValue::doubleProperty(const String& name, double defaultValue)
 void DictionaryValue::remove(const String& name)
 {
     m_data.erase(name);
-    m_order.erase(std::remove(m_order.begin(), m_order.end(), name), m_order.end());
+    std::erase(m_order, name);
 }

 void DictionaryValue::AppendSerialized(std::vector<uint8_t>* bytes) const {
diff --git a/deps/inspector_protocol/pdl.py b/deps/inspector_protocol/pdl.py
index 5b53291fd5b..eb8f2474531 100644
--- a/deps/inspector_protocol/pdl.py
+++ b/deps/inspector_protocol/pdl.py
@@ -89,15 +89,16 @@ def parse(data, file_name, map_binary_to_string, source_set):
       protocol['domains'].append(domain)
       continue

-    match = re.compile(
-        r'^include (.*)').match(line)
+    match = re.compile(r'^include (.*)').match(line)
     if match:
       included_filename = match.group(1)
       if os.path.isabs(included_filename):
         raise Exception("Only relative paths are supported in includes")
-      resolved_path = os.path.normpath(os.path.join(os.path.dirname(file_name), included_filename))
+      resolved_path = os.path.normpath(
+          os.path.join(os.path.dirname(file_name), included_filename))
       with open(resolved_path, 'r') as file:
-        included_data = parse(file.read(), resolved_path, map_binary_to_string, source_set)
+        included_data = parse(file.read(), resolved_path, map_binary_to_string,
+                              source_set)
         protocol['domains'].extend(included_data['domains'])
       continue

diff --git a/deps/inspector_protocol/templates/TypeBuilder_cpp.template b/deps/inspector_protocol/templates/TypeBuilder_cpp.template
index a38182c311a..2f70a9d1299 100644
--- a/deps/inspector_protocol/templates/TypeBuilder_cpp.template
+++ b/deps/inspector_protocol/templates/TypeBuilder_cpp.template
@@ -18,6 +18,7 @@ namespace {{namespace}} {
 namespace {{domain.domain}} {

 using {{config.crdtp.namespace}}::DeserializerState;
+using {{config.crdtp.namespace}}::FallthroughCallback;
 using {{config.crdtp.namespace}}::ProtocolTypeTraits;

 // ------------- Enum values from types.
@@ -155,14 +156,14 @@ public:
         , m_backend(backend) {}
     ~DomainDispatcherImpl() override { }

-    using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable);
+    using CallHandler = void (DomainDispatcherImpl::*)({{config.crdtp.namespace}}::Dispatchable& dispatchable);

-    std::function<void(const {{config.crdtp.namespace}}::Dispatchable&)> Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name) override;
+    bool Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name, {{config.crdtp.namespace}}::Dispatchable& dispatchable) override;

   {% for command in domain.commands %}
     {% if "redirect" in command %}{% continue %}{% endif %}
     {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %}
-    void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable);
+    void {{command.name}}({{config.crdtp.namespace}}::Dispatchable& dispatchable);
   {% endfor %}
  protected:
     Backend* m_backend;
@@ -191,13 +192,12 @@ DomainDispatcherImpl::CallHandler CommandByName({{config.crdtp.namespace}}::span
 }
 }  // namespace

-std::function<void(const {{config.crdtp.namespace}}::Dispatchable&)> DomainDispatcherImpl::Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name) {
+bool DomainDispatcherImpl::Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name, {{config.crdtp.namespace}}::Dispatchable& dispatchable) {
   CallHandler handler = CommandByName(command_name);
-  if (!handler) return nullptr;
+  if (!handler) return false;

-  return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable) {
-    (this->*handler)(dispatchable);
-  };
+  (this->*handler)(dispatchable);
+  return true;
 }

   {% for command in domain.commands %}
@@ -208,9 +208,8 @@ std::function<void(const {{config.crdtp.namespace}}::Dispatchable&)> DomainDispa

 class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DomainDispatcher::Callback {
 public:
-    {{command_name_title}}CallbackImpl(std::unique_ptr<DomainDispatcher::WeakPtr> backendImpl, int callId, {{config.crdtp.namespace}}::span<uint8_t> message)
-        : DomainDispatcher::Callback(std::move(backendImpl), callId,
-{{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}.{{command.name}}"), message) { }
+    {{command_name_title}}CallbackImpl(std::unique_ptr<DomainDispatcher::WeakPtr> backendImpl, {{config.crdtp.namespace}}::Dispatchable& dispatchable)
+        : DomainDispatcher::Callback(std::move(backendImpl), dispatchable) { }

     void sendSuccess(
       {%- for parameter in command.returns -%}
@@ -270,7 +269,7 @@ CRDTP_END_DESERIALIZER()

 }  // namespace

-void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable)
+void DomainDispatcherImpl::{{command.name}}({{config.crdtp.namespace}}::Dispatchable& dispatchable)
 {
     // Prepare input parameters.
     {% if "parameters" in command %}
@@ -304,6 +303,13 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
         {{protocol.resolve_type(parameter).to_pass_type % ("params." + parameter.name)}}
         {%- endif -%}
       {%- endfor %}
+
+      {%- if protocol.should_pass_associated_data(domain.domain, command.name) -%}
+        {%- if command.parameters -%}, {% endif -%}
+        {{config.crdtp.namespace}}::ConvertAssociatedData<std::remove_cvref_t<{{protocol.type_definition(domain.domain + ".string").pass_type}}>>(
+            dispatchable.AssociatedData())
+      {%- endif -%}
+
       {%- if "returns" in command %}
         {%- for parameter in command.returns -%}
           {%- if not loop.first or command.parameters -%}, {% endif -%}
@@ -311,7 +317,7 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
         {%- endfor %}
       {% endif %});
     if (response.IsFallThrough()) {
-        channel()->FallThrough(dispatchable.CallId(), {{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}.{{command.name}}"), dispatchable.Serialized());
+        dispatchable.DispatchFallThrough(response.Message());
         return;
     }
       {% if "returns" in command %}
@@ -344,7 +350,7 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
         {%- endif -%}
       {%- endfor -%}
         {%- if command.parameters -%}, {% endif -%}
-        std::make_unique<{{command_name_title}}CallbackImpl>(weakPtr(), dispatchable.CallId(), dispatchable.Serialized()));
+        std::make_unique<{{command_name_title}}CallbackImpl>(weakPtr(), dispatchable));
     {% endif %}
 }
   {% endfor %}
diff --git a/deps/inspector_protocol/templates/TypeBuilder_h.template b/deps/inspector_protocol/templates/TypeBuilder_h.template
index 33c5d1af535..38b1fcf6da2 100644
--- a/deps/inspector_protocol/templates/TypeBuilder_h.template
+++ b/deps/inspector_protocol/templates/TypeBuilder_h.template
@@ -256,8 +256,12 @@ public:
         {{protocol.resolve_type(parameter).pass_type}} in_{{parameter.name}}
       {%- endif -%}
     {%- endfor -%}
-    {%- if protocol.is_async_command(domain.domain, command.name) -%}
+    {%- if protocol.should_pass_associated_data(domain.domain, command.name) -%}
       {%- if command.parameters -%}, {% endif -%}
+      {{protocol.type_definition(domain.domain + ".string").pass_type}} associated_data
+    {%- endif -%}
+    {%- if protocol.is_async_command(domain.domain, command.name) -%}
+      {%- if command.parameters or protocol.should_pass_associated_data(domain.domain, command.name) -%}, {% endif -%}
         std::unique_ptr<{{command.name | to_title_case}}Callback> callback
     {%- else -%}
       {%- for parameter in command.returns -%}