fix: ErrorOverlay 不可见 — Stack 在 Visibility(visible:false) 时坍缩为 0×0

当 _hasMainFrameError=true 时,Visibility(visible:false) 将 WebView
替换为 SizedBox.shrink()(0×0),这是 Stack 唯一的非 positioned 子组件。
Scaffold.body 提供松约束(0→max),导致 Stack 尺寸为 0×0,
Positioned.fill 的 ErrorOverlay 也变为 0×0,用户只能看到 Scaffold 的
深色背景而看不到错误页面的内容。

修复方式:用 SizedBox.expand() 包裹 Stack,强制紧约束使其始终填满屏幕。

附带变更:
- 添加 _debugLog() 封装(kDebugMode 保护)
- 在 7 个关键状态转换点添加调试日志
This commit is contained in:
Max 2026-03-24 15:54:38 +08:00
parent 84df0d013f
commit 6e08349556
1 changed files with 95 additions and 30 deletions

View File

@ -199,6 +199,12 @@ class _WebShellPageState extends State<WebShellPage>
return generation == _webViewGeneration;
}
void _debugLog(String message) {
if (kDebugMode) {
debugPrint(message);
}
}
PlatformWebViewWidgetCreationParams _buildAndroidWidgetParams(
PlatformWebViewWidgetCreationParams widgetParams,
AndroidRenderMode renderMode,
@ -282,6 +288,11 @@ class _WebShellPageState extends State<WebShellPage>
_hasMeasuredProgress = true;
}
});
_debugLog(
'🔍 [onProgress] progress=$progress, '
'hasStartedRemote=$_hasStartedRemoteMainFrame, '
'hasMeasured=$_hasMeasuredProgress',
);
},
onNavigationRequest: (request) async {
if (!_isActiveWebViewGeneration(generation)) {
@ -316,6 +327,13 @@ class _WebShellPageState extends State<WebShellPage>
_hasStartedRemoteMainFrame = true;
_cancelStartupWatchdog();
_recordWebViewEvent('页面开始加载:$url');
_debugLog(
'🔍 [onPageStarted] url=$url, '
'hasStartedRemote=$_hasStartedRemoteMainFrame, '
'hasBootstrapped=$_hasBootstrapped, '
'hasMainFrameError=$_hasMainFrameError, '
'watchdog已取消',
);
if (!mounted) {
return;
}
@ -341,6 +359,12 @@ class _WebShellPageState extends State<WebShellPage>
}
_recordWebViewEvent('页面加载完成:$url');
_cancelStartupWatchdog();
_debugLog(
'🔍 [onPageFinished] url=$url, '
'hasMainFrameError=$_hasMainFrameError, '
'injectBridge=${!_hasMainFrameError}, '
'retryCount=$_startupRetryCount',
);
if (!_hasMainFrameError) {
unawaited(_injectAppShellBridge(_controller, url));
}
@ -392,6 +416,11 @@ class _WebShellPageState extends State<WebShellPage>
'url=${error.url}, '
'description=${error.description}',
);
_debugLog(
'🔍 [onWebResourceError] isMainFrame=${error.isForMainFrame}, '
'errorType=${error.errorType}, '
'willSetMainFrameError=${error.isForMainFrame != false}',
);
if (!mounted || error.isForMainFrame == false) {
return;
}
@ -476,10 +505,19 @@ class _WebShellPageState extends State<WebShellPage>
}
Future<void> _handleStartupTimeout() async {
_debugLog(
'🔍 [_handleStartupTimeout] mounted=$mounted, '
'isLoading=$_isLoadingPage, '
'hasMainFrameError=$_hasMainFrameError, '
'hasStartedRemote=$_hasStartedRemoteMainFrame, '
'retryCount=$_startupRetryCount, '
'renderIndex=$_renderModeIndex',
);
if (!mounted ||
!_isLoadingPage ||
_hasMainFrameError ||
_hasStartedRemoteMainFrame) {
_debugLog('🔍 [_handleStartupTimeout] 提前返回,不执行超时恢复');
return;
}
@ -487,6 +525,11 @@ class _WebShellPageState extends State<WebShellPage>
final maxRetryCount = _androidCompatibilityPlan.prefersAggressiveRecovery
? 2
: 1;
_debugLog(
'🔍 [_handleStartupTimeout] switchedRenderMode=$switchedRenderMode, '
'maxRetryCount=$maxRetryCount, '
'currentRetryCount=$_startupRetryCount',
);
if (switchedRenderMode || _startupRetryCount < maxRetryCount) {
final nextRetryCount = _startupRetryCount + 1;
@ -505,6 +548,7 @@ class _WebShellPageState extends State<WebShellPage>
return;
}
_debugLog('🔍 [_handleStartupTimeout] 所有重试已耗尽,显示错误页面');
_setMainFrameError(
title: '页面启动超时',
message: <String>[
@ -516,6 +560,10 @@ class _WebShellPageState extends State<WebShellPage>
}
void _setMainFrameError({required String title, required String message}) {
_debugLog(
'🔍 [_setMainFrameError] title=$title, '
'message=$message',
);
_cancelStartupWatchdog();
if (!mounted) {
return;
@ -528,6 +576,12 @@ class _WebShellPageState extends State<WebShellPage>
_errorMessage = message;
_progress = 0;
});
_debugLog(
'🔍 [_setMainFrameError] 状态已更新: '
'isLoading=$_isLoadingPage, '
'hasMainFrameError=$_hasMainFrameError, '
'→ 应显示 ErrorOverlay',
);
}
Future<void> _recoverFromBrokenStartupState({bool deepReset = false}) async {
@ -1023,6 +1077,15 @@ class _WebShellPageState extends State<WebShellPage>
final showProgressBar =
_isLoadingPage && (!_hasMeasuredProgress || _progress < 100);
final showLaunchOverlay = !_hasBootstrapped && !_hasMainFrameError;
_debugLog(
'🔍 [build] showProgressBar=$showProgressBar, '
'showLaunchOverlay=$showLaunchOverlay, '
'showErrorOverlay=$_hasMainFrameError, '
'showWebView=${!_hasMainFrameError}, '
'isLoading=$_isLoadingPage, '
'hasBootstrapped=$_hasBootstrapped, '
'progress=$_progress',
);
return PopScope<void>(
canPop: false,
@ -1041,39 +1104,41 @@ class _WebShellPageState extends State<WebShellPage>
end: Alignment.bottomCenter,
),
),
child: Stack(
children: <Widget>[
Visibility(
visible: !_hasMainFrameError,
child: _webViewWidget,
),
if (showProgressBar)
Positioned(
top: 0,
left: 0,
right: 0,
child: TopProgressBar(
progress: _progress,
hasMeasuredProgress: _hasMeasuredProgress,
),
child: SizedBox.expand(
child: Stack(
children: <Widget>[
Visibility(
visible: !_hasMainFrameError,
child: _webViewWidget,
),
if (showLaunchOverlay)
Positioned.fill(
child: LaunchOverlay(
progress: _progress,
hasMeasuredProgress: _hasMeasuredProgress,
if (showProgressBar)
Positioned(
top: 0,
left: 0,
right: 0,
child: TopProgressBar(
progress: _progress,
hasMeasuredProgress: _hasMeasuredProgress,
),
),
),
if (_hasMainFrameError)
Positioned.fill(
child: ErrorOverlay(
title: _errorTitle,
message: _errorMessage,
currentUrl: _currentUrl,
onRetry: _reloadPage,
if (showLaunchOverlay)
Positioned.fill(
child: LaunchOverlay(
progress: _progress,
hasMeasuredProgress: _hasMeasuredProgress,
),
),
),
],
if (_hasMainFrameError)
Positioned.fill(
child: ErrorOverlay(
title: _errorTitle,
message: _errorMessage,
currentUrl: _currentUrl,
onRetry: _reloadPage,
),
),
],
),
),
),
),