mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add boilerplate for handling Overlay.setPausedInDebuggerMessage (#44078)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44078 Changelog: [Internal] Adds stub support for the [`Overlay.setPausedInDebuggerMessage`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Overlay#method-setPausedInDebuggerMessage) CDP method to `HostAgent` in the Fusebox backend, and propagates it into the Android and iOS integrations through `HostTargetDelegate`. We take care to call `HostTargetDelegate::onSetPausedInDebuggerMessage()` a final time with a null `message` parameter, regardless of whether the client has actually sent the corresponding CDP message. Since multiple clients might be connected concurrently, we only send the `null` message when the *last* client which has requested a non-null message has disconnected. Reviewed By: robhogan Differential Revision: D56068444 fbshipit-source-id: c26e1cf17dec8d7dbb7edd5ab7fa3133642628ff
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d181f06b58
commit
8afa8beac2
@@ -199,6 +199,11 @@ class RCTBridgeHostTargetDelegate : public facebook::react::jsinspector_modern::
|
||||
[bridge_ reload];
|
||||
}
|
||||
|
||||
void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &) override
|
||||
{
|
||||
// TODO(moti): Implement this
|
||||
}
|
||||
|
||||
private:
|
||||
__weak RCTBridge *bridge_;
|
||||
};
|
||||
|
||||
@@ -1175,6 +1175,7 @@ public class com/facebook/react/bridge/ReactInstanceManagerInspectorTarget : jav
|
||||
|
||||
public abstract interface class com/facebook/react/bridge/ReactInstanceManagerInspectorTarget$TargetDelegate {
|
||||
public abstract fun onReload ()V
|
||||
public abstract fun onSetPausedInDebuggerMessage (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/bridge/ReactMarker {
|
||||
|
||||
+5
@@ -1494,6 +1494,11 @@ public class ReactInstanceManager {
|
||||
public void onReload() {
|
||||
UiThreadUtil.runOnUiThread(() -> mDevSupportManager.handleReloadJS());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPausedInDebuggerMessage(@Nullable String message) {
|
||||
// TODO(moti): Implement this
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -10,11 +10,14 @@ package com.facebook.react.bridge;
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStripAny;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@DoNotStripAny
|
||||
public class ReactInstanceManagerInspectorTarget implements AutoCloseable {
|
||||
public interface TargetDelegate {
|
||||
public void onReload();
|
||||
|
||||
public void onSetPausedInDebuggerMessage(@Nullable String message);
|
||||
}
|
||||
|
||||
private final HybridData mHybridData;
|
||||
|
||||
+6
@@ -26,6 +26,7 @@ import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.infer.annotation.ThreadSafe;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.JSEngineResolutionAlgorithm;
|
||||
import com.facebook.react.MemoryPressureRouter;
|
||||
import com.facebook.react.ReactHost;
|
||||
@@ -466,6 +467,11 @@ public class ReactHostImpl implements ReactHost {
|
||||
.continueWithTask(Task::getResult);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private void setPausedInDebuggerMessage(@Nullable String message) {
|
||||
// TODO(moti): Implement this
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint to destroy the ReactInstance. If the ReactInstance is reloading, will wait until
|
||||
* reload is finished, before destroying.
|
||||
|
||||
+13
@@ -20,6 +20,14 @@ void ReactInstanceManagerInspectorTarget::TargetDelegate::onReload() const {
|
||||
method(self());
|
||||
}
|
||||
|
||||
void ReactInstanceManagerInspectorTarget::TargetDelegate::
|
||||
onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) const {
|
||||
auto method = javaClassStatic()->getMethod<void(local_ref<JString>)>(
|
||||
"onSetPausedInDebuggerMessage");
|
||||
method(self(), request.message ? make_jstring(*request.message) : nullptr);
|
||||
}
|
||||
|
||||
ReactInstanceManagerInspectorTarget::ReactInstanceManagerInspectorTarget(
|
||||
jni::alias_ref<ReactInstanceManagerInspectorTarget::jhybridobject> jobj,
|
||||
jni::alias_ref<JExecutor::javaobject> executor,
|
||||
@@ -82,6 +90,11 @@ void ReactInstanceManagerInspectorTarget::onReload(
|
||||
delegate_->onReload();
|
||||
}
|
||||
|
||||
void ReactInstanceManagerInspectorTarget::onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) {
|
||||
delegate_->onSetPausedInDebuggerMessage(request);
|
||||
}
|
||||
|
||||
HostTarget* ReactInstanceManagerInspectorTarget::getInspectorTarget() {
|
||||
return inspectorTarget_.get();
|
||||
}
|
||||
|
||||
+7
-1
@@ -22,6 +22,8 @@ class ReactInstanceManagerInspectorTarget
|
||||
"Lcom/facebook/react/bridge/ReactInstanceManagerInspectorTarget$TargetDelegate;";
|
||||
|
||||
void onReload() const;
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) const;
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -44,9 +46,13 @@ class ReactInstanceManagerInspectorTarget
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
void onReload(const PageReloadRequest& request) override;
|
||||
jsinspector_modern::HostTarget* getInspectorTarget();
|
||||
|
||||
// HostTargetDelegate methods
|
||||
void onReload(const PageReloadRequest& request) override;
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest&) override;
|
||||
|
||||
private:
|
||||
friend HybridBase;
|
||||
|
||||
|
||||
+5
@@ -73,6 +73,11 @@ void JReactHostInspectorTarget::onReload(const PageReloadRequest& request) {
|
||||
javaReactHostImpl_->reload("CDP Page.reload");
|
||||
}
|
||||
|
||||
void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) {
|
||||
javaReactHostImpl_->setPausedInDebuggerMessage(request.message);
|
||||
}
|
||||
|
||||
HostTarget* JReactHostInspectorTarget::getInspectorTarget() {
|
||||
return inspectorTarget_ ? inspectorTarget_.get() : nullptr;
|
||||
}
|
||||
|
||||
+9
@@ -29,6 +29,13 @@ struct JReactHostImpl : public jni::JavaClass<JReactHostImpl> {
|
||||
"reload");
|
||||
return method(self(), reason);
|
||||
}
|
||||
|
||||
void setPausedInDebuggerMessage(std::optional<std::string> message) {
|
||||
static auto method =
|
||||
javaClassStatic()->getMethod<void(jni::local_ref<jni::JString>)>(
|
||||
"setPausedInDebuggerMessage");
|
||||
method(self(), message ? jni::make_jstring(*message) : nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
class JReactHostInspectorTarget
|
||||
@@ -48,6 +55,8 @@ class JReactHostInspectorTarget
|
||||
static void registerNatives();
|
||||
|
||||
void onReload(const PageReloadRequest& request) override;
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest&) override;
|
||||
|
||||
jsinspector_modern::HostTarget* getInspectorTarget();
|
||||
|
||||
|
||||
@@ -105,6 +105,22 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
: std::nullopt,
|
||||
});
|
||||
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "Overlay.setPausedInDebuggerMessage") {
|
||||
auto message = req.params.isObject() && req.params.count("message")
|
||||
? std::optional(req.params.at("message").asString())
|
||||
: std::nullopt;
|
||||
if (!isPausedInDebuggerOverlayVisible_ && message.has_value()) {
|
||||
targetController_.incrementPauseOverlayCounter();
|
||||
} else if (isPausedInDebuggerOverlayVisible_ && !message.has_value()) {
|
||||
targetController_.decrementPauseOverlayCounter();
|
||||
}
|
||||
isPausedInDebuggerOverlayVisible_ = message.has_value();
|
||||
targetController_.getDelegate().onSetPausedInDebuggerMessage({
|
||||
.message = message,
|
||||
});
|
||||
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "FuseboxClient.setClientMetadata") {
|
||||
@@ -154,6 +170,20 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
req.method + " not implemented yet"));
|
||||
}
|
||||
|
||||
HostAgent::~HostAgent() {
|
||||
if (isPausedInDebuggerOverlayVisible_) {
|
||||
// In case of a non-graceful shutdown of the session, ensure we clean up
|
||||
// the "paused on debugger" overlay if we've previously asked the
|
||||
// integrator to display it.
|
||||
isPausedInDebuggerOverlayVisible_ = false;
|
||||
if (!targetController_.decrementPauseOverlayCounter()) {
|
||||
targetController_.getDelegate().onSetPausedInDebuggerMessage({
|
||||
.message = std::nullopt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HostAgent::sendFuseboxNotice() {
|
||||
static constexpr auto kFuseboxNotice = ANSI_COLOR_BG_YELLOW
|
||||
"Welcome to the new React Native debugger (codename " ANSI_WEIGHT_BOLD
|
||||
|
||||
@@ -46,6 +46,13 @@ class HostAgent final {
|
||||
HostTarget::SessionMetadata sessionMetadata,
|
||||
SessionState& sessionState);
|
||||
|
||||
HostAgent(const HostAgent&) = delete;
|
||||
HostAgent(HostAgent&&) = delete;
|
||||
HostAgent& operator=(const HostAgent&) = delete;
|
||||
HostAgent& operator=(HostAgent&&) = delete;
|
||||
|
||||
~HostAgent();
|
||||
|
||||
/**
|
||||
* Handle a CDP request. The response will be sent over the provided
|
||||
* \c FrontendChannel synchronously or asynchronously.
|
||||
@@ -90,6 +97,7 @@ class HostAgent final {
|
||||
const HostTarget::SessionMetadata sessionMetadata_;
|
||||
std::shared_ptr<InstanceAgent> instanceAgent_;
|
||||
FuseboxClientType fuseboxClientType_{FuseboxClientType::Unknown};
|
||||
bool isPausedInDebuggerOverlayVisible_{false};
|
||||
|
||||
/**
|
||||
* A shared reference to the session's state. This is only safe to access
|
||||
|
||||
@@ -162,4 +162,16 @@ bool HostTargetController::hasInstance() const {
|
||||
return target_.hasInstance();
|
||||
}
|
||||
|
||||
void HostTargetController::incrementPauseOverlayCounter() {
|
||||
++pauseOverlayCounter_;
|
||||
}
|
||||
|
||||
bool HostTargetController::decrementPauseOverlayCounter() {
|
||||
assert(pauseOverlayCounter_ > 0 && "Pause overlay counter underflow");
|
||||
if (--pauseOverlayCounter_ == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
|
||||
@@ -67,6 +67,21 @@ class HostTargetDelegate {
|
||||
}
|
||||
};
|
||||
|
||||
struct OverlaySetPausedInDebuggerMessageRequest {
|
||||
/**
|
||||
* The message to display in the overlay. If nullopt, hide the overlay.
|
||||
*/
|
||||
std::optional<std::string> message;
|
||||
|
||||
/**
|
||||
* Equality operator, useful for unit tests
|
||||
*/
|
||||
inline bool operator==(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& rhs) const {
|
||||
return message == rhs.message;
|
||||
}
|
||||
};
|
||||
|
||||
virtual ~HostTargetDelegate();
|
||||
|
||||
/**
|
||||
@@ -75,6 +90,19 @@ class HostTargetDelegate {
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,8 +117,28 @@ class HostTargetController final {
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,13 +42,21 @@ class HostTargetTest : public Test {
|
||||
|
||||
void connect() {
|
||||
ASSERT_FALSE(toPage_) << "Can only connect once in a HostTargetTest.";
|
||||
toPage_ = page_->connect(
|
||||
auto conn = makeConnection();
|
||||
toPage_ = std::move(conn.first);
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<ILocalConnection>, MockRemoteConnection&>
|
||||
makeConnection() {
|
||||
size_t connectionIndex = remoteConnections_.objectsVended();
|
||||
auto toPage = page_->connect(
|
||||
remoteConnections_.make_unique(),
|
||||
{.integrationName = "HostTargetTest"});
|
||||
|
||||
// We'll always get an onDisconnect call when we tear
|
||||
// down the test. Expect it in order to satisfy the strict mock.
|
||||
EXPECT_CALL(*remoteConnections_[0], onDisconnect());
|
||||
EXPECT_CALL(*remoteConnections_[connectionIndex], onDisconnect());
|
||||
return {std::move(toPage), *remoteConnections_[connectionIndex]};
|
||||
}
|
||||
|
||||
MockHostTargetDelegate hostTargetDelegate_;
|
||||
@@ -206,6 +214,108 @@ TEST_F(HostTargetProtocolTest, PageReloadMethod) {
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(HostTargetProtocolTest, OverlaySetPausedInDebuggerMessageMethod) {
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = std::nullopt})))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Overlay.setPausedInDebuggerMessage"
|
||||
})");
|
||||
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = "Paused in debugger"})))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
toPage_->sendMessage(R"({
|
||||
"id": 2,
|
||||
"method": "Overlay.setPausedInDebuggerMessage",
|
||||
"params": {
|
||||
"message": "Paused in debugger"
|
||||
}
|
||||
})");
|
||||
|
||||
// A cleanup message is sent automatically when we destroy the session.
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = std::nullopt})))
|
||||
.RetiresOnSaturation();
|
||||
}
|
||||
|
||||
TEST_F(HostTargetProtocolTest, OverlaySetPausedInDebuggerMultipleClients) {
|
||||
auto [toPage2, fromPage2] = makeConnection();
|
||||
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = "Paused in debugger - client 1"})))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Overlay.setPausedInDebuggerMessage",
|
||||
"params": {
|
||||
"message": "Paused in debugger - client 1"
|
||||
}
|
||||
})");
|
||||
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = "Paused in debugger - client 2"})))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(fromPage2, onMessage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
toPage2->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Overlay.setPausedInDebuggerMessage",
|
||||
"params": {
|
||||
"message": "Paused in debugger - client 2"
|
||||
}
|
||||
})");
|
||||
|
||||
toPage2.reset();
|
||||
|
||||
// The cleanup message is sent exactly once.
|
||||
EXPECT_CALL(
|
||||
hostTargetDelegate_,
|
||||
onSetPausedInDebuggerMessage(
|
||||
Eq(HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest{
|
||||
.message = std::nullopt})))
|
||||
.Times(1)
|
||||
.RetiresOnSaturation();
|
||||
}
|
||||
|
||||
TEST_F(HostTargetProtocolTest, RegisterUnregisterInstanceWithoutEvents) {
|
||||
auto& instanceTarget = page_->registerInstance(instanceTargetDelegate_);
|
||||
|
||||
|
||||
@@ -119,6 +119,11 @@ class MockHostTargetDelegate : public HostTargetDelegate {
|
||||
public:
|
||||
// HostTargetDelegate methods
|
||||
MOCK_METHOD(void, onReload, (const PageReloadRequest& request), (override));
|
||||
MOCK_METHOD(
|
||||
void,
|
||||
onSetPausedInDebuggerMessage,
|
||||
(const OverlaySetPausedInDebuggerMessageRequest& request),
|
||||
(override));
|
||||
};
|
||||
|
||||
class MockInstanceTargetDelegate : public InstanceTargetDelegate {};
|
||||
|
||||
@@ -183,6 +183,9 @@ class JsiIntegrationPortableTest : public ::testing::Test,
|
||||
(void)request;
|
||||
reload();
|
||||
}
|
||||
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest&) override {}
|
||||
};
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
|
||||
@@ -38,6 +38,11 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
|
||||
[static_cast<id<RCTReloadListener>>(host_) didReceiveReloadCommand];
|
||||
}
|
||||
|
||||
void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &) override
|
||||
{
|
||||
// TODO(moti): Implement this
|
||||
}
|
||||
|
||||
private:
|
||||
__weak RCTHost *host_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user