ServerDrawer: add a fiber capture point guaranteed to actually mount
This commit is contained in:
parent
094f18d736
commit
5ad56932fc
3 changed files with 62 additions and 63 deletions
|
|
@ -3,6 +3,7 @@ import { View, Text, Pressable, Animated, Dimensions, StyleSheet, BackHandler }
|
|||
import { lazy } from "../lib/lazy";
|
||||
import { rawFind, rawFindByFunctionProps } from "../lib/rawFind";
|
||||
import { getFlux, getHaptic, getColorModule } from "../lib/commonModules";
|
||||
import { captureFiberRef } from "../lib/fiberCapture";
|
||||
import { GuildNode } from "../utils/theme";
|
||||
import GuildItem from "./GuildItem";
|
||||
import FolderItem from "./FolderItem";
|
||||
|
|
@ -122,6 +123,11 @@ export default function ServerDrawerSheet({ gestureContext }: { gestureContext:
|
|||
|
||||
return (
|
||||
<View style={st.alignTop}>
|
||||
{/* Invisible - see lib/fiberCapture.ts. This is the reliable capture point: it's
|
||||
guaranteed to mount whenever the drawer itself is visibly rendering, independent of
|
||||
whether hideGuildsBar.tsx's own capture point (which depends on that separate
|
||||
intercept actually firing) works or not. */}
|
||||
<View ref={captureFiberRef} style={{ width: 1, height: 1 }} />
|
||||
<View
|
||||
style={[st.grid, { paddingHorizontal: padX, gap: GAP }]}
|
||||
onLayout={onLayout}
|
||||
|
|
|
|||
51
plugins/server-drawer/src/lib/fiberCapture.ts
Normal file
51
plugins/server-drawer/src/lib/fiberCapture.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Reads React's internal fiber pointer directly off a native view instance and walks up to the
|
||||
* root, storing both under window.__serverDrawerFiberRoot/__serverDrawerFiberSelf for Eval scripts
|
||||
* to search afterward. Same technique Key Inspector's own Settings-screen capture uses, but kept
|
||||
* under separate globals - Key Inspector's capture only reaches whatever tree its own Settings
|
||||
* screen renders in (very likely a separate native modal/root from MainTabs' guild tab content),
|
||||
* so a capture point actually inside this plugin's own rendered output is what's needed to reach
|
||||
* anything under the Guilds tab (confirmed live: a Settings-screen capture couldn't find
|
||||
* QuestDockWithQuestContext at all, even navigating to the Guilds tab first).
|
||||
*/
|
||||
export function captureFiberRef(instance: any) {
|
||||
if (!instance) return;
|
||||
try {
|
||||
let fiber: any = null;
|
||||
|
||||
if (instance._internalFiberInstanceHandleDEV) {
|
||||
fiber = instance._internalFiberInstanceHandleDEV;
|
||||
} else if (instance._internalInstanceHandle) {
|
||||
fiber = instance._internalInstanceHandle;
|
||||
} else if (instance.__internalInstanceHandle) {
|
||||
fiber = instance.__internalInstanceHandle;
|
||||
} else {
|
||||
const fiberKey = Object.keys(instance).find(
|
||||
(k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$")
|
||||
);
|
||||
if (fiberKey) fiber = instance[fiberKey];
|
||||
}
|
||||
if (!fiber) return;
|
||||
|
||||
if (fiber.return === undefined && fiber.child === undefined) {
|
||||
const nested = fiber.stateNode ?? fiber.fiber ?? fiber._debugOwner;
|
||||
if (nested && (nested.return !== undefined || nested.child !== undefined)) {
|
||||
fiber = nested;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let root = fiber;
|
||||
let guard = 0;
|
||||
while (root.return && guard < 5000) {
|
||||
root = root.return;
|
||||
guard++;
|
||||
}
|
||||
|
||||
(window as any).__serverDrawerFiberRoot = root;
|
||||
(window as any).__serverDrawerFiberSelf = fiber;
|
||||
} catch {
|
||||
// Best-effort diagnostic capture - a failure here shouldn't affect the caller's own render.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +1,14 @@
|
|||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { registerTypeDetector, registerIntercept } from "../lib/createElementIntercept";
|
||||
import { captureFiberRef } from "../lib/fiberCapture";
|
||||
|
||||
const TAG = "[ServerDrawer]";
|
||||
|
||||
// Key Inspector's own fiber capture only fires from a View rendered inside ITS OWN Settings
|
||||
// screen - and Settings very likely renders as a separate native modal/root from MainTabs' guild
|
||||
// tab content, so a fiber captured there can never reach anything under the Guilds tab (Quest
|
||||
// Dock included) via .return, no matter what screen was visited beforehand. Confirmed live: two
|
||||
// independent searches for Quest Dock components both came up empty even while a real quest was
|
||||
// visibly rendering on the Guilds screen, and a fiber-tree walk from a Settings-screen capture
|
||||
// couldn't find QuestDockWithQuestContext at all.
|
||||
//
|
||||
// GuildsBar's hidden slot is a reliable alternative capture point: it's confirmed to actually
|
||||
// mount on the Guilds screen (that's this whole file's job), and it's a sibling of the Quest Dock
|
||||
// in the same tree (both live under MainTabsNavigatorPanel), so walking up from here and back down
|
||||
// reaches Quest Dock correctly. Stored under the same window.__keyInspectorFiberRoot/
|
||||
// __keyInspectorFiberSelf globals Key Inspector's Eval scripts already expect, so no existing
|
||||
// script needs to change - this just gives them a capture point that's actually reachable.
|
||||
function captureFiberRef(instance: any) {
|
||||
if (!instance) return;
|
||||
try {
|
||||
let fiber: any = null;
|
||||
|
||||
if (instance._internalFiberInstanceHandleDEV) {
|
||||
fiber = instance._internalFiberInstanceHandleDEV;
|
||||
} else if (instance._internalInstanceHandle) {
|
||||
fiber = instance._internalInstanceHandle;
|
||||
} else if (instance.__internalInstanceHandle) {
|
||||
fiber = instance.__internalInstanceHandle;
|
||||
} else {
|
||||
const fiberKey = Object.keys(instance).find(
|
||||
(k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$")
|
||||
);
|
||||
if (fiberKey) fiber = instance[fiberKey];
|
||||
}
|
||||
if (!fiber) return;
|
||||
|
||||
if (fiber.return === undefined && fiber.child === undefined) {
|
||||
const nested = fiber.stateNode ?? fiber.fiber ?? fiber._debugOwner;
|
||||
if (nested && (nested.return !== undefined || nested.child !== undefined)) {
|
||||
fiber = nested;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let root = fiber;
|
||||
let guard = 0;
|
||||
while (root.return && guard < 5000) {
|
||||
root = root.return;
|
||||
guard++;
|
||||
}
|
||||
|
||||
// Dedicated globals, not window.__keyInspectorFiberRoot/__keyInspectorFiberSelf: Key
|
||||
// Inspector's own Settings screen has its own capture View using those same names, and its
|
||||
// ref re-fires every time that screen mounts - including just scrolling to the Eval console
|
||||
// to paste the next script. Sharing the names meant navigating back to Settings silently
|
||||
// overwrote this capture with one rooted in Settings' own (likely separate-modal) tree,
|
||||
// which is exactly the failure this capture point exists to avoid. Confirmed live: a text
|
||||
// dump meant to read GuildsBar's slot came back with Key Inspector's own UI strings instead.
|
||||
(window as any).__serverDrawerFiberRoot = root;
|
||||
(window as any).__serverDrawerFiberSelf = fiber;
|
||||
} catch {
|
||||
// Best-effort diagnostic capture - a failure here shouldn't affect GuildsBar staying hidden.
|
||||
}
|
||||
}
|
||||
|
||||
// Whether this capture point actually mounts depends entirely on whether the intercept below
|
||||
// fires (unverified independently of it - see lib/fiberCapture.ts for the primary, confirmed
|
||||
// capture point inside ServerDrawerSheet.tsx itself, which is guaranteed to mount whenever the
|
||||
// drawer is visibly showing). Kept here too as a second chance, harmless either way.
|
||||
function Nothing() {
|
||||
return React.createElement(View, {
|
||||
ref: captureFiberRef,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue