Skip to content

Commit 0dea7f5

Browse files
committed
Add unstable_fastRefreshComplete CDP event (#56273)
Summary: Pull Request resolved: #56273 Adds a new, experimental `ReactNativeApplication.unstable_fastRefreshComplete` CDP event, emitted to subscribed active CDP sessions when a Fast Refresh update completes. **Notes** - As with D97486551, we reuse the `changeId` block in `HMRClient.js`, ensuring duplicate updates for the same change are not reported. Changelog: [Internal] Reviewed By: GijsWeterings, hoxyq Differential Revision: D98493216 fbshipit-source-id: b0b81a210fb84873e9358aa5484038062f110103
1 parent 6a44d21 commit 0dea7f5

File tree

5 files changed

+92
-1
lines changed

5 files changed

+92
-1
lines changed

packages/react-native/Libraries/Utilities/HMRClient.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,15 @@ Error: ${e.message}`;
229229
}
230230
});
231231

232-
client.on('update-done', () => {
232+
client.on('update-done', body => {
233233
pendingUpdatesCount--;
234234
if (pendingUpdatesCount === 0) {
235235
DevLoadingView.hide();
236+
const changeId = body?.changeId;
237+
if (changeId != null && changeId !== lastMarkerChangeId) {
238+
lastMarkerChangeId = changeId;
239+
emitFastRefreshCompleteEvents();
240+
}
236241
}
237242
});
238243

@@ -378,4 +383,25 @@ function showCompileError() {
378383
throw error;
379384
}
380385

386+
function emitFastRefreshCompleteEvents() {
387+
// Add marker entry in performance timeline
388+
performance.mark('Fast Refresh - Update done', {
389+
detail: {
390+
devtools: {
391+
dataType: 'marker',
392+
color: 'primary',
393+
tooltipText: 'Fast Refresh \u269b',
394+
},
395+
},
396+
});
397+
398+
// Notify CDP clients via internal binding
399+
if (
400+
// $FlowFixMe[prop-missing] - Injected by RuntimeTarget
401+
typeof globalThis.__notifyFastRefreshComplete === 'function'
402+
) {
403+
globalThis.__notifyFastRefreshComplete();
404+
}
405+
}
406+
381407
export default HMRClient;

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
#include "RuntimeAgent.h"
99
#include "SessionState.h"
1010

11+
#include <folly/dynamic.h>
12+
#include <jsinspector-modern/cdp/CdpJson.h>
13+
14+
#include <chrono>
1115
#include <utility>
1216

1317
namespace facebook::react::jsinspector_modern {
@@ -119,6 +123,21 @@ void RuntimeAgent::notifyBindingCalled(
119123
"name", bindingName)("payload", payload)));
120124
}
121125

126+
void RuntimeAgent::notifyFastRefreshComplete() {
127+
if (!sessionState_.isReactNativeApplicationDomainEnabled) {
128+
return;
129+
}
130+
folly::dynamic params = folly::dynamic::object(
131+
"timestamp",
132+
std::chrono::duration_cast<std::chrono::milliseconds>(
133+
std::chrono::system_clock::now().time_since_epoch())
134+
.count());
135+
frontendChannel_(
136+
cdp::jsonNotification(
137+
"ReactNativeApplication.unstable_fastRefreshComplete",
138+
std::move(params)));
139+
}
140+
122141
RuntimeAgent::ExportedState RuntimeAgent::getExportedState() {
123142
return {
124143
.delegateState = delegate_ ? delegate_->getExportedState() : nullptr,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ class RuntimeAgent final {
7272

7373
void notifyBindingCalled(const std::string &bindingName, const std::string &payload);
7474

75+
/**
76+
* Called by RuntimeTarget when JS calls __notifyFastRefreshComplete().
77+
* Emits a ReactNativeApplication.unstable_fastRefreshComplete CDP
78+
* notification if the ReactNativeApplication domain is enabled.
79+
*/
80+
void notifyFastRefreshComplete();
81+
7582
struct ExportedState {
7683
std::unique_ptr<RuntimeAgentDelegate::ExportedState> delegateState;
7784
};

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ void RuntimeTarget::installGlobals() {
6060
// NOTE: RuntimeTarget::installNetworkReporterAPI is in
6161
// RuntimeTargetNetwork.cpp
6262
installNetworkReporterAPI();
63+
64+
installFastRefreshHandler();
6365
}
6466

6567
std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
@@ -141,6 +143,37 @@ void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
141143
});
142144
}
143145

146+
void RuntimeTarget::installFastRefreshHandler() {
147+
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
148+
auto globalObj = runtime.global();
149+
try {
150+
auto name =
151+
jsi::PropNameID::forUtf8(runtime, "__notifyFastRefreshComplete");
152+
globalObj.setProperty(
153+
runtime,
154+
name,
155+
jsi::Function::createFromHostFunction(
156+
runtime,
157+
name,
158+
0,
159+
[selfExecutor](
160+
jsi::Runtime& /*rt*/,
161+
const jsi::Value&,
162+
const jsi::Value*,
163+
size_t) -> jsi::Value {
164+
selfExecutor([](auto& self) {
165+
self.agents_.forEach(
166+
[](auto& agent) { agent.notifyFastRefreshComplete(); });
167+
});
168+
169+
return jsi::Value::undefined();
170+
}));
171+
} catch (jsi::JSError&) {
172+
// Swallow JavaScript exceptions that occur while setting up the global.
173+
}
174+
});
175+
}
176+
144177
void RuntimeTarget::emitDebuggerSessionCreated() {
145178
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
146179
try {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
289289
*/
290290
void installGlobals();
291291

292+
/**
293+
* Installs __notifyFastRefreshComplete on the runtime's global object.
294+
* When called from JS, dispatches to all connected RuntimeAgents.
295+
*/
296+
void installFastRefreshHandler();
297+
292298
/**
293299
* Install the console API handler.
294300
*/

0 commit comments

Comments
 (0)