@symbiote-native/engine 1.3.0 → 1.4.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/build/accessibility-props.d.ts +0 -11
- package/build/accessibility-props.js +30 -68
- package/build/animated/graph.js +1 -1
- package/build/animated/leaf-lifecycle.js +2 -2
- package/build/asset-source-resolver.d.ts +2 -0
- package/build/asset-source-resolver.js +13 -0
- package/build/back-handler/index.d.ts +1 -5
- package/build/back-handler/index.js +0 -6
- package/build/debug.js +8 -22
- package/build/dispatch.js +3 -9
- package/build/events/delivery.d.ts +9 -0
- package/build/events/delivery.js +143 -0
- package/build/events/index.js +199 -660
- package/build/events/names.d.ts +24 -0
- package/build/events/names.js +80 -0
- package/build/events/press.d.ts +20 -0
- package/build/events/press.js +89 -0
- package/build/events/responder.d.ts +6 -0
- package/build/events/responder.js +124 -0
- package/build/fabric-props.js +74 -179
- package/build/fabric.d.ts +0 -13
- package/build/fabric.js +18 -38
- package/build/host-access.d.ts +1 -128
- package/build/host-access.js +96 -205
- package/build/host-behavior.d.ts +0 -100
- package/build/host-behavior.js +125 -311
- package/build/image-loader.js +10 -23
- package/build/image-source-resolver.js +3 -7
- package/build/image-source-write.d.ts +0 -11
- package/build/image-source-write.js +14 -34
- package/build/imperative.d.ts +2 -28
- package/build/imperative.js +60 -93
- package/build/index.d.ts +6 -2
- package/build/index.js +29 -39
- package/build/mutation-buffer.d.ts +3 -177
- package/build/mutation-buffer.js +162 -316
- package/build/native-engine.d.ts +6 -102
- package/build/native-engine.js +60 -141
- package/build/native-events.js +9 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +15 -31
- package/build/node-events.d.ts +11 -0
- package/build/node-events.js +145 -0
- package/build/node-instance.d.ts +8 -0
- package/build/node-instance.js +168 -0
- package/build/node-props.d.ts +13 -0
- package/build/node-props.js +131 -0
- package/build/node-route.d.ts +2 -0
- package/build/node-route.js +151 -0
- package/build/node-style.d.ts +15 -0
- package/build/node-style.js +214 -0
- package/build/node-tree.d.ts +6 -0
- package/build/node-tree.js +159 -0
- package/build/node-types.d.ts +70 -0
- package/build/node-types.js +36 -0
- package/build/node.d.ts +7 -309
- package/build/node.js +9 -1564
- package/build/post-commit.js +3 -8
- package/build/process-aspect-ratio.js +3 -7
- package/build/process-background-longhands.js +10 -19
- package/build/process-filter.js +11 -19
- package/build/process-font-variant.js +3 -7
- package/build/registry.d.ts +0 -33
- package/build/registry.js +22 -57
- package/build/report-error.js +4 -18
- package/build/structured-style.d.ts +0 -9
- package/build/structured-style.js +16 -31
- package/build/styles.js +3 -6
- package/build/surface.d.ts +0 -26
- package/build/surface.js +29 -76
- package/build/text-input-state.js +4 -8
- package/build/touch-history.js +5 -11
- package/build/tree-host.d.ts +7 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/cpp/SymbioteEngineBindings.cpp +19 -18
- package/cpp/SymbioteTree.cpp +81 -156
- package/cpp/SymbioteTree.h +6 -0
- package/package.json +2 -2
package/build/native-engine.d.ts
CHANGED
|
@@ -1,62 +1,10 @@
|
|
|
1
1
|
import type { ICommittedRecord } from './tree-host';
|
|
2
|
-
/**
|
|
3
|
-
* The ABI this file knows how to talk to. A binary reporting anything else is refused outright rather
|
|
4
|
-
* than probed method by method — the two artefacts ship separately (a pod and an npm package), so
|
|
5
|
-
* disagreement is routine, and a partial match is the shape that corrupts memory quietly.
|
|
6
|
-
*
|
|
7
|
-
* Exported for the tests, which must DERIVE their supported and unsupported arms from it rather than
|
|
8
|
-
* restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
|
|
9
|
-
* exactly the message this constant exists to produce, so the failure looks like the feature working.
|
|
10
|
-
*/
|
|
11
2
|
export declare const SUPPORTED_NATIVE_VERSION = 4;
|
|
12
|
-
/**
|
|
13
|
-
* What `installJSIBindingsWithRuntime:` puts on the global.
|
|
14
|
-
*
|
|
15
|
-
* `allocInt32Array` hands back a view over memory NATIVE owns — not a copy — which is the only reason
|
|
16
|
-
* any of this exists. Every other route from JS to native structure costs a JSI crossing per element,
|
|
17
|
-
* and the census that sized this design counted ~4 000 reads for a two-row swap.
|
|
18
|
-
*/
|
|
19
3
|
export type INativeEngineBindings = {
|
|
20
4
|
version: number;
|
|
21
5
|
allocInt32Array: (lengthInElements: number) => Int32Array;
|
|
22
|
-
/**
|
|
23
|
-
* Item 8c-1's bring-up probe: shadow trees the real `UIManager` holds, or -1 when we could not
|
|
24
|
-
* reach one at all. Not a capability the engine uses — it exists so one device run answers whether
|
|
25
|
-
* our pod compiles against ReactCommon's renderer, links against the prebuilt framework, and can
|
|
26
|
-
* resolve the UIManager from a plain JSI runtime. See the C++ side for why -1 and 0 differ.
|
|
27
|
-
*/
|
|
28
6
|
probeUIManager: () => number;
|
|
29
|
-
/**
|
|
30
|
-
* Replay one recorded mutation batch — the five fields of `IMutationBatch`, spread, because JSI
|
|
31
|
-
* reads five arguments cheaper than it reads five properties off one object.
|
|
32
|
-
*
|
|
33
|
-
* `ops` is read as MEMORY on the native side and never becomes JS values; the four side tables
|
|
34
|
-
* carry what JSI has to marshal either way.
|
|
35
|
-
*
|
|
36
|
-
* `handles` is the load-bearing one and it travels OUT, not back. It holds the placeholder object
|
|
37
|
-
* for every slot the ops address, and the host attaches each created node's
|
|
38
|
-
* `shared_ptr<const ShadowNode>` to the object at that slot as JSI `NativeState` — so the objects
|
|
39
|
-
* the adapter is already holding become the real handles in place. Nothing is returned.
|
|
40
|
-
*
|
|
41
|
-
* That is the whole lifetime design, and it is RN's own: `nativeFabricUIManager.createNode()`
|
|
42
|
-
* hands back an object whose NativeState owns the node, so Hermes collecting the object is what
|
|
43
|
-
* frees it. The version this replaced kept a `Map<int, shared_ptr>` in C++ instead — a second
|
|
44
|
-
* owner nothing could tell to let go, which cost 792 -> 1492 MB across one benchmark suite.
|
|
45
|
-
*/
|
|
46
7
|
applyOps: (ops: Int32Array, strings: readonly string[], values: readonly unknown[], instanceHandles: readonly unknown[], handles: readonly object[]) => void;
|
|
47
|
-
/**
|
|
48
|
-
* The four reads of `ITreeHost`, taking the same placeholder object `applyOps` put the node on.
|
|
49
|
-
*
|
|
50
|
-
* None is on a commit path — they run at GESTURE or lifecycle rate (a host behavior seeing the
|
|
51
|
-
* props it reacts to, an app measuring a ref, a framework seam navigating what it just built), so
|
|
52
|
-
* the crossing cost is irrelevant. `undefined` / empty is an ordinary answer from all of them: a
|
|
53
|
-
* handle native has not seen and a genuinely absent value are indistinguishable here, and both
|
|
54
|
-
* degrade.
|
|
55
|
-
*
|
|
56
|
-
* `getViewName` answers the RESOLVED name, which native may have changed at insert — a `<Text>`
|
|
57
|
-
* inside another `<Text>` commits as `RCTVirtualText`, and only the side holding the parent link
|
|
58
|
-
* knows.
|
|
59
|
-
*/
|
|
60
8
|
getProp: (handle: object, key: string) => unknown;
|
|
61
9
|
getProps: (handle: object) => Readonly<Record<string, unknown>>;
|
|
62
10
|
/** The one WRITE among them: dirty a node no op named. See `markPropsDirty` (node.ts). */
|
|
@@ -77,63 +25,27 @@ export type INativeEngineBindings = {
|
|
|
77
25
|
committedRecordOf: (handle: object) => ICommittedRecord | undefined;
|
|
78
26
|
/** A TEST read — the payload the last commit sent. See `ITreeHost.committedPayloadOf`. */
|
|
79
27
|
committedPayloadOf: (handle: object) => Readonly<Record<string, unknown>> | undefined;
|
|
80
|
-
/**
|
|
81
|
-
* The imperative six, taking the same placeholder object `applyOps` put the node on.
|
|
82
|
-
*
|
|
83
|
-
* They are here because `nativeFabricUIManager`'s own copies unwrap a handle IT minted, and under
|
|
84
|
-
* the batched applier every handle in play was minted by the batching slot. Ours carry the node on
|
|
85
|
-
* their `NativeState` exactly as RN's do, so these six are the same code reading a different
|
|
86
|
-
* object. An app calling `measure()` on a ref reaches native through here or not at all.
|
|
87
|
-
*
|
|
88
|
-
* The callback protocol is Fabric's own, not ours: `measure` answers six numbers, `measureInWindow`
|
|
89
|
-
* four, and `measureLayout` calls `onFail` when the surface has no committed revision — copied
|
|
90
|
-
* from `UIManagerBinding` so a component that already handles those cases keeps working.
|
|
91
|
-
*/
|
|
92
28
|
dispatchCommand: (handle: object, commandName: string, args: readonly unknown[]) => void;
|
|
93
29
|
sendAccessibilityEvent: (handle: object, eventType: string) => void;
|
|
94
30
|
measure: (handle: object, callback: (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => void) => void;
|
|
95
31
|
measureInWindow: (handle: object, callback: (x: number, y: number, width: number, height: number) => void) => void;
|
|
96
32
|
measureLayout: (handle: object, relativeTo: object, onFail: () => void, onSuccess: (x: number, y: number, width: number, height: number) => void) => void;
|
|
97
33
|
setIsJSResponder: (handle: object, isResponder: boolean, blockNativeResponder: boolean) => void;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*/
|
|
105
|
-
/**
|
|
106
|
-
* The C++ half's diagnostics (`SymbioteDebug.h`). OPTIONAL, like `readSurfaceTelemetry` and for
|
|
107
|
-
* the same reason: an older native binary that predates them must still pass `isBindings` rather
|
|
108
|
-
* than failing bring-up over a member nothing on the critical path needs.
|
|
109
|
-
*/
|
|
34
|
+
getBoundingClientRect?: (handle: object, includeTransform: boolean) => {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
} | undefined;
|
|
110
40
|
setDebugEnabled?: (enabled: boolean) => void;
|
|
111
41
|
takeDebugLog?: () => readonly string[];
|
|
112
42
|
readSurfaceTelemetry?: (surfaceId: number) => {
|
|
113
|
-
/**
|
|
114
|
-
* The inside of a commit, read out of RN's OWN `TransactionTelemetry` rather than timed by us
|
|
115
|
-
* — a `ShadowTreeRevision` carries the telemetry of the commit that produced it.
|
|
116
|
-
*
|
|
117
|
-
* `layoutNodes` is the one that answers the question the timings only pose: Yoga reports how
|
|
118
|
-
* many layoutable nodes it actually touched, so a one-row change that reports the whole tree is
|
|
119
|
-
* a re-layout, not a slow layout.
|
|
120
|
-
*/
|
|
121
43
|
layoutMs: number;
|
|
122
44
|
textMs: number;
|
|
123
|
-
/**
|
|
124
|
-
* `ShadowTree::commit`'s own window, and NOT `materialize`'s — this used to say otherwise, and
|
|
125
|
-
* three rounds of investigation read the number that way. `materialize` runs in `kOpCommit`
|
|
126
|
-
* before `completeSurface` is called, so it falls outside this window and layout's alike.
|
|
127
|
-
*/
|
|
128
45
|
commitMs: number;
|
|
129
46
|
layoutNodes: number;
|
|
130
47
|
textMeasures: number;
|
|
131
|
-
/**
|
|
132
|
-
* Parents that took the targeted-replace path since the last read, zeroed on read. OURS, not
|
|
133
|
-
* RN's — a LIVENESS signal for a fast path whose absence no correctness test can see.
|
|
134
|
-
*/
|
|
135
48
|
targetedReplaces: number;
|
|
136
|
-
/** `materialize`'s own walk and its breakdown — ours, zeroed on read. See `ISurfaceTelemetry`. */
|
|
137
49
|
walkMs: number;
|
|
138
50
|
propsMs: number;
|
|
139
51
|
foldLookupMs: number;
|
|
@@ -173,14 +85,6 @@ export type INativeEngineBindings = {
|
|
|
173
85
|
declare global {
|
|
174
86
|
var __symbioteEngineNative: unknown;
|
|
175
87
|
}
|
|
176
|
-
/**
|
|
177
|
-
* The native bindings, or `undefined` when this platform has none.
|
|
178
|
-
*
|
|
179
|
-
* Resolving the TurboModule is done for its SIDE EFFECT: `RCTTurboModuleManager` runs
|
|
180
|
-
* `installJSIBindingsWithRuntime:` at the moment it creates a module, so touching the module by name
|
|
181
|
-
* is what puts the global there. The module's own methods are not the capability and are not called
|
|
182
|
-
* here — which is why a reader looking for the payload in the spec file will not find it.
|
|
183
|
-
*/
|
|
184
88
|
export declare function nativeEngine(): INativeEngineBindings | undefined;
|
|
185
89
|
/** Test seam: forget what was resolved, so a fixture can install or remove the global between cases. */
|
|
186
90
|
export declare function resetNativeEngine(): void;
|
package/build/native-engine.js
CHANGED
|
@@ -1,157 +1,77 @@
|
|
|
1
|
-
// The JS half of our own native module
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// Two different things get called "the native path" and only one of them is a fork:
|
|
17
|
-
//
|
|
18
|
-
// the STORE (8b') `new Int32Array(n)` vs an `Int32Array` over native memory. `node-table.ts`
|
|
19
|
-
// is byte-identical either way — one implementation, two allocators. There is
|
|
20
|
-
// nothing here that can drift.
|
|
21
|
-
// the APPLIER (8c-1) `replayChildOps` in commit.ts vs `cloneMultiple` inside a commit hook. That
|
|
22
|
-
// IS two implementations of one fold, and a permanent fallback to the JS half
|
|
23
|
-
// would be exactly the silent divergence this repo keeps finding.
|
|
24
|
-
//
|
|
25
|
-
// So the graceful `undefined` below is correct for the store and TRANSITIONAL for the applier. When
|
|
26
|
-
// the applier moves, the JS one is DELETED — the way `node.children` and `node.parent` were — and a
|
|
27
|
-
// missing or mismatched module has to fail LOUD rather than quietly run a second implementation. A
|
|
28
|
-
// silent fallback at that point is a measurement that lies, not resilience.
|
|
29
|
-
//
|
|
30
|
-
// The expensive part of that transition, named here because it is easy to discover too late: ~5 600
|
|
31
|
-
// headless tests drive the JS applier against a fake Fabric slot. Faking `new Int32Array` is free;
|
|
32
|
-
// faking C++ tree cloning is not, so those tests stop covering what actually runs.
|
|
33
|
-
//
|
|
34
|
-
// Fantom was the intended answer and it is NOT available — measured 2026-09-08, four independent
|
|
35
|
-
// blockers. `@react-native/fantom` is not on npm at all (404); it exists only as vendored source
|
|
36
|
-
// under `.vendors/react-native/private/`, marked `private: true`. That vendored tree is RN **main**
|
|
37
|
-
// while this repo consumes 0.86.0, so a tester built from it would exercise the wrong C++. Building
|
|
38
|
-
// it needs a yarn install plus a gradle/CMake build of Hermes, folly and ReactCommon, all written
|
|
39
|
-
// INTO `.vendors/`. And `config/metro.config.js` computes `projectRoot` from `__dirname` with an
|
|
40
|
-
// empty `watchFolders`, so a test file outside that monorepo cannot be resolved — not overridable
|
|
41
|
-
// by env, config merge or CLI. RN's own README says it plainly: "not currently supported for
|
|
42
|
-
// testing application-specific code in React Native apps."
|
|
43
|
-
//
|
|
44
|
-
// So the oracle for a C++ commit path has to be built here: a TypeScript reference implementation
|
|
1
|
+
// The JS half of our own native module. It answers one question — is there a native store on this
|
|
2
|
+
// platform — and every caller must be able to take no. no is the majority case: headless tests,
|
|
3
|
+
// Android (iOS-only for now), a skipped pod install, or a pod/JS version mismatch.
|
|
4
|
+
// nativeEngine() returns the bindings or undefined, and nothing in the engine may require them —
|
|
5
|
+
// the native store is an accelerator behind the same seam the JS one sits behind.
|
|
6
|
+
// Not a licence for two implementations, though. The store (Int32Array vs. native memory) is
|
|
7
|
+
// byte-identical either way, one implementation two allocators — nothing here can drift. The
|
|
8
|
+
// applier (replayChildOps vs. cloneMultiple) IS two implementations of one fold.
|
|
9
|
+
// So undefined below is correct for the store and transitional for the applier: when the applier
|
|
10
|
+
// moves, the JS one is deleted, and a missing or mismatched module must fail loud rather than
|
|
11
|
+
// quietly run a second implementation — a silent fallback then would be a measurement that lies.
|
|
12
|
+
// Fantom (RN's own headless C++ test runner) is not a usable oracle for this: not published to
|
|
13
|
+
// npm, vendored from RN main against a repo pinned to 0.86, needing a from-scratch Hermes/folly/
|
|
14
|
+
// ReactCommon build, and its own README says it isn't supported for app-specific test code yet.
|
|
15
|
+
// So the oracle for a C++ commit path is built here instead: a TypeScript reference implementation
|
|
45
16
|
// the existing suite already exercises, plus a gtest target linking ReactCommon from the installed
|
|
46
|
-
// react-native.
|
|
17
|
+
// react-native.
|
|
47
18
|
import { dlog } from './debug.js';
|
|
48
19
|
import { getNativeModule } from './native-modules/index.js';
|
|
49
20
|
import { isRecord } from './type-guards.js';
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
|
|
57
|
-
* exactly the message this constant exists to produce, so the failure looks like the feature working.
|
|
58
|
-
*/
|
|
21
|
+
// The ABI this file knows how to talk to. A binary reporting anything else is refused outright
|
|
22
|
+
// rather than probed method by method — the two artefacts ship separately (a pod and an npm
|
|
23
|
+
// package), so disagreement is routine and a partial match corrupts memory quietly.
|
|
24
|
+
// Exported for the tests, which must derive their supported/unsupported arms from it rather than
|
|
25
|
+
// restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
|
|
26
|
+
// exactly the message this constant exists to produce.
|
|
59
27
|
export const SUPPORTED_NATIVE_VERSION = 4;
|
|
28
|
+
// A member missing from this list resolves fine at bring-up and throws at the first call site
|
|
29
|
+
// instead. `getBoundingClientRect` stays out on purpose: it is optional on the type.
|
|
30
|
+
const REQUIRED_METHOD_NAMES = [
|
|
31
|
+
'allocInt32Array',
|
|
32
|
+
'probeUIManager',
|
|
33
|
+
'applyOps',
|
|
34
|
+
'getProp',
|
|
35
|
+
'getProps',
|
|
36
|
+
'markPropsDirty',
|
|
37
|
+
'getViewName',
|
|
38
|
+
'parentOf',
|
|
39
|
+
'childrenOf',
|
|
40
|
+
'firstChildOf',
|
|
41
|
+
'nextSiblingOf',
|
|
42
|
+
'parentsOf',
|
|
43
|
+
'subtreesOf',
|
|
44
|
+
'teardownSubtreesOf',
|
|
45
|
+
'ancestorsOf',
|
|
46
|
+
'committedRecordOf',
|
|
47
|
+
'dispatchCommand',
|
|
48
|
+
'sendAccessibilityEvent',
|
|
49
|
+
'measure',
|
|
50
|
+
'measureInWindow',
|
|
51
|
+
'measureLayout',
|
|
52
|
+
'setIsJSResponder',
|
|
53
|
+
];
|
|
60
54
|
function isBindings(value) {
|
|
61
55
|
if (!isRecord(value))
|
|
62
56
|
return false;
|
|
63
57
|
if (typeof value.version !== 'number')
|
|
64
58
|
return false;
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
|
-
if (typeof value.probeUIManager !== 'function')
|
|
68
|
-
return false;
|
|
69
|
-
if (typeof value.applyOps !== 'function')
|
|
70
|
-
return false;
|
|
71
|
-
// The tree reads, checked one by one rather than as a blob for the reason the whole guard is
|
|
72
|
-
// written this way: a member added to the type without a line here resolves fine at bring-up and
|
|
73
|
-
// throws at the first call site, which is a gesture or a `measure()` — one language and several
|
|
74
|
-
// seconds away from the install that caused it.
|
|
75
|
-
if (typeof value.getProp !== 'function')
|
|
76
|
-
return false;
|
|
77
|
-
if (typeof value.getProps !== 'function')
|
|
78
|
-
return false;
|
|
79
|
-
if (typeof value.markPropsDirty !== 'function')
|
|
80
|
-
return false;
|
|
81
|
-
if (typeof value.getViewName !== 'function')
|
|
82
|
-
return false;
|
|
83
|
-
if (typeof value.parentOf !== 'function')
|
|
84
|
-
return false;
|
|
85
|
-
if (typeof value.childrenOf !== 'function')
|
|
86
|
-
return false;
|
|
87
|
-
if (typeof value.firstChildOf !== 'function')
|
|
88
|
-
return false;
|
|
89
|
-
if (typeof value.nextSiblingOf !== 'function')
|
|
90
|
-
return false;
|
|
91
|
-
if (typeof value.parentsOf !== 'function')
|
|
92
|
-
return false;
|
|
93
|
-
if (typeof value.subtreesOf !== 'function')
|
|
94
|
-
return false;
|
|
95
|
-
if (typeof value.teardownSubtreesOf !== 'function')
|
|
96
|
-
return false;
|
|
97
|
-
if (typeof value.ancestorsOf !== 'function')
|
|
98
|
-
return false;
|
|
99
|
-
if (typeof value.committedRecordOf !== 'function')
|
|
100
|
-
return false;
|
|
101
|
-
// The imperative six, checked by name for the same reason as the rest: a pod that has `applyOps`
|
|
102
|
-
// but not these is an OLDER binary, and accepting it would leave `measure()` reaching a method
|
|
103
|
-
// that is not there — at the moment an app measures a ref, not at bring-up.
|
|
104
|
-
if (typeof value.dispatchCommand !== 'function')
|
|
105
|
-
return false;
|
|
106
|
-
if (typeof value.sendAccessibilityEvent !== 'function')
|
|
107
|
-
return false;
|
|
108
|
-
if (typeof value.measure !== 'function')
|
|
109
|
-
return false;
|
|
110
|
-
if (typeof value.measureInWindow !== 'function')
|
|
111
|
-
return false;
|
|
112
|
-
if (typeof value.measureLayout !== 'function')
|
|
113
|
-
return false;
|
|
114
|
-
return typeof value.setIsJSResponder === 'function';
|
|
59
|
+
return REQUIRED_METHOD_NAMES.every(name => typeof value[name] === 'function');
|
|
115
60
|
}
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
// ── AND WHY IT DID MOVE, 1 -> 2, FOR THE HANDLE REWRITE ──────────────────────────────────────────
|
|
124
|
-
//
|
|
125
|
-
// Because the shape guard cannot see this one. Every NAME is unchanged: a v1 pod has `applyOps` and
|
|
126
|
-
// all five, so `isBindings` passes it. What changed is what the arguments MEAN — `applyOps` gained
|
|
127
|
-
// `handles`, which a v1 binary silently ignores, and the five went from taking an integer id to
|
|
128
|
-
// taking the handle object, which a v1 binary reads with `.asNumber()`. The first fails by
|
|
129
|
-
// committing nothing and leaking; the second throws one language away from the cause. That is the
|
|
130
|
-
// same class as a moved field, arriving through a calling convention rather than a struct, and it is
|
|
131
|
-
// exactly what this number exists to refuse.
|
|
132
|
-
//
|
|
133
|
-
// ── AND 3 -> 4, FOR THE SAME REASON THE SHAPE GUARD CANNOT HELP WITH ─────────────────────────────
|
|
134
|
-
//
|
|
135
|
-
// The four tree reads are new NAMES, so the guard above would turn a v3 pod away on its own. But
|
|
136
|
-
// `applyOps` also went from six arguments to five, and the name did not move: a v3 binary reads
|
|
137
|
-
// argument 1 as `childIds` (an `Int32Array`) where JS now passes `strings`, and argument 2 as a
|
|
138
|
-
// props table where JS now passes `values`. Every one of those reads succeeds against the wrong
|
|
139
|
-
// object and commits a wrong tree rather than throwing. That is a calling convention change, which
|
|
140
|
-
// is what this number is for — the guard would have refused this pod by luck, on the reads, and a
|
|
141
|
-
// refusal by luck is not a refusal.
|
|
61
|
+
// SUPPORTED_NATIVE_VERSION guards a memory layout, not a member list: the shape guard above
|
|
62
|
+
// already refuses an older pod lacking a new function's name, so bumping for that alone would
|
|
63
|
+
// cost every app on an older pod its native store for nothing.
|
|
64
|
+
// Bump only when the meaning of existing bytes or arguments changes: a calling-convention change
|
|
65
|
+
// the shape guard can't see, since names stay the same while what they mean does not — every read
|
|
66
|
+
// still resolves to a value, just the wrong one, committing a wrong tree rather than throwing.
|
|
142
67
|
// `undefined` means "resolved, and there is none" — distinct from `resolved === false`, which means
|
|
143
68
|
// nobody has looked. Collapsing the two would re-run the lookup on every miss, and the miss is the
|
|
144
69
|
// common case.
|
|
145
70
|
let resolved = false;
|
|
146
71
|
let bindings;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* Resolving the TurboModule is done for its SIDE EFFECT: `RCTTurboModuleManager` runs
|
|
151
|
-
* `installJSIBindingsWithRuntime:` at the moment it creates a module, so touching the module by name
|
|
152
|
-
* is what puts the global there. The module's own methods are not the capability and are not called
|
|
153
|
-
* here — which is why a reader looking for the payload in the spec file will not find it.
|
|
154
|
-
*/
|
|
72
|
+
// The native bindings, or undefined when this platform has none. Resolving the TurboModule is
|
|
73
|
+
// done for its side effect: RCTTurboModuleManager runs installJSIBindingsWithRuntime: at the
|
|
74
|
+
// moment it creates a module, so touching the module by name is what puts the global there.
|
|
155
75
|
export function nativeEngine() {
|
|
156
76
|
if (resolved)
|
|
157
77
|
return bindings;
|
|
@@ -164,9 +84,8 @@ export function nativeEngine() {
|
|
|
164
84
|
return undefined;
|
|
165
85
|
}
|
|
166
86
|
if (installed.version !== SUPPORTED_NATIVE_VERSION) {
|
|
167
|
-
// Loud, because the repair is a
|
|
168
|
-
// rather than a missing feature
|
|
169
|
-
// replaces the JS half without the native one.
|
|
87
|
+
// Loud, because the repair is a pod install and the symptom otherwise is a wrong memory
|
|
88
|
+
// layout rather than a missing feature — an install routinely replaces the JS half alone.
|
|
170
89
|
dlog(`native-engine: REFUSING native store, ABI ${installed.version} ` +
|
|
171
90
|
`against supported ${SUPPORTED_NATIVE_VERSION} — run \`pod install\`. Using the JS arrays`);
|
|
172
91
|
return undefined;
|
package/build/native-events.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
// native -> JS: receiving native module events
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// RCTDeviceEventEmitter, and `RN$registerCallableModule` cannot steal it: native's
|
|
5
|
-
// `callableModules_.emplace` ignores a duplicate key (ReactInstance.cpp), so a
|
|
6
|
-
// second registration is a silent no-op. So we do NOT register our own hub on a
|
|
7
|
-
// real host: the app injects RN's DeviceEventEmitter (the bus native actually
|
|
8
|
-
// calls) via `setDeviceEventSource`, exactly like setColorProcessor. The built-in
|
|
9
|
-
// hub below stays as the fallback bus for headless/non-RN runs.
|
|
1
|
+
// native -> JS: receiving native module events, all under one callable JS module name. A real
|
|
2
|
+
// host already owns that name, so a second registration is a no-op — the app injects RN's
|
|
3
|
+
// DeviceEventEmitter instead via setDeviceEventSource; below is the fallback for headless runs.
|
|
10
4
|
import { dlog } from './debug.js';
|
|
11
5
|
import { runWrapped } from './dispatch.js';
|
|
12
6
|
import { invariant } from './invariant.js';
|
|
@@ -19,10 +13,9 @@ function emit(eventType, ...args) {
|
|
|
19
13
|
dlog(`device hub emit "${eventType}" -> ${set?.size ?? 0} listener(s)`);
|
|
20
14
|
if (set === undefined)
|
|
21
15
|
return;
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// iterating: a listener may remove itself mid-dispatch.
|
|
16
|
+
// Route through the shared dispatch wrapper so a listener's setState flushes on the sync
|
|
17
|
+
// lane, the same seam Fabric touch events use. Snapshot first: a listener may remove
|
|
18
|
+
// itself mid-dispatch.
|
|
26
19
|
const snapshot = [...set];
|
|
27
20
|
runWrapped(() => {
|
|
28
21
|
for (const listener of snapshot)
|
|
@@ -65,11 +58,9 @@ let injectedSource;
|
|
|
65
58
|
export function setDeviceEventSource(source) {
|
|
66
59
|
injectedSource = source;
|
|
67
60
|
}
|
|
68
|
-
// True only when the module
|
|
69
|
-
//
|
|
70
|
-
// the
|
|
71
|
-
// would throw "undefined is not a function" — the `?.` guards the module, not a
|
|
72
|
-
// missing method. Mirrors RN's NativeEventEmitter constructor probe.
|
|
61
|
+
// True only when the module carries both observe-counter methods. A spec that omits
|
|
62
|
+
// addListener/removeListeners leaves them undefined, so calling through would throw —
|
|
63
|
+
// the guard here protects the module, not a missing method. Mirrors RN's constructor probe.
|
|
73
64
|
function hasObserveCounters(module) {
|
|
74
65
|
return (typeof module.addListener === 'function' &&
|
|
75
66
|
typeof module.removeListeners === 'function');
|
|
@@ -1,25 +1,4 @@
|
|
|
1
1
|
import { type INativeEngineBindings } from './native-engine';
|
|
2
2
|
import { type ITreeHost } from './tree-host';
|
|
3
|
-
/**
|
|
4
|
-
* Present one resolved set of bindings as the engine's tree host.
|
|
5
|
-
*
|
|
6
|
-
* `census` is the only member that does not reach native, and it is deliberately not on the ABI: it
|
|
7
|
-
* has exactly ONE engine caller (`censusRetainedTree`), it is diagnostics, and answering it honestly
|
|
8
|
-
* would cost a full native walk of the very tree the design exists to stop walking. The empty census
|
|
9
|
-
* is what `censusRetainedTree` already answers with no host at all, so a probe reading it sees the
|
|
10
|
-
* same zeroes it has always seen off a device.
|
|
11
|
-
*/
|
|
12
3
|
export declare function nativeTreeHost(bindings: INativeEngineBindings): ITreeHost;
|
|
13
|
-
/**
|
|
14
|
-
* Install it, if this runtime has a native module and nothing has claimed the seam already.
|
|
15
|
-
*
|
|
16
|
-
* PRECEDENCE: an installed host WINS. `installFabric()` is the only other caller of `setTreeHost`,
|
|
17
|
-
* and it puts the TypeScript applier in before any fixture can bind a slot — so a headless run that
|
|
18
|
-
* also happens to carry fake bindings must keep the applier, or the ~5 500 tests written against it
|
|
19
|
-
* would silently start driving a stub. The reverse ordering cannot occur on a device: nothing there
|
|
20
|
-
* installs a host but this.
|
|
21
|
-
*
|
|
22
|
-
* No native module is the ORDINARY answer (`native-engine.ts`'s header lists where), and it stays a
|
|
23
|
-
* quiet one here: the ops simply keep accumulating, exactly as they did before this file existed.
|
|
24
|
-
*/
|
|
25
4
|
export declare function installNativeTreeHost(): void;
|
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
// The NATIVE tree host — `INativeEngineBindings` presented as an `ITreeHost`.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// early, the ops stayed pending forever, and the screen stayed blank with nothing red anywhere.
|
|
7
|
-
//
|
|
8
|
-
// There is no logic here on purpose. Everything the host is asked is something native already
|
|
9
|
-
// answers, so this is a rename and an argument spread; a mapping thin enough to read in one pass is
|
|
10
|
-
// what keeps the two sides auditable against each other.
|
|
1
|
+
// The NATIVE tree host — `INativeEngineBindings` presented as an `ITreeHost`. It is the half that
|
|
2
|
+
// makes the buffer mean anything on a device: `tree-host.ts` records ops and asks a host to turn
|
|
3
|
+
// them into a tree; headlessly, `installFabric()` installs the TS applier instead.
|
|
4
|
+
// No logic here on purpose: everything the host is asked is something native already answers, so
|
|
5
|
+
// this stays a rename plus an argument spread, thin enough to audit against native's own side.
|
|
11
6
|
import { nativeEngine } from './native-engine.js';
|
|
12
7
|
import { EMPTY_CENSUS, setTreeHost, treeHost, } from './tree-host.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* has exactly ONE engine caller (`censusRetainedTree`), it is diagnostics, and answering it honestly
|
|
18
|
-
* would cost a full native walk of the very tree the design exists to stop walking. The empty census
|
|
19
|
-
* is what `censusRetainedTree` already answers with no host at all, so a probe reading it sees the
|
|
20
|
-
* same zeroes it has always seen off a device.
|
|
21
|
-
*/
|
|
8
|
+
// Present one resolved set of bindings as the engine's tree host.
|
|
9
|
+
// `census` is the only member that never reaches native — deliberately not on the ABI, since its
|
|
10
|
+
// one caller (censusRetainedTree) is diagnostics and answering honestly costs a full native walk
|
|
11
|
+
// of the tree the design exists to avoid walking. The empty census matches the no-host answer.
|
|
22
12
|
export function nativeTreeHost(bindings) {
|
|
23
13
|
return {
|
|
24
14
|
// Spread rather than passed as the batch object: JSI reads five arguments cheaper than five
|
|
@@ -46,20 +36,14 @@ export function nativeTreeHost(bindings) {
|
|
|
46
36
|
measureInWindow: bindings.measureInWindow,
|
|
47
37
|
measureLayout: bindings.measureLayout,
|
|
48
38
|
setIsJSResponder: bindings.setIsJSResponder,
|
|
39
|
+
getBoundingClientRect: bindings.getBoundingClientRect,
|
|
49
40
|
};
|
|
50
41
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* also happens to carry fake bindings must keep the applier, or the ~5 500 tests written against it
|
|
57
|
-
* would silently start driving a stub. The reverse ordering cannot occur on a device: nothing there
|
|
58
|
-
* installs a host but this.
|
|
59
|
-
*
|
|
60
|
-
* No native module is the ORDINARY answer (`native-engine.ts`'s header lists where), and it stays a
|
|
61
|
-
* quiet one here: the ops simply keep accumulating, exactly as they did before this file existed.
|
|
62
|
-
*/
|
|
42
|
+
// Install it, if this runtime has a native module and nothing has claimed the seam already.
|
|
43
|
+
// An installed host wins: installFabric() (the only other setTreeHost caller) puts the TS applier
|
|
44
|
+
// in first, so a headless run carrying fake bindings must keep the applier, not the stub.
|
|
45
|
+
// No native module is the ordinary answer (see native-engine.ts); ops then simply keep
|
|
46
|
+
// accumulating, undelivered.
|
|
63
47
|
export function installNativeTreeHost() {
|
|
64
48
|
const bindings = nativeEngine();
|
|
65
49
|
if (bindings === undefined || treeHost() !== undefined)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IEventDispatch, IListener, ISymbioteNode } from './node-types';
|
|
2
|
+
export declare function setBehaviorListener(node: ISymbioteNode, name: string, listener: IListener | undefined): void;
|
|
3
|
+
export declare function setNodeDispatch(node: ISymbioteNode, dispatch: IEventDispatch | undefined): void;
|
|
4
|
+
/** Whether anything at all would receive `name` on this node. */
|
|
5
|
+
export declare function hasListenerFor(node: ISymbioteNode, name: string): boolean;
|
|
6
|
+
export declare function listenerFor(node: ISymbioteNode, name: string): IListener | undefined;
|
|
7
|
+
export declare function setEventListener(node: ISymbioteNode, name: string, value: unknown): void;
|
|
8
|
+
export declare function isOnEventName(key: string): boolean;
|
|
9
|
+
/** `onChange` to `change`. */
|
|
10
|
+
export declare function listenerName(propName: string): string;
|
|
11
|
+
export declare const RESPONDER_EVENTS: ReadonlySet<string>;
|