basalt-core 0.0.0-rc5
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/LICENSE +21 -0
- package/dist/cli/basalt.d.ts +3 -0
- package/dist/cli/basalt.d.ts.map +1 -0
- package/dist/cli/basalt.js +47 -0
- package/dist/cli/basalt.js.map +1 -0
- package/dist/cli/bundle-cli.d.ts +12 -0
- package/dist/cli/bundle-cli.d.ts.map +1 -0
- package/dist/cli/bundle-cli.js +124 -0
- package/dist/cli/bundle-cli.js.map +1 -0
- package/dist/cli/bundleWithAssets.d.ts +79 -0
- package/dist/cli/bundleWithAssets.d.ts.map +1 -0
- package/dist/cli/bundleWithAssets.js +198 -0
- package/dist/cli/bundleWithAssets.js.map +1 -0
- package/dist/cli/desktop.d.ts +339 -0
- package/dist/cli/desktop.d.ts.map +1 -0
- package/dist/cli/desktop.js +1029 -0
- package/dist/cli/desktop.js.map +1 -0
- package/dist/cli/doctor.d.ts +31 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +283 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +77 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +442 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/metro.d.ts +49 -0
- package/dist/cli/metro.d.ts.map +1 -0
- package/dist/cli/metro.js +148 -0
- package/dist/cli/metro.js.map +1 -0
- package/dist/cli/packageApp.d.ts +102 -0
- package/dist/cli/packageApp.d.ts.map +1 -0
- package/dist/cli/packageApp.js +319 -0
- package/dist/cli/packageApp.js.map +1 -0
- package/dist/cli/steps.d.ts +34 -0
- package/dist/cli/steps.d.ts.map +1 -0
- package/dist/cli/steps.js +53 -0
- package/dist/cli/steps.js.map +1 -0
- package/dist/metro-config.d.ts +168 -0
- package/dist/metro-config.d.ts.map +1 -0
- package/dist/metro-config.js +671 -0
- package/dist/metro-config.js.map +1 -0
- package/dist/react-native.config.d.ts +7 -0
- package/dist/react-native.config.d.ts.map +1 -0
- package/dist/react-native.config.js +80 -0
- package/dist/react-native.config.js.map +1 -0
- package/dist/src/DragSource.d.ts +52 -0
- package/dist/src/DragSource.d.ts.map +1 -0
- package/dist/src/DragSource.js +97 -0
- package/dist/src/DragSource.js.map +1 -0
- package/dist/src/DropTarget.d.ts +58 -0
- package/dist/src/DropTarget.d.ts.map +1 -0
- package/dist/src/DropTarget.js +131 -0
- package/dist/src/DropTarget.js.map +1 -0
- package/dist/src/KeyHandler.d.ts +57 -0
- package/dist/src/KeyHandler.d.ts.map +1 -0
- package/dist/src/KeyHandler.js +48 -0
- package/dist/src/KeyHandler.js.map +1 -0
- package/dist/src/Menu.d.ts +72 -0
- package/dist/src/Menu.d.ts.map +1 -0
- package/dist/src/Menu.js +179 -0
- package/dist/src/Menu.js.map +1 -0
- package/dist/src/TitleBar.d.ts +75 -0
- package/dist/src/TitleBar.d.ts.map +1 -0
- package/dist/src/TitleBar.js +236 -0
- package/dist/src/TitleBar.js.map +1 -0
- package/dist/src/Window.d.ts +87 -0
- package/dist/src/Window.d.ts.map +1 -0
- package/dist/src/Window.js +248 -0
- package/dist/src/Window.js.map +1 -0
- package/dist/src/closeRequest.d.ts +55 -0
- package/dist/src/closeRequest.d.ts.map +1 -0
- package/dist/src/closeRequest.js +134 -0
- package/dist/src/closeRequest.js.map +1 -0
- package/dist/src/index.d.ts +45 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +92 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/overrides/Alert.d.ts +69 -0
- package/dist/src/overrides/Alert.d.ts.map +1 -0
- package/dist/src/overrides/Alert.js +112 -0
- package/dist/src/overrides/Alert.js.map +1 -0
- package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
- package/dist/src/overrides/BaseViewConfig.js +72 -0
- package/dist/src/overrides/BaseViewConfig.js.map +1 -0
- package/dist/src/overrides/Platform.linux.d.ts +10 -0
- package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
- package/dist/src/overrides/Platform.linux.js +15 -0
- package/dist/src/overrides/Platform.linux.js.map +1 -0
- package/dist/src/overrides/Platform.macos.d.ts +10 -0
- package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
- package/dist/src/overrides/Platform.macos.js +15 -0
- package/dist/src/overrides/Platform.macos.js.map +1 -0
- package/dist/src/overrides/Platform.windows.d.ts +10 -0
- package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
- package/dist/src/overrides/Platform.windows.js +15 -0
- package/dist/src/overrides/Platform.windows.js.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
- package/dist/src/overrides/RefreshControl.d.ts +52 -0
- package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
- package/dist/src/overrides/RefreshControl.js +92 -0
- package/dist/src/overrides/RefreshControl.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +39 -0
- package/dist/src/overrides/ScrollView.d.ts.map +1 -0
- package/dist/src/overrides/ScrollView.js +59 -0
- package/dist/src/overrides/ScrollView.js.map +1 -0
- package/dist/src/overrides/Share.d.ts +96 -0
- package/dist/src/overrides/Share.d.ts.map +1 -0
- package/dist/src/overrides/Share.js +97 -0
- package/dist/src/overrides/Share.js.map +1 -0
- package/dist/src/overrides/TextInput.d.ts +81 -0
- package/dist/src/overrides/TextInput.d.ts.map +1 -0
- package/dist/src/overrides/TextInput.js +213 -0
- package/dist/src/overrides/TextInput.js.map +1 -0
- package/dist/src/overrides/View.d.ts +36 -0
- package/dist/src/overrides/View.d.ts.map +1 -0
- package/dist/src/overrides/View.js +99 -0
- package/dist/src/overrides/View.js.map +1 -0
- package/dist/src/overrides/createPlatform.d.ts +32 -0
- package/dist/src/overrides/createPlatform.d.ts.map +1 -0
- package/dist/src/overrides/createPlatform.js +84 -0
- package/dist/src/overrides/createPlatform.js.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.js +84 -0
- package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
- package/dist/src/overrides/setUpXHR.d.ts +46 -0
- package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
- package/dist/src/overrides/setUpXHR.js +236 -0
- package/dist/src/overrides/setUpXHR.js.map +1 -0
- package/dist/src/quitRequest.d.ts +55 -0
- package/dist/src/quitRequest.d.ts.map +1 -0
- package/dist/src/quitRequest.js +121 -0
- package/dist/src/quitRequest.js.map +1 -0
- package/dist/src/resourcePath.d.ts +18 -0
- package/dist/src/resourcePath.d.ts.map +1 -0
- package/dist/src/resourcePath.js +31 -0
- package/dist/src/resourcePath.js.map +1 -0
- package/dist/src/responseBody.d.ts +76 -0
- package/dist/src/responseBody.d.ts.map +1 -0
- package/dist/src/responseBody.js +94 -0
- package/dist/src/responseBody.js.map +1 -0
- package/dist/src/responseHeaders.d.ts +40 -0
- package/dist/src/responseHeaders.d.ts.map +1 -0
- package/dist/src/responseHeaders.js +75 -0
- package/dist/src/responseHeaders.js.map +1 -0
- package/dist/src/titleBarState.d.ts +43 -0
- package/dist/src/titleBarState.d.ts.map +1 -0
- package/dist/src/titleBarState.js +77 -0
- package/dist/src/titleBarState.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +113 -0
- package/dist/src/useContextMenu.d.ts.map +1 -0
- package/dist/src/useContextMenu.js +109 -0
- package/dist/src/useContextMenu.js.map +1 -0
- package/dist/src/useDialog.d.ts +98 -0
- package/dist/src/useDialog.d.ts.map +1 -0
- package/dist/src/useDialog.js +112 -0
- package/dist/src/useDialog.js.map +1 -0
- package/dist/src/useDisplays.d.ts +70 -0
- package/dist/src/useDisplays.d.ts.map +1 -0
- package/dist/src/useDisplays.js +118 -0
- package/dist/src/useDisplays.js.map +1 -0
- package/dist/src/useHandledKeys.d.ts +38 -0
- package/dist/src/useHandledKeys.d.ts.map +1 -0
- package/dist/src/useHandledKeys.js +70 -0
- package/dist/src/useHandledKeys.js.map +1 -0
- package/dist/src/useWindow.d.ts +140 -0
- package/dist/src/useWindow.d.ts.map +1 -0
- package/dist/src/useWindow.js +233 -0
- package/dist/src/useWindow.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/native/CMakeLists.txt +355 -0
- package/native/bootstrap.sh +613 -0
- package/native/cmake/CompilerFlags.cmake +49 -0
- package/native/cmake/Expo.cmake +125 -0
- package/native/cmake/ReactNativeCore.cmake +612 -0
- package/native/cmake/Reanimated.cmake +108 -0
- package/native/cmake/Skia.cmake +379 -0
- package/native/cmake/ThirdParty.cmake +290 -0
- package/native/cmake/Worklets.cmake +67 -0
- package/native/cmake/codegen/CMakeLists.txt +32 -0
- package/native/core/AppIdentity.cpp +85 -0
- package/native/core/AppIdentity.h +59 -0
- package/native/core/AppPaths.cpp +96 -0
- package/native/core/AppPaths.h +53 -0
- package/native/core/AppearanceModule.cpp +60 -0
- package/native/core/AppearanceModule.h +40 -0
- package/native/core/Backface.h +59 -0
- package/native/core/BlobModule.cpp +283 -0
- package/native/core/BlobModule.h +76 -0
- package/native/core/BlobRegistry.cpp +75 -0
- package/native/core/BlobRegistry.h +44 -0
- package/native/core/ColorScheme.cpp +105 -0
- package/native/core/ColorScheme.h +59 -0
- package/native/core/ComponentRegistry.h +30 -0
- package/native/core/ControlMetrics.h +37 -0
- package/native/core/CoreModules.cpp +592 -0
- package/native/core/CoreModules.h +308 -0
- package/native/core/DebuggingOverlay.cpp +106 -0
- package/native/core/DebuggingOverlay.h +65 -0
- package/native/core/DesktopControls.cpp +232 -0
- package/native/core/DesktopControls.h +170 -0
- package/native/core/DevBundle.cpp +108 -0
- package/native/core/DevBundle.h +69 -0
- package/native/core/DevMenu.cpp +99 -0
- package/native/core/DevMenu.h +75 -0
- package/native/core/DialogModule.cpp +150 -0
- package/native/core/DialogModule.h +51 -0
- package/native/core/DragAndDrop.cpp +45 -0
- package/native/core/DragAndDrop.h +225 -0
- package/native/core/ExpoImageComponent.cpp +79 -0
- package/native/core/ExpoImageComponent.h +80 -0
- package/native/core/ExpoModules.cpp +415 -0
- package/native/core/ExpoModules.h +115 -0
- package/native/core/ExpoRuntime.cpp +366 -0
- package/native/core/ExpoRuntime.h +42 -0
- package/native/core/FocusRing.h +36 -0
- package/native/core/FontRegistry.h +51 -0
- package/native/core/GestureHandlerModule.cpp +136 -0
- package/native/core/GestureHandlerModule.h +71 -0
- package/native/core/Gestures.cpp +533 -0
- package/native/core/Gestures.h +145 -0
- package/native/core/HoverTracker.cpp +69 -0
- package/native/core/HoverTracker.h +109 -0
- package/native/core/HttpClient.cpp +282 -0
- package/native/core/ImageBytes.cpp +205 -0
- package/native/core/ImageBytes.h +29 -0
- package/native/core/ImageCache.cpp +51 -0
- package/native/core/ImageCache.h +89 -0
- package/native/core/JsRuntimeAccess.cpp +70 -0
- package/native/core/JsRuntimeAccess.h +60 -0
- package/native/core/JsiPromise.h +51 -0
- package/native/core/KeyEvents.cpp +116 -0
- package/native/core/KeyEvents.h +144 -0
- package/native/core/LogBoxSurface.cpp +46 -0
- package/native/core/LogBoxSurface.h +68 -0
- package/native/core/MenuModel.h +117 -0
- package/native/core/MenuModule.cpp +209 -0
- package/native/core/MenuModule.h +66 -0
- package/native/core/MountingWalk.h +524 -0
- package/native/core/PackageModules.cpp.in +25 -0
- package/native/core/PackageModules.h +50 -0
- package/native/core/PlatformConstantsModule.cpp +63 -0
- package/native/core/PlatformConstantsModule.h +64 -0
- package/native/core/PlatformServices.h +213 -0
- package/native/core/PointerButtons.h +63 -0
- package/native/core/PullToRefresh.cpp +57 -0
- package/native/core/PullToRefresh.h +73 -0
- package/native/core/ReanimatedCompat.h +47 -0
- package/native/core/ReanimatedModule.cpp +203 -0
- package/native/core/ReanimatedModule.h +55 -0
- package/native/core/ScrollAnimation.cpp +59 -0
- package/native/core/ScrollAnimation.h +70 -0
- package/native/core/ScrollBounds.cpp +26 -0
- package/native/core/ScrollBounds.h +68 -0
- package/native/core/ScrollIndicator.cpp +50 -0
- package/native/core/ScrollIndicator.h +69 -0
- package/native/core/ScrollMomentum.cpp +71 -0
- package/native/core/ScrollMomentum.h +73 -0
- package/native/core/ScrollSnap.cpp +123 -0
- package/native/core/ScrollSnap.h +80 -0
- package/native/core/ShareFallback.cpp +101 -0
- package/native/core/ShareFallback.h +45 -0
- package/native/core/SkiaPictureViewComponent.cpp +11 -0
- package/native/core/SkiaPictureViewComponent.h +47 -0
- package/native/core/SourceCodeModule.cpp +55 -0
- package/native/core/SourceCodeModule.h +53 -0
- package/native/core/StatusBarModule.cpp +23 -0
- package/native/core/StatusBarModule.h +42 -0
- package/native/core/TestDialog.cpp +148 -0
- package/native/core/TestDialog.h +96 -0
- package/native/core/TestKeys.cpp +78 -0
- package/native/core/TestKeys.h +50 -0
- package/native/core/TestQuitFile.cpp +33 -0
- package/native/core/TestQuitFile.h +60 -0
- package/native/core/TestSettle.h +192 -0
- package/native/core/TitleBarRegions.h +31 -0
- package/native/core/TurboModuleProxy.cpp +51 -0
- package/native/core/TurboModuleProxy.h +46 -0
- package/native/core/UIManagerAccess.cpp +43 -0
- package/native/core/UIManagerAccess.h +43 -0
- package/native/core/WindowBoundsCache.cpp +319 -0
- package/native/core/WindowControl.h +235 -0
- package/native/core/WindowHost.h +158 -0
- package/native/core/WindowMethods.cpp +167 -0
- package/native/core/WindowMethods.h +84 -0
- package/native/core/WindowsModule.cpp +448 -0
- package/native/core/WindowsModule.h +133 -0
- package/native/core/WorkletsModule.cpp +199 -0
- package/native/core/WorkletsModule.h +57 -0
- package/native/core/portability_probe.cpp +210 -0
- package/native/tests/EventRecorder.h +124 -0
- package/native/tests/TestHarness.cpp +109 -0
- package/native/tests/TestHarness.h +89 -0
- package/native/tests/TreeDump.h +49 -0
- package/native/tests/test_app_identity.cpp +114 -0
- package/native/tests/test_app_paths.cpp +48 -0
- package/native/tests/test_appearance.cpp +107 -0
- package/native/tests/test_backface.cpp +79 -0
- package/native/tests/test_blobs.cpp +157 -0
- package/native/tests/test_controls.cpp +185 -0
- package/native/tests/test_devbundle.cpp +103 -0
- package/native/tests/test_drag_and_drop.cpp +143 -0
- package/native/tests/test_gestures.cpp +331 -0
- package/native/tests/test_hover.cpp +92 -0
- package/native/tests/test_image_cache.cpp +109 -0
- package/native/tests/test_js_runtime_access.cpp +137 -0
- package/native/tests/test_key_events.cpp +235 -0
- package/native/tests/test_momentum.cpp +138 -0
- package/native/tests/test_quit_file.cpp +104 -0
- package/native/tests/test_scroll_animation.cpp +120 -0
- package/native/tests/test_scroll_bounds.cpp +66 -0
- package/native/tests/test_scroll_indicator.cpp +149 -0
- package/native/tests/test_scroll_snap.cpp +112 -0
- package/native/tests/test_settle.cpp +115 -0
- package/native/tests/test_share.cpp +75 -0
- package/native/tests/test_sourcecode.cpp +62 -0
- package/native/tests/test_window_close.cpp +119 -0
- package/native/tests/test_window_limits.cpp +123 -0
- package/package.json +43 -0
- package/react-native.config.js +86 -0
- package/supported-versions.json +66 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#include "ImageBytes.h"
|
|
2
|
+
|
|
3
|
+
#include <curl/curl.h>
|
|
4
|
+
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <cctype>
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <fstream>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
|
|
12
|
+
namespace basalt {
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
std::once_flag curlInitFlag;
|
|
17
|
+
|
|
18
|
+
size_t appendBytes(char *data, size_t size, size_t count, void *userData) {
|
|
19
|
+
auto *out = static_cast<std::string *>(userData);
|
|
20
|
+
out->append(data, size * count);
|
|
21
|
+
return size * count;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
bool startsWith(const std::string &value, const char *prefix) {
|
|
25
|
+
return value.rfind(prefix, 0) == 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Standard base64, ignoring whitespace and tolerating missing padding. Written
|
|
29
|
+
// out rather than taken from glib or Foundation because this file is the half
|
|
30
|
+
// that has neither.
|
|
31
|
+
bool decodeBase64(const std::string &input, std::string *out) {
|
|
32
|
+
static constexpr std::string_view kAlphabet =
|
|
33
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
34
|
+
|
|
35
|
+
std::array<int8_t, 256> reverse{};
|
|
36
|
+
reverse.fill(-1);
|
|
37
|
+
for (size_t i = 0; i < kAlphabet.size(); i++) {
|
|
38
|
+
reverse[static_cast<unsigned char>(kAlphabet[i])] = static_cast<int8_t>(i);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
uint32_t accumulator = 0;
|
|
42
|
+
int bits = 0;
|
|
43
|
+
for (const char c : input) {
|
|
44
|
+
if (c == '=' ) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
if (std::isspace(static_cast<unsigned char>(c)) != 0) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const int8_t value = reverse[static_cast<unsigned char>(c)];
|
|
51
|
+
if (value < 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
accumulator = (accumulator << 6) | static_cast<uint32_t>(value);
|
|
55
|
+
bits += 6;
|
|
56
|
+
if (bits >= 8) {
|
|
57
|
+
bits -= 8;
|
|
58
|
+
out->push_back(static_cast<char>((accumulator >> bits) & 0xFF));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Percent-decoding, for the path half of a file: URI. A path with a space in it
|
|
65
|
+
// arrives as %20 and opening it literally fails with "no such file", which is a
|
|
66
|
+
// confusing thing to be told about a file that plainly exists.
|
|
67
|
+
std::string percentDecode(const std::string &input) {
|
|
68
|
+
std::string out;
|
|
69
|
+
out.reserve(input.size());
|
|
70
|
+
for (size_t i = 0; i < input.size(); i++) {
|
|
71
|
+
if (input[i] == '%' && i + 2 < input.size()) {
|
|
72
|
+
const auto hex = input.substr(i + 1, 2);
|
|
73
|
+
char *end = nullptr;
|
|
74
|
+
const long value = std::strtol(hex.c_str(), &end, 16);
|
|
75
|
+
if (end != nullptr && *end == '\0') {
|
|
76
|
+
out.push_back(static_cast<char>(value));
|
|
77
|
+
i += 2;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
out.push_back(input[i]);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bool readFile(const std::string &path, std::string *out, std::string *error) {
|
|
87
|
+
std::ifstream file(path, std::ios::binary);
|
|
88
|
+
if (!file) {
|
|
89
|
+
*error = "could not open " + path;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
std::ostringstream contents;
|
|
93
|
+
contents << file.rdbuf();
|
|
94
|
+
*out = contents.str();
|
|
95
|
+
if (out->empty()) {
|
|
96
|
+
*error = "empty file: " + path;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
} // namespace
|
|
103
|
+
|
|
104
|
+
bool fetchImageBytes(const std::string &uri, std::string *out, std::string *error) {
|
|
105
|
+
out->clear();
|
|
106
|
+
|
|
107
|
+
if (uri.empty()) {
|
|
108
|
+
*error = "empty source uri";
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (startsWith(uri, "data:")) {
|
|
113
|
+
const auto comma = uri.find(',');
|
|
114
|
+
if (comma == std::string::npos) {
|
|
115
|
+
*error = "malformed data: URI";
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (uri.substr(0, comma).find(";base64") == std::string::npos) {
|
|
119
|
+
// Percent-encoded text payloads are legal but never carry an image.
|
|
120
|
+
*error = "only base64 data: URIs are supported";
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (!decodeBase64(uri.substr(comma + 1), out)) {
|
|
124
|
+
out->clear();
|
|
125
|
+
*error = "malformed base64 in data: URI";
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (startsWith(uri, "http://") || startsWith(uri, "https://")) {
|
|
132
|
+
std::call_once(curlInitFlag, []() { curl_global_init(CURL_GLOBAL_DEFAULT); });
|
|
133
|
+
|
|
134
|
+
CURL *handle = curl_easy_init();
|
|
135
|
+
if (handle == nullptr) {
|
|
136
|
+
*error = "could not initialise libcurl";
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
curl_easy_setopt(handle, CURLOPT_URL, uri.c_str());
|
|
140
|
+
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
141
|
+
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, appendBytes);
|
|
142
|
+
curl_easy_setopt(handle, CURLOPT_WRITEDATA, out);
|
|
143
|
+
// Without this, libcurl installs a SIGALRM-based resolver timeout that is
|
|
144
|
+
// not safe to use off the main thread.
|
|
145
|
+
curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
|
|
146
|
+
const CURLcode result = curl_easy_perform(handle);
|
|
147
|
+
long status = 0;
|
|
148
|
+
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
|
|
149
|
+
curl_easy_cleanup(handle);
|
|
150
|
+
|
|
151
|
+
if (result != CURLE_OK) {
|
|
152
|
+
out->clear();
|
|
153
|
+
*error = curl_easy_strerror(result);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (status >= 400) {
|
|
157
|
+
out->clear();
|
|
158
|
+
*error = "http " + std::to_string(status);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Anything else is a path. file:// is the explicit form; a bare path is what
|
|
165
|
+
// a bundled asset looks like once it has been resolved.
|
|
166
|
+
std::string path = uri;
|
|
167
|
+
if (startsWith(uri, "file://")) {
|
|
168
|
+
path = percentDecode(uri.substr(std::string("file://").size()));
|
|
169
|
+
// file:///tmp/x has an empty authority; file://host/x has one this cannot
|
|
170
|
+
// reach, and treating its host as the first path segment would silently
|
|
171
|
+
// read the wrong file.
|
|
172
|
+
if (!path.empty() && path.front() != '/') {
|
|
173
|
+
*error = "remote file: URIs are not supported: " + uri;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#ifdef _WIN32
|
|
178
|
+
// On Windows the leading slash is not part of the path.
|
|
179
|
+
//
|
|
180
|
+
// `file:///C:/x` has the URI path `/C:/x`, and on a POSIX system that is
|
|
181
|
+
// also the filesystem path -- which is why this went unnoticed until a
|
|
182
|
+
// third desktop. Windows has no root above the drive letter, so `/C:/x`
|
|
183
|
+
// does not open and the slash has to come off. Every browser and every URI
|
|
184
|
+
// library does the same.
|
|
185
|
+
//
|
|
186
|
+
// Guarded rather than written portably, even though a POSIX directory
|
|
187
|
+
// literally named `C:` is not a thing anyone has: this file is shared, the
|
|
188
|
+
// other two desktops cannot be tested from here, and a shared behaviour
|
|
189
|
+
// change is not worth making blind for a case that cannot arise.
|
|
190
|
+
if (path.size() >= 3 && path[0] == '/' &&
|
|
191
|
+
((path[1] >= 'A' && path[1] <= 'Z') || (path[1] >= 'a' && path[1] <= 'z')) &&
|
|
192
|
+
path[2] == ':') {
|
|
193
|
+
path.erase(0, 1);
|
|
194
|
+
}
|
|
195
|
+
#endif
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!readFile(path, out, error)) {
|
|
199
|
+
out->clear();
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
} // namespace basalt
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Reading the bytes behind an <Image> source.
|
|
2
|
+
//
|
|
3
|
+
// Not platform-specific, which is why it is here. A URI is a URI: `file://`,
|
|
4
|
+
// a bare path, `data:` and `http(s)` mean the same things on every desktop, and
|
|
5
|
+
// the only part of image loading that differs is decoding the bytes into
|
|
6
|
+
// whatever the toolkit paints -- a GdkTexture on GTK, a CGImage on AppKit.
|
|
7
|
+
//
|
|
8
|
+
// Written second, which is the useful part: this began as private code inside
|
|
9
|
+
// GtkImageLoader, and porting <Image> to macOS was the point at which "the same
|
|
10
|
+
// thing twice" became visible.
|
|
11
|
+
|
|
12
|
+
#pragma once
|
|
13
|
+
|
|
14
|
+
#include <string>
|
|
15
|
+
|
|
16
|
+
namespace basalt {
|
|
17
|
+
|
|
18
|
+
// Schemes understood:
|
|
19
|
+
// file:// read from disk, percent-decoded
|
|
20
|
+
// data: base64 payloads only
|
|
21
|
+
// http, https fetched with libcurl
|
|
22
|
+
// bare paths treated as file paths, which is what a `require()`d asset
|
|
23
|
+
// looks like once Metro has resolved it in a release bundle
|
|
24
|
+
//
|
|
25
|
+
// Blocking, so call it off the thread that draws. Returns false and fills
|
|
26
|
+
// `error` on failure, in which case `out` is left empty.
|
|
27
|
+
bool fetchImageBytes(const std::string &uri, std::string *out, std::string *error);
|
|
28
|
+
|
|
29
|
+
} // namespace basalt
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#include "ImageCache.h"
|
|
2
|
+
|
|
3
|
+
namespace basalt {
|
|
4
|
+
|
|
5
|
+
void ImageCachePolicy::noteUse(const std::string &uri) {
|
|
6
|
+
const auto it = entries_.find(uri);
|
|
7
|
+
if (it == entries_.end()) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
order_.splice(order_.begin(), order_, it->second);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
std::vector<std::string> ImageCachePolicy::insert(const std::string &uri, size_t bytes) {
|
|
14
|
+
// Replacing what is already there: the same URI decoded again, which happens
|
|
15
|
+
// when a host reloads an image whose contents changed.
|
|
16
|
+
remove(uri);
|
|
17
|
+
|
|
18
|
+
order_.push_front(Entry{uri, bytes});
|
|
19
|
+
entries_[uri] = order_.begin();
|
|
20
|
+
bytes_ += bytes;
|
|
21
|
+
|
|
22
|
+
std::vector<std::string> evicted;
|
|
23
|
+
// Never the entry just inserted, however large it is -- it is on screen, and
|
|
24
|
+
// dropping it here would decode it again on the next frame, forever.
|
|
25
|
+
while (bytes_ > budget_ && order_.size() > 1) {
|
|
26
|
+
const Entry &oldest = order_.back();
|
|
27
|
+
evicted.push_back(oldest.uri);
|
|
28
|
+
bytes_ -= oldest.bytes;
|
|
29
|
+
entries_.erase(oldest.uri);
|
|
30
|
+
order_.pop_back();
|
|
31
|
+
}
|
|
32
|
+
return evicted;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void ImageCachePolicy::remove(const std::string &uri) {
|
|
36
|
+
const auto it = entries_.find(uri);
|
|
37
|
+
if (it == entries_.end()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
bytes_ -= it->second->bytes;
|
|
41
|
+
order_.erase(it->second);
|
|
42
|
+
entries_.erase(it);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void ImageCachePolicy::clear() {
|
|
46
|
+
order_.clear();
|
|
47
|
+
entries_.clear();
|
|
48
|
+
bytes_ = 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} // namespace basalt
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// What a decoded image cache keeps, and what it drops.
|
|
2
|
+
//
|
|
3
|
+
// All three hosts cache decoded images by URI in an `unordered_map` that
|
|
4
|
+
// nothing ever removes from. That is fine for the demo, where a handful of
|
|
5
|
+
// images load once, and wrong for the case the cache exists for: a long list
|
|
6
|
+
// of remote images, scrolled, where every one ever seen stays in memory until
|
|
7
|
+
// the process exits.
|
|
8
|
+
//
|
|
9
|
+
// ## Why the policy is here and the pixels are not
|
|
10
|
+
//
|
|
11
|
+
// The three hosts hold three unrelated things -- a `GdkTexture`, a `CGImage`,
|
|
12
|
+
// an `RnWin32Image` -- and none of them can be described in core without
|
|
13
|
+
// dragging a toolkit in with it. What *can* be shared is the decision: which
|
|
14
|
+
// URI to drop next, and when. So this tracks keys and sizes, answers with the
|
|
15
|
+
// URIs to release, and never sees a pixel.
|
|
16
|
+
//
|
|
17
|
+
// It also means the policy is testable without a display, which is the same
|
|
18
|
+
// reason core/ScrollIndicator.h and core/ScrollBounds.h are shaped this way.
|
|
19
|
+
//
|
|
20
|
+
// ## Least recently used
|
|
21
|
+
//
|
|
22
|
+
// The alternative is least recently *added*, which is cheaper and wrong for
|
|
23
|
+
// exactly the case this exists for: scrolling a list back up should not have
|
|
24
|
+
// to re-decode the rows that are about to be on screen again. A use is a hit
|
|
25
|
+
// or an insert, and both move an entry to the front.
|
|
26
|
+
//
|
|
27
|
+
// Not thread-safe, and not meant to be: every host calls this from the thread
|
|
28
|
+
// that owns its views, which is where the decode is handed back to.
|
|
29
|
+
|
|
30
|
+
#pragma once
|
|
31
|
+
|
|
32
|
+
#include <cstddef>
|
|
33
|
+
#include <list>
|
|
34
|
+
#include <string>
|
|
35
|
+
#include <unordered_map>
|
|
36
|
+
#include <vector>
|
|
37
|
+
|
|
38
|
+
namespace basalt {
|
|
39
|
+
|
|
40
|
+
// Roughly a dozen full-screen images on a 1080p display: 1920 x 1080 x 4 bytes
|
|
41
|
+
// is 8.3MB, so this is about a dozen of them, or a great many thumbnails.
|
|
42
|
+
//
|
|
43
|
+
// A number rather than a fraction of anything, because the three desktops do
|
|
44
|
+
// not agree on how to ask how much memory there is, and a cache whose size
|
|
45
|
+
// depended on the machine would make a leak reproduce on one and not another.
|
|
46
|
+
inline constexpr size_t kImageCacheBudgetBytes = 96u * 1024u * 1024u;
|
|
47
|
+
|
|
48
|
+
class ImageCachePolicy {
|
|
49
|
+
public:
|
|
50
|
+
explicit ImageCachePolicy(size_t budgetBytes = kImageCacheBudgetBytes)
|
|
51
|
+
: budget_(budgetBytes) {}
|
|
52
|
+
|
|
53
|
+
// Records that a cached image was used, moving it to the front. Does nothing
|
|
54
|
+
// for a URI this does not know, which is what a hit on a host that cached
|
|
55
|
+
// something before this existed looks like.
|
|
56
|
+
void noteUse(const std::string &uri);
|
|
57
|
+
|
|
58
|
+
// Records a newly decoded image, and answers with the URIs whose images
|
|
59
|
+
// should now be released -- oldest first, and never the one just inserted.
|
|
60
|
+
//
|
|
61
|
+
// An image larger than the whole budget is kept rather than refused: it is
|
|
62
|
+
// on screen, and a cache that evicted it immediately would decode it again
|
|
63
|
+
// on the next frame forever.
|
|
64
|
+
std::vector<std::string> insert(const std::string &uri, size_t bytes);
|
|
65
|
+
|
|
66
|
+
// Drops one entry, for a host that released an image for its own reasons.
|
|
67
|
+
void remove(const std::string &uri);
|
|
68
|
+
|
|
69
|
+
void clear();
|
|
70
|
+
|
|
71
|
+
size_t bytes() const { return bytes_; }
|
|
72
|
+
size_t count() const { return entries_.size(); }
|
|
73
|
+
size_t budget() const { return budget_; }
|
|
74
|
+
|
|
75
|
+
private:
|
|
76
|
+
struct Entry {
|
|
77
|
+
std::string uri;
|
|
78
|
+
size_t bytes{0};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Front is most recently used. A list so that moving an entry is a splice
|
|
82
|
+
// and the iterators the map holds stay valid.
|
|
83
|
+
std::list<Entry> order_;
|
|
84
|
+
std::unordered_map<std::string, std::list<Entry>::iterator> entries_;
|
|
85
|
+
size_t bytes_{0};
|
|
86
|
+
size_t budget_;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
} // namespace basalt
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#include "JsRuntimeAccess.h"
|
|
2
|
+
|
|
3
|
+
#include <mutex>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
#include <utility>
|
|
6
|
+
|
|
7
|
+
namespace basalt {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
// A mutex rather than a bare static, because unlike the UIManager this is read
|
|
12
|
+
// from whatever thread a subprocess reader happens to be on while the main
|
|
13
|
+
// thread may be clearing it on the way to quitting.
|
|
14
|
+
std::mutex &lock() {
|
|
15
|
+
static std::mutex mutex;
|
|
16
|
+
return mutex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
RuntimeRunner &runner() {
|
|
20
|
+
static RuntimeRunner function;
|
|
21
|
+
return function;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class RunnerCallInvoker : public facebook::react::CallInvoker {
|
|
25
|
+
public:
|
|
26
|
+
void invokeAsync(facebook::react::CallFunc &&func) noexcept override {
|
|
27
|
+
runOnJsRuntime(std::move(func));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void invokeSync(facebook::react::CallFunc && /*func*/) override {
|
|
31
|
+
// See the header: the only door a host leaves open posts. A caller wanting
|
|
32
|
+
// this wants something this platform cannot give it, and saying so here is
|
|
33
|
+
// better than a deadlock in whatever it was doing.
|
|
34
|
+
throw std::logic_error(
|
|
35
|
+
"basalt: invokeSync on the runtime-runner CallInvoker. This invoker is "
|
|
36
|
+
"ReactHost::runOnRuntimeScheduler, which posts and cannot run work inline; "
|
|
37
|
+
"see core/JsRuntimeAccess.h.");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
} // namespace
|
|
42
|
+
|
|
43
|
+
void setRuntimeRunner(RuntimeRunner function) {
|
|
44
|
+
std::lock_guard<std::mutex> guard(lock());
|
|
45
|
+
runner() = std::move(function);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
bool runOnJsRuntime(std::function<void(facebook::jsi::Runtime &)> work) {
|
|
49
|
+
// Copied out under the lock and called outside it: the runner posts to another
|
|
50
|
+
// thread, and holding a lock across a call into the host is how the drop and
|
|
51
|
+
// key listeners were careful too.
|
|
52
|
+
RuntimeRunner function;
|
|
53
|
+
{
|
|
54
|
+
std::lock_guard<std::mutex> guard(lock());
|
|
55
|
+
function = runner();
|
|
56
|
+
}
|
|
57
|
+
if (!function) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function(std::move(work));
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker() {
|
|
65
|
+
static std::shared_ptr<facebook::react::CallInvoker> invoker =
|
|
66
|
+
std::make_shared<RunnerCallInvoker>();
|
|
67
|
+
return invoker;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} // namespace basalt
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Reaching the JavaScript thread from native code that is not a TurboModule.
|
|
2
|
+
//
|
|
3
|
+
// A TurboModule is handed a CallInvoker when it is constructed, which is how
|
|
4
|
+
// every module here answers asynchronously: the drop listener, the key listener
|
|
5
|
+
// and the file dialogs all capture `jsInvoker_` and call back through it. An
|
|
6
|
+
// Expo module is not a TurboModule -- it is a plain jsi object installed into
|
|
7
|
+
// `globalThis.expo.modules`, built by a function that is given a runtime and
|
|
8
|
+
// nothing else (see ExpoModules.h) -- so it has no invoker to capture and no way
|
|
9
|
+
// to emit an event or settle a promise later.
|
|
10
|
+
//
|
|
11
|
+
// Until now nothing needed one, because every Expo module here was synchronous
|
|
12
|
+
// underneath and JsiPromise.h's already-settled promises were the honest answer.
|
|
13
|
+
// The first module with a subprocess in it breaks that: output arrives on a
|
|
14
|
+
// reader thread minutes after the call that started it returned.
|
|
15
|
+
//
|
|
16
|
+
// What the hosts have is `ReactHost::runOnRuntimeScheduler`, which is the same
|
|
17
|
+
// door the reload path and the dev menu already go through. So each host leaves
|
|
18
|
+
// it here as it starts, the way it leaves the event-listener installer in
|
|
19
|
+
// UIManagerAccess.h, and core code that needs the JavaScript thread asks for it
|
|
20
|
+
// by name rather than by owning a pointer to the host.
|
|
21
|
+
//
|
|
22
|
+
// **Asynchronous only.** `runOnRuntimeScheduler` posts; it cannot run work
|
|
23
|
+
// inline and wait. `jsCallInvoker()` below is a CallInvoker over it, which makes
|
|
24
|
+
// React Native's own AsyncPromise available to an Expo module, and its
|
|
25
|
+
// `invokeSync` throws rather than pretending: a synchronous call from another
|
|
26
|
+
// thread into a runtime that is busy is a deadlock, and the invoker that offers
|
|
27
|
+
// it would be lying about what this door can do.
|
|
28
|
+
|
|
29
|
+
#pragma once
|
|
30
|
+
|
|
31
|
+
#include <ReactCommon/CallInvoker.h>
|
|
32
|
+
|
|
33
|
+
#include <jsi/jsi.h>
|
|
34
|
+
|
|
35
|
+
#include <functional>
|
|
36
|
+
#include <memory>
|
|
37
|
+
|
|
38
|
+
namespace basalt {
|
|
39
|
+
|
|
40
|
+
// Posts `work` to the JavaScript thread. Anything that touches a runtime from
|
|
41
|
+
// another thread goes through here.
|
|
42
|
+
using RuntimeRunner = std::function<void(std::function<void(facebook::jsi::Runtime &)>)>;
|
|
43
|
+
|
|
44
|
+
// Called by each host once its ReactHost exists, and again with nullptr as it
|
|
45
|
+
// shuts down -- work posted to a host that is going away must not run.
|
|
46
|
+
void setRuntimeRunner(RuntimeRunner runner);
|
|
47
|
+
|
|
48
|
+
// False when no host left one, which is every build that is not a host: the
|
|
49
|
+
// tests and the portability probe. The caller's `work` is then never run, and a
|
|
50
|
+
// caller that has something to report is the one that decides whether silence is
|
|
51
|
+
// acceptable.
|
|
52
|
+
bool runOnJsRuntime(std::function<void(facebook::jsi::Runtime &)> work);
|
|
53
|
+
|
|
54
|
+
// The above as a CallInvoker, for React Native's bridging helpers -- chiefly
|
|
55
|
+
// `AsyncPromise`, which is how a native call that finishes later hands back a
|
|
56
|
+
// promise. One instance, shared; it holds nothing but the call to
|
|
57
|
+
// runOnJsRuntime, so it stays valid across a reload that replaces the runner.
|
|
58
|
+
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker();
|
|
59
|
+
|
|
60
|
+
} // namespace basalt
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Settled promises, built the only way a jsi host function can build one:
|
|
2
|
+
// through JavaScript's own `Promise`.
|
|
3
|
+
//
|
|
4
|
+
// Every asynchronous native API on this platform is synchronous underneath --
|
|
5
|
+
// reading the clipboard, registering a font, asking whether a URL can be opened
|
|
6
|
+
// are all immediate calls. The JavaScript signatures are async because they are
|
|
7
|
+
// async *somewhere*, usually on a platform where the work crosses a process
|
|
8
|
+
// boundary. So the honest implementation is to do the work and hand back a
|
|
9
|
+
// promise that is already settled, rather than to invent a thread.
|
|
10
|
+
//
|
|
11
|
+
// A header rather than a file because these are three lines each and the
|
|
12
|
+
// alternative -- a fourth copy in the next module -- is how the first three
|
|
13
|
+
// happened.
|
|
14
|
+
|
|
15
|
+
#pragma once
|
|
16
|
+
|
|
17
|
+
#include <jsi/jsi.h>
|
|
18
|
+
|
|
19
|
+
#include <string>
|
|
20
|
+
|
|
21
|
+
namespace basalt {
|
|
22
|
+
|
|
23
|
+
// `Promise.resolve(value)`.
|
|
24
|
+
inline facebook::jsi::Value resolved(facebook::jsi::Runtime &runtime,
|
|
25
|
+
facebook::jsi::Value &&value) {
|
|
26
|
+
return runtime.global()
|
|
27
|
+
.getPropertyAsObject(runtime, "Promise")
|
|
28
|
+
.getPropertyAsFunction(runtime, "resolve")
|
|
29
|
+
.call(runtime, std::move(value));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `Promise.resolve()`, for a function whose result is that it finished.
|
|
33
|
+
inline facebook::jsi::Value resolved(facebook::jsi::Runtime &runtime) {
|
|
34
|
+
return resolved(runtime, facebook::jsi::Value::undefined());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// `Promise.reject(new Error(message))`. An Error rather than a string: a
|
|
38
|
+
// rejection reason that is not an Error loses its stack, and every JavaScript
|
|
39
|
+
// caller assumes `error.message`.
|
|
40
|
+
inline facebook::jsi::Value rejected(facebook::jsi::Runtime &runtime,
|
|
41
|
+
const std::string &message) {
|
|
42
|
+
facebook::jsi::Function error = runtime.global().getPropertyAsFunction(runtime, "Error");
|
|
43
|
+
facebook::jsi::Value reason =
|
|
44
|
+
error.callAsConstructor(runtime, facebook::jsi::String::createFromUtf8(runtime, message));
|
|
45
|
+
return runtime.global()
|
|
46
|
+
.getPropertyAsObject(runtime, "Promise")
|
|
47
|
+
.getPropertyAsFunction(runtime, "reject")
|
|
48
|
+
.call(runtime, std::move(reason));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} // namespace basalt
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#include "KeyEvents.h"
|
|
2
|
+
|
|
3
|
+
#include <mutex>
|
|
4
|
+
#include <unordered_map>
|
|
5
|
+
#include <utility>
|
|
6
|
+
|
|
7
|
+
namespace basalt {
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
struct Registry {
|
|
11
|
+
std::mutex mutex;
|
|
12
|
+
std::unordered_map<facebook::react::Tag, std::vector<KeyCombination>> claims;
|
|
13
|
+
std::function<void(facebook::react::Tag, const KeyCombination &)> listener;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
Registry ®istry() {
|
|
17
|
+
static Registry shared;
|
|
18
|
+
return shared;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
} // namespace
|
|
22
|
+
|
|
23
|
+
void setHandledKeys(facebook::react::Tag tag, std::vector<KeyCombination> combinations) {
|
|
24
|
+
Registry &state = registry();
|
|
25
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
26
|
+
// Assigned, not merged. A re-render hands over the whole list, and merging
|
|
27
|
+
// would keep a shortcut alive after the app stopped declaring it -- which
|
|
28
|
+
// would look like the shortcut working and be a leak of behaviour.
|
|
29
|
+
state.claims[tag] = std::move(combinations);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void clearHandledKeys(facebook::react::Tag tag) {
|
|
33
|
+
Registry &state = registry();
|
|
34
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
35
|
+
state.claims.erase(tag);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
bool handlesKey(facebook::react::Tag tag, const KeyCombination &pressed) {
|
|
39
|
+
Registry &state = registry();
|
|
40
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
41
|
+
const auto it = state.claims.find(tag);
|
|
42
|
+
if (it == state.claims.end()) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
for (const KeyCombination &claimed : it->second) {
|
|
46
|
+
if (claimed == pressed) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::optional<facebook::react::Tag> handledBy(
|
|
54
|
+
const std::vector<facebook::react::Tag> &path, const KeyCombination &pressed) {
|
|
55
|
+
Registry &state = registry();
|
|
56
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
57
|
+
if (state.claims.empty()) {
|
|
58
|
+
return std::nullopt;
|
|
59
|
+
}
|
|
60
|
+
// In order, which is innermost first: the caller walks outwards from the
|
|
61
|
+
// focused view, so the first claim found is the most specific one.
|
|
62
|
+
for (const facebook::react::Tag tag : path) {
|
|
63
|
+
const auto it = state.claims.find(tag);
|
|
64
|
+
if (it == state.claims.end()) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const KeyCombination &claimed : it->second) {
|
|
68
|
+
if (claimed == pressed) {
|
|
69
|
+
return tag;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return std::nullopt;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
std::optional<facebook::react::Tag> handledByAny(const KeyCombination &pressed) {
|
|
77
|
+
Registry &state = registry();
|
|
78
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
79
|
+
for (const auto &[tag, combinations] : state.claims) {
|
|
80
|
+
for (const KeyCombination &claimed : combinations) {
|
|
81
|
+
if (claimed == pressed) {
|
|
82
|
+
return tag;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return std::nullopt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
std::size_t handledKeyViewCount() {
|
|
90
|
+
Registry &state = registry();
|
|
91
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
92
|
+
return state.claims.size();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void setKeyListener(std::function<void(facebook::react::Tag, const KeyCombination &)> listener) {
|
|
96
|
+
Registry &state = registry();
|
|
97
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
98
|
+
state.listener = std::move(listener);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void reportKey(facebook::react::Tag tag, const KeyCombination &pressed) {
|
|
102
|
+
std::function<void(facebook::react::Tag, const KeyCombination &)> listener;
|
|
103
|
+
{
|
|
104
|
+
Registry &state = registry();
|
|
105
|
+
const std::lock_guard<std::mutex> lock(state.mutex);
|
|
106
|
+
listener = state.listener;
|
|
107
|
+
}
|
|
108
|
+
// Outside the lock, as core/DragAndDrop.cpp does and for the same reason: the
|
|
109
|
+
// listener reaches JavaScript, and holding a lock across that invites the
|
|
110
|
+
// deadlock where JavaScript re-enters to change its own claims.
|
|
111
|
+
if (listener) {
|
|
112
|
+
listener(tag, pressed);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
} // namespace basalt
|