basalt-core 0.0.0-rc5 → 0.1.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/dist/cli/basalt.d.ts.map +1 -1
- package/dist/cli/basalt.js +18 -1
- package/dist/cli/basalt.js.map +1 -1
- package/dist/cli/bundle-cli.js +0 -0
- package/dist/cli/packageApp.d.ts.map +1 -1
- package/dist/cli/packageApp.js +7 -1
- package/dist/cli/packageApp.js.map +1 -1
- package/dist/metro-config.d.ts.map +1 -1
- package/dist/metro-config.js +53 -5
- package/dist/metro-config.js.map +1 -1
- package/dist/src/KeyHandler.d.ts +1 -1
- package/dist/src/KeyHandler.d.ts.map +1 -1
- package/dist/src/KeyHandler.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/overrides/Alert.d.ts +1 -1
- package/dist/src/overrides/Alert.js +1 -1
- package/dist/src/overrides/BaseViewConfig.d.ts +10 -3
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -1
- package/dist/src/overrides/BaseViewConfig.js +21 -3
- package/dist/src/overrides/BaseViewConfig.js.map +1 -1
- package/dist/src/overrides/Button.d.ts +81 -0
- package/dist/src/overrides/Button.d.ts.map +1 -0
- package/dist/src/overrides/Button.js +109 -0
- package/dist/src/overrides/Button.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +25 -2
- package/dist/src/overrides/ScrollView.d.ts.map +1 -1
- package/dist/src/overrides/ScrollView.js +64 -7
- package/dist/src/overrides/ScrollView.js.map +1 -1
- package/dist/src/overrides/Settings.d.ts +81 -0
- package/dist/src/overrides/Settings.d.ts.map +1 -0
- package/dist/src/overrides/Settings.js +120 -0
- package/dist/src/overrides/Settings.js.map +1 -0
- package/dist/src/overrides/processDecelerationRate.d.ts +20 -0
- package/dist/src/overrides/processDecelerationRate.d.ts.map +1 -0
- package/dist/src/overrides/processDecelerationRate.js +31 -0
- package/dist/src/overrides/processDecelerationRate.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +57 -6
- package/dist/src/useContextMenu.d.ts.map +1 -1
- package/dist/src/useContextMenu.js +34 -1
- package/dist/src/useContextMenu.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/native/CMakeLists.txt +51 -0
- package/native/core/ActionSheet.cpp +75 -0
- package/native/core/ActionSheet.h +90 -0
- package/native/core/AppStateHost.cpp +76 -0
- package/native/core/AppStateHost.h +68 -0
- package/native/core/BackgroundLayers.h +282 -0
- package/native/core/BlendModes.h +67 -0
- package/native/core/CoreModules.cpp +380 -11
- package/native/core/CoreModules.h +140 -4
- package/native/core/CrashHandler.cpp +246 -0
- package/native/core/CrashHandler.h +65 -0
- package/native/core/CursorNames.h +110 -0
- package/native/core/DevMenu.cpp +6 -5
- package/native/core/DeviceMetrics.cpp +87 -0
- package/native/core/DeviceMetrics.h +80 -0
- package/native/core/Filters.h +336 -0
- package/native/core/FontFitting.h +183 -0
- package/native/core/FontScaling.h +153 -0
- package/native/core/FontVariants.h +148 -0
- package/native/core/Gradients.h +523 -0
- package/native/core/ImageAnimation.h +109 -0
- package/native/core/ImageBytes.cpp +28 -1
- package/native/core/ImageBytes.h +17 -1
- package/native/core/ImageProgress.h +85 -0
- package/native/core/LabelRegistry.h +189 -0
- package/native/core/LegacyShadow.h +90 -0
- package/native/core/LiveRegions.h +114 -0
- package/native/core/MenuModule.cpp +35 -4
- package/native/core/MountingWalk.h +125 -2
- package/native/core/PlatformServices.h +187 -3
- package/native/core/SafeAreaComponent.cpp +115 -0
- package/native/core/SafeAreaComponent.h +105 -0
- package/native/core/ScreensComponent.cpp +201 -0
- package/native/core/ScreensComponent.h +216 -0
- package/native/core/ScrollBounds.cpp +8 -0
- package/native/core/ScrollBounds.h +14 -0
- package/native/core/ScrollIndicator.h +67 -0
- package/native/core/ScrollMomentum.cpp +24 -0
- package/native/core/ScrollMomentum.h +14 -0
- package/native/core/ScrollSnap.cpp +121 -12
- package/native/core/ScrollSnap.h +45 -0
- package/native/core/ScrollVisiblePosition.cpp +68 -0
- package/native/core/ScrollVisiblePosition.h +101 -0
- package/native/core/SettingsStore.cpp +199 -0
- package/native/core/SettingsStore.h +127 -0
- package/native/core/ShadowShape.h +38 -0
- package/native/core/SystemLocale.cpp +103 -0
- package/native/core/SystemLocale.h +86 -0
- package/native/core/TestDialog.cpp +45 -6
- package/native/core/TestDialog.h +15 -0
- package/native/core/TextAlignments.h +240 -0
- package/native/core/TextChecking.h +133 -0
- package/native/core/TextColors.h +83 -0
- package/native/core/TextDecorations.h +101 -0
- package/native/core/TextDirection.cpp +242 -0
- package/native/core/TextDirection.h +88 -0
- package/native/core/TextHighlight.h +30 -0
- package/native/core/TextSelection.cpp +97 -0
- package/native/core/TextSelection.h +161 -0
- package/native/core/TextShadows.h +96 -0
- package/native/core/TextVerticalAlign.h +33 -0
- package/native/core/UIManagerAccess.cpp +36 -0
- package/native/core/UIManagerAccess.h +28 -0
- package/native/core/WindowBoundsCache.cpp +58 -1
- package/native/core/WindowControl.h +24 -0
- package/native/core/WritingDirections.h +52 -0
- package/native/core/portability_probe.cpp +7 -0
- package/native/tests/RenamedFontFile.h +161 -0
- package/native/tests/TestHarness.cpp +18 -0
- package/native/tests/test_action_sheet.cpp +157 -0
- package/native/tests/test_app_state.cpp +60 -0
- package/native/tests/test_background_layers.cpp +281 -0
- package/native/tests/test_blend_modes.cpp +79 -0
- package/native/tests/test_blobs.cpp +1 -1
- package/native/tests/test_device_metrics.cpp +126 -0
- package/native/tests/test_filters.cpp +271 -0
- package/native/tests/test_font_fitting.cpp +172 -0
- package/native/tests/test_font_scaling.cpp +127 -0
- package/native/tests/test_font_variants.cpp +84 -0
- package/native/tests/test_gradients.cpp +390 -0
- package/native/tests/test_image_animation.cpp +105 -0
- package/native/tests/test_image_progress.cpp +94 -0
- package/native/tests/test_label_registry.cpp +262 -0
- package/native/tests/test_legacy_shadow.cpp +165 -0
- package/native/tests/test_live_regions.cpp +108 -0
- package/native/tests/test_menu_entries.cpp +192 -0
- package/native/tests/test_momentum.cpp +45 -0
- package/native/tests/test_scroll_bounds.cpp +42 -0
- package/native/tests/test_scroll_indicator.cpp +61 -0
- package/native/tests/test_scroll_snap.cpp +129 -0
- package/native/tests/test_scroll_visible_position.cpp +121 -0
- package/native/tests/test_settings_store.cpp +257 -0
- package/native/tests/test_shadow_shape.cpp +47 -0
- package/native/tests/test_system_locale.cpp +36 -0
- package/native/tests/test_text_alignment.cpp +299 -0
- package/native/tests/test_text_checking.cpp +31 -0
- package/native/tests/test_text_colors.cpp +92 -0
- package/native/tests/test_text_decorations.cpp +100 -0
- package/native/tests/test_text_selection.cpp +168 -0
- package/native/tests/test_text_shadows.cpp +132 -0
- package/package.json +4 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#include "CrashHandler.h"
|
|
2
|
+
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
|
|
7
|
+
#if defined(_WIN32)
|
|
8
|
+
#include <windows.h>
|
|
9
|
+
#else
|
|
10
|
+
#include <csignal>
|
|
11
|
+
#include <execinfo.h>
|
|
12
|
+
#include <pthread.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
namespace basalt {
|
|
17
|
+
|
|
18
|
+
namespace {
|
|
19
|
+
|
|
20
|
+
#if !defined(_WIN32)
|
|
21
|
+
|
|
22
|
+
constexpr int kFatalSignals[] = {SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT};
|
|
23
|
+
constexpr std::size_t kFatalSignalCount = sizeof(kFatalSignals) / sizeof(kFatalSignals[0]);
|
|
24
|
+
|
|
25
|
+
// What was installed before, so each handler can put it back and re-raise. An
|
|
26
|
+
// empty slot is one this never touched.
|
|
27
|
+
struct sigaction gPrevious[kFatalSignalCount];
|
|
28
|
+
|
|
29
|
+
// The thread that installed the handler, which is the main thread: the handler
|
|
30
|
+
// runs on whichever thread took the signal, and for a hang that is the first
|
|
31
|
+
// thing a reader needs to know. A backtrace that cannot be told apart from the
|
|
32
|
+
// JS thread's is half a diagnosis, which cost a round trip here once already.
|
|
33
|
+
// Recorded rather than derived, because `pthread_main_np` is not portable and
|
|
34
|
+
// `gettid`-style checks say nothing on a host whose main thread is not tid 1.
|
|
35
|
+
pthread_t gMainThread = {};
|
|
36
|
+
bool gMainThreadKnown = false;
|
|
37
|
+
|
|
38
|
+
int slotFor(int signalNumber) {
|
|
39
|
+
for (std::size_t i = 0; i < kFatalSignalCount; i++) {
|
|
40
|
+
if (kFatalSignals[i] == signalNumber) {
|
|
41
|
+
return static_cast<int>(i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Everything below runs in a signal handler, so everything below is
|
|
48
|
+
// async-signal-safe. No allocation, no locks, no formatting.
|
|
49
|
+
|
|
50
|
+
void writeText(const char *text) {
|
|
51
|
+
if (text == nullptr) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
std::size_t length = 0;
|
|
55
|
+
while (text[length] != '\0') {
|
|
56
|
+
length++;
|
|
57
|
+
}
|
|
58
|
+
// The result is deliberately ignored: there is nothing useful to do about a
|
|
59
|
+
// failed write from here, and a partial one still says more than nothing.
|
|
60
|
+
const ssize_t written = ::write(STDERR_FILENO, text, length);
|
|
61
|
+
(void)written;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void writeHex(std::uintptr_t value) {
|
|
65
|
+
char digits[] = "0123456789abcdef";
|
|
66
|
+
// 16 nibbles, a "0x", and the terminator.
|
|
67
|
+
char buffer[19];
|
|
68
|
+
buffer[0] = '0';
|
|
69
|
+
buffer[1] = 'x';
|
|
70
|
+
std::size_t at = 2;
|
|
71
|
+
bool leading = true;
|
|
72
|
+
for (int shift = 60; shift >= 0; shift -= 4) {
|
|
73
|
+
const unsigned nibble = static_cast<unsigned>((value >> shift) & 0xF);
|
|
74
|
+
if (nibble == 0 && leading && shift != 0) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
leading = false;
|
|
78
|
+
buffer[at++] = digits[nibble];
|
|
79
|
+
}
|
|
80
|
+
buffer[at] = '\0';
|
|
81
|
+
writeText(buffer);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const char *nameFor(int signalNumber) {
|
|
85
|
+
switch (signalNumber) {
|
|
86
|
+
case SIGSEGV:
|
|
87
|
+
return "SIGSEGV";
|
|
88
|
+
case SIGBUS:
|
|
89
|
+
return "SIGBUS";
|
|
90
|
+
case SIGILL:
|
|
91
|
+
return "SIGILL";
|
|
92
|
+
case SIGFPE:
|
|
93
|
+
return "SIGFPE";
|
|
94
|
+
case SIGABRT:
|
|
95
|
+
return "SIGABRT";
|
|
96
|
+
default:
|
|
97
|
+
return "signal";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void onFatalSignal(int signalNumber, siginfo_t *info, void * /*context*/) {
|
|
102
|
+
writeText("\n*** basalt: ");
|
|
103
|
+
writeText(nameFor(signalNumber));
|
|
104
|
+
if (info != nullptr) {
|
|
105
|
+
writeText(" at ");
|
|
106
|
+
writeHex(reinterpret_cast<std::uintptr_t>(info->si_addr));
|
|
107
|
+
}
|
|
108
|
+
// Said plainly, because the whole point is telling the loop apart from JS.
|
|
109
|
+
// `pthread_equal` on two stored values is a comparison and nothing more, so
|
|
110
|
+
// it is safe enough here even though it is not on the promised list.
|
|
111
|
+
if (gMainThreadKnown) {
|
|
112
|
+
writeText(::pthread_equal(::pthread_self(), gMainThread) != 0
|
|
113
|
+
? ", the main thread"
|
|
114
|
+
: ", NOT the main thread");
|
|
115
|
+
}
|
|
116
|
+
writeText(", thread ");
|
|
117
|
+
// The id rather than the name: `pthread_getname_np` takes no lock on either
|
|
118
|
+
// platform but is not promised to be safe here, and the id is what the glog
|
|
119
|
+
// lines around this already print.
|
|
120
|
+
writeHex(static_cast<std::uintptr_t>(
|
|
121
|
+
reinterpret_cast<std::uintptr_t>(pthread_self())));
|
|
122
|
+
writeText(" ***\n");
|
|
123
|
+
|
|
124
|
+
// 64 frames: deep enough for a mounting walk inside a dispatch block inside a
|
|
125
|
+
// run loop, which is the stack this was written for.
|
|
126
|
+
void *frames[64];
|
|
127
|
+
const int count = ::backtrace(frames, 64);
|
|
128
|
+
// Writes straight to the descriptor and allocates nothing, unlike
|
|
129
|
+
// `backtrace_symbols`, which is why it is this one.
|
|
130
|
+
::backtrace_symbols_fd(frames, count, STDERR_FILENO);
|
|
131
|
+
writeText("\n");
|
|
132
|
+
|
|
133
|
+
// Back to whatever was there, then re-raise. The exit status stays the signal,
|
|
134
|
+
// which is what the harness reports, and a handler the runtime installed for
|
|
135
|
+
// its own purposes still gets its turn.
|
|
136
|
+
const int slot = slotFor(signalNumber);
|
|
137
|
+
if (slot >= 0) {
|
|
138
|
+
::sigaction(signalNumber, &gPrevious[slot], nullptr);
|
|
139
|
+
} else {
|
|
140
|
+
::signal(signalNumber, SIG_DFL);
|
|
141
|
+
}
|
|
142
|
+
::raise(signalNumber);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#else
|
|
146
|
+
|
|
147
|
+
LONG WINAPI onUnhandledException(EXCEPTION_POINTERS *info) {
|
|
148
|
+
// Not a signal handler, so this is allowed more than its POSIX counterpart.
|
|
149
|
+
// It stays just as narrow anyway: the process is already broken, and a handler
|
|
150
|
+
// that needs the heap to say so is a handler that says nothing when the heap
|
|
151
|
+
// is what broke.
|
|
152
|
+
char line[128];
|
|
153
|
+
const DWORD code =
|
|
154
|
+
info != nullptr && info->ExceptionRecord != nullptr
|
|
155
|
+
? info->ExceptionRecord->ExceptionCode
|
|
156
|
+
: 0;
|
|
157
|
+
const void *at = info != nullptr && info->ExceptionRecord != nullptr
|
|
158
|
+
? info->ExceptionRecord->ExceptionAddress
|
|
159
|
+
: nullptr;
|
|
160
|
+
|
|
161
|
+
int length = wsprintfA(line,
|
|
162
|
+
"\n*** basalt: exception 0x%08lx at %p, thread %lu ***\n",
|
|
163
|
+
static_cast<unsigned long>(code),
|
|
164
|
+
at,
|
|
165
|
+
GetCurrentThreadId());
|
|
166
|
+
DWORD written = 0;
|
|
167
|
+
WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
|
|
168
|
+
|
|
169
|
+
// No symbols: a release build ships no .pdb, so dbghelp would resolve nothing
|
|
170
|
+
// and linking it would buy an address it already has. The module base is here
|
|
171
|
+
// so the frames below can be turned into offsets against the binary.
|
|
172
|
+
length = wsprintfA(line, "module %p\n", static_cast<void *>(GetModuleHandleW(nullptr)));
|
|
173
|
+
WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
|
|
174
|
+
|
|
175
|
+
void *frames[64];
|
|
176
|
+
const USHORT count = CaptureStackBackTrace(0, 64, frames, nullptr);
|
|
177
|
+
for (USHORT i = 0; i < count; i++) {
|
|
178
|
+
length = wsprintfA(line, "%2u %p\n", static_cast<unsigned>(i), frames[i]);
|
|
179
|
+
WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// On to whatever Windows would have done, so the exit status and any crash
|
|
183
|
+
// reporting behave as before.
|
|
184
|
+
return EXCEPTION_CONTINUE_SEARCH;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#endif
|
|
188
|
+
|
|
189
|
+
} // namespace
|
|
190
|
+
|
|
191
|
+
namespace {
|
|
192
|
+
|
|
193
|
+
// BASALT_TEST_CRASH, which exists so that the handler can be tested at all.
|
|
194
|
+
//
|
|
195
|
+
// A handler is only ever exercised on the worst day, so one that was never run
|
|
196
|
+
// is a guess. This makes the worst day reproducible: the scenario in
|
|
197
|
+
// scripts/integration_test.py sets it and asserts the marker, a frame list and an
|
|
198
|
+
// exit status that is still the signal.
|
|
199
|
+
//
|
|
200
|
+
// `raise` rather than writing through a null pointer: a deliberate null
|
|
201
|
+
// dereference is undefined behaviour and a compiler is within its rights to
|
|
202
|
+
// delete it, which would make the test pass by not crashing.
|
|
203
|
+
void crashIfAsked() {
|
|
204
|
+
const char *value = std::getenv("BASALT_TEST_CRASH");
|
|
205
|
+
if (value == nullptr || *value == '\0') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
#if defined(_WIN32)
|
|
209
|
+
// An access violation, since Windows has no raise(SIGSEGV) worth the name: the
|
|
210
|
+
// unhandled-exception filter is what is being tested, and only a real exception
|
|
211
|
+
// reaches it.
|
|
212
|
+
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nullptr);
|
|
213
|
+
#else
|
|
214
|
+
::raise(SIGSEGV);
|
|
215
|
+
#endif
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
} // namespace
|
|
219
|
+
|
|
220
|
+
void installCrashHandler() {
|
|
221
|
+
#if defined(_WIN32)
|
|
222
|
+
SetUnhandledExceptionFilter(onUnhandledException);
|
|
223
|
+
crashIfAsked();
|
|
224
|
+
#else
|
|
225
|
+
// Once, while allocating is still allowed: glibc's first `backtrace` dlopens
|
|
226
|
+
// libgcc, and a handler is not the place to find that out.
|
|
227
|
+
void *warm[1];
|
|
228
|
+
(void)::backtrace(warm, 1);
|
|
229
|
+
|
|
230
|
+
// Called from main() on every host, so this is the main thread.
|
|
231
|
+
gMainThread = ::pthread_self();
|
|
232
|
+
gMainThreadKnown = true;
|
|
233
|
+
|
|
234
|
+
struct sigaction action = {};
|
|
235
|
+
action.sa_sigaction = onFatalSignal;
|
|
236
|
+
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
237
|
+
sigemptyset(&action.sa_mask);
|
|
238
|
+
|
|
239
|
+
for (std::size_t i = 0; i < kFatalSignalCount; i++) {
|
|
240
|
+
::sigaction(kFatalSignals[i], &action, &gPrevious[i]);
|
|
241
|
+
}
|
|
242
|
+
crashIfAsked();
|
|
243
|
+
#endif
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
} // namespace basalt
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// What the host says when it dies.
|
|
2
|
+
//
|
|
3
|
+
// Written because it had nothing to say. On 2026-10-05 a reload teardown took
|
|
4
|
+
// SIGSEGV on CI's Mac, twice, and three attempts produced no address, no stack
|
|
5
|
+
// and no thread -- only the harness noticing the process was gone and reporting
|
|
6
|
+
// "host exited -11". The bug was found by reading `ReactHost` instead, and the
|
|
7
|
+
// next one will not be as guessable.
|
|
8
|
+
//
|
|
9
|
+
// Deliberately not glog's `InstallFailureSignalHandler`. That would be one line,
|
|
10
|
+
// and its signal list is fixed and includes SIGTERM -- which is how the harness
|
|
11
|
+
// ends a host that is working. A stack trace and a changed exit status there
|
|
12
|
+
// would break the scenarios that read both. See docs/ci-performance.md on
|
|
13
|
+
// BASALT_TEST_QUIT_FILE for what SIGTERM is load-bearing for.
|
|
14
|
+
//
|
|
15
|
+
// ## What it prints
|
|
16
|
+
//
|
|
17
|
+
// To stderr, which is where the harness is already looking:
|
|
18
|
+
//
|
|
19
|
+
// *** basalt: SIGSEGV at 0x0, thread 0x16dd57000 ***
|
|
20
|
+
// 0 basalt_appkit 0x0000000102a4c1b8 _ZN6basalt19AppKitMountingM...
|
|
21
|
+
// 1 basalt_appkit 0x0000000102a4b9e4 ...
|
|
22
|
+
//
|
|
23
|
+
// The names are mangled, and stay that way: demangling means
|
|
24
|
+
// `abi::__cxa_demangle`, which allocates, and a handler that needs the heap says
|
|
25
|
+
// nothing exactly when the heap is what broke. Pipe the log through `c++filt`.
|
|
26
|
+
//
|
|
27
|
+
// On Windows there are no symbols, for want of a .pdb in a release build, so it
|
|
28
|
+
// prints the module base and the return addresses: subtract the one from the
|
|
29
|
+
// others and the offsets resolve against the binary. Measured on CI, which is
|
|
30
|
+
// the only place this can be:
|
|
31
|
+
//
|
|
32
|
+
// *** basalt: exception 0xc0000005 at 00007FF92942EFDC, thread 2776 ***
|
|
33
|
+
// module 00007FF621B00000
|
|
34
|
+
// 0 00007FF621EE0094
|
|
35
|
+
// 1 00007FF9294A05BD
|
|
36
|
+
//
|
|
37
|
+
// Note the shape: `%p` there is upper case and carries no `0x`, unlike
|
|
38
|
+
// `backtrace_symbols_fd` above. Anything reading this output has to take both.
|
|
39
|
+
//
|
|
40
|
+
// ## What it may call
|
|
41
|
+
//
|
|
42
|
+
// Almost nothing. A signal handler may only use async-signal-safe functions, so
|
|
43
|
+
// there is no `LOG()`, no `std::string`, no formatting and no allocation here:
|
|
44
|
+
// glog takes a lock to write a line and a handler that waits for a lock held by
|
|
45
|
+
// the thread it interrupted never returns. `write`, `backtrace_symbols_fd`,
|
|
46
|
+
// `raise` and integer arithmetic are the whole vocabulary.
|
|
47
|
+
//
|
|
48
|
+
// `backtrace` is the exception: on glibc its first call dlopens libgcc, which
|
|
49
|
+
// allocates, so `installCrashHandler` calls it once while that is still safe.
|
|
50
|
+
//
|
|
51
|
+
// @format
|
|
52
|
+
#pragma once
|
|
53
|
+
|
|
54
|
+
namespace basalt {
|
|
55
|
+
|
|
56
|
+
// Installs handlers for the signals that mean the process is broken: SIGSEGV,
|
|
57
|
+
// SIGBUS, SIGILL, SIGFPE and SIGABRT. Not SIGTERM and not SIGINT, which mean
|
|
58
|
+
// somebody asked it to stop.
|
|
59
|
+
//
|
|
60
|
+
// Each handler prints, restores whatever was there before, and re-raises, so the
|
|
61
|
+
// exit status is still the signal and anything the runtime installed for its own
|
|
62
|
+
// purposes still runs. Call once, early, after logging is up.
|
|
63
|
+
void installCrashHandler();
|
|
64
|
+
|
|
65
|
+
} // namespace basalt
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// What a React Native `cursor` value is called, in CSS's own words.
|
|
2
|
+
//
|
|
3
|
+
// The prop is a desktop prop: a hand over a button, an I-beam over text, a
|
|
4
|
+
// resize arrow over a divider. Every host ignored it, and
|
|
5
|
+
// backlog/desktop-capabilities.md said the opposite -- its entry on cursor
|
|
6
|
+
// control described itself as the work left "beyond what the `cursor` style
|
|
7
|
+
// property covers", which nothing covered.
|
|
8
|
+
//
|
|
9
|
+
// ## Why a name rather than a per-host enum
|
|
10
|
+
//
|
|
11
|
+
// React Native's `Cursor` is CSS's keyword list, and so is GDK's: GTK takes the
|
|
12
|
+
// keyword straight through `gtk_widget_set_cursor_from_name`. AppKit has its own
|
|
13
|
+
// small set of NSCursors and maps the names onto it, losing the ones macOS has no
|
|
14
|
+
// cursor for. Keeping the keyword as the thing that crosses the seam means there
|
|
15
|
+
// is one place that says what `cursor: 'grab'` is called and each view layer
|
|
16
|
+
// answers for its own platform, rather than two enums that can drift apart in
|
|
17
|
+
// their middles.
|
|
18
|
+
//
|
|
19
|
+
// Header-only and in core because both mounting managers need it and both know
|
|
20
|
+
// React Native; the view layers do not, and they take a string.
|
|
21
|
+
|
|
22
|
+
#pragma once
|
|
23
|
+
|
|
24
|
+
#include <react/renderer/components/view/primitives.h>
|
|
25
|
+
|
|
26
|
+
namespace basalt {
|
|
27
|
+
|
|
28
|
+
// The CSS keyword, or nullptr for "nothing to apply" -- which is `auto`, meaning
|
|
29
|
+
// the cursor is whatever the surrounding view says it is, and `url`, which in
|
|
30
|
+
// CSS names a custom image that React Native carries nothing for.
|
|
31
|
+
inline const char *cursorName(facebook::react::Cursor cursor) {
|
|
32
|
+
using facebook::react::Cursor;
|
|
33
|
+
switch (cursor) {
|
|
34
|
+
case Cursor::Auto:
|
|
35
|
+
return nullptr;
|
|
36
|
+
case Cursor::Alias:
|
|
37
|
+
return "alias";
|
|
38
|
+
case Cursor::AllScroll:
|
|
39
|
+
return "all-scroll";
|
|
40
|
+
case Cursor::Cell:
|
|
41
|
+
return "cell";
|
|
42
|
+
case Cursor::ColResize:
|
|
43
|
+
return "col-resize";
|
|
44
|
+
case Cursor::ContextMenu:
|
|
45
|
+
return "context-menu";
|
|
46
|
+
case Cursor::Copy:
|
|
47
|
+
return "copy";
|
|
48
|
+
case Cursor::Crosshair:
|
|
49
|
+
return "crosshair";
|
|
50
|
+
case Cursor::Default:
|
|
51
|
+
return "default";
|
|
52
|
+
case Cursor::EResize:
|
|
53
|
+
return "e-resize";
|
|
54
|
+
case Cursor::EWResize:
|
|
55
|
+
return "ew-resize";
|
|
56
|
+
case Cursor::Grab:
|
|
57
|
+
return "grab";
|
|
58
|
+
case Cursor::Grabbing:
|
|
59
|
+
return "grabbing";
|
|
60
|
+
case Cursor::Help:
|
|
61
|
+
return "help";
|
|
62
|
+
case Cursor::Move:
|
|
63
|
+
return "move";
|
|
64
|
+
case Cursor::NEResize:
|
|
65
|
+
return "ne-resize";
|
|
66
|
+
case Cursor::NESWResize:
|
|
67
|
+
return "nesw-resize";
|
|
68
|
+
case Cursor::NResize:
|
|
69
|
+
return "n-resize";
|
|
70
|
+
case Cursor::NSResize:
|
|
71
|
+
return "ns-resize";
|
|
72
|
+
case Cursor::NWResize:
|
|
73
|
+
return "nw-resize";
|
|
74
|
+
case Cursor::NWSEResize:
|
|
75
|
+
return "nwse-resize";
|
|
76
|
+
case Cursor::NoDrop:
|
|
77
|
+
return "no-drop";
|
|
78
|
+
case Cursor::None:
|
|
79
|
+
return "none";
|
|
80
|
+
case Cursor::NotAllowed:
|
|
81
|
+
return "not-allowed";
|
|
82
|
+
case Cursor::Pointer:
|
|
83
|
+
return "pointer";
|
|
84
|
+
case Cursor::Progress:
|
|
85
|
+
return "progress";
|
|
86
|
+
case Cursor::RowResize:
|
|
87
|
+
return "row-resize";
|
|
88
|
+
case Cursor::SResize:
|
|
89
|
+
return "s-resize";
|
|
90
|
+
case Cursor::SEResize:
|
|
91
|
+
return "se-resize";
|
|
92
|
+
case Cursor::SWResize:
|
|
93
|
+
return "sw-resize";
|
|
94
|
+
case Cursor::Text:
|
|
95
|
+
return "text";
|
|
96
|
+
case Cursor::Url:
|
|
97
|
+
return nullptr;
|
|
98
|
+
case Cursor::WResize:
|
|
99
|
+
return "w-resize";
|
|
100
|
+
case Cursor::Wait:
|
|
101
|
+
return "wait";
|
|
102
|
+
case Cursor::ZoomIn:
|
|
103
|
+
return "zoom-in";
|
|
104
|
+
case Cursor::ZoomOut:
|
|
105
|
+
return "zoom-out";
|
|
106
|
+
}
|
|
107
|
+
return nullptr;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
} // namespace basalt
|
package/native/core/DevMenu.cpp
CHANGED
|
@@ -73,11 +73,12 @@ void showDevMenu(facebook::react::ReactHost *reactHost, double x, double y) {
|
|
|
73
73
|
request.y = y;
|
|
74
74
|
request.entries.reserve(items->size());
|
|
75
75
|
for (const DevMenuItem &item : *items) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
// Assigned, not a designated initializer: see MenuEntry::separator for why
|
|
77
|
+
// naming some fields and not others does not survive a new one.
|
|
78
|
+
MenuEntry entry;
|
|
79
|
+
entry.label = item.label;
|
|
80
|
+
entry.shortcut = item.shortcut;
|
|
81
|
+
request.entries.push_back(std::move(entry));
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// Through presentMenu rather than showMenu, so BASALT_TEST_MENU can answer
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#include "DeviceMetrics.h"
|
|
2
|
+
|
|
3
|
+
#include "FontScaling.h"
|
|
4
|
+
|
|
5
|
+
namespace basalt {
|
|
6
|
+
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
// Which display a point is on, or nothing when no display contains it -- a
|
|
10
|
+
// window dragged to a monitor that has just been unplugged, or a host that has
|
|
11
|
+
// reported no displays at all.
|
|
12
|
+
const DisplayInfo *displayContaining(const std::vector<DisplayInfo> &displays,
|
|
13
|
+
double x,
|
|
14
|
+
double y) {
|
|
15
|
+
for (const DisplayInfo &display : displays) {
|
|
16
|
+
if (x >= display.x && x < display.x + display.width && y >= display.y &&
|
|
17
|
+
y < display.y + display.height) {
|
|
18
|
+
return &display;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return nullptr;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DisplayInfo *primaryDisplay(const std::vector<DisplayInfo> &displays) {
|
|
25
|
+
for (const DisplayInfo &display : displays) {
|
|
26
|
+
if (display.primary) {
|
|
27
|
+
return &display;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return displays.empty() ? nullptr : &displays.front();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
DeviceMetrics deviceMetricsFrom(const WindowBounds &window,
|
|
36
|
+
double surfaceWidth,
|
|
37
|
+
double surfaceHeight,
|
|
38
|
+
const std::vector<DisplayInfo> &displays,
|
|
39
|
+
double fontScale) {
|
|
40
|
+
const double scale = fontScale > 0.0 ? fontScale : 1.0;
|
|
41
|
+
|
|
42
|
+
// The display the window's centre is on, falling back to the primary one: a
|
|
43
|
+
// window that has not been placed yet is at the origin, which is the primary
|
|
44
|
+
// display's corner on every host.
|
|
45
|
+
const DisplayInfo *on = displayContaining(displays,
|
|
46
|
+
window.x + window.width / 2.0,
|
|
47
|
+
window.y + window.height / 2.0);
|
|
48
|
+
if (on == nullptr) {
|
|
49
|
+
on = primaryDisplay(displays);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
DeviceMetrics metrics;
|
|
53
|
+
metrics.window.scale = on != nullptr && on->scaleFactor > 0.0 ? on->scaleFactor : 1.0;
|
|
54
|
+
metrics.window.fontScale = scale;
|
|
55
|
+
// The surface when the host has started one, which is the area an app can
|
|
56
|
+
// lay out in; the window's frame otherwise.
|
|
57
|
+
metrics.window.width = surfaceWidth > 0.0 ? surfaceWidth : window.width;
|
|
58
|
+
metrics.window.height = surfaceHeight > 0.0 ? surfaceHeight : window.height;
|
|
59
|
+
|
|
60
|
+
metrics.screen = metrics.window;
|
|
61
|
+
if (on != nullptr) {
|
|
62
|
+
metrics.screen.width = on->width;
|
|
63
|
+
metrics.screen.height = on->height;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Nothing reported yet. The screen is a better answer than zero for an app
|
|
67
|
+
// laying out during its first render; see the header.
|
|
68
|
+
if (metrics.window.width <= 0.0 || metrics.window.height <= 0.0) {
|
|
69
|
+
metrics.window.width = metrics.screen.width;
|
|
70
|
+
metrics.window.height = metrics.screen.height;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return metrics;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
DeviceMetrics currentDeviceMetrics() {
|
|
77
|
+
double surfaceWidth = 0.0;
|
|
78
|
+
double surfaceHeight = 0.0;
|
|
79
|
+
lastKnownSurfaceSize(&surfaceWidth, &surfaceHeight);
|
|
80
|
+
return deviceMetricsFrom(lastKnownWindowBounds(),
|
|
81
|
+
surfaceWidth,
|
|
82
|
+
surfaceHeight,
|
|
83
|
+
lastKnownDisplays(),
|
|
84
|
+
static_cast<double>(systemFontScale()));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace basalt
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// What `Dimensions` and `PixelRatio` answer with.
|
|
2
|
+
//
|
|
3
|
+
// React Native's `Dimensions.get('window')` is one of the most-used APIs it
|
|
4
|
+
// has, and on this platform it reported a fiction: ReactCxxPlatform's
|
|
5
|
+
// `DeviceInfoModule::getConstants` returns a hardcoded 1280x720 with a
|
|
6
|
+
// `// TODO: Wire this to come from the actual app size` beside it and no seam
|
|
7
|
+
// to wire. `scale` came back 0 and `fontScale` was never initialised at all --
|
|
8
|
+
// the generated struct leaves it so -- which made `PixelRatio.get()` zero and
|
|
9
|
+
// `PixelRatio.roundToNearestPixel(x)` NaN, silently, in every app.
|
|
10
|
+
//
|
|
11
|
+
// Everything needed is already here: `lastKnownWindowBounds()` is the window
|
|
12
|
+
// the host reports on every change, `lastKnownDisplays()` carries each
|
|
13
|
+
// display's scale factor, and `systemFontScale()` is the text scale the font
|
|
14
|
+
// machinery already multiplies by. This is the arithmetic that turns the three
|
|
15
|
+
// into React Native's two payloads, written once so three hosts cannot
|
|
16
|
+
// disagree about which display a window is on.
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include "WindowControl.h"
|
|
21
|
+
|
|
22
|
+
#include <vector>
|
|
23
|
+
|
|
24
|
+
namespace basalt {
|
|
25
|
+
|
|
26
|
+
// One of React Native's `DisplayMetrics`: a size in logical pixels, how many
|
|
27
|
+
// physical pixels each of those is, and the text scale.
|
|
28
|
+
struct DisplayMetrics {
|
|
29
|
+
double width{0.0};
|
|
30
|
+
double height{0.0};
|
|
31
|
+
// Never zero. A caller dividing by it -- which is exactly what
|
|
32
|
+
// `PixelRatio.roundToNearestPixel` does -- should not have to check.
|
|
33
|
+
double scale{1.0};
|
|
34
|
+
double fontScale{1.0};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
struct DeviceMetrics {
|
|
38
|
+
DisplayMetrics window;
|
|
39
|
+
DisplayMetrics screen;
|
|
40
|
+
|
|
41
|
+
bool operator==(const DeviceMetrics &other) const {
|
|
42
|
+
return window.width == other.window.width && window.height == other.window.height &&
|
|
43
|
+
window.scale == other.window.scale && window.fontScale == other.window.fontScale &&
|
|
44
|
+
screen.width == other.screen.width && screen.height == other.screen.height &&
|
|
45
|
+
screen.scale == other.screen.scale && screen.fontScale == other.screen.fontScale;
|
|
46
|
+
}
|
|
47
|
+
bool operator!=(const DeviceMetrics &other) const { return !(*this == other); }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// The window and the screen it is on, from what the host has reported.
|
|
51
|
+
//
|
|
52
|
+
// `window` is the window's own size; `screen` is the display it sits on, which
|
|
53
|
+
// is the distinction React Native draws and which matters on a desktop in a way
|
|
54
|
+
// it never does on a phone: a window is a fraction of a screen, and an app
|
|
55
|
+
// deciding how much it can show wants the first while one deciding where to put
|
|
56
|
+
// itself wants the second.
|
|
57
|
+
//
|
|
58
|
+
// Which display a window is on is decided by its centre, so a window straddling
|
|
59
|
+
// two reports the one most of it is on. A window the host has not reported yet
|
|
60
|
+
// -- everything is zero -- borrows the primary display's size rather than
|
|
61
|
+
// reporting a zero an app would divide by; that is the first render, before the
|
|
62
|
+
// first layout, and a zero there is worse than an estimate.
|
|
63
|
+
// `surfaceWidth` and `surfaceHeight` are the area React lays out in, which is
|
|
64
|
+
// what `window` reports when it is known: a desktop window's frame includes a
|
|
65
|
+
// title bar the app cannot put anything in, and the two differ by exactly that
|
|
66
|
+
// on AppKit. Zero means the host has not started a surface yet, and then the
|
|
67
|
+
// window's own size is used instead.
|
|
68
|
+
DeviceMetrics deviceMetricsFrom(const WindowBounds &window,
|
|
69
|
+
double surfaceWidth,
|
|
70
|
+
double surfaceHeight,
|
|
71
|
+
const std::vector<DisplayInfo> &displays,
|
|
72
|
+
double fontScale);
|
|
73
|
+
|
|
74
|
+
// The same, from what the host last reported and what the font machinery is
|
|
75
|
+
// scaling by. Safe from any thread, which is the point: a TurboModule method
|
|
76
|
+
// runs on the JavaScript thread and the toolkit may only be asked on the UI
|
|
77
|
+
// one.
|
|
78
|
+
DeviceMetrics currentDeviceMetrics();
|
|
79
|
+
|
|
80
|
+
} // namespace basalt
|