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.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/basalt.d.ts +3 -0
  3. package/dist/cli/basalt.d.ts.map +1 -0
  4. package/dist/cli/basalt.js +47 -0
  5. package/dist/cli/basalt.js.map +1 -0
  6. package/dist/cli/bundle-cli.d.ts +12 -0
  7. package/dist/cli/bundle-cli.d.ts.map +1 -0
  8. package/dist/cli/bundle-cli.js +124 -0
  9. package/dist/cli/bundle-cli.js.map +1 -0
  10. package/dist/cli/bundleWithAssets.d.ts +79 -0
  11. package/dist/cli/bundleWithAssets.d.ts.map +1 -0
  12. package/dist/cli/bundleWithAssets.js +198 -0
  13. package/dist/cli/bundleWithAssets.js.map +1 -0
  14. package/dist/cli/desktop.d.ts +339 -0
  15. package/dist/cli/desktop.d.ts.map +1 -0
  16. package/dist/cli/desktop.js +1029 -0
  17. package/dist/cli/desktop.js.map +1 -0
  18. package/dist/cli/doctor.d.ts +31 -0
  19. package/dist/cli/doctor.d.ts.map +1 -0
  20. package/dist/cli/doctor.js +283 -0
  21. package/dist/cli/doctor.js.map +1 -0
  22. package/dist/cli/init.d.ts +77 -0
  23. package/dist/cli/init.d.ts.map +1 -0
  24. package/dist/cli/init.js +442 -0
  25. package/dist/cli/init.js.map +1 -0
  26. package/dist/cli/metro.d.ts +49 -0
  27. package/dist/cli/metro.d.ts.map +1 -0
  28. package/dist/cli/metro.js +148 -0
  29. package/dist/cli/metro.js.map +1 -0
  30. package/dist/cli/packageApp.d.ts +102 -0
  31. package/dist/cli/packageApp.d.ts.map +1 -0
  32. package/dist/cli/packageApp.js +319 -0
  33. package/dist/cli/packageApp.js.map +1 -0
  34. package/dist/cli/steps.d.ts +34 -0
  35. package/dist/cli/steps.d.ts.map +1 -0
  36. package/dist/cli/steps.js +53 -0
  37. package/dist/cli/steps.js.map +1 -0
  38. package/dist/metro-config.d.ts +168 -0
  39. package/dist/metro-config.d.ts.map +1 -0
  40. package/dist/metro-config.js +671 -0
  41. package/dist/metro-config.js.map +1 -0
  42. package/dist/react-native.config.d.ts +7 -0
  43. package/dist/react-native.config.d.ts.map +1 -0
  44. package/dist/react-native.config.js +80 -0
  45. package/dist/react-native.config.js.map +1 -0
  46. package/dist/src/DragSource.d.ts +52 -0
  47. package/dist/src/DragSource.d.ts.map +1 -0
  48. package/dist/src/DragSource.js +97 -0
  49. package/dist/src/DragSource.js.map +1 -0
  50. package/dist/src/DropTarget.d.ts +58 -0
  51. package/dist/src/DropTarget.d.ts.map +1 -0
  52. package/dist/src/DropTarget.js +131 -0
  53. package/dist/src/DropTarget.js.map +1 -0
  54. package/dist/src/KeyHandler.d.ts +57 -0
  55. package/dist/src/KeyHandler.d.ts.map +1 -0
  56. package/dist/src/KeyHandler.js +48 -0
  57. package/dist/src/KeyHandler.js.map +1 -0
  58. package/dist/src/Menu.d.ts +72 -0
  59. package/dist/src/Menu.d.ts.map +1 -0
  60. package/dist/src/Menu.js +179 -0
  61. package/dist/src/Menu.js.map +1 -0
  62. package/dist/src/TitleBar.d.ts +75 -0
  63. package/dist/src/TitleBar.d.ts.map +1 -0
  64. package/dist/src/TitleBar.js +236 -0
  65. package/dist/src/TitleBar.js.map +1 -0
  66. package/dist/src/Window.d.ts +87 -0
  67. package/dist/src/Window.d.ts.map +1 -0
  68. package/dist/src/Window.js +248 -0
  69. package/dist/src/Window.js.map +1 -0
  70. package/dist/src/closeRequest.d.ts +55 -0
  71. package/dist/src/closeRequest.d.ts.map +1 -0
  72. package/dist/src/closeRequest.js +134 -0
  73. package/dist/src/closeRequest.js.map +1 -0
  74. package/dist/src/index.d.ts +45 -0
  75. package/dist/src/index.d.ts.map +1 -0
  76. package/dist/src/index.js +92 -0
  77. package/dist/src/index.js.map +1 -0
  78. package/dist/src/overrides/Alert.d.ts +69 -0
  79. package/dist/src/overrides/Alert.d.ts.map +1 -0
  80. package/dist/src/overrides/Alert.js +112 -0
  81. package/dist/src/overrides/Alert.js.map +1 -0
  82. package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
  83. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
  84. package/dist/src/overrides/BaseViewConfig.js +72 -0
  85. package/dist/src/overrides/BaseViewConfig.js.map +1 -0
  86. package/dist/src/overrides/Platform.linux.d.ts +10 -0
  87. package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
  88. package/dist/src/overrides/Platform.linux.js +15 -0
  89. package/dist/src/overrides/Platform.linux.js.map +1 -0
  90. package/dist/src/overrides/Platform.macos.d.ts +10 -0
  91. package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
  92. package/dist/src/overrides/Platform.macos.js +15 -0
  93. package/dist/src/overrides/Platform.macos.js.map +1 -0
  94. package/dist/src/overrides/Platform.windows.d.ts +10 -0
  95. package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
  96. package/dist/src/overrides/Platform.windows.js +15 -0
  97. package/dist/src/overrides/Platform.windows.js.map +1 -0
  98. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
  99. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
  100. package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
  101. package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
  102. package/dist/src/overrides/RefreshControl.d.ts +52 -0
  103. package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
  104. package/dist/src/overrides/RefreshControl.js +92 -0
  105. package/dist/src/overrides/RefreshControl.js.map +1 -0
  106. package/dist/src/overrides/ScrollView.d.ts +39 -0
  107. package/dist/src/overrides/ScrollView.d.ts.map +1 -0
  108. package/dist/src/overrides/ScrollView.js +59 -0
  109. package/dist/src/overrides/ScrollView.js.map +1 -0
  110. package/dist/src/overrides/Share.d.ts +96 -0
  111. package/dist/src/overrides/Share.d.ts.map +1 -0
  112. package/dist/src/overrides/Share.js +97 -0
  113. package/dist/src/overrides/Share.js.map +1 -0
  114. package/dist/src/overrides/TextInput.d.ts +81 -0
  115. package/dist/src/overrides/TextInput.d.ts.map +1 -0
  116. package/dist/src/overrides/TextInput.js +213 -0
  117. package/dist/src/overrides/TextInput.js.map +1 -0
  118. package/dist/src/overrides/View.d.ts +36 -0
  119. package/dist/src/overrides/View.d.ts.map +1 -0
  120. package/dist/src/overrides/View.js +99 -0
  121. package/dist/src/overrides/View.js.map +1 -0
  122. package/dist/src/overrides/createPlatform.d.ts +32 -0
  123. package/dist/src/overrides/createPlatform.d.ts.map +1 -0
  124. package/dist/src/overrides/createPlatform.js +84 -0
  125. package/dist/src/overrides/createPlatform.js.map +1 -0
  126. package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
  127. package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
  128. package/dist/src/overrides/setUpDeveloperTools.js +84 -0
  129. package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
  130. package/dist/src/overrides/setUpXHR.d.ts +46 -0
  131. package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
  132. package/dist/src/overrides/setUpXHR.js +236 -0
  133. package/dist/src/overrides/setUpXHR.js.map +1 -0
  134. package/dist/src/quitRequest.d.ts +55 -0
  135. package/dist/src/quitRequest.d.ts.map +1 -0
  136. package/dist/src/quitRequest.js +121 -0
  137. package/dist/src/quitRequest.js.map +1 -0
  138. package/dist/src/resourcePath.d.ts +18 -0
  139. package/dist/src/resourcePath.d.ts.map +1 -0
  140. package/dist/src/resourcePath.js +31 -0
  141. package/dist/src/resourcePath.js.map +1 -0
  142. package/dist/src/responseBody.d.ts +76 -0
  143. package/dist/src/responseBody.d.ts.map +1 -0
  144. package/dist/src/responseBody.js +94 -0
  145. package/dist/src/responseBody.js.map +1 -0
  146. package/dist/src/responseHeaders.d.ts +40 -0
  147. package/dist/src/responseHeaders.d.ts.map +1 -0
  148. package/dist/src/responseHeaders.js +75 -0
  149. package/dist/src/responseHeaders.js.map +1 -0
  150. package/dist/src/titleBarState.d.ts +43 -0
  151. package/dist/src/titleBarState.d.ts.map +1 -0
  152. package/dist/src/titleBarState.js +77 -0
  153. package/dist/src/titleBarState.js.map +1 -0
  154. package/dist/src/useContextMenu.d.ts +113 -0
  155. package/dist/src/useContextMenu.d.ts.map +1 -0
  156. package/dist/src/useContextMenu.js +109 -0
  157. package/dist/src/useContextMenu.js.map +1 -0
  158. package/dist/src/useDialog.d.ts +98 -0
  159. package/dist/src/useDialog.d.ts.map +1 -0
  160. package/dist/src/useDialog.js +112 -0
  161. package/dist/src/useDialog.js.map +1 -0
  162. package/dist/src/useDisplays.d.ts +70 -0
  163. package/dist/src/useDisplays.d.ts.map +1 -0
  164. package/dist/src/useDisplays.js +118 -0
  165. package/dist/src/useDisplays.js.map +1 -0
  166. package/dist/src/useHandledKeys.d.ts +38 -0
  167. package/dist/src/useHandledKeys.d.ts.map +1 -0
  168. package/dist/src/useHandledKeys.js +70 -0
  169. package/dist/src/useHandledKeys.js.map +1 -0
  170. package/dist/src/useWindow.d.ts +140 -0
  171. package/dist/src/useWindow.d.ts.map +1 -0
  172. package/dist/src/useWindow.js +233 -0
  173. package/dist/src/useWindow.js.map +1 -0
  174. package/dist/tsconfig.tsbuildinfo +1 -0
  175. package/native/CMakeLists.txt +355 -0
  176. package/native/bootstrap.sh +613 -0
  177. package/native/cmake/CompilerFlags.cmake +49 -0
  178. package/native/cmake/Expo.cmake +125 -0
  179. package/native/cmake/ReactNativeCore.cmake +612 -0
  180. package/native/cmake/Reanimated.cmake +108 -0
  181. package/native/cmake/Skia.cmake +379 -0
  182. package/native/cmake/ThirdParty.cmake +290 -0
  183. package/native/cmake/Worklets.cmake +67 -0
  184. package/native/cmake/codegen/CMakeLists.txt +32 -0
  185. package/native/core/AppIdentity.cpp +85 -0
  186. package/native/core/AppIdentity.h +59 -0
  187. package/native/core/AppPaths.cpp +96 -0
  188. package/native/core/AppPaths.h +53 -0
  189. package/native/core/AppearanceModule.cpp +60 -0
  190. package/native/core/AppearanceModule.h +40 -0
  191. package/native/core/Backface.h +59 -0
  192. package/native/core/BlobModule.cpp +283 -0
  193. package/native/core/BlobModule.h +76 -0
  194. package/native/core/BlobRegistry.cpp +75 -0
  195. package/native/core/BlobRegistry.h +44 -0
  196. package/native/core/ColorScheme.cpp +105 -0
  197. package/native/core/ColorScheme.h +59 -0
  198. package/native/core/ComponentRegistry.h +30 -0
  199. package/native/core/ControlMetrics.h +37 -0
  200. package/native/core/CoreModules.cpp +592 -0
  201. package/native/core/CoreModules.h +308 -0
  202. package/native/core/DebuggingOverlay.cpp +106 -0
  203. package/native/core/DebuggingOverlay.h +65 -0
  204. package/native/core/DesktopControls.cpp +232 -0
  205. package/native/core/DesktopControls.h +170 -0
  206. package/native/core/DevBundle.cpp +108 -0
  207. package/native/core/DevBundle.h +69 -0
  208. package/native/core/DevMenu.cpp +99 -0
  209. package/native/core/DevMenu.h +75 -0
  210. package/native/core/DialogModule.cpp +150 -0
  211. package/native/core/DialogModule.h +51 -0
  212. package/native/core/DragAndDrop.cpp +45 -0
  213. package/native/core/DragAndDrop.h +225 -0
  214. package/native/core/ExpoImageComponent.cpp +79 -0
  215. package/native/core/ExpoImageComponent.h +80 -0
  216. package/native/core/ExpoModules.cpp +415 -0
  217. package/native/core/ExpoModules.h +115 -0
  218. package/native/core/ExpoRuntime.cpp +366 -0
  219. package/native/core/ExpoRuntime.h +42 -0
  220. package/native/core/FocusRing.h +36 -0
  221. package/native/core/FontRegistry.h +51 -0
  222. package/native/core/GestureHandlerModule.cpp +136 -0
  223. package/native/core/GestureHandlerModule.h +71 -0
  224. package/native/core/Gestures.cpp +533 -0
  225. package/native/core/Gestures.h +145 -0
  226. package/native/core/HoverTracker.cpp +69 -0
  227. package/native/core/HoverTracker.h +109 -0
  228. package/native/core/HttpClient.cpp +282 -0
  229. package/native/core/ImageBytes.cpp +205 -0
  230. package/native/core/ImageBytes.h +29 -0
  231. package/native/core/ImageCache.cpp +51 -0
  232. package/native/core/ImageCache.h +89 -0
  233. package/native/core/JsRuntimeAccess.cpp +70 -0
  234. package/native/core/JsRuntimeAccess.h +60 -0
  235. package/native/core/JsiPromise.h +51 -0
  236. package/native/core/KeyEvents.cpp +116 -0
  237. package/native/core/KeyEvents.h +144 -0
  238. package/native/core/LogBoxSurface.cpp +46 -0
  239. package/native/core/LogBoxSurface.h +68 -0
  240. package/native/core/MenuModel.h +117 -0
  241. package/native/core/MenuModule.cpp +209 -0
  242. package/native/core/MenuModule.h +66 -0
  243. package/native/core/MountingWalk.h +524 -0
  244. package/native/core/PackageModules.cpp.in +25 -0
  245. package/native/core/PackageModules.h +50 -0
  246. package/native/core/PlatformConstantsModule.cpp +63 -0
  247. package/native/core/PlatformConstantsModule.h +64 -0
  248. package/native/core/PlatformServices.h +213 -0
  249. package/native/core/PointerButtons.h +63 -0
  250. package/native/core/PullToRefresh.cpp +57 -0
  251. package/native/core/PullToRefresh.h +73 -0
  252. package/native/core/ReanimatedCompat.h +47 -0
  253. package/native/core/ReanimatedModule.cpp +203 -0
  254. package/native/core/ReanimatedModule.h +55 -0
  255. package/native/core/ScrollAnimation.cpp +59 -0
  256. package/native/core/ScrollAnimation.h +70 -0
  257. package/native/core/ScrollBounds.cpp +26 -0
  258. package/native/core/ScrollBounds.h +68 -0
  259. package/native/core/ScrollIndicator.cpp +50 -0
  260. package/native/core/ScrollIndicator.h +69 -0
  261. package/native/core/ScrollMomentum.cpp +71 -0
  262. package/native/core/ScrollMomentum.h +73 -0
  263. package/native/core/ScrollSnap.cpp +123 -0
  264. package/native/core/ScrollSnap.h +80 -0
  265. package/native/core/ShareFallback.cpp +101 -0
  266. package/native/core/ShareFallback.h +45 -0
  267. package/native/core/SkiaPictureViewComponent.cpp +11 -0
  268. package/native/core/SkiaPictureViewComponent.h +47 -0
  269. package/native/core/SourceCodeModule.cpp +55 -0
  270. package/native/core/SourceCodeModule.h +53 -0
  271. package/native/core/StatusBarModule.cpp +23 -0
  272. package/native/core/StatusBarModule.h +42 -0
  273. package/native/core/TestDialog.cpp +148 -0
  274. package/native/core/TestDialog.h +96 -0
  275. package/native/core/TestKeys.cpp +78 -0
  276. package/native/core/TestKeys.h +50 -0
  277. package/native/core/TestQuitFile.cpp +33 -0
  278. package/native/core/TestQuitFile.h +60 -0
  279. package/native/core/TestSettle.h +192 -0
  280. package/native/core/TitleBarRegions.h +31 -0
  281. package/native/core/TurboModuleProxy.cpp +51 -0
  282. package/native/core/TurboModuleProxy.h +46 -0
  283. package/native/core/UIManagerAccess.cpp +43 -0
  284. package/native/core/UIManagerAccess.h +43 -0
  285. package/native/core/WindowBoundsCache.cpp +319 -0
  286. package/native/core/WindowControl.h +235 -0
  287. package/native/core/WindowHost.h +158 -0
  288. package/native/core/WindowMethods.cpp +167 -0
  289. package/native/core/WindowMethods.h +84 -0
  290. package/native/core/WindowsModule.cpp +448 -0
  291. package/native/core/WindowsModule.h +133 -0
  292. package/native/core/WorkletsModule.cpp +199 -0
  293. package/native/core/WorkletsModule.h +57 -0
  294. package/native/core/portability_probe.cpp +210 -0
  295. package/native/tests/EventRecorder.h +124 -0
  296. package/native/tests/TestHarness.cpp +109 -0
  297. package/native/tests/TestHarness.h +89 -0
  298. package/native/tests/TreeDump.h +49 -0
  299. package/native/tests/test_app_identity.cpp +114 -0
  300. package/native/tests/test_app_paths.cpp +48 -0
  301. package/native/tests/test_appearance.cpp +107 -0
  302. package/native/tests/test_backface.cpp +79 -0
  303. package/native/tests/test_blobs.cpp +157 -0
  304. package/native/tests/test_controls.cpp +185 -0
  305. package/native/tests/test_devbundle.cpp +103 -0
  306. package/native/tests/test_drag_and_drop.cpp +143 -0
  307. package/native/tests/test_gestures.cpp +331 -0
  308. package/native/tests/test_hover.cpp +92 -0
  309. package/native/tests/test_image_cache.cpp +109 -0
  310. package/native/tests/test_js_runtime_access.cpp +137 -0
  311. package/native/tests/test_key_events.cpp +235 -0
  312. package/native/tests/test_momentum.cpp +138 -0
  313. package/native/tests/test_quit_file.cpp +104 -0
  314. package/native/tests/test_scroll_animation.cpp +120 -0
  315. package/native/tests/test_scroll_bounds.cpp +66 -0
  316. package/native/tests/test_scroll_indicator.cpp +149 -0
  317. package/native/tests/test_scroll_snap.cpp +112 -0
  318. package/native/tests/test_settle.cpp +115 -0
  319. package/native/tests/test_share.cpp +75 -0
  320. package/native/tests/test_sourcecode.cpp +62 -0
  321. package/native/tests/test_window_close.cpp +119 -0
  322. package/native/tests/test_window_limits.cpp +123 -0
  323. package/package.json +43 -0
  324. package/react-native.config.js +86 -0
  325. 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 &registry() {
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