diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart index 60444f517..0b89f64ba 100755 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart @@ -479,6 +479,7 @@ class InAppWebView extends StatefulWidget { }) : this.fromPlatformCreationParams( key: key, params: PlatformInAppWebViewWidgetCreationParams( + key: key, controllerFromPlatform: (PlatformInAppWebViewController controller) => InAppWebViewController.fromPlatform(platform: controller), diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart index 3bbd75b5d..7ab8a738d 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart @@ -117,6 +117,20 @@ class CustomFlutterViewControllerValue { CustomFlutterViewControllerValue.uninitialized() : this(isInitialized: false); } +/// The URL a creation request was going to load, read back from its creation +/// params. It is the only identifier both sides share before the webview +/// exists, so it is what lets a host match a failure to its own request. +String? _requestedUrlOf(dynamic arguments) { + if (arguments is! Map) return null; + final urlRequest = arguments['initialUrlRequest']; + if (urlRequest is Map) { + final url = urlRequest['url']; + if (url != null) return url.toString(); + } + final file = arguments['initialFile']; + return file?.toString(); +} + /// Controls a WebView and provides streams for various change events. class CustomPlatformViewController extends ValueNotifier { @@ -142,6 +156,7 @@ class CustomPlatformViewController /// Initializes the underlying platform view. Future initialize({ Function(int id)? onPlatformViewCreated, + Key? creationKey, dynamic arguments, }) async { if (_isDisposed) { @@ -158,7 +173,13 @@ class CustomPlatformViewController if (!_creatingCompleter.isCompleted) { _creatingCompleter.complete(); } - WindowsWebViewCreationFailures.report(error, stackTrace); + final failure = WindowsWebViewCreationFailure( + error, + stackTrace, + requestedUrl: _requestedUrlOf(arguments), + creationKey: creationKey, + ); + WindowsWebViewCreationFailures.reportFailure(failure); rethrow; } @@ -330,9 +351,17 @@ class CustomPlatformView extends StatefulWidget { final Function(int id)? onPlatformViewCreated; + /// The caller-supplied key that identifies this creation attempt. + /// + /// It is carried on [WindowsWebViewCreationFailure] so listeners on the + /// global failure stream can identify the failed widget even when multiple + /// WebViews request the same URL. + final Key? creationKey; + const CustomPlatformView({ this.creationParams, this.onPlatformViewCreated, + this.creationKey, this.scaleFactor, this.filterQuality = FilterQuality.none, }); @@ -405,6 +434,7 @@ class _CustomPlatformViewState extends State widget.onPlatformViewCreated?.call(id); setState(() {}); }, + creationKey: widget.creationKey, arguments: widget.creationParams, ) .ignore(); diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart index c4afd5d0c..96809aec6 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart @@ -330,6 +330,7 @@ class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget { return CustomPlatformView( onPlatformViewCreated: _onPlatformViewCreated, + creationKey: params.key, creationParams: { 'initialUrlRequest': params.initialUrlRequest?.toMap(), 'initialFile': params.initialFile, diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/webview_creation_failure.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/webview_creation_failure.dart index 0c9c1a07e..6ad45a2ea 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/webview_creation_failure.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/webview_creation_failure.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter/widgets.dart'; + /// A native WebView creation failure, as reported by the Windows plugin. class WindowsWebViewCreationFailure { /// The error thrown by the `createInAppWebView` platform call. On Windows it @@ -8,10 +10,29 @@ class WindowsWebViewCreationFailure { final StackTrace stackTrace; - const WindowsWebViewCreationFailure(this.error, this.stackTrace); + /// The URL (or file path) the failed webview was asked to load, taken from + /// its creation params. Hosts use it to tell whose creation failed: several + /// webviews can be created concurrently and this stream is global. + final String? requestedUrl; + + /// The [InAppWebView] key for the failed creation attempt. + /// + /// URLs are not unique: multiple WebViews can create the same URL at once. + /// Use this field to match a failure to the right widget. Supply a distinct + /// key to each concurrently-created WebView. + final Key? creationKey; + + const WindowsWebViewCreationFailure( + this.error, + this.stackTrace, { + this.requestedUrl, + this.creationKey, + }); @override - String toString() => 'WindowsWebViewCreationFailure($error)'; + String toString() => + 'WindowsWebViewCreationFailure(' + '$error, requestedUrl: $requestedUrl, creationKey: $creationKey)'; } /// Broadcasts native WebView creation failures to the host application. @@ -27,9 +48,25 @@ class WindowsWebViewCreationFailures { static Stream get stream => _controller.stream; - static void report(Object error, StackTrace stackTrace) { + static void report( + Object error, + StackTrace stackTrace, { + String? requestedUrl, + Key? creationKey, + }) { + reportFailure( + WindowsWebViewCreationFailure( + error, + stackTrace, + requestedUrl: requestedUrl, + creationKey: creationKey, + ), + ); + } + + static void reportFailure(WindowsWebViewCreationFailure failure) { if (_controller.hasListener) { - _controller.add(WindowsWebViewCreationFailure(error, stackTrace)); + _controller.add(failure); } } } diff --git a/flutter_inappwebview_windows/test/custom_platform_view_creation_failure_test.dart b/flutter_inappwebview_windows/test/custom_platform_view_creation_failure_test.dart index 9466efea6..0b6975df2 100644 --- a/flutter_inappwebview_windows/test/custom_platform_view_creation_failure_test.dart +++ b/flutter_inappwebview_windows/test/custom_platform_view_creation_failure_test.dart @@ -205,4 +205,58 @@ void main() { expect(tester.takeException(), isNull); }, ); + + testWidgets('creation failures carry the failed widget key', (tester) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(IN_APP_WEBVIEW_STATIC_CHANNEL, (call) async { + pluginChannelCalls.add(call); + final arguments = call.arguments! as Map; + if (arguments['creation'] == 'fails') { + throw PlatformException(code: '0', message: 'creation failed'); + } + return 1; + }); + final failures = []; + final subscription = WindowsWebViewCreationFailures.stream.listen( + failures.add, + ); + addTearDown(subscription.cancel); + const failedKey = ValueKey('failed'); + const succeededKey = ValueKey('succeeded'); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Row( + children: [ + Expanded( + child: CustomPlatformView( + creationKey: failedKey, + creationParams: const { + 'creation': 'fails', + 'initialUrlRequest': {'url': 'file:///plugins/a/index.html'}, + }, + ), + ), + Expanded( + child: CustomPlatformView( + creationKey: succeededKey, + creationParams: const { + 'creation': 'succeeds', + 'initialUrlRequest': {'url': 'file:///plugins/a/index.html'}, + }, + ), + ), + ], + ), + ), + ); + await tester.pump(); + await tester.pump(); + + expect(failures, hasLength(1)); + expect(failures.single.requestedUrl, 'file:///plugins/a/index.html'); + expect(failures.single.creationKey, failedKey); + expect(tester.takeException(), isNull); + }); } diff --git a/flutter_inappwebview_windows/test/webview_creation_failure_test.dart b/flutter_inappwebview_windows/test/webview_creation_failure_test.dart index aa900e16f..1521e36a9 100644 --- a/flutter_inappwebview_windows/test/webview_creation_failure_test.dart +++ b/flutter_inappwebview_windows/test/webview_creation_failure_test.dart @@ -15,6 +15,22 @@ void main() { expect(received.single.error, same(error)); }); + test('the requested URL is carried so hosts can tell whose creation failed', + () async { + final received = []; + final sub = WindowsWebViewCreationFailures.stream.listen(received.add); + addTearDown(sub.cancel); + + WindowsWebViewCreationFailures.report( + Exception('boom'), + StackTrace.current, + requestedUrl: 'file:///plugins/a/index.html', + ); + await Future.delayed(Duration.zero); + + expect(received.single.requestedUrl, 'file:///plugins/a/index.html'); + }); + test('reporting without listeners does not throw', () { expect( () => WindowsWebViewCreationFailures.report(