forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce NativeDebuggerSessionObserver module (facebook#45577)
Summary: Pull Request resolved: facebook#45577 # Changelog: [Internal] This diff adds new native module, which can be used from JavaScript. The API includes: 1. `hasActiveSession`: returns a boolean flag, which can be used for determining if 1 or more debugging sessions are active for current HostTarget. 2. `subscribe`: receives a callback, which will be executed once the debugging state changes. To be more precise, this will only be called when state is changing from no active sessions to 1 session or the other way around. Callback should expect to receive one boolean argument, which can be used for determining if there is an active session. Reviewed By: huntie Differential Revision: D59975264 fbshipit-source-id: dd095954529f573f38e9fae1792465a59e639d23
- Loading branch information
1 parent
8af5e89
commit 6fde836
Showing
7 changed files
with
365 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
packages/react-native/ReactCommon/jsinspector-modern/HostTargetSessionObserver.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
#include <cassert> | ||
#include <memory> | ||
#include <mutex> | ||
|
||
#include "HostTargetSessionObserver.h" | ||
|
||
namespace facebook::react::jsinspector_modern { | ||
|
||
HostTargetSessionObserver& HostTargetSessionObserver::getInstance() { | ||
static HostTargetSessionObserver instance; | ||
return instance; | ||
} | ||
|
||
// Will be called by HostTargetSession on inspector thread. | ||
void HostTargetSessionObserver::onHostTargetSessionCreated() { | ||
std::lock_guard<std::mutex> lock(mutex_); | ||
|
||
++activeSessionCount_; | ||
if (activeSessionCount_ == 1) { | ||
for (auto& subscriber : subscribers_) { | ||
subscriber.second(true); | ||
} | ||
} | ||
} | ||
|
||
// Will be called by HostTargetSession on inspector thread. | ||
void HostTargetSessionObserver::onHostTargetSessionDestroyed() { | ||
std::lock_guard<std::mutex> lock(mutex_); | ||
|
||
assert( | ||
activeSessionCount_ > 0 && "Unexpected overflow of HostTarget sessions"); | ||
--activeSessionCount_; | ||
if (activeSessionCount_ == 0) { | ||
for (auto& subscriber : subscribers_) { | ||
subscriber.second(false); | ||
} | ||
} | ||
} | ||
|
||
// Will be called by NativeDebuggerSessionObserver on JS thread. | ||
bool HostTargetSessionObserver::hasActiveSessions() { | ||
std::lock_guard<std::mutex> lock(mutex_); | ||
|
||
return activeSessionCount_ > 0; | ||
} | ||
|
||
// Will be called by NativeDebuggerSessionObserver on JS thread. | ||
std::function<void()> HostTargetSessionObserver::subscribe( | ||
std::function<void(bool)> callback) { | ||
std::lock_guard<std::mutex> lock(mutex_); | ||
|
||
auto subscriberIndex = subscriberIndex_++; | ||
subscribers_.emplace(subscriberIndex, std::move(callback)); | ||
|
||
// Since HostTargetSessionObserver is a singleton, it is expected to outlive | ||
// all potential subscribers | ||
return [this, subscriberIndexToRemove = subscriberIndex]() { | ||
std::lock_guard<std::mutex> lock(mutex_); | ||
subscribers_.erase(subscriberIndexToRemove); | ||
}; | ||
} | ||
|
||
} // namespace facebook::react::jsinspector_modern |
51 changes: 51 additions & 0 deletions
51
packages/react-native/ReactCommon/jsinspector-modern/HostTargetSessionObserver.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* 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 <cstdint> | ||
#include <functional> | ||
#include <map> | ||
#include <mutex> | ||
|
||
namespace facebook::react::jsinspector_modern { | ||
|
||
class HostTargetSessionObserver { | ||
public: | ||
static HostTargetSessionObserver& getInstance(); | ||
|
||
/* | ||
* Not copyable. | ||
*/ | ||
HostTargetSessionObserver(const HostTargetSessionObserver&) = delete; | ||
HostTargetSessionObserver& operator=(const HostTargetSessionObserver&) = | ||
delete; | ||
|
||
/* | ||
* Not movable. | ||
*/ | ||
HostTargetSessionObserver(HostTargetSessionObserver&&) = delete; | ||
HostTargetSessionObserver& operator=(HostTargetSessionObserver&&) = delete; | ||
|
||
void onHostTargetSessionCreated(); | ||
void onHostTargetSessionDestroyed(); | ||
|
||
bool hasActiveSessions(); | ||
std::function<void()> subscribe(std::function<void(bool)> callback); | ||
|
||
private: | ||
HostTargetSessionObserver() = default; | ||
~HostTargetSessionObserver() = default; | ||
|
||
int activeSessionCount_ = 0; | ||
std::map<uint32_t, std::function<void(bool)>> subscribers_; | ||
uint32_t subscriberIndex_ = 0; | ||
|
||
std::mutex mutex_; | ||
}; | ||
|
||
} // namespace facebook::react::jsinspector_modern |
146 changes: 146 additions & 0 deletions
146
packages/react-native/ReactCommon/jsinspector-modern/tests/HostTargetSessionObserverTest.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
#include <folly/executors/QueuedImmediateExecutor.h> | ||
#include <gmock/gmock.h> | ||
#include <gtest/gtest.h> | ||
|
||
#include <jsinspector-modern/HostTarget.h> | ||
#include <jsinspector-modern/HostTargetSessionObserver.h> | ||
#include <jsinspector-modern/InspectorInterfaces.h> | ||
|
||
#include <memory> | ||
|
||
#include "InspectorMocks.h" | ||
#include "UniquePtrFactory.h" | ||
|
||
using namespace ::testing; | ||
|
||
namespace facebook::react::jsinspector_modern { | ||
|
||
namespace { | ||
|
||
class HostTargetSessionObserverTest : public Test { | ||
folly::QueuedImmediateExecutor immediateExecutor_; | ||
|
||
protected: | ||
HostTargetSessionObserverTest() { | ||
EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _, _, _)) | ||
.WillRepeatedly(runtimeAgentDelegates_.lazily_make_unique< | ||
FrontendChannel, | ||
SessionState&, | ||
std::unique_ptr<RuntimeAgentDelegate::ExportedState>, | ||
const ExecutionContextDescription&, | ||
RuntimeExecutor>()); | ||
} | ||
|
||
void connect() { | ||
auto connection = makeConnection(); | ||
|
||
pageConnectionsPointers_.push_back(std::move(connection.first)); | ||
} | ||
|
||
std::pair<std::unique_ptr<ILocalConnection>, MockRemoteConnection&> | ||
makeConnection() { | ||
size_t connectionIndex = remoteConnections_.objectsVended(); | ||
auto toPage = page_->connect(remoteConnections_.make_unique()); | ||
|
||
// 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_[connectionIndex], onDisconnect()); | ||
return {std::move(toPage), *remoteConnections_[connectionIndex]}; | ||
} | ||
|
||
MockHostTargetDelegate hostTargetDelegate_; | ||
|
||
VoidExecutor inspectorExecutor_ = [this](auto callback) { | ||
immediateExecutor_.add(callback); | ||
}; | ||
|
||
std::shared_ptr<HostTarget> page_ = | ||
HostTarget::create(hostTargetDelegate_, inspectorExecutor_); | ||
|
||
MockRuntimeTargetDelegate runtimeTargetDelegate_; | ||
|
||
UniquePtrFactory<StrictMock<MockRuntimeAgentDelegate>> runtimeAgentDelegates_; | ||
|
||
MOCK_METHOD(void, subscriptionCallback, (bool hasActiveSession)); | ||
|
||
private: | ||
UniquePtrFactory<StrictMock<MockRemoteConnection>> remoteConnections_; | ||
|
||
protected: | ||
std::vector<std::unique_ptr<ILocalConnection>> pageConnectionsPointers_; | ||
}; | ||
} // namespace | ||
|
||
TEST_F(HostTargetSessionObserverTest, HasNoActiveSessionsByDefault) { | ||
EXPECT_FALSE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
} | ||
|
||
TEST_F(HostTargetSessionObserverTest, HasActiveSessionOnceConnected) { | ||
connect(); | ||
EXPECT_TRUE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
} | ||
|
||
TEST_F(HostTargetSessionObserverTest, HasNoActiveSessionsOnceDisconnected) { | ||
connect(); | ||
EXPECT_TRUE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
|
||
pageConnectionsPointers_[0]->disconnect(); | ||
EXPECT_FALSE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
} | ||
|
||
TEST_F(HostTargetSessionObserverTest, WorksWithMultipleConnections) { | ||
connect(); | ||
EXPECT_TRUE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
|
||
connect(); | ||
EXPECT_TRUE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
|
||
pageConnectionsPointers_[0]->disconnect(); | ||
EXPECT_TRUE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
|
||
pageConnectionsPointers_[1]->disconnect(); | ||
EXPECT_FALSE(HostTargetSessionObserver::getInstance().hasActiveSessions()); | ||
} | ||
|
||
TEST_F(HostTargetSessionObserverTest, CorrectlyNotifiesSubscribers) { | ||
auto callback = [&](bool hasActiveSession) { | ||
this->subscriptionCallback(hasActiveSession); | ||
}; | ||
auto unsubscribe = | ||
HostTargetSessionObserver::getInstance().subscribe(callback); | ||
|
||
EXPECT_CALL(*this, subscriptionCallback(true)).Times(1); | ||
connect(); | ||
connect(); | ||
|
||
pageConnectionsPointers_[0]->disconnect(); | ||
EXPECT_CALL(*this, subscriptionCallback(false)).Times(1); | ||
pageConnectionsPointers_[1]->disconnect(); | ||
} | ||
|
||
TEST_F(HostTargetSessionObserverTest, SupportsUnsubscribing) { | ||
auto callback = [&](bool hasActiveSession) { | ||
this->subscriptionCallback(hasActiveSession); | ||
}; | ||
auto unsubscribe = | ||
HostTargetSessionObserver::getInstance().subscribe(callback); | ||
|
||
EXPECT_CALL(*this, subscriptionCallback(true)).Times(1); | ||
connect(); | ||
connect(); | ||
|
||
unsubscribe(); | ||
|
||
EXPECT_CALL(*this, subscriptionCallback(false)).Times(0); | ||
pageConnectionsPointers_[0]->disconnect(); | ||
pageConnectionsPointers_[1]->disconnect(); | ||
} | ||
|
||
} // namespace facebook::react::jsinspector_modern |
41 changes: 41 additions & 0 deletions
41
...s/react-native/ReactCommon/react/nativemodule/debugging/NativeDebuggerSessionObserver.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
#include "NativeDebuggerSessionObserver.h" | ||
#include <jsinspector-modern/HostTargetSessionObserver.h> | ||
|
||
#include "Plugins.h" | ||
|
||
std::shared_ptr<facebook::react::TurboModule> | ||
NativeDebuggerSessionObserverModuleProvider( | ||
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) { | ||
return std::make_shared<facebook::react::NativeDebuggerSessionObserver>( | ||
std::move(jsInvoker)); | ||
} | ||
|
||
namespace facebook::react { | ||
|
||
NativeDebuggerSessionObserver::NativeDebuggerSessionObserver( | ||
std::shared_ptr<CallInvoker> jsInvoker) | ||
: NativeDebuggerSessionObserverCxxSpec(std::move(jsInvoker)) {} | ||
|
||
bool NativeDebuggerSessionObserver::hasActiveSession( | ||
jsi::Runtime& /*runtime*/) { | ||
return jsinspector_modern::HostTargetSessionObserver::getInstance() | ||
.hasActiveSessions(); | ||
} | ||
|
||
std::function<void()> NativeDebuggerSessionObserver::subscribe( | ||
jsi::Runtime& /*runtime*/, | ||
AsyncCallback<bool> callback) { | ||
return jsinspector_modern::HostTargetSessionObserver::getInstance().subscribe( | ||
[callback = std::move(callback)](bool sessionStatus) { | ||
callback(sessionStatus); | ||
}); | ||
} | ||
|
||
} // namespace facebook::react |
26 changes: 26 additions & 0 deletions
26
...ges/react-native/ReactCommon/react/nativemodule/debugging/NativeDebuggerSessionObserver.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* 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 <FBReactNativeSpec/FBReactNativeSpecJSI.h> | ||
|
||
namespace facebook::react { | ||
|
||
class NativeDebuggerSessionObserver | ||
: public NativeDebuggerSessionObserverCxxSpec< | ||
NativeDebuggerSessionObserver> { | ||
public: | ||
NativeDebuggerSessionObserver(std::shared_ptr<CallInvoker> jsInvoker); | ||
|
||
bool hasActiveSession(jsi::Runtime& runtime); | ||
std::function<void()> subscribe( | ||
jsi::Runtime& runtime, | ||
AsyncCallback<bool> callback); | ||
}; | ||
|
||
} // namespace facebook::react |
23 changes: 23 additions & 0 deletions
23
packages/react-native/src/private/specs/modules/NativeDebuggerSessionObserver.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
* | ||
* @flow strict | ||
* @format | ||
* @oncall react_native | ||
*/ | ||
|
||
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport'; | ||
|
||
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry'; | ||
|
||
export interface Spec extends TurboModule { | ||
+hasActiveSession: () => boolean; | ||
+subscribe: (callback: (hasActiveSession: boolean) => void) => () => void; | ||
} | ||
|
||
export default (TurboModuleRegistry.get<Spec>( | ||
'NativeDebuggerSessionObserverCxx', | ||
): ?Spec); |