Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
#include <folly/json.h>
#include <jsinspector-modern/cdp/CdpJson.h>

#include <algorithm>
#include <chrono>
#include <functional>
#include <string>
#include <string_view>

using namespace std::chrono;
Expand Down Expand Up @@ -236,6 +238,53 @@ class HostAgent::Impl final {
};
}
}
if (req.method == "Page.addScriptToEvaluateOnNewDocument") {
// @cdp Page.addScriptToEvaluateOnNewDocument registers a script that
// will be evaluated in every new JS runtime created for this Host
// (e.g. after a reload), BEFORE the app's main bundle runs. We store
// it as session state and let each new RuntimeAgent replay it onto its
// runtime, mirroring the handling of @cdp Runtime.addBinding. Per CDP
// semantics the script does NOT run in the runtime that is current
// when it is registered; the client must reload to apply it.
std::string source =
req.params.isObject() && (req.params.count("source") != 0u)
? req.params.at("source").asString()
: std::string();
std::string identifier =
std::to_string(sessionState_.nextScriptToEvaluateOnNewDocumentId++);
sessionState_.scriptsToEvaluateOnNewDocument.push_back(
{.identifier = identifier, .source = std::move(source)});

frontendChannel_(
cdp::jsonResult(
req.id, folly::dynamic::object("identifier", identifier)));

return {
.isFinishedHandlingRequest = true,
.shouldSendOKResponse = false,
};
}
if (req.method == "Page.removeScriptToEvaluateOnNewDocument") {
std::string identifier =
req.params.isObject() && (req.params.count("identifier") != 0u)
? req.params.at("identifier").asString()
: std::string();
auto& scripts = sessionState_.scriptsToEvaluateOnNewDocument;
scripts.erase(
std::remove_if(
scripts.begin(),
scripts.end(),
[&identifier](
const SessionState::ScriptToEvaluateOnNewDocument& script) {
return script.identifier == identifier;
}),
scripts.end());

return {
.isFinishedHandlingRequest = true,
.shouldSendOKResponse = true,
};
}
if (req.method == "Overlay.setPausedInDebuggerMessage") {
auto message =
req.params.isObject() && (req.params.count("message") != 0u)
Expand Down Expand Up @@ -397,6 +446,11 @@ class HostAgent::Impl final {
return;
}

if (requestState.isFinishedHandlingRequest) {
// The handler already sent its own response via frontendChannel_.
return;
}

throw NotImplementedException(req.method);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ RuntimeAgent::RuntimeAgent(
}
}

// Replay any scripts registered via @cdp
// Page.addScriptToEvaluateOnNewDocument onto this newly created runtime, in
// registration order, so they evaluate before the app's main bundle.
for (const auto& script : sessionState_.scriptsToEvaluateOnNewDocument) {
targetController_.installScriptToEvaluateOnNewDocument(script.source);
}

if (sessionState_.isRuntimeDomainEnabled) {
targetController_.notifyDomainStateChanged(
RuntimeTargetController::Domain::Runtime, true, *this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
});
}

void RuntimeTarget::installScriptToEvaluateOnNewDocument(
const std::string& source) {
jsExecutor_([source](jsi::Runtime& runtime) {
try {
runtime.evaluateJavaScript(
std::make_shared<jsi::StringBuffer>(source),
"<addScriptToEvaluateOnNewDocument>");
} catch (jsi::JSIException&) {
// Swallow exceptions thrown while evaluating the injected script so a
// faulty script cannot break the app. This mirrors how
// installBindingHandler isolates binding-setup failures.
}
});
}

void RuntimeTarget::installFastRefreshHandler() {
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
auto globalObj = runtime.global();
Expand Down Expand Up @@ -313,6 +328,11 @@ void RuntimeTargetController::installBindingHandler(
target_.installBindingHandler(bindingName);
}

void RuntimeTargetController::installScriptToEvaluateOnNewDocument(
const std::string& source) {
target_.installScriptToEvaluateOnNewDocument(source);
}

void RuntimeTargetController::enableSamplingProfiler() {
target_.enableSamplingProfiler();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ class RuntimeTargetController {
*/
void installBindingHandler(const std::string &bindingName);

/**
* Evaluates the given JavaScript source on the runtime's thread before any
* user code runs. Used to replay @cdp
* Page.addScriptToEvaluateOnNewDocument scripts onto a freshly created
* runtime.
*/
void installScriptToEvaluateOnNewDocument(const std::string &source);

/**
* Notifies the target that an agent has received an enable or disable
* message for the given domain.
Expand Down Expand Up @@ -289,6 +297,14 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
*/
void installBindingHandler(const std::string &bindingName);

/**
* Evaluates the given JavaScript source on the runtime's thread before any
* user code runs. Used to replay @cdp
* Page.addScriptToEvaluateOnNewDocument scripts onto a freshly created
* runtime.
*/
void installScriptToEvaluateOnNewDocument(const std::string &source);

/**
* Installs any global values we want to expose to framework/user JavaScript
* code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace facebook::react::jsinspector_modern {

Expand Down Expand Up @@ -43,6 +44,36 @@ struct SessionState {
*/
std::unordered_map<std::string, ExecutionContextSelectorSet> subscribedBindings;

/**
* A single script registered during this session using @cdp
* Page.addScriptToEvaluateOnNewDocument.
*/
struct ScriptToEvaluateOnNewDocument {
/** Opaque identifier returned to the frontend, used by @cdp
* Page.removeScriptToEvaluateOnNewDocument. */
std::string identifier;
/** The JavaScript source to evaluate. */
std::string source;
};

/**
* Scripts registered during this session using @cdp
* Page.addScriptToEvaluateOnNewDocument, in registration order.
*
* Like subscribedBindings, these are treated as session state: each new
* RuntimeAgent replays them onto its runtime so they evaluate before any
* user code (i.e. before the app's main bundle). Per CDP semantics they do
* NOT run in the runtime that is current when they are registered - the
* client must trigger a reload (@cdp Page.reload) for them to take effect.
*/
std::vector<ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;

/**
* Monotonic counter for generating the identifiers returned from @cdp
* Page.addScriptToEvaluateOnNewDocument.
*/
unsigned int nextScriptToEvaluateOnNewDocumentId{1};

/**
* Messages logged through the HostAgent::sendConsoleMessage and
* InstanceAgent::sendConsoleMessage utilities that have not yet been sent to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,59 @@ TEST_F(HostTargetProtocolTest, PageReloadMethod) {
})");
}

TEST_F(HostTargetProtocolTest, PageAddAndRemoveScriptToEvaluateOnNewDocument) {
InSequence s;

// The first registered script gets identifier "1".
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
"id": 1,
"result": {"identifier": "1"}
})")))
.RetiresOnSaturation();
toPage_->sendMessage(R"({
"id": 1,
"method": "Page.addScriptToEvaluateOnNewDocument",
"params": {"source": "globalThis.__a = 1;"}
})");

// The second registration gets a distinct, monotonically increasing id "2".
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
"id": 2,
"result": {"identifier": "2"}
})")))
.RetiresOnSaturation();
toPage_->sendMessage(R"({
"id": 2,
"method": "Page.addScriptToEvaluateOnNewDocument",
"params": {"source": "globalThis.__b = 2;"}
})");

// Removing a registered script succeeds with an empty result.
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
"id": 3,
"result": {}
})")))
.RetiresOnSaturation();
toPage_->sendMessage(R"({
"id": 3,
"method": "Page.removeScriptToEvaluateOnNewDocument",
"params": {"identifier": "1"}
})");

// Removing an unknown identifier is a lenient no-op that still succeeds
// (matching Chrome's behaviour).
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
"id": 4,
"result": {}
})")))
.RetiresOnSaturation();
toPage_->sendMessage(R"({
"id": 4,
"method": "Page.removeScriptToEvaluateOnNewDocument",
"params": {"identifier": "999"}
})");
}

TEST_F(HostTargetProtocolTest, OverlaySetPausedInDebuggerMessageMethod) {
InSequence s;

Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -10866,6 +10866,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -11040,7 +11041,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -10492,6 +10492,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -10666,7 +10667,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -10719,6 +10719,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -10893,7 +10894,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -12708,6 +12708,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -12869,7 +12870,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -12396,6 +12396,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -12557,7 +12558,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
8 changes: 8 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -12571,6 +12571,7 @@ class facebook::react::jsinspector_modern::RuntimeTargetController {
public void emitTracingStateChange(bool isTracing);
public void enableSamplingProfiler();
public void installBindingHandler(const std::string& bindingName);
public void installScriptToEvaluateOnNewDocument(const std::string& source);
public void notifyDomainStateChanged(facebook::react::jsinspector_modern::RuntimeTargetController::Domain domain, bool enabled, const facebook::react::jsinspector_modern::RuntimeAgent& notifyingAgent);
}

Expand Down Expand Up @@ -12732,7 +12733,14 @@ struct facebook::react::jsinspector_modern::SessionState {
public bool isRuntimeDomainEnabled;
public facebook::react::jsinspector_modern::RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
public std::unordered_map<std::string, facebook::react::jsinspector_modern::ExecutionContextSelectorSet> subscribedBindings;
public std::vector<facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument> scriptsToEvaluateOnNewDocument;
public std::vector<facebook::react::jsinspector_modern::SimpleConsoleMessage> pendingSimpleConsoleMessages;
public unsigned int nextScriptToEvaluateOnNewDocumentId;
}

struct facebook::react::jsinspector_modern::SessionState::ScriptToEvaluateOnNewDocument {
public std::string identifier;
public std::string source;
}

struct facebook::react::jsinspector_modern::SimpleConsoleMessage {
Expand Down
Loading
Loading