From 7d4109368dc0cd08de0cc94f2b42140f70db4758 Mon Sep 17 00:00:00 2001 From: Chris Klochek Date: Wed, 7 Feb 2024 15:27:18 -0500 Subject: [PATCH 1/2] Allow backend to send commands to execute preamble scripts. --- replay-assets/replay_command_handlers.js | 12 +++++ .../core/v8/record_replay_interface.cc | 51 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/replay-assets/replay_command_handlers.js b/replay-assets/replay_command_handlers.js index 51749222e4302a..b96fb2e1bbcbc7 100644 --- a/replay-assets/replay_command_handlers.js +++ b/replay-assets/replay_command_handlers.js @@ -29,6 +29,8 @@ const { fromJsCollectEventListeners, fromJsDomPerformSearch, + loadPreambleScript, + // network getCurrentNetworkRequestEvent, getCurrentNetworkStreamData, @@ -272,6 +274,7 @@ const CommandCallbacks = { "Target.getCurrentNetworkRequestEvent": Target_getCurrentNetworkRequestEvent, "Target.getCurrentNetworkStreamData": Target_getCurrentNetworkStreamData, "Target.topFrameLocation": Target_topFrameLocation, + "Target.loadPreamble": Target_loadPreamble, "Pause.evaluateInFrame": Pause_evaluateInFrame, "Pause.evaluateInGlobal": Pause_evaluateInGlobal, "Pause.getAllFrames": Pause_getAllFrames, @@ -451,6 +454,10 @@ function Target_getCurrentNetworkStreamData(params) { } } +function Target_loadPreamble(script) { + loadPreambleScript(script); +} + function Target_topFrameLocation() { try { const { location } = sendCDPMessage("Debugger.getTopFrameLocation"); @@ -3043,6 +3050,10 @@ function replayEval(fn) { } } +function registerGlobal(name, val) { + window.top.__RECORD_REPLAY_GLOBALS__[name] = val; +} + /** ########################################################################### * Export JS API methods via `__RECORD_REPLAY__`. * This is officially available for scripts in `eval*` commands to use. @@ -3059,6 +3070,7 @@ Object.assign(__RECORD_REPLAY__, { warning, getFrameArgumentsArray, getCurrentEvaluateFrame, + registerGlobal, replayEval }); diff --git a/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc b/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc index ad4839988c628f..7a78c463b09600 100644 --- a/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc +++ b/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc @@ -17,6 +17,7 @@ #include "base/record_replay.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/v8_value_converter.h" +#include "third_party/blink/renderer/bindings/core/v8/local_window_proxy.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_document.h" #include "third_party/blink/renderer/bindings/core/v8/v8_node.h" @@ -823,6 +824,10 @@ const char* gOnNewWindowScript = R""""( // __RECORD_REPLAY__? window.__RECORD_REPLAY__ = window.top.__RECORD_REPLAY__; window.__RECORD_REPLAY_ARGUMENTS__ = window.top.__RECORD_REPLAY_ARGUMENTS__; + + for (g of window.top.__RECORD_REPLAY_GLOBALS__) { + window[g] = window.top[g]; + } })() )""""; @@ -2350,6 +2355,46 @@ static void fromJsEndReplayCode( recordreplay::ExitReplayCode(); } +// Connects replay globals that must be accessible from all contexts; MUST be called +// with AutoMarkReplayCode. +static void ConnectPreambleGlobals(LocalFrame* frame, v8::Isolate *isolate) { + for (Frame* child = frame->Tree().FirstChild(); child != nullptr; child = child->Tree().NextSibling()) { + auto* child_local_frame = DynamicTo(child); + if (child_local_frame != nullptr) { + v8::Local frameContext = child_local_frame->WindowProxy(DOMWrapperWorld::MainWorld())->ContextIfInitialized(); + if (!frameContext.IsEmpty()) { + RunScript(isolate, frameContext, gOnNewWindowScript, + "record-replay-OnNewWindow"); + } + ConnectPreambleGlobals(child_local_frame, isolate); + } + } +} + + +// Executes the provided script in the root context, and then propagates all registered globals +// throughout all descendent contexts. +static void loadPreambleScript( + const v8::FunctionCallbackInfo& args) { + CHECK(args.Length() == 1 && args[0]->IsString() && + "[RuntimeError] must be called with a string"); + + v8::String::Utf8Value script(args.GetIsolate(), args[0]); + + recordreplay::AutoMarkReplayCode amrc; + + LocalFrame* rootFrame = GetLocalFrameRoot(args.GetIsolate()); + v8::Local rootContext = rootFrame->WindowProxy(DOMWrapperWorld::MainWorld())->ContextIfInitialized(); + + if (!rootContext.IsEmpty()) { + RunScript(args.GetIsolate(), rootContext, *script, "record-replay-preambleScript"); + + ConnectPreambleGlobals(rootFrame, args.GetIsolate()); + } else { + recordreplay::Warning("loadPreambleScript: no root context found. Skipping."); + } +} + /** ########################################################################### * misc * ##########################################################################*/ @@ -2502,6 +2547,10 @@ static void InitializeRecordReplayApiObjects(v8::Isolate* isolate, LocalFrame* l DefineProperty(isolate, context->Global(), "__RECORD_REPLAY_ARGUMENTS__", args); + v8::Local globals = v8::Object::New(isolate); + DefineProperty(isolate, context->Global(), "__RECORD_REPLAY_GLOBALS__", + globals); + DefineProperty(isolate, args, "REPLAY_CDT_PAUSE_OBJECT_GROUP", ToV8String(isolate, REPLAY_CDT_PAUSE_OBJECT_GROUP)); @@ -2570,6 +2619,8 @@ static void InitializeRecordReplayApiObjects(v8::Isolate* isolate, LocalFrame* l fromJsBeginReplayCode); SetFunctionProperty(isolate, args, "endReplayCode", fromJsEndReplayCode); + SetFunctionProperty(isolate, args, "loadPreambleScript", + loadPreambleScript); // unsorted Replay stuff SetFunctionProperty( From 8805488580df4b2abff820fa24255a2cd6009d1b Mon Sep 17 00:00:00 2001 From: Chris Klochek Date: Thu, 8 Feb 2024 14:40:34 -0500 Subject: [PATCH 2/2] Fixes. --- .../renderer/bindings/core/v8/record_replay_interface.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc b/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc index 7a78c463b09600..15c103e6cb869a 100644 --- a/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc +++ b/third_party/blink/renderer/bindings/core/v8/record_replay_interface.cc @@ -92,6 +92,8 @@ extern "C" void V8RecordReplayFinishRecording(); extern "C" void V8RecordReplaySetCrashReason(const char* reason); extern "C" char* V8RecordReplayReadAssetFileContents(const char* aPath, size_t* aLength); +static void RunScript(v8::Isolate* isolate, v8::Local context, const char* source_raw, const char* filename); + static const char REPLAY_CDT_PAUSE_OBJECT_GROUP[] = "REPLAY_CDT_PAUSE_OBJECT_GROUP"; @@ -825,7 +827,7 @@ const char* gOnNewWindowScript = R""""( window.__RECORD_REPLAY__ = window.top.__RECORD_REPLAY__; window.__RECORD_REPLAY_ARGUMENTS__ = window.top.__RECORD_REPLAY_ARGUMENTS__; - for (g of window.top.__RECORD_REPLAY_GLOBALS__) { + for (const g of window.top.__RECORD_REPLAY_GLOBALS__) { window[g] = window.top[g]; } })() @@ -2547,7 +2549,7 @@ static void InitializeRecordReplayApiObjects(v8::Isolate* isolate, LocalFrame* l DefineProperty(isolate, context->Global(), "__RECORD_REPLAY_ARGUMENTS__", args); - v8::Local globals = v8::Object::New(isolate); + v8::Local globals = v8::Map::New(isolate); DefineProperty(isolate, context->Global(), "__RECORD_REPLAY_GLOBALS__", globals);