Skip to content

Commit d6380e4

Browse files
huntiefacebook-github-bot
authored andcommitted
Add single host inspector state assertion over CDP
Summary: Introduces a new `InspectorSystemState` class and `ReactNativeApplication.systemStateChanged` CDP event, used to assert whether more than one React Native Host has been registered for the current app lifetime. This will be used to disable the Performance and Network features in React Native DevTools when the debugger backend is in this currently unsupported state. We intend to implement host lifecycle correctness across all features soon. Changelog: [Internal] Differential Revision: D86201689
1 parent 7cb10a8 commit d6380e4

File tree

8 files changed

+145
-0
lines changed

8 files changed

+145
-0
lines changed

packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
1212
#include "InspectorFlags.h"
13+
#include "InspectorSystemState.h"
1314
#include "NetworkIOAgent.h"
1415
#include "SessionState.h"
1516
#include "TracingAgent.h"
@@ -216,6 +217,9 @@ class HostAgent::Impl final {
216217
cdp::jsonNotification(
217218
"ReactNativeApplication.metadataUpdated",
218219
createHostMetadataPayload(hostMetadata_)));
220+
if (!InspectorSystemState::getInstance().isSingleHost()) {
221+
emitSystemStateChanged(false);
222+
}
219223

220224
auto stashedTraceRecording =
221225
targetController_.getDelegate()
@@ -374,6 +378,17 @@ class HostAgent::Impl final {
374378
tracingAgent_.emitExternalTraceRecording(std::move(traceRecording));
375379
}
376380

381+
void emitSystemStateChanged(bool isSingleHost) {
382+
frontendChannel_(
383+
cdp::jsonNotification(
384+
"ReactNativeApplication.systemStateChanged",
385+
folly::dynamic::object("isSingleHost", isSingleHost)));
386+
387+
if (!isSingleHost) {
388+
frontendChannel_(cdp::jsonNotification("Network.disable"));
389+
}
390+
}
391+
377392
private:
378393
enum class FuseboxClientType { Unknown, Fusebox, NonFusebox };
379394

@@ -480,6 +495,7 @@ class HostAgent::Impl final {
480495
}
481496
void emitExternalTraceRecording(tracing::TraceRecordingState traceRecording) {
482497
}
498+
void emitSystemStateChanged(bool isSingleHost) {}
483499
};
484500

485501
#endif // REACT_NATIVE_DEBUGGER_ENABLED
@@ -519,6 +535,10 @@ void HostAgent::emitExternalTraceRecording(
519535
impl_->emitExternalTraceRecording(std::move(traceRecording));
520536
}
521537

538+
void HostAgent::emitSystemStateChanged(bool isSingleHost) const {
539+
impl_->emitSystemStateChanged(isSingleHost);
540+
}
541+
522542
#pragma mark - Tracing
523543

524544
HostTracingAgent::HostTracingAgent(tracing::TraceRecordingState& state)

packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ class HostAgent final {
7979
*/
8080
void emitExternalTraceRecording(tracing::TraceRecordingState traceRecording) const;
8181

82+
/**
83+
* Emits a system state changed event when the number of ReactHost instances
84+
* changes.
85+
*/
86+
void emitSystemStateChanged(bool isSingleHost) const;
87+
8288
private:
8389
// We use the private implementation idiom to ensure this class has the same
8490
// layout regardless of whether REACT_NATIVE_DEBUGGER_ENABLED is defined. The

packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class HostTargetSession {
115115
hostAgent_.emitExternalTraceRecording(std::move(traceRecording));
116116
}
117117

118+
void emitSystemStateChanged(bool isSingleHost) const {
119+
hostAgent_.emitSystemStateChanged(isSingleHost);
120+
}
121+
118122
private:
119123
// Owned by this instance, but shared (weakly) with the frontend channel
120124
std::shared_ptr<RAIIRemoteConnection> remote_;
@@ -402,4 +406,10 @@ void HostTarget::emitTraceRecordingForFirstFuseboxClient(
402406
});
403407
}
404408

409+
void HostTarget::emitSystemStateChanged(bool isSingleHost) const {
410+
sessions_.forEach([isSingleHost](HostTargetSession& session) {
411+
session.emitSystemStateChanged(isSingleHost);
412+
});
413+
}
414+
405415
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,11 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
320320
*/
321321
void emitTraceRecordingForFirstFuseboxClient(tracing::TraceRecordingState traceRecording) const;
322322

323+
/**
324+
* Emits a system state changed event to all active sessions.
325+
*/
326+
void emitSystemStateChanged(bool isSingleHost) const;
327+
323328
private:
324329
/**
325330
* Constructs a new HostTarget.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "InspectorSystemState.h"
9+
10+
#include <mutex>
11+
12+
namespace facebook::react::jsinspector_modern {
13+
14+
InspectorSystemState& InspectorSystemState::getInstance() {
15+
static InspectorSystemState instance;
16+
return instance;
17+
}
18+
19+
void InspectorSystemState::incrementHostCount() {
20+
int previousCount = hostCount_.fetch_add(1);
21+
if (previousCount == 1) {
22+
std::lock_guard<std::mutex> lock(callbackMutex_);
23+
if (stateChangeCallback_) {
24+
stateChangeCallback_(false);
25+
}
26+
}
27+
}
28+
29+
bool InspectorSystemState::isSingleHost() const {
30+
return hostCount_.load() <= 1;
31+
}
32+
33+
void InspectorSystemState::setStateChangeCallback(
34+
std::function<void(bool)> callback) {
35+
std::lock_guard<std::mutex> lock(callbackMutex_);
36+
stateChangeCallback_ = std::move(callback);
37+
}
38+
39+
} // namespace facebook::react::jsinspector_modern
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <atomic>
11+
#include <functional>
12+
#include <mutex>
13+
14+
namespace facebook::react::jsinspector_modern {
15+
16+
/**
17+
* A singleton class that tracks system-wide state for the inspector
18+
* (Meyers singleton pattern).
19+
*
20+
* TODO(huntie): Clean up this class and all calls once we implement host
21+
* lifecycle correctness for Performance and Network.
22+
*/
23+
class InspectorSystemState {
24+
public:
25+
static InspectorSystemState &getInstance();
26+
27+
/**
28+
* Increment the total count of React Hosts that have been registered during
29+
* the app lifetime.
30+
*/
31+
void incrementHostCount();
32+
33+
/**
34+
* Check if the app has only registered a single host.
35+
*/
36+
bool isSingleHost() const;
37+
38+
/**
39+
* Set the callback to be invoked when the system state changes.
40+
*/
41+
void setStateChangeCallback(std::function<void(bool)> callback);
42+
43+
private:
44+
InspectorSystemState() = default;
45+
InspectorSystemState(const InspectorSystemState &) = delete;
46+
InspectorSystemState &operator=(const InspectorSystemState &) = delete;
47+
~InspectorSystemState() = default;
48+
49+
std::atomic<int> hostCount_{0};
50+
std::mutex callbackMutex_;
51+
std::function<void(bool)> stateChangeCallback_;
52+
};
53+
54+
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCxxPlatform/react/devsupport/inspector/Inspector.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "InspectorPackagerConnectionDelegate.h"
1010

1111
#include <glog/logging.h>
12+
#include <jsinspector-modern/InspectorSystemState.h>
1213
#include <utility>
1314

1415
namespace facebook::react {
@@ -194,6 +195,14 @@ void Inspector::ensureHostTarget(
194195
return nullptr;
195196
},
196197
capabilities);
198+
199+
jsinspector_modern::InspectorSystemState::getInstance()
200+
.setStateChangeCallback(
201+
[weakTarget = std::weak_ptr(target_)](bool isSingleHost) {
202+
if (auto strongTarget = weakTarget.lock()) {
203+
strongTarget->emitSystemStateChanged(isSingleHost);
204+
}
205+
});
197206
}
198207

199208
void Inspector::invokeElsePost(

packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <glog/logging.h>
1414
#include <jserrorhandler/JsErrorHandler.h>
1515
#include <jsinspector-modern/InspectorFlags.h>
16+
#include <jsinspector-modern/InspectorSystemState.h>
1617
#include <react/coremodules/AppStateModule.h>
1718
#include <react/coremodules/DeviceInfoModule.h>
1819
#include <react/coremodules/PlatformConstantsModule.h>
@@ -114,6 +115,7 @@ ReactHost::ReactHost(
114115
.has_value()) {
115116
throw std::runtime_error("No WebSocketClientFactory provided");
116117
}
118+
jsinspector_modern::InspectorSystemState::getInstance().incrementHostCount();
117119
createReactInstance();
118120
}
119121

0 commit comments

Comments
 (0)