diff --git a/.cirrus.yml b/.cirrus.yml index b505c43f4c7de..d672e76ec8e93 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -172,6 +172,7 @@ task: - $FRAMEWORK_PATH/flutter/bin/flutter config --local-engine=host_debug_unopt --no-analytics --enable-web - $FRAMEWORK_PATH/flutter/bin/flutter pub get --local-engine=host_debug_unopt - $FRAMEWORK_PATH/flutter/bin/flutter drive -v --target=test_driver/text_editing_e2e.dart -d web-server --release --browser-name=chrome --local-engine=host_debug_unopt + - $FRAMEWORK_PATH/flutter/bin/flutter drive -v --target=test_driver/platform_messages_e2e.dart -d web-server --release --browser-name=chrome --local-engine=host_debug_unopt - $FRAMEWORK_PATH/flutter/bin/flutter drive -v --target=test_driver/treeshaking_e2e.dart -d web-server --profile --browser-name=chrome --local-engine=host_debug_unopt - $FRAMEWORK_PATH/flutter/bin/flutter drive -v --target=test_driver/image_loading_e2e.dart -d web-server --release --browser-name=chrome --local-engine=host_debug_unopt diff --git a/e2etests/web/regular_integration_tests/lib/platform_messages_main.dart b/e2etests/web/regular_integration_tests/lib/platform_messages_main.dart new file mode 100644 index 0000000000000..4201da2b47b77 --- /dev/null +++ b/e2etests/web/regular_integration_tests/lib/platform_messages_main.dart @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() => runApp(MyApp()); + +Future dataFuture; + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + key: const Key('mainapp'), + title: 'Integration Test App For Platform Messages', + home: MyHomePage(title: 'Integration Test App For Platform Messages'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final TextEditingController _controller = + TextEditingController(text: 'Text1'); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Hello World', + ), + // Create a text form field since we can't test clipboard unless + // html document has focus. + TextFormField( + key: const Key('input'), + enabled: true, + controller: _controller, + //initialValue: 'Text1', + decoration: const InputDecoration( + labelText: 'Text Input Field:', + ), + ), + ], + ), + ), + ); + } +} diff --git a/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e.dart b/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e.dart new file mode 100644 index 0000000000000..cd6a816b67f67 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:html' as html; +// ignore: undefined_shown_name +import 'dart:ui' as ui show platformViewRegistry; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:regular_integration_tests/platform_messages_main.dart' as app; + +import 'package:e2e/e2e.dart'; + +void main() async { + E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding; + + testWidgets('platform message for Clipboard.setData reply with future', + (WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + + // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 + SystemChannels.textInput.setMockMethodCallHandler(null); + // Focus on a TextFormField. + final Finder finder = find.byKey(const Key('input')); + expect(finder, findsOneWidget); + await tester.tap(find.byKey(const Key('input'))); + // Focus in input, otherwise clipboard will fail with + // 'document is not focused' platform exception. + html.document.querySelector('input').focus(); + await Clipboard.setData(const ClipboardData(text: 'sample text')); + }, skip: true); // https://github.com/flutter/flutter/issues/54296 + + testWidgets('Should create and dispose view embedder', + (WidgetTester tester) async { + int viewInstanceCount = 0; + + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + // ignore: undefined_prefixed_name + ui.platformViewRegistry.registerViewFactory('MyView', (int viewId) { + ++viewInstanceCount; + return html.DivElement(); + }); + + app.main(); + await tester.pumpAndSettle(); + final Map createArgs = { + 'id': '567', + 'viewType': 'MyView', + }; + await SystemChannels.platform_views.invokeMethod('create', createArgs); + final Map disposeArgs = { + 'id': '567', + }; + await SystemChannels.platform_views.invokeMethod('dispose', disposeArgs); + expect(viewInstanceCount, 1); + }); +} diff --git a/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e_test.dart b/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e_test.dart new file mode 100644 index 0000000000000..a29203f7dcdd9 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/platform_messages_e2e_test.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:e2e/e2e_driver.dart' as e2e; + +Future main() async => e2e.main(); diff --git a/lib/web_ui/lib/src/engine/clipboard.dart b/lib/web_ui/lib/src/engine/clipboard.dart index de119a2842e73..cc5ed5532c96c 100644 --- a/lib/web_ui/lib/src/engine/clipboard.dart +++ b/lib/web_ui/lib/src/engine/clipboard.dart @@ -18,6 +18,7 @@ class ClipboardMessageHandler { void setDataMethodCall( MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { const MethodCodec codec = JSONMethodCodec(); + bool errorEnvelopeEncoded = false; _copyToClipboardStrategy .setData(methodCall.arguments['text']) .then((bool success) { @@ -26,10 +27,15 @@ class ClipboardMessageHandler { } else { callback(codec.encodeErrorEnvelope( code: 'copy_fail', message: 'Clipboard.setData failed')); + errorEnvelopeEncoded = true; } }).catchError((dynamic _) { - callback(codec.encodeErrorEnvelope( - code: 'copy_fail', message: 'Clipboard.setData failed')); + // Don't encode a duplicate reply if we already failed and an error + // was already encoded. + if (!errorEnvelopeEncoded) { + callback(codec.encodeErrorEnvelope( + code: 'copy_fail', message: 'Clipboard.setData failed')); + } }); } diff --git a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart index 4cb6e64b7a74a..60d218d0d0e47 100644 --- a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart @@ -69,11 +69,9 @@ class HtmlViewEmbedder { switch (decoded.method) { case 'create': _create(decoded, callback); - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'dispose': _dispose(decoded, callback); - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; } callback(null); diff --git a/lib/web_ui/lib/src/engine/platform_views.dart b/lib/web_ui/lib/src/engine/platform_views.dart index 2454cd44edb1d..d422f9a3ecf84 100644 --- a/lib/web_ui/lib/src/engine/platform_views.dart +++ b/lib/web_ui/lib/src/engine/platform_views.dart @@ -50,11 +50,9 @@ void handlePlatformViewCall( switch (decoded.method) { case 'create': _createPlatformView(decoded, callback); - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'dispose': _disposePlatformView(decoded, callback); - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; } callback(null);