补齐移动壳进程崩溃恢复边界
移动壳 WebView 进程失败增加防循环恢复 加载失败面板承接连续进程恢复失败 配置检查和文档同步崩溃恢复约束
This commit is contained in:
@@ -75,6 +75,9 @@ function logMobileShellDeepLinkFailure(label: string, error: unknown) {
|
||||
console.warn(`mobile shell deep link failed for ${label}`, error);
|
||||
}
|
||||
|
||||
const MOBILE_WEBVIEW_PROCESS_FAILURE_WINDOW_MS = 30_000;
|
||||
const MOBILE_WEBVIEW_PROCESS_FAILURE_RELOAD_LIMIT = 1;
|
||||
|
||||
type MobileWebViewLoadErrorEvent = {
|
||||
nativeEvent: {
|
||||
url: string;
|
||||
@@ -96,6 +99,10 @@ export default function ShellApp() {
|
||||
const isShellMountedRef = useRef(true);
|
||||
const nativeCanGoBackRef = useRef(false);
|
||||
const h5CanGoBackRef = useRef(false);
|
||||
const webViewProcessFailureRef = useRef({
|
||||
count: 0,
|
||||
firstFailureAt: 0,
|
||||
});
|
||||
const [canGoBack, setCanGoBack] = useState(false);
|
||||
const baseWebUrl = resolveMobileShellBaseWebUrl(
|
||||
process.env.EXPO_PUBLIC_GENARRATIVE_WEB_URL,
|
||||
@@ -117,6 +124,12 @@ export default function ShellApp() {
|
||||
const reloadCurrentWebView = useCallback(() => {
|
||||
webViewRef.current?.reload();
|
||||
}, []);
|
||||
const resetWebViewProcessFailureWindow = useCallback(() => {
|
||||
webViewProcessFailureRef.current = {
|
||||
count: 0,
|
||||
firstFailureAt: 0,
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
isShellMountedRef.current = true;
|
||||
|
||||
@@ -197,6 +210,7 @@ export default function ShellApp() {
|
||||
allowedOrigin: allowedWebOrigin,
|
||||
urlOptions,
|
||||
openWebViewUrl(url) {
|
||||
resetWebViewProcessFailureWindow();
|
||||
resetNavigationCanGoBack();
|
||||
setLoadFailure(null);
|
||||
setWebUrl(url);
|
||||
@@ -205,7 +219,13 @@ export default function ShellApp() {
|
||||
});
|
||||
|
||||
return () => configureMobileHostBridgeNavigation(null);
|
||||
}, [allowedWebOrigin, reloadCurrentWebView, resetNavigationCanGoBack, urlOptions]);
|
||||
}, [
|
||||
allowedWebOrigin,
|
||||
reloadCurrentWebView,
|
||||
resetNavigationCanGoBack,
|
||||
resetWebViewProcessFailureWindow,
|
||||
urlOptions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
@@ -222,6 +242,7 @@ export default function ShellApp() {
|
||||
if (resolution.status === 'rejected') {
|
||||
logMobileShellDeepLinkFailure(`${source}.rejected`, url);
|
||||
}
|
||||
resetWebViewProcessFailureWindow();
|
||||
resetNavigationCanGoBack();
|
||||
setLoadFailure(null);
|
||||
setWebUrl(resolution.url);
|
||||
@@ -249,7 +270,12 @@ export default function ShellApp() {
|
||||
disposed = true;
|
||||
subscription.remove();
|
||||
};
|
||||
}, [baseWebUrl, resetNavigationCanGoBack, urlOptions]);
|
||||
}, [
|
||||
baseWebUrl,
|
||||
resetNavigationCanGoBack,
|
||||
resetWebViewProcessFailureWindow,
|
||||
urlOptions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = BackHandler.addEventListener(
|
||||
@@ -336,6 +362,7 @@ export default function ShellApp() {
|
||||
}
|
||||
|
||||
setLoadFailure(null);
|
||||
resetWebViewProcessFailureWindow();
|
||||
injectLifecycleEvent(AppState.currentState);
|
||||
void getMobileNetworkStatus()
|
||||
.then(injectNetworkStatusEvent)
|
||||
@@ -375,8 +402,49 @@ export default function ShellApp() {
|
||||
};
|
||||
const handleRetryLoadFailure = () => {
|
||||
setLoadFailure(null);
|
||||
resetWebViewProcessFailureWindow();
|
||||
reloadCurrentWebView();
|
||||
};
|
||||
const handleWebViewProcessFailure = (label: string) => {
|
||||
const now = Date.now();
|
||||
const current = webViewProcessFailureRef.current;
|
||||
const isWithinFailureWindow =
|
||||
current.firstFailureAt > 0 &&
|
||||
now - current.firstFailureAt <= MOBILE_WEBVIEW_PROCESS_FAILURE_WINDOW_MS;
|
||||
const nextFailureWindow = isWithinFailureWindow
|
||||
? {
|
||||
count: current.count + 1,
|
||||
firstFailureAt: current.firstFailureAt,
|
||||
}
|
||||
: {
|
||||
count: 1,
|
||||
firstFailureAt: now,
|
||||
};
|
||||
webViewProcessFailureRef.current = nextFailureWindow;
|
||||
console.warn('mobile WebView process failed', {
|
||||
count: nextFailureWindow.count,
|
||||
label,
|
||||
url: webUrl,
|
||||
});
|
||||
|
||||
if (nextFailureWindow.count <= MOBILE_WEBVIEW_PROCESS_FAILURE_RELOAD_LIMIT) {
|
||||
reloadCurrentWebView();
|
||||
return;
|
||||
}
|
||||
|
||||
resetNavigationCanGoBack();
|
||||
setLoadFailure(
|
||||
normalizeMobileShellLoadFailure(
|
||||
{
|
||||
type: 'process',
|
||||
url: webUrl,
|
||||
description: 'WebView renderer terminated repeatedly',
|
||||
},
|
||||
allowedWebOrigin,
|
||||
webUrl,
|
||||
),
|
||||
);
|
||||
};
|
||||
const handleBlockedFileDownload = () => undefined;
|
||||
|
||||
return (
|
||||
@@ -404,8 +472,12 @@ export default function ShellApp() {
|
||||
injectedJavaScriptBeforeContentLoaded={MOBILE_WEBVIEW_BEFORE_CONTENT_SCRIPT}
|
||||
onFileDownload={handleBlockedFileDownload}
|
||||
onMessage={handleMessage}
|
||||
onContentProcessDidTerminate={reloadCurrentWebView}
|
||||
onRenderProcessGone={reloadCurrentWebView}
|
||||
onContentProcessDidTerminate={() => {
|
||||
handleWebViewProcessFailure('content_process_terminated');
|
||||
}}
|
||||
onRenderProcessGone={() => {
|
||||
handleWebViewProcessFailure('render_process_gone');
|
||||
}}
|
||||
onLoad={handleWebViewLoad}
|
||||
onError={handleWebViewLoadError}
|
||||
onHttpError={handleWebViewHttpError}
|
||||
|
||||
Reference in New Issue
Block a user