@react-native-quickjs/quickjs 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/NOTICE +31 -0
- package/README.md +217 -0
- package/ReactNativeQuickJS.podspec +197 -0
- package/android/CMakeLists.txt +134 -0
- package/android/build.gradle +214 -0
- package/android/quickjs.gradle +39 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/reactnativequickjs/quickjs/QuickJSInstance.kt +39 -0
- package/android/src/main/java/com/reactnativequickjs/quickjs/QuickJSPackage.kt +32 -0
- package/android/src/main/jni/JJSRuntimeFactory.h +37 -0
- package/android/src/main/jni/JQuickJSInstance.cpp +27 -0
- package/android/src/main/jni/JQuickJSInstance.h +46 -0
- package/android/src/main/jni/OnLoad.cpp +15 -0
- package/app.plugin.js +11 -0
- package/apple/RCTQuickJSInstanceFactory.h +32 -0
- package/apple/RCTQuickJSInstanceFactory.mm +16 -0
- package/bin/qjsc/darwin-arm64/qjsc +0 -0
- package/bin/qjsc/darwin-x64/qjsc +0 -0
- package/bin/qjsc/linux-arm64/qjsc +0 -0
- package/bin/qjsc/linux-x64/qjsc +0 -0
- package/bin/qjsc/manifest.json +11 -0
- package/bin/qjsc/win32-x64/qjsc.exe +0 -0
- package/bin/react-native-quickjs.js +28 -0
- package/cmake/jsi.cmake +24 -0
- package/cmake/quickjs.cmake +125 -0
- package/engine/quickjs-rel/LICENSE +24 -0
- package/engine/quickjs-rel/MANIFEST.json +55 -0
- package/engine/quickjs-rel/builtin-array-fromasync.h +120 -0
- package/engine/quickjs-rel/builtin-iterator-zip-keyed.h +332 -0
- package/engine/quickjs-rel/builtin-iterator-zip.h +337 -0
- package/engine/quickjs-rel/cutils.h +1998 -0
- package/engine/quickjs-rel/dtoa.c +1619 -0
- package/engine/quickjs-rel/dtoa.h +87 -0
- package/engine/quickjs-rel/libregexp-opcode.h +73 -0
- package/engine/quickjs-rel/libregexp.c +3478 -0
- package/engine/quickjs-rel/libregexp.h +101 -0
- package/engine/quickjs-rel/libunicode-table.h +5173 -0
- package/engine/quickjs-rel/libunicode.c +2069 -0
- package/engine/quickjs-rel/libunicode.h +172 -0
- package/engine/quickjs-rel/list.h +107 -0
- package/engine/quickjs-rel/quickjs-atom.h +280 -0
- package/engine/quickjs-rel/quickjs-c-atomics.h +54 -0
- package/engine/quickjs-rel/quickjs-opcode.h +385 -0
- package/engine/quickjs-rel/quickjs.c +64854 -0
- package/engine/quickjs-rel/quickjs.h +1577 -0
- package/modules/cdp/quickjs-cdp.c +1386 -0
- package/modules/cdp/quickjs-cdp.h +122 -0
- package/modules/cdp/react/QuickJSInspector.cpp +322 -0
- package/modules/cdp/react/QuickJSInspector.h +114 -0
- package/modules/hermes-compat/include/hermes/DebuggerAPI.h +20 -0
- package/modules/hermes-compat/include/hermes/Public/CrashManager.h +47 -0
- package/modules/hermes-compat/include/hermes/Public/CtorConfig.h +88 -0
- package/modules/hermes-compat/include/hermes/Public/GCConfig.h +83 -0
- package/modules/hermes-compat/include/hermes/Public/HermesExport.h +17 -0
- package/modules/hermes-compat/include/hermes/Public/RuntimeConfig.h +66 -0
- package/modules/hermes-compat/include/hermes/Public/SamplingProfiler.h +22 -0
- package/modules/hermes-compat/include/hermes/hermes.h +195 -0
- package/modules/hermes-compat/include/hermes/inspector/RuntimeAdapter.h +43 -0
- package/modules/hermes-compat/include/hermes/inspector-modern/chrome/Registration.h +31 -0
- package/modules/hermes-compat/include/hermes-compat/Diagnostics.h +35 -0
- package/modules/hermes-compat/src/HermesCompat.cpp +557 -0
- package/package.json +104 -0
- package/scripts/bytecode/compile.js +99 -0
- package/scripts/expo/plugin.js +231 -0
- package/scripts/postinstall.js +74 -0
- package/scripts/react_native_quickjs_pods.rb +237 -0
- package/scripts/setup/edits.js +311 -0
- package/scripts/setup/run.js +121 -0
- package/src/bytecode/QuickJSBytecode.cpp +50 -0
- package/src/bytecode/QuickJSBytecode.h +50 -0
- package/src/module/QuickJSCompat.cpp +92 -0
- package/src/module/QuickJSCompat.h +19 -0
- package/src/module/QuickJSModule.cpp +70 -0
- package/src/module/QuickJSModule.h +108 -0
- package/src/module/QuickJSModuleNative.h +93 -0
- package/src/runtime/QuickJSInstance.cpp +37 -0
- package/src/runtime/QuickJSInstance.h +74 -0
- package/src/runtime/QuickJSRuntime.cpp +1864 -0
- package/src/runtime/QuickJSRuntime.h +529 -0
- package/src/runtime/QuickJSRuntimeConfig.h +103 -0
- package/src/runtime/QuickJSRuntimeFactory.cpp +34 -0
- package/src/runtime/QuickJSRuntimeFactory.h +24 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
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
|
+
* The Chrome DevTools Protocol, for QuickJS.
|
|
8
|
+
*
|
|
9
|
+
* This is plain C over <quickjs.h> and nothing else, with no generated
|
|
10
|
+
* marshalling code: protocol messages are JSON, and
|
|
11
|
+
* the engine already has a JSON parser, so they are handled as JSValues on a
|
|
12
|
+
* private context of the agent's own.
|
|
13
|
+
*
|
|
14
|
+
* THREADS
|
|
15
|
+
* Every protocol message is handled on the JS thread. qjs_cdp_send_message()
|
|
16
|
+
* may be called from any thread; it copies the message and queues it. The
|
|
17
|
+
* queue is drained by qjs_cdp_poll(), which the embedder calls on the JS
|
|
18
|
+
* thread, and by the pause loop, which is the only reason the queue exists:
|
|
19
|
+
* while execution is stopped at a breakpoint the JS thread is inside the
|
|
20
|
+
* engine, and the frontend still has to be answered.
|
|
21
|
+
*
|
|
22
|
+
* PARSE-TIME INSTRUMENTATION
|
|
23
|
+
* The engine emits statement traps only into code parsed while a trace
|
|
24
|
+
* handler is installed, so qjs_cdp_new() must be called before any script is
|
|
25
|
+
* evaluated. A runtime that started without one cannot be attached to later.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
#ifndef QUICKJS_CDP_H
|
|
29
|
+
#define QUICKJS_CDP_H
|
|
30
|
+
|
|
31
|
+
#include <quickjs.h>
|
|
32
|
+
#include <stdbool.h>
|
|
33
|
+
#include <stddef.h>
|
|
34
|
+
|
|
35
|
+
#ifdef __cplusplus
|
|
36
|
+
extern "C" {
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
typedef struct QJSCDPAgent QJSCDPAgent;
|
|
40
|
+
|
|
41
|
+
/* Receives one complete CDP message to deliver to the frontend. Called on the
|
|
42
|
+
JS thread. `json` is owned by the agent and is not valid after the call.
|
|
43
|
+
|
|
44
|
+
`session` is the token that came with the request being answered, or NULL for
|
|
45
|
+
an event, which belongs to every session at once. A reply must reach only the
|
|
46
|
+
frontend that asked: two frontends can be attached, and answering both makes
|
|
47
|
+
each of them see a response to something it never sent. */
|
|
48
|
+
typedef void QJSCDPSendFunc(
|
|
49
|
+
void *opaque, void *session, const char *json, size_t len);
|
|
50
|
+
|
|
51
|
+
/* Creates the agent and installs the engine's trace handler on `ctx`.
|
|
52
|
+
`execution_context_id` is the id reported in Runtime.executionContextCreated
|
|
53
|
+
and echoed back in every remote object. */
|
|
54
|
+
QJSCDPAgent *qjs_cdp_new(
|
|
55
|
+
JSContext *ctx, int execution_context_id, QJSCDPSendFunc *send,
|
|
56
|
+
void *send_opaque);
|
|
57
|
+
|
|
58
|
+
void qjs_cdp_free(QJSCDPAgent *agent);
|
|
59
|
+
|
|
60
|
+
/* True when this agent implements `method`.
|
|
61
|
+
A request it does not implement must be left to whoever else is listening
|
|
62
|
+
rather than answered with an error: the frontend talks to several agents at
|
|
63
|
+
once, and refusing on behalf of all of them is not ours to do. */
|
|
64
|
+
bool qjs_cdp_handles(const char *method);
|
|
65
|
+
|
|
66
|
+
/* Tells the agent which execution context it is speaking for. The embedder
|
|
67
|
+
assigns the id, and a Runtime.evaluate naming any other context is refused
|
|
68
|
+
rather than run in this one. */
|
|
69
|
+
void qjs_cdp_set_execution_context(QJSCDPAgent *agent, int id);
|
|
70
|
+
|
|
71
|
+
/* Queues one CDP message from `session`. Any thread. */
|
|
72
|
+
void qjs_cdp_send_message(
|
|
73
|
+
QJSCDPAgent *agent, void *session, const char *json, size_t len);
|
|
74
|
+
|
|
75
|
+
/* Handles every queued message. JS thread only. */
|
|
76
|
+
void qjs_cdp_poll(QJSCDPAgent *agent);
|
|
77
|
+
|
|
78
|
+
/* Reports a console call to the frontend as Runtime.consoleAPICalled. `type` is
|
|
79
|
+
the protocol's name for it ("log", "warning", ...). The values are borrowed
|
|
80
|
+
for the duration of the call. JS thread only. */
|
|
81
|
+
void qjs_cdp_console_message(
|
|
82
|
+
QJSCDPAgent *agent, const char *type, const JSValue *args, int count);
|
|
83
|
+
|
|
84
|
+
/* Tells the agent a script was loaded, so it can answer Debugger.scriptParsed
|
|
85
|
+
and resolve breakpoints set against `url`. Call after evaluating each script.
|
|
86
|
+
JS thread only. */
|
|
87
|
+
void qjs_cdp_script_loaded(
|
|
88
|
+
QJSCDPAgent *agent, const char *url, const char *source);
|
|
89
|
+
|
|
90
|
+
/* Everything a replacement agent needs to carry on where this one left off:
|
|
91
|
+
whether the debugger was enabled and which breakpoints the frontend has set.
|
|
92
|
+
React Native destroys and recreates the runtime on a reload, and the frontend
|
|
93
|
+
is not told -- from its side the breakpoints it set are still set.
|
|
94
|
+
|
|
95
|
+
Export returns a string the caller frees with free(); import takes one back. */
|
|
96
|
+
char *qjs_cdp_export_state(QJSCDPAgent *agent);
|
|
97
|
+
void qjs_cdp_import_state(QJSCDPAgent *agent, const char *json);
|
|
98
|
+
|
|
99
|
+
/* Whether the debugger is switched on for the runtime.
|
|
100
|
+
|
|
101
|
+
Debugger.enable turns it on by itself, so a lone embedder needs none of this.
|
|
102
|
+
Turning it OFF is different: enabling is per session and two frontends can
|
|
103
|
+
disagree, so Debugger.disable from one of them is not the whole story. An
|
|
104
|
+
embedder that supports several sessions calls the setter with the union.
|
|
105
|
+
After an import this is whatever it was before the reload. */
|
|
106
|
+
bool qjs_cdp_debugger_enabled(QJSCDPAgent *agent);
|
|
107
|
+
void qjs_cdp_set_debugger_enabled(QJSCDPAgent *agent, bool enabled);
|
|
108
|
+
|
|
109
|
+
/* The call stack right now, as a Runtime.StackTrace JSON object. Returns a
|
|
110
|
+
string the caller frees with free(), or NULL when nothing is running.
|
|
111
|
+
`frames_to_skip` drops that many innermost frames, which is how a native
|
|
112
|
+
function leaves itself out of the trace it is capturing. JS thread only. */
|
|
113
|
+
char *qjs_cdp_capture_stack_trace(QJSCDPAgent *agent, int frames_to_skip);
|
|
114
|
+
|
|
115
|
+
/* Asks the running program to stop at the next statement. Any thread. */
|
|
116
|
+
void qjs_cdp_pause(QJSCDPAgent *agent);
|
|
117
|
+
|
|
118
|
+
#ifdef __cplusplus
|
|
119
|
+
}
|
|
120
|
+
#endif
|
|
121
|
+
|
|
122
|
+
#endif /* QUICKJS_CDP_H */
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
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 "QuickJSInspector.h"
|
|
9
|
+
|
|
10
|
+
#include <folly/json.h>
|
|
11
|
+
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <cstdlib>
|
|
14
|
+
#include <utility>
|
|
15
|
+
|
|
16
|
+
#include "QuickJSRuntime.h"
|
|
17
|
+
|
|
18
|
+
using namespace facebook::react;
|
|
19
|
+
using namespace facebook::react::jsinspector_modern;
|
|
20
|
+
|
|
21
|
+
namespace qjs {
|
|
22
|
+
|
|
23
|
+
/// One debugging session. Requests arrive on the inspector thread and are
|
|
24
|
+
/// queued; the agent handles them on the JS thread, or from inside the pause
|
|
25
|
+
/// loop when execution is stopped.
|
|
26
|
+
class InspectorSession : public RuntimeAgentDelegate {
|
|
27
|
+
public:
|
|
28
|
+
InspectorSession(
|
|
29
|
+
QuickJSInspectorDelegate &owner, QJSCDPAgent *agent,
|
|
30
|
+
FrontendChannel channel, RuntimeExecutor executor, bool debuggerEnabled)
|
|
31
|
+
: owner_(owner),
|
|
32
|
+
agent_(agent),
|
|
33
|
+
channel_(std::move(channel)),
|
|
34
|
+
executor_(std::move(executor)),
|
|
35
|
+
debuggerEnabled_(debuggerEnabled) {}
|
|
36
|
+
|
|
37
|
+
~InspectorSession() override {
|
|
38
|
+
owner_.forget(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
bool handleRequest(const cdp::PreparsedRequest &req) override {
|
|
42
|
+
if (!qjs_cdp_handles(req.method.c_str())) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Enabling a domain is per session, not per runtime: one frontend can be
|
|
46
|
+
// watching the debugger while another is not, and sending Debugger events
|
|
47
|
+
// to the second is a message it never asked for.
|
|
48
|
+
if (req.method == "Debugger.enable") {
|
|
49
|
+
debuggerEnabled_ = true;
|
|
50
|
+
owner_.debuggerEnabledChanged();
|
|
51
|
+
} else if (req.method == "Debugger.disable") {
|
|
52
|
+
debuggerEnabled_ = false;
|
|
53
|
+
owner_.debuggerEnabledChanged();
|
|
54
|
+
}
|
|
55
|
+
const std::string json = req.toJson();
|
|
56
|
+
qjs_cdp_send_message(agent_, this, json.c_str(), json.size());
|
|
57
|
+
// Nothing happens until someone runs the queue on the JS thread. While
|
|
58
|
+
// paused this task will not run at all, which is correct: the pause loop is
|
|
59
|
+
// draining the same queue.
|
|
60
|
+
executor_(
|
|
61
|
+
[agent = agent_](facebook::jsi::Runtime &) { qjs_cdp_poll(agent); });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Handed to the agent that replaces this one when the runtime is reloaded.
|
|
66
|
+
std::unique_ptr<ExportedState> getExportedState() override;
|
|
67
|
+
|
|
68
|
+
bool debuggerEnabled() const {
|
|
69
|
+
return debuggerEnabled_;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void deliver(std::string_view message) const {
|
|
73
|
+
if (!debuggerEnabled_ &&
|
|
74
|
+
message.find("\"method\":\"Debugger.") != std::string_view::npos) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
channel_(message);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private:
|
|
81
|
+
QuickJSInspectorDelegate &owner_;
|
|
82
|
+
QJSCDPAgent *agent_;
|
|
83
|
+
FrontendChannel channel_;
|
|
84
|
+
RuntimeExecutor executor_;
|
|
85
|
+
bool debuggerEnabled_;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
namespace {
|
|
89
|
+
|
|
90
|
+
/// The debugger state that outlives one runtime: whether it was enabled, and
|
|
91
|
+
/// the breakpoints the frontend believes it has set.
|
|
92
|
+
class CarriedState : public RuntimeAgentDelegate::ExportedState {
|
|
93
|
+
public:
|
|
94
|
+
explicit CarriedState(std::string json) : json_(std::move(json)) {}
|
|
95
|
+
|
|
96
|
+
const std::string &json() const {
|
|
97
|
+
return json_;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private:
|
|
101
|
+
std::string json_;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/// A captured stack, kept as the JSON the frontend will eventually be given.
|
|
105
|
+
/// Capture happens on the JS thread while the frames are alive; serialization
|
|
106
|
+
/// can happen later, and by then they are gone.
|
|
107
|
+
class CapturedStack : public StackTrace {
|
|
108
|
+
public:
|
|
109
|
+
explicit CapturedStack(std::string json) : json_(std::move(json)) {}
|
|
110
|
+
|
|
111
|
+
const std::string &json() const {
|
|
112
|
+
return json_;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private:
|
|
116
|
+
std::string json_;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
} // namespace
|
|
120
|
+
|
|
121
|
+
namespace {
|
|
122
|
+
|
|
123
|
+
const char *consoleTypeName(ConsoleAPIType type) {
|
|
124
|
+
switch (type) {
|
|
125
|
+
case ConsoleAPIType::kLog:
|
|
126
|
+
return "log";
|
|
127
|
+
case ConsoleAPIType::kDebug:
|
|
128
|
+
return "debug";
|
|
129
|
+
case ConsoleAPIType::kInfo:
|
|
130
|
+
return "info";
|
|
131
|
+
case ConsoleAPIType::kError:
|
|
132
|
+
return "error";
|
|
133
|
+
case ConsoleAPIType::kWarning:
|
|
134
|
+
return "warning";
|
|
135
|
+
case ConsoleAPIType::kDir:
|
|
136
|
+
return "dir";
|
|
137
|
+
case ConsoleAPIType::kDirXML:
|
|
138
|
+
return "dirxml";
|
|
139
|
+
case ConsoleAPIType::kTable:
|
|
140
|
+
return "table";
|
|
141
|
+
case ConsoleAPIType::kTrace:
|
|
142
|
+
return "trace";
|
|
143
|
+
case ConsoleAPIType::kStartGroup:
|
|
144
|
+
return "startGroup";
|
|
145
|
+
case ConsoleAPIType::kStartGroupCollapsed:
|
|
146
|
+
return "startGroupCollapsed";
|
|
147
|
+
case ConsoleAPIType::kEndGroup:
|
|
148
|
+
return "endGroup";
|
|
149
|
+
case ConsoleAPIType::kClear:
|
|
150
|
+
return "clear";
|
|
151
|
+
case ConsoleAPIType::kAssert:
|
|
152
|
+
return "assert";
|
|
153
|
+
case ConsoleAPIType::kTimeEnd:
|
|
154
|
+
return "timeEnd";
|
|
155
|
+
case ConsoleAPIType::kCount:
|
|
156
|
+
return "count";
|
|
157
|
+
default:
|
|
158
|
+
return "log";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
} // namespace
|
|
163
|
+
|
|
164
|
+
bool QuickJSInspectorDelegate::supportsConsole() const {
|
|
165
|
+
// True, and that is the switch: with it React Native stops using its own
|
|
166
|
+
// string-only fallback and routes console calls through here, where each
|
|
167
|
+
// argument becomes a real remote object the frontend can expand.
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void QuickJSInspectorDelegate::addConsoleMessage(
|
|
172
|
+
facebook::jsi::Runtime &runtime, ConsoleMessage message) {
|
|
173
|
+
auto &quickjs = dynamic_cast<QuickJSRuntime &>(runtime);
|
|
174
|
+
|
|
175
|
+
// toJSValue borrows -- the jsi::Value still owns the reference -- which is
|
|
176
|
+
// what the agent wants, since it only reads each argument to describe it.
|
|
177
|
+
std::vector<JSValue> args;
|
|
178
|
+
args.reserve(message.args.size());
|
|
179
|
+
for (const facebook::jsi::Value &arg : message.args) {
|
|
180
|
+
args.push_back(quickjs.toJSValue(arg));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
qjs_cdp_console_message(
|
|
184
|
+
agent_, consoleTypeName(message.type), args.data(), (int)args.size());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
|
|
188
|
+
InspectorSession::getExportedState() {
|
|
189
|
+
char *json = qjs_cdp_export_state(agent_);
|
|
190
|
+
if (!json) {
|
|
191
|
+
return std::make_unique<ExportedState>();
|
|
192
|
+
}
|
|
193
|
+
auto carried = std::make_unique<CarriedState>(std::string(json));
|
|
194
|
+
free(json);
|
|
195
|
+
return carried;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
std::unique_ptr<StackTrace> QuickJSInspectorDelegate::captureStackTrace(
|
|
199
|
+
facebook::jsi::Runtime &runtime, size_t framesToSkip) {
|
|
200
|
+
(void)runtime;
|
|
201
|
+
char *json = qjs_cdp_capture_stack_trace(agent_, (int)framesToSkip);
|
|
202
|
+
if (!json) {
|
|
203
|
+
return StackTrace::empty();
|
|
204
|
+
}
|
|
205
|
+
auto captured = std::make_unique<CapturedStack>(std::string(json));
|
|
206
|
+
free(json);
|
|
207
|
+
return captured;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
std::optional<folly::dynamic> QuickJSInspectorDelegate::serializeStackTrace(
|
|
211
|
+
const StackTrace &stackTrace) {
|
|
212
|
+
const auto *captured = dynamic_cast<const CapturedStack *>(&stackTrace);
|
|
213
|
+
if (!captured) {
|
|
214
|
+
return std::nullopt;
|
|
215
|
+
}
|
|
216
|
+
return folly::parseJson(captured->json());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
QuickJSInspectorDelegate::QuickJSInspectorDelegate(JSContext *ctx)
|
|
220
|
+
: FallbackRuntimeTargetDelegate("QuickJS") {
|
|
221
|
+
agent_ = qjs_cdp_new(ctx, 1, &QuickJSInspectorDelegate::deliver, this);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
QuickJSInspectorDelegate::~QuickJSInspectorDelegate() {
|
|
225
|
+
qjs_cdp_free(agent_);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
void QuickJSInspectorDelegate::deliver(
|
|
229
|
+
void *opaque, void *session, const char *json, size_t len) {
|
|
230
|
+
auto *self = static_cast<QuickJSInspectorDelegate *>(opaque);
|
|
231
|
+
const std::string_view message(json, len);
|
|
232
|
+
std::lock_guard<std::mutex> guard(self->lock_);
|
|
233
|
+
|
|
234
|
+
// A reply belongs to the session that asked and to no other; an event has no
|
|
235
|
+
// session and belongs to all of them.
|
|
236
|
+
for (auto *live : self->sessions_) {
|
|
237
|
+
if (session == nullptr || live == session) {
|
|
238
|
+
live->deliver(message);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
std::unique_ptr<RuntimeAgentDelegate>
|
|
244
|
+
QuickJSInspectorDelegate::createAgentDelegate(
|
|
245
|
+
FrontendChannel channel, SessionState &sessionState,
|
|
246
|
+
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
|
|
247
|
+
previouslyExportedState,
|
|
248
|
+
const ExecutionContextDescription &executionContextDescription,
|
|
249
|
+
RuntimeExecutor runtimeExecutor) {
|
|
250
|
+
(void)sessionState;
|
|
251
|
+
|
|
252
|
+
// A reload replaces the runtime without telling the frontend, so the
|
|
253
|
+
// breakpoints it set have to be carried over to the agent that takes over.
|
|
254
|
+
if (auto *carried =
|
|
255
|
+
dynamic_cast<CarriedState *>(previouslyExportedState.get())) {
|
|
256
|
+
qjs_cdp_import_state(agent_, carried->json().c_str());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// React Native assigns the execution context id, and the frontend addresses
|
|
260
|
+
// requests to it by number.
|
|
261
|
+
qjs_cdp_set_execution_context(agent_, executionContextDescription.id);
|
|
262
|
+
|
|
263
|
+
// A reload replaces the agent but not the frontend, so a session that had
|
|
264
|
+
// the debugger enabled before still has it enabled after.
|
|
265
|
+
auto session = std::make_unique<InspectorSession>(
|
|
266
|
+
*this, agent_, std::move(channel), std::move(runtimeExecutor),
|
|
267
|
+
qjs_cdp_debugger_enabled(agent_));
|
|
268
|
+
{
|
|
269
|
+
std::lock_guard<std::mutex> guard(lock_);
|
|
270
|
+
sessions_.push_back(session.get());
|
|
271
|
+
}
|
|
272
|
+
return session;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void QuickJSInspectorDelegate::forget(InspectorSession *session) {
|
|
276
|
+
{
|
|
277
|
+
std::lock_guard<std::mutex> guard(lock_);
|
|
278
|
+
sessions_.erase(
|
|
279
|
+
std::remove(sessions_.begin(), sessions_.end(), session),
|
|
280
|
+
sessions_.end());
|
|
281
|
+
}
|
|
282
|
+
debuggerEnabledChanged();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// The debugger is on for the runtime when it is on for anybody. One frontend
|
|
286
|
+
/// turning it off must not stop the statement traps another is relying on.
|
|
287
|
+
void QuickJSInspectorDelegate::debuggerEnabledChanged() {
|
|
288
|
+
bool any = false;
|
|
289
|
+
{
|
|
290
|
+
std::lock_guard<std::mutex> guard(lock_);
|
|
291
|
+
for (const auto *session : sessions_) {
|
|
292
|
+
any = any || session->debuggerEnabled();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
qjs_cdp_set_debugger_enabled(agent_, any);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void QuickJSInspectorDelegate::scriptLoaded(
|
|
299
|
+
const std::string &url, const std::string &source) {
|
|
300
|
+
qjs_cdp_script_loaded(agent_, url.c_str(), source.c_str());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
QuickJSInspectorRuntime::QuickJSInspectorRuntime(
|
|
304
|
+
std::unique_ptr<facebook::jsi::Runtime> runtime)
|
|
305
|
+
: runtime_(std::move(runtime)) {
|
|
306
|
+
auto &quickjs = dynamic_cast<QuickJSRuntime &>(*runtime_);
|
|
307
|
+
delegate_ = std::make_unique<QuickJSInspectorDelegate>(quickjs.context());
|
|
308
|
+
quickjs.setScriptEvaluatedHook(
|
|
309
|
+
[this](const std::string &url, const std::string &source) {
|
|
310
|
+
delegate_->scriptLoaded(url, source);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
facebook::jsi::Runtime &QuickJSInspectorRuntime::getRuntime() noexcept {
|
|
315
|
+
return *runtime_;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
RuntimeTargetDelegate &QuickJSInspectorRuntime::getRuntimeTargetDelegate() {
|
|
319
|
+
return *delegate_;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
} // namespace qjs
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
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
|
+
* Plugs the CDP layer into React Native's inspector.
|
|
8
|
+
*
|
|
9
|
+
* React Native owns the WebSocket, the page target and the session; all this
|
|
10
|
+
* has to do is hand CDP messages to the agent and hand its replies back. The
|
|
11
|
+
* protocol work is all in modules/cdp, which knows nothing about any of this.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#pragma once
|
|
15
|
+
|
|
16
|
+
#include <jsinspector-modern/ReactCdp.h>
|
|
17
|
+
#include <react/runtime/JSRuntimeFactory.h>
|
|
18
|
+
|
|
19
|
+
#include <memory>
|
|
20
|
+
#include <mutex>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <vector>
|
|
23
|
+
|
|
24
|
+
extern "C" {
|
|
25
|
+
#include "quickjs-cdp.h"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
namespace qjs {
|
|
29
|
+
|
|
30
|
+
class InspectorSession;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Owns the agent for one runtime.
|
|
34
|
+
*
|
|
35
|
+
* Constructed with the runtime, not when a frontend connects: the engine emits
|
|
36
|
+
* statement traps at parse time, so a runtime that was not instrumented before
|
|
37
|
+
* its first script ran cannot be debugged afterwards.
|
|
38
|
+
*/
|
|
39
|
+
class QuickJSInspectorDelegate : public facebook::react::jsinspector_modern::
|
|
40
|
+
FallbackRuntimeTargetDelegate {
|
|
41
|
+
public:
|
|
42
|
+
explicit QuickJSInspectorDelegate(JSContext *ctx);
|
|
43
|
+
~QuickJSInspectorDelegate() override;
|
|
44
|
+
|
|
45
|
+
std::unique_ptr<facebook::react::jsinspector_modern::RuntimeAgentDelegate>
|
|
46
|
+
createAgentDelegate(
|
|
47
|
+
facebook::react::jsinspector_modern::FrontendChannel channel,
|
|
48
|
+
facebook::react::jsinspector_modern::SessionState &sessionState,
|
|
49
|
+
std::unique_ptr<facebook::react::jsinspector_modern::
|
|
50
|
+
RuntimeAgentDelegate::ExportedState>
|
|
51
|
+
previouslyExportedState,
|
|
52
|
+
const facebook::react::jsinspector_modern::ExecutionContextDescription
|
|
53
|
+
&executionContextDescription,
|
|
54
|
+
facebook::react::RuntimeExecutor runtimeExecutor) override;
|
|
55
|
+
|
|
56
|
+
void addConsoleMessage(
|
|
57
|
+
facebook::jsi::Runtime &runtime,
|
|
58
|
+
facebook::react::jsinspector_modern::ConsoleMessage message) override;
|
|
59
|
+
|
|
60
|
+
bool supportsConsole() const override;
|
|
61
|
+
|
|
62
|
+
std::unique_ptr<facebook::react::jsinspector_modern::StackTrace>
|
|
63
|
+
captureStackTrace(
|
|
64
|
+
facebook::jsi::Runtime &runtime, size_t framesToSkip) override;
|
|
65
|
+
|
|
66
|
+
std::optional<folly::dynamic> serializeStackTrace(
|
|
67
|
+
const facebook::react::jsinspector_modern::StackTrace &stackTrace)
|
|
68
|
+
override;
|
|
69
|
+
|
|
70
|
+
/// Tells the frontend a script exists, and resolves breakpoints set against
|
|
71
|
+
/// its URL. JS thread only.
|
|
72
|
+
void scriptLoaded(const std::string &url, const std::string &source);
|
|
73
|
+
|
|
74
|
+
/// Called by a session as it goes away, so that a reply can never be routed
|
|
75
|
+
/// to a frontend that has disconnected.
|
|
76
|
+
void forget(InspectorSession *session);
|
|
77
|
+
|
|
78
|
+
/// Recomputes whether any session still wants the debugger, and tells the
|
|
79
|
+
/// agent. Called whenever a session enables, disables, or goes away.
|
|
80
|
+
void debuggerEnabledChanged();
|
|
81
|
+
|
|
82
|
+
private:
|
|
83
|
+
static void deliver(
|
|
84
|
+
void *opaque, void *session, const char *json, size_t len);
|
|
85
|
+
|
|
86
|
+
QJSCDPAgent *agent_;
|
|
87
|
+
std::mutex lock_;
|
|
88
|
+
std::vector<InspectorSession *> sessions_; // not owned
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The JSRuntime React Native asks for, with the debugger attached.
|
|
93
|
+
*
|
|
94
|
+
* The delegate is built here, immediately after the runtime and before any
|
|
95
|
+
* script has been evaluated, rather than when a frontend connects. React
|
|
96
|
+
* Native calls getRuntimeTargetDelegate() lazily, long after the bundle has
|
|
97
|
+
* run, and by then it is far too late to instrument anything.
|
|
98
|
+
*/
|
|
99
|
+
class QuickJSInspectorRuntime : public facebook::react::JSRuntime {
|
|
100
|
+
public:
|
|
101
|
+
explicit QuickJSInspectorRuntime(
|
|
102
|
+
std::unique_ptr<facebook::jsi::Runtime> runtime);
|
|
103
|
+
|
|
104
|
+
facebook::jsi::Runtime &getRuntime() noexcept override;
|
|
105
|
+
|
|
106
|
+
facebook::react::jsinspector_modern::RuntimeTargetDelegate &
|
|
107
|
+
getRuntimeTargetDelegate() override;
|
|
108
|
+
|
|
109
|
+
private:
|
|
110
|
+
std::unique_ptr<facebook::jsi::Runtime> runtime_;
|
|
111
|
+
std::unique_ptr<QuickJSInspectorDelegate> delegate_;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
} // namespace qjs
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
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
|
+
namespace facebook::hermes::debugger {
|
|
11
|
+
|
|
12
|
+
// Named so that IHermes::getDebugger has a complete return type. Hermes's
|
|
13
|
+
// debugger is expressed in bytecode offsets and has no QuickJS meaning;
|
|
14
|
+
// debugging here goes through jsinspector-modern and modules/cdp instead.
|
|
15
|
+
class Debugger {
|
|
16
|
+
public:
|
|
17
|
+
Debugger() = default;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
} // namespace facebook::hermes::debugger
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Ammar Ahmed.
|
|
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 <cstdint>
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <string>
|
|
13
|
+
|
|
14
|
+
namespace hermes::vm {
|
|
15
|
+
|
|
16
|
+
/// Named so that code passing a crash manager compiles. QuickJS installs no
|
|
17
|
+
/// crash handlers, so a registered callback is never invoked.
|
|
18
|
+
class CrashManager {
|
|
19
|
+
public:
|
|
20
|
+
using CallbackKey = int;
|
|
21
|
+
using CallbackFunc = void (*)(int fd);
|
|
22
|
+
|
|
23
|
+
/// Out of line, and so this class's key function: it is what places the
|
|
24
|
+
/// vtable in this library. react-native-worklets references the base class
|
|
25
|
+
/// vtable directly, not only NopCrashManager's, and with every virtual
|
|
26
|
+
/// defined inline no translation unit here emits it -- the shim then loads
|
|
27
|
+
/// on its own but fails dlopen the moment worklets links against it.
|
|
28
|
+
virtual ~CrashManager();
|
|
29
|
+
|
|
30
|
+
virtual void registerMemory(void *, size_t) {}
|
|
31
|
+
virtual void unregisterMemory(void *) {}
|
|
32
|
+
virtual void setCustomData(const char *, const char *) {}
|
|
33
|
+
virtual void removeCustomData(const char *) {}
|
|
34
|
+
virtual CallbackKey registerCallback(CallbackFunc) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
virtual void unregisterCallback(CallbackKey) {}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/// react-native-worklets constructs one of these, so this class needs its own
|
|
41
|
+
/// vtable here too, by the same rule.
|
|
42
|
+
class NopCrashManager : public CrashManager {
|
|
43
|
+
public:
|
|
44
|
+
~NopCrashManager() override;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace hermes::vm
|