/* * 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 "ExecutionContextManager.h" #include "HostCommand.h" #include "InspectorInterfaces.h" #include "InstanceTarget.h" #include "NetworkIOAgent.h" #include "ScopedExecutor.h" #include "WeakList.h" #include #include #ifndef JSINSPECTOR_EXPORT #ifdef _MSC_VER #ifdef CREATE_SHARED_LIBRARY #define JSINSPECTOR_EXPORT __declspec(dllexport) #else #define JSINSPECTOR_EXPORT #endif // CREATE_SHARED_LIBRARY #else // _MSC_VER #define JSINSPECTOR_EXPORT __attribute__((visibility("default"))) #endif // _MSC_VER #endif // !defined(JSINSPECTOR_EXPORT) namespace facebook::react::jsinspector_modern { class HostTargetSession; class HostAgent; class HostCommandSender; class HostTarget; struct HostTargetMetadata { std::optional appDisplayName; std::optional appIdentifier; std::optional deviceName; std::optional integrationName; std::optional platform; std::optional reactNativeVersion; }; /** * Receives events from a HostTarget. This is a shared interface that each * React Native platform needs to implement in order to integrate with the * debugging stack. */ class HostTargetDelegate : public LoadNetworkResourceDelegate { public: HostTargetDelegate() = default; HostTargetDelegate(const HostTargetDelegate&) = delete; HostTargetDelegate(HostTargetDelegate&&) = delete; HostTargetDelegate& operator=(const HostTargetDelegate&) = delete; HostTargetDelegate& operator=(HostTargetDelegate&&) = delete; // TODO(moti): This is 1:1 the shape of the corresponding CDP message - // consider reusing typed/generated CDP interfaces when we have those. struct PageReloadRequest { // It isn't clear what the ignoreCache parameter of @cdp Page.reload should // mean in React Native. We parse it, but don't do anything with it yet. std::optional ignoreCache; // TODO: Implement scriptToEvaluateOnLoad parameter of @cdp Page.reload. std::optional scriptToEvaluateOnLoad; /** * Equality operator, useful for unit tests */ inline bool operator==(const PageReloadRequest& rhs) const { return ignoreCache == rhs.ignoreCache && scriptToEvaluateOnLoad == rhs.scriptToEvaluateOnLoad; } }; struct OverlaySetPausedInDebuggerMessageRequest { /** * The message to display in the overlay. If nullopt, hide the overlay. */ std::optional message; /** * Equality operator, useful for unit tests */ inline bool operator==( const OverlaySetPausedInDebuggerMessageRequest& rhs) const { return message == rhs.message; } }; virtual ~HostTargetDelegate() override; /** * Returns a metadata object describing the host. This is called on an * initial response to @cdp ReactNativeApplication.enable. */ virtual HostTargetMetadata getMetadata() = 0; /** * Called when the debugger requests a reload of the page. This is called on * the thread on which messages are dispatched to the session (that is, where * ILocalConnection::sendMessage was called). */ virtual void onReload(const PageReloadRequest& request) = 0; /** * Called when the debugger requests that the "paused in debugger" overlay be * shown or hidden. If the message is nullopt, hide the overlay, otherwise * show it with the given message. This is called on the inspector thread. * * If this method is called with a non-null message, it's guaranteed to * eventually be called again with a null message. In all other respects, * the timing and payload of these messages are fully controlled by the * client. */ virtual void onSetPausedInDebuggerMessage( const OverlaySetPausedInDebuggerMessageRequest& request) = 0; /** * Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP * request. Platform implementations should override this to perform a * network request of the given URL, and use listener's callbacks on receipt * of headers, data chunks, and errors. */ void loadNetworkResource( const LoadNetworkResourceRequest& /*params*/, ScopedExecutor /*executor*/) override { throw NotImplementedException( "LoadNetworkResourceDelegate.loadNetworkResource is not implemented by this host target delegate."); } }; /** * The limited interface that HostTarget exposes to its associated * sessions/agents. */ class HostTargetController final { public: explicit HostTargetController(HostTarget& target); HostTargetDelegate& getDelegate(); bool hasInstance() const; /** * Increments the target's pause overlay counter. The counter represents the * exact number of Agents that have (concurrently) requested the pause * overlay to be shown. It's the caller's responsibility to only call this * when the pause overlay's requested state transitions from hidden to * visible. */ void incrementPauseOverlayCounter(); /** * Decrements the target's pause overlay counter. The counter represents the * exact number of Agents that have (concurrently) requested the pause * overlay to be shown. It's the caller's responsibility to only call this * when the pause overlay's requested state transitions from hidden to * visible. * \returns false if the counter has reached 0, otherwise true. */ bool decrementPauseOverlayCounter(); private: HostTarget& target_; size_t pauseOverlayCounter_{0}; }; /** * The top-level Target in a React Native app. This is equivalent to the * "Host" in React Native's architecture - the entity that manages the * lifecycle of a React Instance. */ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis { public: /** * Constructs a new HostTarget. * \param delegate The HostTargetDelegate that will * receive events from this HostTarget. The caller is responsible for ensuring * that the HostTargetDelegate outlives this object. * \param executor An executor that may be used to call methods on this * HostTarget while it exists. \c create additionally guarantees that the * executor will not be called after the HostTarget is destroyed. * \note Copies of the provided executor may be destroyed on arbitrary * threads, including after the HostTarget is destroyed. Callers must ensure * that such destructor calls are safe - e.g. if using a lambda as the * executor, all captured values must be safe to destroy from any thread. */ static std::shared_ptr create( HostTargetDelegate& delegate, VoidExecutor executor); HostTarget(const HostTarget&) = delete; HostTarget(HostTarget&&) = delete; HostTarget& operator=(const HostTarget&) = delete; HostTarget& operator=(HostTarget&&) = delete; ~HostTarget(); /** * Creates a new Session connected to this HostTarget, wrapped in an * interface which is compatible with \c IInspector::addPage. * The caller is responsible for destroying the connection before HostTarget * is destroyed, on the same thread where HostTarget's constructor and * destructor execute. */ std::unique_ptr connect( std::unique_ptr connectionToFrontend); /** * Registers an instance with this HostTarget. * \param delegate The InstanceTargetDelegate that will receive events from * this InstanceTarget. The caller is responsible for ensuring that the * InstanceTargetDelegate outlives this object. * \return An InstanceTarget reference representing the newly created * instance. This reference is only valid until unregisterInstance is called * (or the HostTarget is destroyed). \pre There isn't currently an instance * registered with this HostTarget. */ InstanceTarget& registerInstance(InstanceTargetDelegate& delegate); /** * Unregisters an instance from this HostTarget. * \param instance The InstanceTarget reference previously returned by * registerInstance. */ void unregisterInstance(InstanceTarget& instance); /** * Sends an imperative command to the HostTarget. May be called from any * thread. */ void sendCommand(HostCommand command); private: /** * Constructs a new HostTarget. * The caller must call setExecutor immediately afterwards. * \param delegate The HostTargetDelegate that will * receive events from this HostTarget. The caller is responsible for ensuring * that the HostTargetDelegate outlives this object. */ HostTarget(HostTargetDelegate& delegate); HostTargetDelegate& delegate_; WeakList sessions_; HostTargetController controller_{*this}; // executionContextManager_ is a shared_ptr to guarantee its validity while // the InstanceTarget is alive (just in case the InstanceTarget ends up // briefly outliving the HostTarget, which it generally shouldn't). std::shared_ptr executionContextManager_; std::shared_ptr currentInstance_{nullptr}; std::unique_ptr commandSender_; inline HostTargetDelegate& getDelegate() { return delegate_; } inline bool hasInstance() const { return currentInstance_ != nullptr; } // Necessary to allow HostAgent to access HostTarget's internals in a // controlled way (i.e. only HostTargetController gets friend access, while // HostAgent itself doesn't). friend class HostTargetController; }; folly::dynamic hostMetadataToDynamic(const HostTargetMetadata& metadata); } // namespace facebook::react::jsinspector_modern