Extract mouse interop runtime lifecycle seam
This commit is contained in:
parent
19a4a8988c
commit
5ab4c5cd0f
5 changed files with 174 additions and 112 deletions
|
|
@ -76,6 +76,7 @@ const petWindowContentSource = readFileSync(join(appDir, "src", "familiar-window
|
|||
const petWindowInteractionSource = readFileSync(join(appDir, "src", "familiar-window-interactions.ts"), "utf8");
|
||||
const petWindowMouseInteropSource = readFileSync(join(appDir, "src", "familiar-window-mouse-interop.ts"), "utf8");
|
||||
const petWindowMouseInteropRuntimeSource = readFileSync(join(appDir, "src", "familiar-window-mouse-interop-runtime.ts"), "utf8");
|
||||
const petWindowMouseInteropRuntimeLifecycleSource = readFileSync(join(appDir, "src", "familiar-window-mouse-interop-runtime-lifecycle.ts"), "utf8");
|
||||
const petWindowMouseInteropSupportSource = readFileSync(join(appDir, "src", "familiar-window-mouse-interop-support.ts"), "utf8");
|
||||
const petWindowPluginMenuSource = readFileSync(join(appDir, "src", "familiar-window-plugin-menu.ts"), "utf8");
|
||||
const petWindowRendererBridgeSource = readFileSync(join(appDir, "src", "familiar-window-renderer-bridge.ts"), "utf8");
|
||||
|
|
@ -271,7 +272,7 @@ const controlCenterSettingsCombinedSource = [
|
|||
].join("\n");
|
||||
const petWindowRenderCombinedSource = `${petWindowSource}\n${petWindowRenderSource}\n${petWindowRenderBubblesSource}`;
|
||||
const petWindowContentCombinedSource = `${petWindowSource}\n${petWindowContentSource}`;
|
||||
const petWindowRuntimeCombinedSource = `${petWindowSource}\n${petWindowHostSource}\n${petWindowInteractionSource}\n${petWindowMouseInteropSource}\n${petWindowMouseInteropRuntimeSource}\n${petWindowMouseInteropSupportSource}`;
|
||||
const petWindowRuntimeCombinedSource = `${petWindowSource}\n${petWindowHostSource}\n${petWindowInteractionSource}\n${petWindowMouseInteropSource}\n${petWindowMouseInteropRuntimeSource}\n${petWindowMouseInteropRuntimeLifecycleSource}\n${petWindowMouseInteropSupportSource}`;
|
||||
assert.match(loggerSource, /familiaros\.log/, "desktop logger must write a user-sendable familiaros.log file.");
|
||||
assert.match(loggerSource, /familiaros\.previous\.log/, "desktop logger must retain a previous log file for bug reports.");
|
||||
assert.match(loggerSource, /FAMILIAROS_LOG_LEVEL/, "desktop logger must support verbose dev logging via environment.");
|
||||
|
|
@ -324,8 +325,8 @@ assert.match(petWindowSource, /function clearTransientReaction/, "finite reactio
|
|||
assert.match(petWindowRendererBridgeSource, /sendWindowChannel\(window, "familiaros:familiar-reaction-state"/, "finite reaction animations must clear sprite state without reloading the bubble.");
|
||||
assert.match(petPreloadSource, /familiaros:familiar-reaction-state/, "familiar preload must accept in-place reaction state updates.");
|
||||
assert.match(petWindowMouseInteropSupportSource, /webContents:\s*window\.webContents/, "mouse interop support must capture webContents once so cleanup can avoid touching a destroyed BrowserWindow.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /const removeListeners = \(\): void => \{[\s\S]*?if \(!context\.webContents\.isDestroyed\(\)\)/, "mouse interop cleanup must avoid touching destroyed webContents objects.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /context\.window\.on\("close", removeListeners\);\s*context\.window\.once\("closed", removeListeners\);/, "familiar window cleanup must run before and after close so agent lease release and Cmd/Ctrl+W are idempotent.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /const removeListeners = \(\): void => \{[\s\S]*?if \(!context\.webContents\.isDestroyed\(\)\)/, "mouse interop cleanup must avoid touching destroyed webContents objects.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /context\.window\.on\("close", removeListeners\);\s*context\.window\.once\("closed", removeListeners\);/, "familiar window cleanup must run before and after close so agent lease release and Cmd\/Ctrl\+W are idempotent.");
|
||||
assert.match(displaySource, /width:\s*220/, "familiar windows must stay tightly bounded around familiar and bubble.");
|
||||
assert.match(displaySource, /height:\s*320/, "familiar windows must be tall enough for adaptive long message bubbles at large familiar scale without becoming a huge click shield.");
|
||||
assert.match(petWindowRenderCombinedSource, /function getBubbleClassName|export function getBubbleClassName/, "familiar bubbles must classify explicit messages by length.");
|
||||
|
|
@ -361,6 +362,8 @@ assert.match(petWindowInteractionSource, /from "\.\/familiar-window-mouse-intero
|
|||
assert.match(petWindowMouseInteropSource, /export function installMousePassthroughAndDrag/, "mouse interop runtime must export the mouse passthrough seam.");
|
||||
assert.match(petWindowMouseInteropSource, /from "\.\/familiar-window-mouse-interop-runtime(?:\.js)?"/, "mouse interop shell must compose the extracted runtime seam.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /export function attachPetWindowMouseInteropRuntime/, "mouse interop runtime seam must export listener and IPC attachment.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /from "\.\/familiar-window-mouse-interop-runtime-lifecycle(?:\.js)?"/, "mouse interop runtime seam must compose the extracted lifecycle seam.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /attachPetWindowMouseInteropLifecycle\(context, maps, \{/, "mouse interop runtime seam must delegate listener lifecycle attachment.");
|
||||
assert.match(petWindowRuntimeCombinedSource, /setIgnoreMouseEvents\(true, \{ forward: true \}\)/, "transparent familiar window background must use OS-level mouse passthrough.");
|
||||
assert.match(petWindowRuntimeCombinedSource, /setIgnoreMouseEvents\(false\)/, "visible familiar and bubble hit targets must re-enable mouse handling.");
|
||||
assert.match(petWindowRuntimeCombinedSource, /familiaros:familiar-ready/, "familiar windows must resync passthrough after each renderer reload.");
|
||||
|
|
@ -368,11 +371,12 @@ assert.match(petWindowMouseInteropSupportSource, /export function rearmPassthrou
|
|||
assert.match(petWindowMouseInteropSupportSource, /scheduleWindowsMouseForwardingRearm\(context, `\$\{reason\}\+75ms`, 75\);[\s\S]*?scheduleWindowsMouseForwardingRearm\(context, `\$\{reason\}\+175ms`, 175\);/, "Windows familiar reloads must retry mouse forwarding rearm after load settles.");
|
||||
assert.match(petWindowRuntimeCombinedSource, /familiaros:familiar-probe-hit-test/, "Windows familiar reloads must probe current cursor hit target when mousemove forwarding is stale.");
|
||||
assert.match(petWindowMouseInteropSource, /export function recoverPetMouseInterop/, "familiar windows must expose a controlled mouse interop recovery hook for OS display and resume events.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /recovery\.set\(context\.window, \(reason\) =>[\s\S]*?scheduleMouseInteropRecovery\(context, reason\)/, "familiar windows must register their mouse interop recovery callback.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /export function attachPetWindowMouseInteropLifecycle/, "mouse interop lifecycle seam must export listener lifecycle attachment.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /recovery\.set\(context\.window, \(reason\) =>[\s\S]*?scheduleMouseInteropRecovery\(context, reason\)/, "familiar windows must register their mouse interop recovery callback.");
|
||||
assert.match(petWindowMouseInteropSupportSource, /export function scheduleWindowsForwardingWatch[\s\S]*?rearmWindowsMouseForwarding\(context, reason, false\)[\s\S]*?scheduleWindowsForwardingWatch\(context, reason\)/, "Windows familiar passthrough must keep rearming while idle so hover and drag recover after familiar reloads without noisy logs.");
|
||||
assert.match(petPreloadSource, /familiaros:familiar-probe-hit-test[\s\S]*?elementFromPoint\(clientX, clientY\)[\s\S]*?reportInteractiveHit/, "familiar preload must answer main-process cursor hit-test probes.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /did-finish-load", rearmAfterLoad/, "familiar windows must re-arm mouse passthrough after every content load.");
|
||||
assert.match(petWindowMouseInteropRuntimeSource, /did-fail-load", handleLoadFailure/, "familiar windows must restore passthrough after failed content loads.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /did-finish-load", rearmAfterLoad/, "familiar windows must re-arm mouse passthrough after every content load.");
|
||||
assert.match(petWindowMouseInteropRuntimeLifecycleSource, /did-fail-load", handleLoadFailure/, "familiar windows must restore passthrough after failed content loads.");
|
||||
assert.match(petWindowHostSource, /window\.setIgnoreMouseEvents\(false\);[\s\S]*?await window\.loadFile/, "familiar reloads must reset OS mouse passthrough before navigation.");
|
||||
assert.match(petWindowHostSource, /export function allocateWindowLoadSequence/, "familiar content reloads must allocate request sequence before async rendering.");
|
||||
assert.match(petWindowSource, /tryUpdateLoadedPetContent\(window, render, "default", sequence\)/, "default familiar transient updates must avoid BrowserWindow reloads when the familiar document is already loaded.");
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ familiar-window.ts
|
|||
├── familiar-window-host.ts (Electron BrowserWindow shell, navigation allowlist, and serialized HTML load queue)
|
||||
├── familiar-window-interactions.ts (public interaction facade plus motion-state publishing)
|
||||
├── familiar-window-mouse-interop.ts (public mouse passthrough/drag facade plus recovery accessors)
|
||||
├── familiar-window-mouse-interop-runtime.ts (IPC handler and listener attachment for familiar window mouse interop)
|
||||
├── familiar-window-mouse-interop-runtime.ts (IPC handler attachment and lifecycle seam delegation for familiar window mouse interop)
|
||||
├── familiar-window-mouse-interop-runtime-lifecycle.ts (listener registration, cleanup, and load/reset recovery for familiar window mouse interop)
|
||||
├── familiar-window-mouse-interop-support.ts (context creation, platform passthrough helpers, and hit-test probes)
|
||||
├── familiar-window-plugin-menu.ts (pet context menu, default-pet plugin commands, plugin command forms)
|
||||
├── familiar-window-renderer-bridge.ts (bubble layout, reaction, sprite, audio, and TTS renderer IPC helpers)
|
||||
|
|
@ -248,7 +249,8 @@ plugin-service-local-support.ts → plugin-local-loader.ts validates selected fo
|
|||
- `familiar-window-layout.ts`: Extracted familiar bubble sizing, scroll-cap selection, and base window layout calculations
|
||||
- `familiar-window-interactions.ts`: Public familiar-window interaction facade that re-exports mouse interop and owns motion-state publishing
|
||||
- `familiar-window-mouse-interop.ts`: Public familiar mouse passthrough, drag/scale state, and recovery facade over the extracted runtime seam
|
||||
- `familiar-window-mouse-interop-runtime.ts`: Extracted familiar mouse IPC handlers, window/webContents listener attachment, cleanup, and recovery registration
|
||||
- `familiar-window-mouse-interop-runtime.ts`: Extracted familiar mouse IPC handlers and delegation into the lifecycle attachment seam
|
||||
- `familiar-window-mouse-interop-runtime-lifecycle.ts`: Extracted familiar mouse window/webContents listener attachment, cleanup, navigation/load reset, and recovery registration
|
||||
- `familiar-window-mouse-interop-support.ts`: Extracted familiar mouse interop context, platform-specific passthrough rearm helpers, cursor hit-test probes, and payload guards
|
||||
- `familiar-window-plugin-menu.ts`: Extracted pet context-menu construction, default familiar plugin command menus, and plugin command form hosting
|
||||
- `familiar-window-renderer-bridge.ts`: Extracted renderer IPC helpers for bubble layout updates, reaction state, sprite overrides, audio playback, and TTS
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
import { ipcMain, type BrowserWindow, type IpcMainEvent } from "electron";
|
||||
|
||||
import {
|
||||
clearRearmTimers,
|
||||
clearWindowsForwardingWatch,
|
||||
rearmPassthrough,
|
||||
scheduleMouseInteropRecovery,
|
||||
setPassthrough,
|
||||
type PetWindowMouseInteropContext,
|
||||
} from "./familiar-window-mouse-interop-support.js";
|
||||
import { debug } from "./logger.js";
|
||||
|
||||
export interface PetWindowMouseInteropRuntimeMaps {
|
||||
readonly recovery: WeakMap<BrowserWindow, (reason: string) => void>;
|
||||
readonly dragging: WeakMap<BrowserWindow, boolean>;
|
||||
readonly scaling: WeakMap<BrowserWindow, boolean>;
|
||||
}
|
||||
|
||||
export type PetWindowMouseInteropRuntimeHandlers = {
|
||||
readonly handleReady: (event: IpcMainEvent) => void;
|
||||
readonly handleHitTest: (event: IpcMainEvent, interactive: unknown, source: unknown) => void;
|
||||
readonly handleDragStart: (event: IpcMainEvent, point: unknown) => void;
|
||||
readonly handleDragMove: (event: IpcMainEvent, point: unknown) => void;
|
||||
readonly handleDragEnd: (event: IpcMainEvent) => void;
|
||||
readonly handleBubbleDismissed: (event: IpcMainEvent, dismissToken: unknown) => void;
|
||||
readonly handlePromptRequested: (event: IpcMainEvent) => void;
|
||||
readonly handleScaleStart: (event: IpcMainEvent) => void;
|
||||
readonly handleScalePreview: (event: IpcMainEvent, payload: unknown) => void;
|
||||
readonly handleScaleEnd: (event: IpcMainEvent, payload: unknown) => void;
|
||||
readonly handleBubbleAction: (event: IpcMainEvent, dismissToken: unknown, actionId: unknown) => void;
|
||||
readonly handleBubbleSubmit: (event: IpcMainEvent, dismissToken: unknown, values: unknown) => void;
|
||||
readonly handlePetEvent: (event: IpcMainEvent, name: unknown, payload: unknown) => void;
|
||||
};
|
||||
|
||||
export function attachPetWindowMouseInteropLifecycle(
|
||||
context: PetWindowMouseInteropContext,
|
||||
maps: PetWindowMouseInteropRuntimeMaps,
|
||||
handlers: PetWindowMouseInteropRuntimeHandlers,
|
||||
): void {
|
||||
const resetForNavigation = (): void => {
|
||||
context.state.dragging = null;
|
||||
maps.dragging.set(context.window, false);
|
||||
context.state.rendererReady = false;
|
||||
context.state.lastInteractive = false;
|
||||
clearRearmTimers(context);
|
||||
debug("familiar.window", "navigation reset passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
setPassthrough(context, false);
|
||||
};
|
||||
|
||||
const rearmAfterLoad = (): void => {
|
||||
context.state.dragging = null;
|
||||
maps.dragging.set(context.window, false);
|
||||
context.state.lastInteractive = false;
|
||||
debug("familiar.window", "load rearm passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
rearmPassthrough(context, "did-finish-load");
|
||||
};
|
||||
|
||||
const handleDomReady = (): void => {
|
||||
if (!context.state.rendererReady) {
|
||||
setPassthrough(context, true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadFailure = (): void => {
|
||||
context.state.dragging = null;
|
||||
context.state.lastInteractive = false;
|
||||
debug("familiar.window", "load failure rearm passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
setPassthrough(context, true);
|
||||
};
|
||||
|
||||
const removeListeners = (): void => {
|
||||
if (context.state.listenersRemoved) return;
|
||||
context.state.listenersRemoved = true;
|
||||
ipcMain.off("familiaros:familiar-ready", handlers.handleReady);
|
||||
ipcMain.off("familiaros:familiar-hit-test", handlers.handleHitTest);
|
||||
ipcMain.off("familiaros:familiar-drag-start", handlers.handleDragStart);
|
||||
ipcMain.off("familiaros:familiar-drag-move", handlers.handleDragMove);
|
||||
ipcMain.off("familiaros:familiar-drag-end", handlers.handleDragEnd);
|
||||
ipcMain.off("familiaros:bubble-dismissed", handlers.handleBubbleDismissed);
|
||||
ipcMain.off("familiaros:familiar-open-prompt", handlers.handlePromptRequested);
|
||||
ipcMain.off("familiaros:familiar-scale-start", handlers.handleScaleStart);
|
||||
ipcMain.off("familiaros:familiar-scale-preview", handlers.handleScalePreview);
|
||||
ipcMain.off("familiaros:familiar-scale-end", handlers.handleScaleEnd);
|
||||
ipcMain.off("familiaros:bubble-action", handlers.handleBubbleAction);
|
||||
ipcMain.off("familiaros:bubble-submit", handlers.handleBubbleSubmit);
|
||||
ipcMain.off("familiaros:familiar-event", handlers.handlePetEvent);
|
||||
clearRearmTimers(context);
|
||||
clearWindowsForwardingWatch(context);
|
||||
maps.recovery.delete(context.window);
|
||||
maps.dragging.delete(context.window);
|
||||
maps.scaling.delete(context.window);
|
||||
if (!context.webContents.isDestroyed()) {
|
||||
context.webContents.off("did-start-navigation", resetForNavigation);
|
||||
context.webContents.off("did-start-loading", resetForNavigation);
|
||||
context.webContents.off("did-finish-load", rearmAfterLoad);
|
||||
context.webContents.off("dom-ready", handleDomReady);
|
||||
context.webContents.off("did-fail-load", handleLoadFailure);
|
||||
}
|
||||
};
|
||||
|
||||
maps.recovery.set(context.window, (reason) =>
|
||||
scheduleMouseInteropRecovery(context, reason),
|
||||
);
|
||||
|
||||
ipcMain.on("familiaros:familiar-ready", handlers.handleReady);
|
||||
ipcMain.on("familiaros:familiar-hit-test", handlers.handleHitTest);
|
||||
ipcMain.on("familiaros:familiar-drag-start", handlers.handleDragStart);
|
||||
ipcMain.on("familiaros:familiar-drag-move", handlers.handleDragMove);
|
||||
ipcMain.on("familiaros:familiar-drag-end", handlers.handleDragEnd);
|
||||
ipcMain.on("familiaros:bubble-dismissed", handlers.handleBubbleDismissed);
|
||||
ipcMain.on("familiaros:familiar-open-prompt", handlers.handlePromptRequested);
|
||||
ipcMain.on("familiaros:familiar-scale-start", handlers.handleScaleStart);
|
||||
ipcMain.on("familiaros:familiar-scale-preview", handlers.handleScalePreview);
|
||||
ipcMain.on("familiaros:familiar-scale-end", handlers.handleScaleEnd);
|
||||
ipcMain.on("familiaros:bubble-action", handlers.handleBubbleAction);
|
||||
ipcMain.on("familiaros:bubble-submit", handlers.handleBubbleSubmit);
|
||||
ipcMain.on("familiaros:familiar-event", handlers.handlePetEvent);
|
||||
context.webContents.on("did-start-navigation", resetForNavigation);
|
||||
context.webContents.on("did-start-loading", resetForNavigation);
|
||||
context.webContents.on("did-finish-load", rearmAfterLoad);
|
||||
context.webContents.on("dom-ready", handleDomReady);
|
||||
context.webContents.on("did-fail-load", handleLoadFailure);
|
||||
context.window.on("close", removeListeners);
|
||||
context.window.once("closed", removeListeners);
|
||||
}
|
||||
|
|
@ -1,26 +1,23 @@
|
|||
import { ipcMain, type BrowserWindow, type IpcMainEvent } from "electron";
|
||||
import type { IpcMainEvent } from "electron";
|
||||
|
||||
import type { PetScaleValue } from "./app-state.js";
|
||||
import {
|
||||
attachPetWindowMouseInteropLifecycle,
|
||||
type PetWindowMouseInteropRuntimeMaps,
|
||||
} from "./familiar-window-mouse-interop-runtime-lifecycle.js";
|
||||
import {
|
||||
allowedPetEventNames,
|
||||
clearRearmTimers,
|
||||
clearWindowsForwardingWatch,
|
||||
isFromWindow,
|
||||
isRecord,
|
||||
isScreenPoint,
|
||||
rearmPassthrough,
|
||||
scheduleMouseInteropRecovery,
|
||||
scheduleWindowsForwardingWatch,
|
||||
setPassthrough,
|
||||
type PetWindowMouseInteropContext,
|
||||
} from "./familiar-window-mouse-interop-support.js";
|
||||
import { debug } from "./logger.js";
|
||||
|
||||
export interface PetWindowMouseInteropRuntimeMaps {
|
||||
readonly recovery: WeakMap<BrowserWindow, (reason: string) => void>;
|
||||
readonly dragging: WeakMap<BrowserWindow, boolean>;
|
||||
readonly scaling: WeakMap<BrowserWindow, boolean>;
|
||||
}
|
||||
export type { PetWindowMouseInteropRuntimeMaps } from "./familiar-window-mouse-interop-runtime-lifecycle.js";
|
||||
|
||||
export function attachPetWindowMouseInteropRuntime(
|
||||
context: PetWindowMouseInteropContext,
|
||||
|
|
@ -264,95 +261,19 @@ export function attachPetWindowMouseInteropRuntime(
|
|||
context.hooks.onPetEvent?.(name, data);
|
||||
};
|
||||
|
||||
const resetForNavigation = (): void => {
|
||||
context.state.dragging = null;
|
||||
maps.dragging.set(context.window, false);
|
||||
context.state.rendererReady = false;
|
||||
context.state.lastInteractive = false;
|
||||
clearRearmTimers(context);
|
||||
debug("familiar.window", "navigation reset passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
setPassthrough(context, false);
|
||||
};
|
||||
|
||||
const rearmAfterLoad = (): void => {
|
||||
context.state.dragging = null;
|
||||
maps.dragging.set(context.window, false);
|
||||
context.state.lastInteractive = false;
|
||||
debug("familiar.window", "load rearm passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
rearmPassthrough(context, "did-finish-load");
|
||||
};
|
||||
|
||||
const handleDomReady = (): void => {
|
||||
if (!context.state.rendererReady) {
|
||||
setPassthrough(context, true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadFailure = (): void => {
|
||||
context.state.dragging = null;
|
||||
context.state.lastInteractive = false;
|
||||
debug("familiar.window", "load failure rearm passthrough", {
|
||||
windowId: context.windowId,
|
||||
});
|
||||
setPassthrough(context, true);
|
||||
};
|
||||
|
||||
const removeListeners = (): void => {
|
||||
if (context.state.listenersRemoved) return;
|
||||
context.state.listenersRemoved = true;
|
||||
ipcMain.off("familiaros:familiar-ready", handleReady);
|
||||
ipcMain.off("familiaros:familiar-hit-test", handleHitTest);
|
||||
ipcMain.off("familiaros:familiar-drag-start", handleDragStart);
|
||||
ipcMain.off("familiaros:familiar-drag-move", handleDragMove);
|
||||
ipcMain.off("familiaros:familiar-drag-end", handleDragEnd);
|
||||
ipcMain.off("familiaros:bubble-dismissed", handleBubbleDismissed);
|
||||
ipcMain.off("familiaros:familiar-open-prompt", handlePromptRequested);
|
||||
ipcMain.off("familiaros:familiar-scale-start", handleScaleStart);
|
||||
ipcMain.off("familiaros:familiar-scale-preview", handleScalePreview);
|
||||
ipcMain.off("familiaros:familiar-scale-end", handleScaleEnd);
|
||||
ipcMain.off("familiaros:bubble-action", handleBubbleAction);
|
||||
ipcMain.off("familiaros:bubble-submit", handleBubbleSubmit);
|
||||
ipcMain.off("familiaros:familiar-event", handlePetEvent);
|
||||
clearRearmTimers(context);
|
||||
clearWindowsForwardingWatch(context);
|
||||
maps.recovery.delete(context.window);
|
||||
maps.dragging.delete(context.window);
|
||||
maps.scaling.delete(context.window);
|
||||
if (!context.webContents.isDestroyed()) {
|
||||
context.webContents.off("did-start-navigation", resetForNavigation);
|
||||
context.webContents.off("did-start-loading", resetForNavigation);
|
||||
context.webContents.off("did-finish-load", rearmAfterLoad);
|
||||
context.webContents.off("dom-ready", handleDomReady);
|
||||
context.webContents.off("did-fail-load", handleLoadFailure);
|
||||
}
|
||||
};
|
||||
|
||||
maps.recovery.set(context.window, (reason) =>
|
||||
scheduleMouseInteropRecovery(context, reason),
|
||||
);
|
||||
|
||||
ipcMain.on("familiaros:familiar-ready", handleReady);
|
||||
ipcMain.on("familiaros:familiar-hit-test", handleHitTest);
|
||||
ipcMain.on("familiaros:familiar-drag-start", handleDragStart);
|
||||
ipcMain.on("familiaros:familiar-drag-move", handleDragMove);
|
||||
ipcMain.on("familiaros:familiar-drag-end", handleDragEnd);
|
||||
ipcMain.on("familiaros:bubble-dismissed", handleBubbleDismissed);
|
||||
ipcMain.on("familiaros:familiar-open-prompt", handlePromptRequested);
|
||||
ipcMain.on("familiaros:familiar-scale-start", handleScaleStart);
|
||||
ipcMain.on("familiaros:familiar-scale-preview", handleScalePreview);
|
||||
ipcMain.on("familiaros:familiar-scale-end", handleScaleEnd);
|
||||
ipcMain.on("familiaros:bubble-action", handleBubbleAction);
|
||||
ipcMain.on("familiaros:bubble-submit", handleBubbleSubmit);
|
||||
ipcMain.on("familiaros:familiar-event", handlePetEvent);
|
||||
context.webContents.on("did-start-navigation", resetForNavigation);
|
||||
context.webContents.on("did-start-loading", resetForNavigation);
|
||||
context.webContents.on("did-finish-load", rearmAfterLoad);
|
||||
context.webContents.on("dom-ready", handleDomReady);
|
||||
context.webContents.on("did-fail-load", handleLoadFailure);
|
||||
context.window.on("close", removeListeners);
|
||||
context.window.once("closed", removeListeners);
|
||||
attachPetWindowMouseInteropLifecycle(context, maps, {
|
||||
handleReady,
|
||||
handleHitTest,
|
||||
handleDragStart,
|
||||
handleDragMove,
|
||||
handleDragEnd,
|
||||
handleBubbleDismissed,
|
||||
handlePromptRequested,
|
||||
handleScaleStart,
|
||||
handleScalePreview,
|
||||
handleScaleEnd,
|
||||
handleBubbleAction,
|
||||
handleBubbleSubmit,
|
||||
handlePetEvent,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ import { fileURLToPath } from "node:url";
|
|||
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const familiarWindowMouseInteropSource = readFileSync(resolve(desktopRoot, "src/familiar-window-mouse-interop.ts"), "utf8");
|
||||
const familiarWindowMouseInteropRuntimeSource = readFileSync(resolve(desktopRoot, "src/familiar-window-mouse-interop-runtime.ts"), "utf8");
|
||||
const familiarWindowMouseInteropRuntimeLifecycleSource = readFileSync(resolve(desktopRoot, "src/familiar-window-mouse-interop-runtime-lifecycle.ts"), "utf8");
|
||||
|
||||
assert.match(familiarWindowMouseInteropSource, /from "\.\/familiar-window-mouse-interop-runtime(?:\.js)?"/, "mouse interop shell must compose the extracted runtime seam.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /export function attachPetWindowMouseInteropRuntime/, "mouse interop runtime seam must export listener and IPC attachment.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /recovery\.set\(context\.window, \(reason\) =>[\s\S]*?scheduleMouseInteropRecovery\(context, reason\)/, "mouse interop runtime seam must register the recovery callback against the public recovery facade.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /ipcMain\.on\("familiaros:familiar-drag-start", handleDragStart\)/, "mouse interop runtime seam must register drag-start IPC handling.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /context\.webContents\.on\("did-finish-load", rearmAfterLoad\)/, "mouse interop runtime seam must re-arm passthrough after reload.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /context\.window\.once\("closed", removeListeners\)/, "mouse interop runtime seam must detach listeners when the familiar window closes.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /from "\.\/familiar-window-mouse-interop-runtime-lifecycle(?:\.js)?"/, "mouse interop runtime seam must compose the extracted lifecycle seam.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeSource, /attachPetWindowMouseInteropLifecycle\(context, maps, \{/, "mouse interop runtime seam must delegate listener lifecycle attachment.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeLifecycleSource, /export function attachPetWindowMouseInteropLifecycle/, "mouse interop lifecycle seam must export listener lifecycle attachment.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeLifecycleSource, /recovery\.set\(context\.window, \(reason\) =>[\s\S]*?scheduleMouseInteropRecovery\(context, reason\)/, "mouse interop lifecycle seam must register the recovery callback against the public recovery facade.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeLifecycleSource, /ipcMain\.on\("familiaros:familiar-drag-start", handlers\.handleDragStart\)/, "mouse interop lifecycle seam must register drag-start IPC handling.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeLifecycleSource, /context\.webContents\.on\("did-finish-load", rearmAfterLoad\)/, "mouse interop lifecycle seam must re-arm passthrough after reload.");
|
||||
assert.match(familiarWindowMouseInteropRuntimeLifecycleSource, /context\.window\.once\("closed", removeListeners\)/, "mouse interop lifecycle seam must detach listeners when the familiar window closes.");
|
||||
|
||||
console.error("Familiar window mouse interop runtime seam validation passed.");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue