From c380a35f3db2a2a26808aaafad21f95080e2a2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 3 Sep 2025 16:28:40 +0200 Subject: [PATCH 1/4] Add support on annotation hook --- deps/v8/src/api/api.cc | 3 +++ lib/internal/bootstrap/node.js | 1 + src/node_process_methods.cc | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index a4ce1eb2d235a5..f14d408cb1b00c 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -10416,6 +10416,7 @@ static const char* (*gRecordReplayGetRecordingId)(); static char* (*gRecordReplayCurrentExecutionPoint)(); static void (*gRecordReplayFree)(void*); static size_t (*gRecordReplayElapsedTimeMs)(); +static void (*gRecordReplayOnAnnotation)(const char*, const char*); namespace internal { @@ -10929,6 +10930,8 @@ void recordreplay::SetRecordingOrReplaying(void* handle) { RecordReplayLoadSymbol(handle, "RecordReplayCurrentExecutionPoint", gRecordReplayCurrentExecutionPoint); RecordReplayLoadSymbol(handle, "RecordReplayFree", gRecordReplayFree); RecordReplayLoadSymbol(handle, "RecordReplayElapsedTimeMs", gRecordReplayElapsedTimeMs); + RecordReplayLoadSymbol(handle, "RecordReplayOnAnnotation", + gRecordReplayOnAnnotation); void (*setDefaultCommandCallback)(char* (*callback)(const char* command, const char* params)); RecordReplayLoadSymbol(handle, "RecordReplaySetDefaultCommandCallback", setDefaultCommandCallback); diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 79260d6e68238f..dc9dc668493863 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -173,6 +173,7 @@ const rawMethods = internalBinding('process_methods'); recordingId: rawMethods.recordReplayRecordingId, currentPoint: rawMethods.recordReplayCurrentExecutionPoint, elapsedTime: rawMethods.recordReplayElapsedTimeMs, + annotationHook: rawMethods.recordReplayAnnotationHook, currentPointURL() { const recordingId = rawMethods.recordReplayRecordingId(); diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index cc498120a1f7ce..8fb88a351910cb 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -34,6 +34,8 @@ typedef int mode_t; #include // tcgetattr, tcsetattr #endif +static const char* AnnotationHookJSName = "recordreplay.annotationHook"; + namespace v8 { extern void FunctionCallbackIsRecordingOrReplaying(const FunctionCallbackInfo& args); @@ -546,6 +548,36 @@ static void RecordReplaySendCDPMessage(const FunctionCallbackInfo& args) gRecordReplayInspectorSession->Dispatch(messageView); } +// Called from javascript. +// `recordreplay.annotationHook(kind, contents)` +// Since this function is called from userland JS, we avoid assertions. +// We don't want flawed uses of the API to crash the recording. +static void RecordReplayAnnotationHook( + const FunctionCallbackInfo& args) { + if (!(args.Length() >= 2 && args[0]->IsString())) { + v8::recordreplay::Print("[RuntimeError] %s called with incorrect arguments", + AnnotationHookJSName); + return; + } + + v8::Isolate* isolate = args.GetIsolate(); + v8::Local payload = v8::Object::New(isolate); + v8::Local context = isolate->GetCurrentContext(); + payload->Set(context, ToV8String(isolate, "message"), args[1]).Check(); + + v8::Local json; + if (!v8::JSON::Stringify(context, payload).ToLocal(&json)) { + v8::recordreplay::Print( + "[RuntimeError] %s contents failed to json stringify", + AnnotationHookJSName); + return; + } + + v8::String::Utf8Value kind(args.GetIsolate(), args[0]); + v8::String::Utf8Value contents(args.GetIsolate(), json); + v8::recordreplay::OnAnnotation(*kind, *contents); +} + v8::CFunction BindingData::fast_number_(v8::CFunction::Make(FastNumber)); v8::CFunction BindingData::fast_bigint_(v8::CFunction::Make(FastBigInt)); @@ -697,6 +729,8 @@ static void Initialize(Local target, v8::FunctionCallbackRecordReplayCurrentExecutionPoint); env->SetMethod(target, "recordReplayElapsedTimeMs", v8::FunctionCallbackRecordReplayElapsedTimeMs); + env->SetMethod( + target, "recordReplayAnnotationHook", RecordReplayAnnotationHook); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { @@ -740,6 +774,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(v8::FunctionCallbackRecordReplayGetRecordingId); registry->Register(v8::FunctionCallbackRecordReplayCurrentExecutionPoint); registry->Register(v8::FunctionCallbackRecordReplayElapsedTimeMs); + registry->Register(RecordReplayAnnotationHook); } } // namespace process From 42a4dcfae3dce32611bf3f232c5ccd99c4b8691e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 3 Sep 2025 16:35:44 +0200 Subject: [PATCH 2/4] fixup thing --- src/node_process_methods.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 8fb88a351910cb..d23fe542b2f460 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -563,7 +563,7 @@ static void RecordReplayAnnotationHook( v8::Isolate* isolate = args.GetIsolate(); v8::Local payload = v8::Object::New(isolate); v8::Local context = isolate->GetCurrentContext(); - payload->Set(context, ToV8String(isolate, "message"), args[1]).Check(); + payload->Set(context, v8::String::NewFromUtf8(isolate, "message").ToLocalChecked(), args[1]).Check(); v8::Local json; if (!v8::JSON::Stringify(context, payload).ToLocal(&json)) { From 932d9a4f41bc568f5168a2f21807960f7017e314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 3 Sep 2025 17:23:30 +0200 Subject: [PATCH 3/4] try this --- deps/v8/include/v8.h | 1 + deps/v8/src/api/api.cc | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 4f9857a388ce78..72e4c9fa014aee 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -12696,6 +12696,7 @@ static void* IdPointer(int id); static bool HasDivergedFromRecording(); static bool AllowSideEffects(); +static void OnAnnotation(const char* kind, const char* contents); }; // class recordreplay diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index f14d408cb1b00c..6d4dffdddcf6d2 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -10823,6 +10823,12 @@ bool recordreplay::AllowSideEffects() { return true; } +void recordreplay::OnAnnotation(const char* kind, const char* contents) { + if (IsRecordingOrReplaying()) { + gRecordReplayOnAnnotation(kind, contents); + } +} + extern "C" size_t V8RecordReplayNewBookmark() { if (recordreplay::IsRecordingOrReplaying()) { return gRecordReplayNewBookmark(); From 78a92831686161a3fe2c7c76383b06ad9b3640ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 4 Sep 2025 09:44:57 +0200 Subject: [PATCH 4/4] empty