11import 'dart:developer' ;
2+ import 'dart:io' show Platform;
23
34import 'package:openlist_mobile/contant/native_bridge.dart' ;
45import 'package:openlist_mobile/generated_api.dart' ;
@@ -34,7 +35,7 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
3435 // iOS specific: Enable page caching and state preservation
3536 cacheEnabled: true ,
3637 sharedCookiesEnabled: true ,
37- limitsNavigationsToAppBoundDomains: false ,
38+ limitsNavigationsToAppBoundDomains: Platform .isIOS ,
3839 // Enable disk and memory cache for better state preservation
3940 cacheMode: CacheMode .LOAD_DEFAULT ,
4041 // Prevent WebView from being suspended in background
@@ -48,6 +49,56 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
4849 bool _canGoBack = false ;
4950 bool _isLoading = false ;
5051
52+ static const Set <String > _inAppSafeSchemes = {
53+ "about" ,
54+ "data" ,
55+ "file" ,
56+ };
57+
58+ bool _isLoopbackHost (String host) {
59+ final normalized = host.toLowerCase ();
60+ return normalized == "localhost" ||
61+ normalized == "127.0.0.1" ||
62+ normalized == "::1" ;
63+ }
64+
65+ bool _isAllowedInAppNavigation (Uri uri) {
66+ final scheme = uri.scheme.toLowerCase ();
67+
68+ if (_inAppSafeSchemes.contains (scheme)) {
69+ return true ;
70+ }
71+
72+ if (scheme == "http" || scheme == "https" ) {
73+ if (! Platform .isIOS) {
74+ return true ;
75+ }
76+ return _isLoopbackHost (uri.host);
77+ }
78+
79+ return false ;
80+ }
81+
82+ Future <void > _openExternalUri (String uriString) async {
83+ final silentMode = await NativeBridge .appConfig.isSilentJumpAppEnabled ();
84+ if (silentMode) {
85+ NativeCommon ().startActivityFromUri (uriString);
86+ return ;
87+ }
88+
89+ if (! mounted) return ;
90+
91+ Get .showSnackbar (GetSnackBar (
92+ message: S .current.jumpToOtherApp,
93+ duration: const Duration (seconds: 5 ),
94+ mainButton: TextButton (
95+ onPressed: () {
96+ NativeCommon ().startActivityFromUri (uriString);
97+ },
98+ child: Text (S .current.goTo),
99+ )));
100+ }
101+
51102 onClickNavigationBar () {
52103 log ("onClickNavigationBar" );
53104 _webViewController? .reload ();
@@ -156,38 +207,23 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
156207 shouldOverrideUrlLoading: (controller, navigationAction) async {
157208 log ("shouldOverrideUrlLoading ${navigationAction .request .url }" );
158209
159- var uri = navigationAction.request.url! ;
160- if (! [
161- "http" ,
162- "https" ,
163- "file" ,
164- "chrome" ,
165- "data" ,
166- "javascript" ,
167- "about"
168- ].contains (uri.scheme)) {
169- log ("shouldOverrideUrlLoading ${uri .toString ()}" );
170- final silentMode =
171- await NativeBridge .appConfig.isSilentJumpAppEnabled ();
172- if (silentMode) {
173- NativeCommon ().startActivityFromUri (uri.toString ());
174- } else {
175- Get .showSnackbar (GetSnackBar (
176- message: S .current.jumpToOtherApp,
177- duration: const Duration (seconds: 5 ),
178- mainButton: TextButton (
179- onPressed: () {
180- NativeCommon ()
181- .startActivityFromUri (uri.toString ());
182- },
183- child: Text (S .current.goTo),
184- )));
185- }
210+ final uri = navigationAction.request.url;
211+ if (uri == null ) {
212+ return NavigationActionPolicy .CANCEL ;
213+ }
214+
215+ if (_isAllowedInAppNavigation (uri)) {
216+ return NavigationActionPolicy .ALLOW ;
217+ }
186218
219+ final scheme = uri.scheme.toLowerCase ();
220+ if (Platform .isIOS && scheme == "javascript" ) {
221+ log ("Blocked javascript navigation on iOS: ${uri .toString ()}" );
187222 return NavigationActionPolicy .CANCEL ;
188223 }
189224
190- return NavigationActionPolicy .ALLOW ;
225+ await _openExternalUri (uri.toString ());
226+ return NavigationActionPolicy .CANCEL ;
191227 },
192228 onReceivedError: (controller, request, error) async {
193229 log ("WebView error: ${error .description }" );
0 commit comments