diff --git a/UPDATELOG.md b/UPDATELOG.md index c7262cc7..112f6e5c 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -48,6 +48,7 @@ - 修复静默启动不加载完整 WebView 的问题 - 修复 Linux WebKit 网络进程的崩溃 - 修复实际导入成功但显示导入失败的问题 +- 修复 macOS 连接界面显示异常 ## v2.4.2 diff --git a/src/components/connection/connection-table.tsx b/src/components/connection/connection-table.tsx index 97f65f08..4b5ec8a7 100644 --- a/src/components/connection/connection-table.tsx +++ b/src/components/connection/connection-table.tsx @@ -1,8 +1,13 @@ -import { DataGrid, GridColDef, GridColumnResizeParams } from "@mui/x-data-grid"; +import { + DataGrid, + GridColDef, + GridColumnResizeParams, + useGridApiRef, +} from "@mui/x-data-grid"; import dayjs from "dayjs"; import { useLocalStorage } from "foxact/use-local-storage"; import { t } from "i18next"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import parseTraffic from "@/utils/parse-traffic"; import { truncateStr } from "@/utils/truncate-str"; @@ -14,6 +19,125 @@ interface Props { export const ConnectionTable = (props: Props) => { const { connections, onShowDetail } = props; + const apiRef = useGridApiRef(); + useEffect(() => { + const PATCH_FLAG_KEY = "__clashPatchedPublishEvent" as const; + const ORIGINAL_KEY = "__clashOriginalPublishEvent" as const; + let isUnmounted = false; + let retryHandle: ReturnType | null = null; + let cleanupOriginal: (() => void) | null = null; + + const scheduleRetry = () => { + if (isUnmounted || retryHandle !== null) return; + retryHandle = setTimeout(() => { + retryHandle = null; + ensurePatched(); + }, 16); + }; + + // Safari occasionally emits grid events without an event object, + // and MUI expects `defaultMuiPrevented` to exist. Normalize here to avoid crashes. + const createFallbackEvent = () => { + const fallback = { + defaultMuiPrevented: false, + preventDefault() { + fallback.defaultMuiPrevented = true; + }, + }; + return fallback; + }; + + const ensureMuiEvent = ( + value: unknown, + ): { + defaultMuiPrevented: boolean; + preventDefault: () => void; + [key: string]: unknown; + } => { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return createFallbackEvent(); + } + + const eventObject = value as { + defaultMuiPrevented?: unknown; + preventDefault?: () => void; + [key: string]: unknown; + }; + + if (typeof eventObject.defaultMuiPrevented !== "boolean") { + eventObject.defaultMuiPrevented = false; + } + + if (typeof eventObject.preventDefault !== "function") { + eventObject.preventDefault = () => { + eventObject.defaultMuiPrevented = true; + }; + } + + return eventObject as { + defaultMuiPrevented: boolean; + preventDefault: () => void; + [key: string]: unknown; + }; + }; + + const ensurePatched = () => { + if (isUnmounted) return; + const api = apiRef.current; + + if (!api?.publishEvent) { + scheduleRetry(); + return; + } + + const metadataApi = api as unknown as typeof api & + Record; + if (metadataApi[PATCH_FLAG_KEY] === true) return; + + const originalPublishEvent = api.publishEvent; + + const patchedPublishEvent = ((...rawArgs: unknown[]) => { + rawArgs[2] = ensureMuiEvent(rawArgs[2]); + + return ( + originalPublishEvent as unknown as (...args: unknown[]) => void + ).apply(api, rawArgs); + }) as typeof originalPublishEvent; + + api.publishEvent = patchedPublishEvent; + metadataApi[PATCH_FLAG_KEY] = true; + metadataApi[ORIGINAL_KEY] = originalPublishEvent; + + cleanupOriginal = () => { + const storedOriginal = metadataApi[ORIGINAL_KEY] as + | typeof originalPublishEvent + | undefined; + + api.publishEvent = ( + typeof storedOriginal === "function" + ? storedOriginal + : originalPublishEvent + ) as typeof originalPublishEvent; + + delete metadataApi[PATCH_FLAG_KEY]; + delete metadataApi[ORIGINAL_KEY]; + }; + }; + + ensurePatched(); + + return () => { + isUnmounted = true; + if (retryHandle !== null) { + clearTimeout(retryHandle); + retryHandle = null; + } + if (cleanupOriginal) { + cleanupOriginal(); + cleanupOriginal = null; + } + }; + }, [apiRef]); const [columnVisible, setColumnVisible] = useState< Partial> @@ -156,6 +280,7 @@ export const ConnectionTable = (props: Props) => { return (