Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ class InAppWebView extends StatefulWidget {
}) : this.fromPlatformCreationParams(
key: key,
params: PlatformInAppWebViewWidgetCreationParams(
key: key,
controllerFromPlatform:
(PlatformInAppWebViewController controller) =>
InAppWebViewController.fromPlatform(platform: controller),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CustomFlutterViewControllerValue> {
Expand All @@ -142,6 +156,7 @@ class CustomPlatformViewController
/// Initializes the underlying platform view.
Future<void> initialize({
Function(int id)? onPlatformViewCreated,
Key? creationKey,
dynamic arguments,
}) async {
if (_isDisposed) {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -405,6 +434,7 @@ class _CustomPlatformViewState extends State<CustomPlatformView>
widget.onPlatformViewCreated?.call(id);
setState(() {});
},
creationKey: widget.creationKey,
arguments: widget.creationParams,
)
.ignore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget {

return CustomPlatformView(
onPlatformViewCreated: _onPlatformViewCreated,
creationKey: params.key,
creationParams: <String, dynamic>{
'initialUrlRequest': params.initialUrlRequest?.toMap(),
'initialFile': params.initialFile,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -27,9 +48,25 @@ class WindowsWebViewCreationFailures {

static Stream<WindowsWebViewCreationFailure> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<dynamic, dynamic>;
if (arguments['creation'] == 'fails') {
throw PlatformException(code: '0', message: 'creation failed');
}
return 1;
});
final failures = <WindowsWebViewCreationFailure>[];
final subscription = WindowsWebViewCreationFailures.stream.listen(
failures.add,
);
addTearDown(subscription.cancel);
const failedKey = ValueKey<String>('failed');
const succeededKey = ValueKey<String>('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);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <WindowsWebViewCreationFailure>[];
final sub = WindowsWebViewCreationFailures.stream.listen(received.add);
addTearDown(sub.cancel);

WindowsWebViewCreationFailures.report(
Exception('boom'),
StackTrace.current,
requestedUrl: 'file:///plugins/a/index.html',
);
await Future<void>.delayed(Duration.zero);

expect(received.single.requestedUrl, 'file:///plugins/a/index.html');
});

test('reporting without listeners does not throw', () {
expect(
() => WindowsWebViewCreationFailures.report(
Expand Down
Loading