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 a4ce1eb2d235a5..6d4dffdddcf6d2 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 { @@ -10822,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(); @@ -10929,6 +10936,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..d23fe542b2f460 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, v8::String::NewFromUtf8(isolate, "message").ToLocalChecked(), 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