From fdf8036f78ef5fb4ad5eac327628ba0bb35a4780 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Fri, 16 Feb 2024 08:28:51 -0800 Subject: [PATCH] Lift execution context management out of Runtime (#43063) Summary: Changelog: [Internal] Moves the responsibility for managing CDP execution contexts out of the Runtime and into the Instance. This includes the responsibilities to: 1. Assign execution context IDs/names 2. Emit events when execution contexts are created/destroyed 3. Route CDP messages to the correct Runtime **Re 1:** We currently assign a *constant* execution context ID, which diverges from V8's implementation but is in line with what Hermes has done so far. I'll follow up separately to assign (locally) unique IDs, since this diff is long enough already. **Re 3:** Right now, the message routing responsibility is mostly theoretical: only one Runtime exists at a time and "routing" can be done by RuntimeAgent simply deciding whether or not to act on a message (since it receives all messages by default and knows its own `ExecutionContextDescription`). True multi-Runtime / multi-context support is firmly a future concern, and we can revisit this ( = probably hoist more logic into Instance) when we get there. In the `ExecutionContextNotifications` integration test we can see that a few minor bugs in the current Hermes-based implementation are fixed, and also that execution context management is now engine-agnostic (so we can use `JsiIntegrationPortableTest` instead of `JsiIntegrationHermesTest`). Reviewed By: huntie Differential Revision: D53759776 --- .../ReactCommon/cxxreact/JSExecutor.cpp | 5 +- .../ReactCommon/cxxreact/JSExecutor.h | 4 +- .../hermes/executor/HermesExecutorFactory.cpp | 5 +- .../hermes/executor/HermesExecutorFactory.h | 4 +- .../chrome/HermesRuntimeAgentDelegate.cpp | 18 ++- .../chrome/HermesRuntimeAgentDelegate.h | 5 + .../jsinspector-modern/ExecutionContext.h | 23 +++ .../jsinspector-modern/InstanceAgent.cpp | 37 +++++ .../jsinspector-modern/InstanceAgent.h | 2 + .../jsinspector-modern/InstanceTarget.cpp | 10 +- .../ReactCommon/jsinspector-modern/ReactCdp.h | 1 + .../jsinspector-modern/RuntimeAgent.cpp | 4 +- .../jsinspector-modern/RuntimeAgent.h | 11 ++ .../jsinspector-modern/RuntimeTarget.cpp | 12 +- .../jsinspector-modern/RuntimeTarget.h | 26 +++- .../jsinspector-modern/tests/InspectorMocks.h | 11 +- .../tests/JsiIntegrationTest.cpp | 146 +++++++++++------- .../tests/PageTargetTest.cpp | 20 ++- ...JsiIntegrationTestGenericEngineAdapter.cpp | 3 +- .../JsiIntegrationTestGenericEngineAdapter.h | 3 +- .../JsiIntegrationTestHermesEngineAdapter.cpp | 9 +- .../JsiIntegrationTestHermesEngineAdapter.h | 3 +- .../react/runtime/JSRuntimeFactory.cpp | 5 +- .../react/runtime/JSRuntimeFactory.h | 4 +- .../react/runtime/hermes/HermesInstance.cpp | 5 +- 25 files changed, 286 insertions(+), 90 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/ExecutionContext.h diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp index ba34e3f8fa22..b6130f1ecfe4 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp @@ -38,7 +38,10 @@ double JSExecutor::performanceNow() { std::unique_ptr JSExecutor::createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) { + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) { + (void)executionContextDescription; return std::make_unique( std::move(frontendChannel), sessionState, getDescription()); } diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h index 3a6ef14b7bae..ac0c4ce64a32 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h @@ -146,7 +146,9 @@ class RN_EXPORT JSExecutor : public jsinspector_modern::RuntimeTargetDelegate { virtual std::unique_ptr createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) override; + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) override; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp index 52c2ece8a5d4..09c1dad50451 100644 --- a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp +++ b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp @@ -260,12 +260,15 @@ HermesExecutor::HermesExecutor( std::unique_ptr HermesExecutor::createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) { + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) { std::shared_ptr hermesRuntimeShared(runtime_, &hermesRuntime_); return std::unique_ptr( new jsinspector_modern::HermesRuntimeAgentDelegate( frontendChannel, sessionState, + executionContextDescription, hermesRuntimeShared, [jsQueueWeak = std::weak_ptr(jsQueue_), runtimeWeak = std::weak_ptr(runtime_)](auto fn) { diff --git a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.h b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.h index 4ae521539d4e..a7658fea1a9d 100644 --- a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.h +++ b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.h @@ -57,7 +57,9 @@ class HermesExecutor : public JSIExecutor { virtual std::unique_ptr createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) override; + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) override; private: JSIScopedTimeoutInvoker timeoutInvoker_; diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.cpp index 2a6f089e048b..57a19a8978e2 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.cpp @@ -65,6 +65,8 @@ class HermesRuntimeAgentDelegateAdapter */ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate { using HermesCDPHandler = hermes::inspector_modern::chrome::CDPHandler; + using HermesExecutionContextDescription = + hermes::inspector_modern::chrome::CDPHandlerExecutionContextDescription; public: /** @@ -73,6 +75,10 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate { * \param sessionState The state of the current CDP session. This will only * be accessed on the main thread (during the constructor, in handleRequest, * etc). + * \param executionContextDescription A description of the execution context + * represented by this runtime. This is used for disambiguating the + * source/destination of CDP messages when there are multiple runtimes + * (concurrently or over the life of a Page). * \param runtime The HermesRuntime that this agent is attached to. * \param runtimeExecutor A callback for scheduling work on the JS thread. * \c runtimeExecutor may drop scheduled work if the runtime is destroyed @@ -81,6 +87,7 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate { Impl( FrontendChannel frontendChannel, SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription, std::shared_ptr runtime, RuntimeExecutor runtimeExecutor) : hermes_(HermesCDPHandler::create( @@ -90,7 +97,13 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate { /* waitForDebugger */ false, /* enableConsoleAPICapturing */ false, /* state */ nullptr, - {.isRuntimeDomainEnabled = sessionState.isRuntimeDomainEnabled})) { + {.isRuntimeDomainEnabled = sessionState.isRuntimeDomainEnabled}, + HermesExecutionContextDescription{ + .id = executionContextDescription.id, + .origin = executionContextDescription.origin, + .name = executionContextDescription.name, + .auxData = std::nullopt, + .shouldSendNotifications = false})) { hermes_->registerCallbacks( /* msgCallback */ [frontendChannel = @@ -142,6 +155,7 @@ class HermesRuntimeAgentDelegate::Impl final Impl( FrontendChannel frontendChannel, SessionState& sessionState, + const ExecutionContextDescription&, std::shared_ptr runtime, RuntimeExecutor) : FallbackRuntimeAgentDelegate( @@ -155,11 +169,13 @@ class HermesRuntimeAgentDelegate::Impl final HermesRuntimeAgentDelegate::HermesRuntimeAgentDelegate( FrontendChannel frontendChannel, SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription, std::shared_ptr runtime, RuntimeExecutor runtimeExecutor) : impl_(std::make_unique( std::move(frontendChannel), sessionState, + executionContextDescription, std::move(runtime), std::move(runtimeExecutor))) {} diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.h b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.h index 5700ab02e6a3..4f8e92c47e06 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.h +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.h @@ -26,6 +26,10 @@ class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate { * \param sessionState The state of the current CDP session. This will only * be accessed on the main thread (during the constructor, in handleRequest, * etc). + * \param executionContextDescription A description of the execution context + * represented by this runtime. This is used for disambiguating the + * source/destination of CDP messages when there are multiple runtimes + * (concurrently or over the life of a Page). * \param runtime The HermesRuntime that this agent is attached to. * \param runtimeExecutor A callback for scheduling work on the JS thread. * \c runtimeExecutor may drop scheduled work if the runtime is destroyed @@ -34,6 +38,7 @@ class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate { HermesRuntimeAgentDelegate( FrontendChannel frontendChannel, SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription, std::shared_ptr runtime, RuntimeExecutor runtimeExecutor); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ExecutionContext.h b/packages/react-native/ReactCommon/jsinspector-modern/ExecutionContext.h new file mode 100644 index 000000000000..f397e08f27fb --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ExecutionContext.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +struct ExecutionContextDescription { + int32_t id{}; + std::string origin{""}; + std::string name{""}; + std::optional uniqueId; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.cpp index b58e597d7dd9..fdb57b0b4c7d 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.cpp @@ -21,6 +21,10 @@ InstanceAgent::InstanceAgent( } bool InstanceAgent::handleRequest(const cdp::PreparsedRequest& req) { + if (req.method == "Runtime.enable") { + maybeSendExecutionContextCreatedNotification(); + // Fall through + } if (runtimeAgent_ && runtimeAgent_->handleRequest(req)) { return true; } @@ -28,11 +32,44 @@ bool InstanceAgent::handleRequest(const cdp::PreparsedRequest& req) { } void InstanceAgent::setCurrentRuntime(RuntimeTarget* runtimeTarget) { + auto previousRuntimeAgent = std::move(runtimeAgent_); if (runtimeTarget) { runtimeAgent_ = runtimeTarget->createAgent(frontendChannel_, sessionState_); } else { runtimeAgent_.reset(); } + if (!sessionState_.isRuntimeDomainEnabled) { + return; + } + if (previousRuntimeAgent != nullptr) { + auto& previousContext = + previousRuntimeAgent->getExecutionContextDescription(); + folly::dynamic params = + folly::dynamic::object("executionContextId", previousContext.id); + if (previousContext.uniqueId.has_value()) { + params["executionContextUniqueId"] = *previousContext.uniqueId; + } + folly::dynamic contextDestroyed = folly::dynamic::object( + "method", "Runtime.executionContextDestroyed")("params", params); + frontendChannel_(folly::toJson(contextDestroyed)); + } + maybeSendExecutionContextCreatedNotification(); +} + +void InstanceAgent::maybeSendExecutionContextCreatedNotification() { + if (runtimeAgent_ != nullptr) { + auto& newContext = runtimeAgent_->getExecutionContextDescription(); + folly::dynamic params = folly::dynamic::object( + "context", + folly::dynamic::object("id", newContext.id)( + "origin", newContext.origin)("name", newContext.name)); + if (newContext.uniqueId.has_value()) { + params["uniqueId"] = *newContext.uniqueId; + } + folly::dynamic contextCreated = folly::dynamic::object( + "method", "Runtime.executionContextCreated")("params", params); + frontendChannel_(folly::toJson(contextCreated)); + } } } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.h index da84283519d1..d33670275663 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.h @@ -55,6 +55,8 @@ class InstanceAgent final { void setCurrentRuntime(RuntimeTarget* runtime); private: + void maybeSendExecutionContextCreatedNotification(); + FrontendChannel frontendChannel_; InstanceTarget& target_; std::shared_ptr runtimeAgent_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InstanceTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InstanceTarget.cpp index 8030ec740d3f..e98389292ca6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InstanceTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InstanceTarget.cpp @@ -50,7 +50,15 @@ RuntimeTarget& InstanceTarget::registerRuntime( RuntimeExecutor jsExecutor) { assert(!currentRuntime_ && "Only one Runtime allowed"); currentRuntime_ = RuntimeTarget::create( - delegate, jsExecutor, makeVoidExecutor(executorFromThis())); + ExecutionContextDescription{ + // TODO: IDs should be unique within the current Page. + .id = 1, + .origin = "", + .name = "main", + .uniqueId = std::nullopt}, + delegate, + jsExecutor, + makeVoidExecutor(executorFromThis())); agents_.forEach([currentRuntime = &*currentRuntime_](InstanceAgent& agent) { agent.setCurrentRuntime(currentRuntime); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ReactCdp.h b/packages/react-native/ReactCommon/jsinspector-modern/ReactCdp.h index d81d695867e9..c23e6a73db32 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/ReactCdp.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/ReactCdp.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp index a1761a2d0a55..5691eb4876f7 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp @@ -12,12 +12,14 @@ namespace facebook::react::jsinspector_modern { RuntimeAgent::RuntimeAgent( FrontendChannel frontendChannel, RuntimeTarget& target, + const ExecutionContextDescription& executionContextDescription, SessionState& sessionState, std::unique_ptr delegate) : frontendChannel_(std::move(frontendChannel)), target_(target), sessionState_(sessionState), - delegate_(std::move(delegate)) { + delegate_(std::move(delegate)), + executionContextDescription_(executionContextDescription) { (void)target_; (void)sessionState_; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h index 5cc60a7436b9..1f8f076352ff 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h @@ -33,6 +33,10 @@ class RuntimeAgent final { * \param target The RuntimeTarget that this agent is attached to. The * caller is responsible for ensuring that the RuntimeTarget outlives this * object. + * \param executionContextDescription A description of the execution context + * represented by this runtime. This is used for disambiguating the + * source/destination of CDP messages when there are multiple runtimes + * (concurrently or over the life of a Page). * \param sessionState The state of the session that created this agent. * \param delegate The RuntimeAgentDelegate providing engine-specific * CDP functionality. @@ -40,6 +44,7 @@ class RuntimeAgent final { RuntimeAgent( FrontendChannel frontendChannel, RuntimeTarget& target, + const ExecutionContextDescription& executionContextDescription, SessionState& sessionState, std::unique_ptr delegate); @@ -55,11 +60,17 @@ class RuntimeAgent final { */ bool handleRequest(const cdp::PreparsedRequest& req); + inline const ExecutionContextDescription& getExecutionContextDescription() + const { + return executionContextDescription_; + } + private: FrontendChannel frontendChannel_; RuntimeTarget& target_; SessionState& sessionState_; const std::unique_ptr delegate_; + const ExecutionContextDescription executionContextDescription_; }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index 52cdc0516c27..4feb014ea816 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -10,19 +10,23 @@ namespace facebook::react::jsinspector_modern { std::shared_ptr RuntimeTarget::create( + const ExecutionContextDescription& executionContextDescription, RuntimeTargetDelegate& delegate, RuntimeExecutor jsExecutor, VoidExecutor selfExecutor) { std::shared_ptr runtimeTarget{ - new RuntimeTarget(delegate, jsExecutor)}; + new RuntimeTarget(executionContextDescription, delegate, jsExecutor)}; runtimeTarget->setExecutor(selfExecutor); return runtimeTarget; } RuntimeTarget::RuntimeTarget( + const ExecutionContextDescription& executionContextDescription, RuntimeTargetDelegate& delegate, RuntimeExecutor jsExecutor) - : delegate_(delegate), jsExecutor_(jsExecutor) {} + : executionContextDescription_(executionContextDescription), + delegate_(delegate), + jsExecutor_(jsExecutor) {} std::shared_ptr RuntimeTarget::createAgent( FrontendChannel channel, @@ -30,8 +34,10 @@ std::shared_ptr RuntimeTarget::createAgent( auto runtimeAgent = std::make_shared( channel, *this, + executionContextDescription_, sessionState, - delegate_.createAgentDelegate(channel, sessionState)); + delegate_.createAgentDelegate( + channel, sessionState, executionContextDescription_)); agents_.insert(runtimeAgent); return runtimeAgent; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 984a38a29b78..fda93d69c75d 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -8,6 +8,8 @@ #pragma once #include + +#include "ExecutionContext.h" #include "InspectorInterfaces.h" #include "RuntimeAgent.h" #include "ScopedExecutor.h" @@ -43,7 +45,8 @@ class RuntimeTargetDelegate { virtual ~RuntimeTargetDelegate() = default; virtual std::unique_ptr createAgentDelegate( FrontendChannel channel, - SessionState& sessionState) = 0; + SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription) = 0; }; /** @@ -55,9 +58,13 @@ class JSINSPECTOR_EXPORT RuntimeTarget /** * Constructs a new RuntimeTarget. The caller must call setExecutor * immediately afterwards. - * \param delegate The object that will receive events from this target. - * The caller is responsible for ensuring that the delegate outlives this - * object. + * \param executionContextDescription A description of the execution context + * represented by this runtime. This is used for disambiguating the + * source/destination of CDP messages when there are multiple runtimes + * (concurrently or over the life of a Page). + * \param delegate The object that will receive events from this target. The + * caller is responsible for + * ensuring that the delegate outlives this object. * \param jsExecutor A RuntimeExecutor that can be used to schedule work on * the JS runtime's thread. The executor's queue should be empty when * RuntimeTarget is constructed (i.e. anything scheduled during the @@ -67,6 +74,7 @@ class JSINSPECTOR_EXPORT RuntimeTarget * executor will not be called after the RuntimeTarget is destroyed. */ static std::shared_ptr create( + const ExecutionContextDescription& executionContextDescription, RuntimeTargetDelegate& delegate, RuntimeExecutor jsExecutor, VoidExecutor selfExecutor); @@ -94,6 +102,10 @@ class JSINSPECTOR_EXPORT RuntimeTarget /** * Constructs a new RuntimeTarget. The caller must call setExecutor * immediately afterwards. + * \param executionContextDescription A description of the execution context + * represented by this runtime. This is used for disambiguating the + * source/destination of CDP messages when there are multiple runtimes + * (concurrently or over the life of a Page). * \param delegate The object that will receive events from this target. * The caller is responsible for ensuring that the delegate outlives this * object. @@ -102,8 +114,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget * RuntimeTarget is constructed (i.e. anything scheduled during the * constructor should be executed before any user code is run). */ - RuntimeTarget(RuntimeTargetDelegate& delegate, RuntimeExecutor jsExecutor); + RuntimeTarget( + const ExecutionContextDescription& executionContextDescription, + RuntimeTargetDelegate& delegate, + RuntimeExecutor jsExecutor); + const ExecutionContextDescription executionContextDescription_; RuntimeTargetDelegate& delegate_; RuntimeExecutor jsExecutor_; WeakList agents_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h index 4a7d31adfbbd..08c1f3540660 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h @@ -129,7 +129,9 @@ class MockRuntimeTargetDelegate : public RuntimeTargetDelegate { MOCK_METHOD( std::unique_ptr, createAgentDelegate, - (FrontendChannel channel, SessionState& sessionState), + (FrontendChannel channel, + SessionState& sessionState, + const ExecutionContextDescription&), (override)); }; @@ -137,9 +139,11 @@ class MockRuntimeAgentDelegate : public RuntimeAgentDelegate { public: inline MockRuntimeAgentDelegate( FrontendChannel frontendChannel, - SessionState& sessionState) + SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription) : frontendChannel(std::move(frontendChannel)), - sessionState(sessionState) {} + sessionState(sessionState), + executionContextDescription(executionContextDescription) {} // RuntimeAgentDelegate methods MOCK_METHOD( @@ -150,6 +154,7 @@ class MockRuntimeAgentDelegate : public RuntimeAgentDelegate { const FrontendChannel frontendChannel; SessionState& sessionState; + const ExecutionContextDescription executionContextDescription; }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp index 105199c179e0..825f37ad1523 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp @@ -174,6 +174,94 @@ TYPED_TEST(JsiIntegrationPortableTest, ErrorOnUnknownMethod) { })"); } +TYPED_TEST(JsiIntegrationPortableTest, ExecutionContextNotifications) { + this->connect(); + + InSequence s; + + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextCreated", + "params": { + "context": { + "id": 1, + "origin": "", + "name": "main" + } + } + })"))) + .RetiresOnSaturation(); + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "id": 1, + "result": {} + })"))); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Runtime.enable" + })"); + + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextDestroyed", + "params": { + "executionContextId": 1 + } + })"))) + .RetiresOnSaturation(); + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextsCleared" + })"))) + .RetiresOnSaturation(); + + // TODO: Each new execution context should receive a new ID. + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextCreated", + "params": { + "context": { + "id": 1, + "origin": "", + "name": "main" + } + } + })"))) + .RetiresOnSaturation(); + // Simulate a reload triggered by the app (not by the debugger). + this->reload(); + + // TODO: Each new execution context should receive a new ID. + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextDestroyed", + "params": { + "executionContextId": 1 + } + })"))) + .RetiresOnSaturation(); + + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextsCleared" + })"))) + .RetiresOnSaturation(); + // TODO: Each new execution context should receive a new ID. + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "method": "Runtime.executionContextCreated", + "params": { + "context": { + "id": 1, + "origin": "", + "name": "main" + } + } + })"))) + .RetiresOnSaturation(); + EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({ + "id": 2, + "result": {} + })"))) + .RetiresOnSaturation(); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Page.reload" + })"); +} + //////////////////////////////////////////////////////////////////////////////// TEST_F(JsiIntegrationHermesTest, EvaluateExpression) { @@ -195,62 +283,4 @@ TEST_F(JsiIntegrationHermesTest, EvaluateExpression) { })"); } -TEST_F(JsiIntegrationHermesTest, ExecutionContextNotifications) { - connect(); - - InSequence s; - - // NOTE: This is the wrong sequence of responses from Hermes - the - // notification should come before the method response. - EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ - "id": 1, - "result": {} - })"))); - EXPECT_CALL( - fromPage(), - onMessage(JsonParsed( - AllOf(AtJsonPtr("/method", Eq("Runtime.executionContextCreated")))))) - .RetiresOnSaturation(); - - toPage_->sendMessage(R"({ - "id": 1, - "method": "Runtime.enable" - })"); - - // NOTE: Missing a Runtime.executionContextDestroyed notification here. - - EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ - "method": "Runtime.executionContextsCleared" - })"))) - .RetiresOnSaturation(); - EXPECT_CALL( - fromPage(), - onMessage(JsonParsed( - AllOf(AtJsonPtr("/method", Eq("Runtime.executionContextCreated")))))) - .RetiresOnSaturation(); - // Simulate a reload triggered by the app (not by the debugger). - reload(); - - // NOTE: Missing a Runtime.executionContextDestroyed notification here. - - EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ - "method": "Runtime.executionContextsCleared" - })"))) - .RetiresOnSaturation(); - EXPECT_CALL( - fromPage(), - onMessage(JsonParsed( - AllOf(AtJsonPtr("/method", Eq("Runtime.executionContextCreated")))))) - .RetiresOnSaturation(); - EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ - "id": 2, - "result": {} - })"))) - .RetiresOnSaturation(); - toPage_->sendMessage(R"({ - "id": 2, - "method": "Page.reload" - })"); -} - } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/PageTargetTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/PageTargetTest.cpp index efddba194720..d43bbff7e4e3 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/PageTargetTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/PageTargetTest.cpp @@ -31,10 +31,11 @@ class PageTargetTest : public Test { protected: PageTargetTest() { - EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _)) - .WillRepeatedly( - runtimeAgentDelegates_ - .lazily_make_unique()); + EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _)) + .WillRepeatedly(runtimeAgentDelegates_.lazily_make_unique< + FrontendChannel, + SessionState&, + const ExecutionContextDescription&>()); } void connect() { @@ -443,7 +444,7 @@ TEST_F(PageTargetProtocolTest, MessageRoutingWhileNoRuntimeAgentDelegate) { TEST_F(PageTargetProtocolTest, InstanceWithNullRuntimeAgentDelegate) { InSequence s; - EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _)) + EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _)) .WillRepeatedly(ReturnNull()); auto& instanceTarget = page_->registerInstance(instanceTargetDelegate_); @@ -475,13 +476,20 @@ TEST_F(PageTargetProtocolTest, RuntimeAgentDelegateHasAccessToSessionState) { EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ "id": 1, "result": {} - })"))); + })"))) + .RetiresOnSaturation(); toPage_->sendMessage(R"({ "id": 1, "method": "Runtime.enable" })"); auto& instanceTarget = page_->registerInstance(instanceTargetDelegate_); + + EXPECT_CALL( + fromPage(), + onMessage( + JsonParsed(AtJsonPtr("/method", "Runtime.executionContextCreated")))) + .RetiresOnSaturation(); instanceTarget.registerRuntime(runtimeTargetDelegate_, runtimeExecutor_); ASSERT_TRUE(runtimeAgentDelegates_[0]); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp index 2ea6f7cdb430..15adb108738a 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp @@ -23,7 +23,8 @@ JsiIntegrationTestGenericEngineAdapter::JsiIntegrationTestGenericEngineAdapter( std::unique_ptr JsiIntegrationTestGenericEngineAdapter::createAgentDelegate( FrontendChannel frontendChannel, - SessionState& sessionState) { + SessionState& sessionState, + const ExecutionContextDescription&) { return std::unique_ptr( new FallbackRuntimeAgentDelegate( frontendChannel, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.h index 83ea5952acbb..e87c212c0904 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.h @@ -27,7 +27,8 @@ class JsiIntegrationTestGenericEngineAdapter : public RuntimeTargetDelegate { virtual std::unique_ptr createAgentDelegate( FrontendChannel frontendChannel, - SessionState& sessionState) override; + SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription) override; jsi::Runtime& getRuntime() const noexcept; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp index 5caa9980a941..534eeeb65463 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp @@ -22,10 +22,15 @@ JsiIntegrationTestHermesEngineAdapter::JsiIntegrationTestHermesEngineAdapter( std::unique_ptr JsiIntegrationTestHermesEngineAdapter::createAgentDelegate( FrontendChannel frontendChannel, - SessionState& sessionState) { + SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription) { return std::unique_ptr( new HermesRuntimeAgentDelegate( - frontendChannel, sessionState, runtime_, getRuntimeExecutor())); + frontendChannel, + sessionState, + executionContextDescription, + runtime_, + getRuntimeExecutor())); } jsi::Runtime& JsiIntegrationTestHermesEngineAdapter::getRuntime() diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.h index bffce0db32d3..47540a76bc81 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.h @@ -27,7 +27,8 @@ class JsiIntegrationTestHermesEngineAdapter : public RuntimeTargetDelegate { virtual std::unique_ptr createAgentDelegate( FrontendChannel frontendChannel, - SessionState& sessionState) override; + SessionState& sessionState, + const ExecutionContextDescription& executionContextDescription) override; jsi::Runtime& getRuntime() const noexcept; diff --git a/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.cpp b/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.cpp index 30d689a68c22..28567871d552 100644 --- a/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.cpp +++ b/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.cpp @@ -21,7 +21,10 @@ JSIRuntimeHolder::JSIRuntimeHolder(std::unique_ptr runtime) std::unique_ptr JSIRuntimeHolder::createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) { + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) { + (void)executionContextDescription; return std::make_unique( std::move(frontendChannel), sessionState, runtime_->description()); } diff --git a/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.h b/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.h index a61830a76975..af97c2d940ea 100644 --- a/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.h +++ b/packages/react-native/ReactCommon/react/runtime/JSRuntimeFactory.h @@ -43,7 +43,9 @@ class JSIRuntimeHolder : public JSRuntime { jsi::Runtime& getRuntime() noexcept override; std::unique_ptr createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) override; + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) override; explicit JSIRuntimeHolder(std::unique_ptr runtime); diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp index 15365f3e5643..3de09a64c3db 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp @@ -106,11 +106,14 @@ class HermesJSRuntime : public JSRuntime { std::unique_ptr createAgentDelegate( jsinspector_modern::FrontendChannel frontendChannel, - jsinspector_modern::SessionState& sessionState) override { + jsinspector_modern::SessionState& sessionState, + const jsinspector_modern::ExecutionContextDescription& + executionContextDescription) override { return std::unique_ptr( new jsinspector_modern::HermesRuntimeAgentDelegate( frontendChannel, sessionState, + executionContextDescription, runtime_, [msgQueueThreadWeak = std::weak_ptr(msgQueueThread_), runtimeWeak = std::weak_ptr(runtime_)](auto fn) {