diff --git a/.cirrus.yml b/.cirrus.yml index 441e3ceec39378de6ab9f507a4ad56b41480293c..77d8b81ca4a6f895ae13fb365ec4d070330d8705 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -146,6 +146,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/image_loading_e2e.dart -d web-server --release --browser-name=chrome --local-engine=host_debug_unopt - name: build_and_test_web_linux_firefox compile_host_script: | diff --git a/e2etests/web/regular_integration_tests/assets/images/1.5x/sample_image1.png b/e2etests/web/regular_integration_tests/assets/images/1.5x/sample_image1.png new file mode 100644 index 0000000000000000000000000000000000000000..f1e0ffab2000794b4383822e004bf7438d25ad73 Binary files /dev/null and b/e2etests/web/regular_integration_tests/assets/images/1.5x/sample_image1.png differ diff --git a/e2etests/web/regular_integration_tests/assets/images/2.0x/sample_image1.png b/e2etests/web/regular_integration_tests/assets/images/2.0x/sample_image1.png new file mode 100644 index 0000000000000000000000000000000000000000..d80b0ff28c5024defa7427a695f346523c37c0c3 Binary files /dev/null and b/e2etests/web/regular_integration_tests/assets/images/2.0x/sample_image1.png differ diff --git a/e2etests/web/regular_integration_tests/assets/images/sample_image1.png b/e2etests/web/regular_integration_tests/assets/images/sample_image1.png new file mode 100644 index 0000000000000000000000000000000000000000..0d1393b8052134902c82d85a4c478db6a716684c Binary files /dev/null and b/e2etests/web/regular_integration_tests/assets/images/sample_image1.png differ diff --git a/e2etests/web/regular_integration_tests/lib/image_loading_main.dart b/e2etests/web/regular_integration_tests/lib/image_loading_main.dart new file mode 100644 index 0000000000000000000000000000000000000000..78991d9652eb2cefd2c2dea33deb7bcd54a40459 --- /dev/null +++ b/e2etests/web/regular_integration_tests/lib/image_loading_main.dart @@ -0,0 +1,32 @@ +// 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() async { + const MethodChannel channel = + OptionalMethodChannel('flutter/web_test_e2e', JSONMethodCodec()); + await channel.invokeMethod( + 'setDevicePixelRatio', + '1.5', + ); + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + MyAppState createState() => MyAppState(); +} + +class MyAppState extends State { + @override + Widget build(BuildContext context) { + return MaterialApp( + key: const Key('mainapp'), + title: 'Integration Test App', + home: Image.asset('assets/images/sample_image1.png') + ); + } +} diff --git a/e2etests/web/regular_integration_tests/pubspec.yaml b/e2etests/web/regular_integration_tests/pubspec.yaml index 98fa40773834ebc5fb3542bbf3c179bb014f84e2..7f70f838183db0374641cb523dbf8bae5013c629 100644 --- a/e2etests/web/regular_integration_tests/pubspec.yaml +++ b/e2etests/web/regular_integration_tests/pubspec.yaml @@ -16,3 +16,7 @@ dev_dependencies: e2e: 0.2.4+4 http: 0.12.0+2 test: any + +flutter: + assets: + - assets/images/ \ No newline at end of file diff --git a/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart new file mode 100644 index 0000000000000000000000000000000000000000..bc6ebb9dba887944b9afcb4f8714de01043fdc57 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart @@ -0,0 +1,25 @@ +// 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; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:regular_integration_tests/image_loading_main.dart' as app; + +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding; + + testWidgets('Image loads asset variant based on device pixel ratio', + (WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + final html.ImageElement imageElement = html.querySelector('img') as html.ImageElement; + expect(imageElement.naturalWidth, 1.5 * 100); + expect(imageElement.naturalHeight, 1.5 * 100); + expect(imageElement.width, 100); + expect(imageElement.height, 100); + }); +} diff --git a/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e_test.dart b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..02edbe5679179af766efa9a5b0d4bbb1136d30e2 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e_test.dart @@ -0,0 +1,17 @@ +// 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:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + + final String dataRequest = + await driver.requestData(null, timeout: const Duration(seconds: 1)); + await driver.close(); + + exit(dataRequest == 'pass' ? 0 : 1); +} 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 31f9dc176bafeea1f52eecd7b0e44e1b37235d63..9deee4f7b60f5aa9ab2fa99d7e9f9932e518b612 100644 --- a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart @@ -69,9 +69,11 @@ 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/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8e2f585b83f201e431b2d73e4598c36a083b7218..a5d630dd59de75bcedba3e3a36ddebfc48a1235e 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -419,12 +419,6 @@ flt-glass-pane * { // DOM tree. setElementAttribute(_sceneHostElement, 'aria-hidden', 'true'); - // We treat browser pixels as device pixels because pointer events, - // position, and sizes all use browser pixel as the unit (i.e. "px" in CSS). - // Therefore, as far as the framework is concerned the device pixel ratio - // is 1.0. - window.debugOverrideDevicePixelRatio(1.0); - if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the // page loads. When it changes the values to correct ones it does not diff --git a/lib/web_ui/lib/src/engine/platform_views.dart b/lib/web_ui/lib/src/engine/platform_views.dart index d422f9a3ecf846368dfd816de7732b85f4fe9ce4..2454cd44edb1d071ec4a2fe68552e03feeb33658 100644 --- a/lib/web_ui/lib/src/engine/platform_views.dart +++ b/lib/web_ui/lib/src/engine/platform_views.dart @@ -50,9 +50,11 @@ 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); diff --git a/lib/web_ui/lib/src/engine/semantics/accessibility.dart b/lib/web_ui/lib/src/engine/semantics/accessibility.dart index a858ebc7ab668c59df7e4d0030b152d18567b620..2d874f8cf01dac6e1b427c10725d2b0fd0a7c5a0 100644 --- a/lib/web_ui/lib/src/engine/semantics/accessibility.dart +++ b/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -52,9 +52,9 @@ class AccessibilityAnnouncements { html.HtmlElement get _domElement => _element ??= _createElement(); /// Decodes the message coming from the 'flutter/accessibility' channel. - void handleMessage(ByteData data) { + void handleMessage(StandardMessageCodec codec, ByteData data) { final Map inputMap = - const StandardMessageCodec().decodeMessage(data); + codec.decodeMessage(data); final Map dataMap = inputMap['data']; final String message = dataMap['message']; if (message != null && message.isNotEmpty) { diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index 4de1fe779e55c1ba349fb868496467d8c8355d8f..823a9963acddc1c72a5b83167ab25c300596a34d 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -10,7 +10,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { _surfaceStack.add(PersistedScene(_lastFrameScene)); } - final List _surfaceStack = []; + final List _surfaceStack = + []; /// The scene built by this scene builder. /// @@ -19,7 +20,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { assert(() { if (_surfaceStack.length != 1) { final String surfacePrintout = _surfaceStack - .map((PersistedContainerSurface surface) => surface.runtimeType) + .map( + (PersistedContainerSurface surface) => surface.runtimeType) .toList() .join(', '); throw Exception('Incorrect sequence of push/pop operations while ' @@ -42,7 +44,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // the live tree. if (surface.oldLayer != null) { assert(surface.oldLayer.runtimeType == surface.runtimeType); - assert(debugAssertSurfaceState(surface.oldLayer, PersistedSurfaceState.active)); + assert(debugAssertSurfaceState( + surface.oldLayer, PersistedSurfaceState.active)); surface.oldLayer.state = PersistedSurfaceState.pendingUpdate; } _adoptSurface(surface); @@ -97,6 +100,16 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { if (matrix4.length != 16) { throw ArgumentError('"matrix4" must have 16 entries.'); } + if (_surfaceStack.length == 1) { + // Top level transform contains view configuration to scale + // scene to devicepixelratio. Use identity instead since CSS uses + // logical device pixels. + if (!ui.debugEmulateFlutterTesterEnvironment) { + assert(matrix4[0] == window.devicePixelRatio && + matrix4[5] == window.devicePixelRatio); + } + matrix4 = Matrix4.identity().storage; + } return _pushSurface(PersistedTransform(oldLayer, matrix4)); } @@ -276,7 +289,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { void addRetained(ui.EngineLayer retainedLayer) { final PersistedContainerSurface retainedSurface = retainedLayer; if (assertionsEnabled) { - assert(debugAssertSurfaceState(retainedSurface, PersistedSurfaceState.active, PersistedSurfaceState.released)); + assert(debugAssertSurfaceState(retainedSurface, + PersistedSurfaceState.active, PersistedSurfaceState.released)); } retainedSurface.tryRetain(); _adoptSurface(retainedSurface); @@ -319,7 +333,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// for more details. @override void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) { - _addPerformanceOverlay(enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); + _addPerformanceOverlay( + enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); } /// Whether we've already warned the user about the lack of the performance @@ -337,7 +352,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ) { if (!_webOnlyDidWarnAboutPerformanceOverlay) { _webOnlyDidWarnAboutPerformanceOverlay = true; - html.window.console.warn('The performance overlay isn\'t supported on the web'); + html.window.console + .warn('The performance overlay isn\'t supported on the web'); } } @@ -377,7 +393,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { _addTexture(offset.dx, offset.dy, width, height, textureId); } - void _addTexture(double dx, double dy, double width, double height, int textureId) { + void _addTexture( + double dx, double dy, double width, double height, int textureId) { // In test mode, allow this to be a no-op. if (!ui.debugEmulateFlutterTesterEnvironment) { throw UnimplementedError('Textures are not supported in Flutter Web'); diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index 369d711e59f38c1ef1563d0e717d3760e93343ca..b13b0a168e604819883eb72ebe9b34fa7a1308e1 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -778,8 +778,11 @@ class TextEditingChannel { final HybridTextEditing implementation; /// Handles "flutter/textinput" platform messages received from the framework. - void handleTextInput(ByteData data) { - final MethodCall call = const JSONMethodCodec().decodeMethodCall(data); + void handleTextInput( + ByteData data, + ui.PlatformMessageResponseCallback callback) { + const JSONMethodCodec codec = JSONMethodCodec(); + final MethodCall call = codec.decodeMethodCall(data); switch (call.method) { case 'TextInput.setClient': implementation.setClient( @@ -815,6 +818,7 @@ class TextEditingChannel { default: throw StateError('Unsupported method call on the flutter/textinput channel: ${call.method}'); } + window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); } /// Sends the 'TextInputClient.updateEditingState' message to the framework. diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index e2004cda2928601c02b12c816c87b80d496a36df..8bfb18039b5fb88623ca5aa747fa644bda48a0bd 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -15,19 +15,11 @@ class EngineWindow extends ui.Window { } @override - double get devicePixelRatio { - if (_debugDevicePixelRatio != null) { - return _debugDevicePixelRatio; - } + double get devicePixelRatio => _debugDevicePixelRatio != null + ? _debugDevicePixelRatio + : browserDevicePixelRatio; - if (experimentalUseSkia) { - return browserDevicePixelRatio; - } else { - return 1.0; - } - } - - /// Returns device pixel ratio returns by browser. + /// Returns device pixel ratio returned by browser. static double get browserDevicePixelRatio { double ratio = html.window.devicePixelRatio; // Guard against WebOS returning 0. @@ -38,10 +30,7 @@ class EngineWindow extends ui.Window { /// /// This is useful in tests to emulate screens of different dimensions. void debugOverrideDevicePixelRatio(double value) { - assert(() { - _debugDevicePixelRatio = value; - return true; - }()); + _debugDevicePixelRatio = value; } double _debugDevicePixelRatio; @@ -160,26 +149,38 @@ class EngineWindow extends ui.Window { case 'HapticFeedback.vibrate': final String type = decoded.arguments; domRenderer.vibrate(_getHapticFeedbackDuration(type)); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'SystemChrome.setApplicationSwitcherDescription': final Map arguments = decoded.arguments; domRenderer.setTitle(arguments['label']); domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'SystemSound.play': // There are no default system sounds on web. + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'Clipboard.setData': ClipboardMessageHandler().setDataMethodCall(decoded, callback); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; case 'Clipboard.getData': ClipboardMessageHandler().getDataMethodCall(callback); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); return; } break; case 'flutter/textinput': - textEditing.channel.handleTextInput(data); + textEditing.channel.handleTextInput(data, callback); + return; + + case 'flutter/web_test_e2e': + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(codec, data) + )); return; case 'flutter/platform_views': @@ -192,7 +193,9 @@ class EngineWindow extends ui.Window { case 'flutter/accessibility': // In widget tests we want to bypass processing of platform messages. - accessibilityAnnouncements.handleMessage(data); + final StandardMessageCodec codec = StandardMessageCodec(); + accessibilityAnnouncements.handleMessage(codec, data); + _replyToPlatformMessage(callback, codec.encodeMessage(true)); return; case 'flutter/navigation': @@ -204,9 +207,11 @@ class EngineWindow extends ui.Window { case 'routePushed': case 'routeReplaced': _browserHistory.setRouteName(message['routeName']); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); break; case 'routePopped': _browserHistory.setRouteName(message['previousRouteName']); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); break; } return; @@ -250,7 +255,9 @@ class EngineWindow extends ui.Window { ByteData data, ) { Future.delayed(Duration.zero).then((_) { - callback(data); + if (callback != null) { + callback(data); + } }); } @@ -315,6 +322,20 @@ class EngineWindow extends ui.Window { Rasterizer rasterizer = experimentalUseSkia ? Rasterizer(Surface()) : null; } +bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData data) { + final MethodCall decoded = codec.decodeMethodCall(data); + final Map message = decoded.arguments; + double ratio = double.parse(decoded.arguments); + bool result = false; + switch(decoded.method) { + case 'setDevicePixelRatio': + window.debugOverrideDevicePixelRatio(ratio); + window.onMetricsChanged(); + return true; + } + return false; +} + /// The window singleton. /// /// `dart:ui` window delegates to this value. However, this value has a wider diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index 5952b19ed8c2fc7151471e3c576ec2e5a41645cd..471876706c0f4017d391c99bdac346fb249710ce 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -29,7 +29,7 @@ Future ensureTestPlatformInitializedThenRunTest( /// are available. Future _platformInitializedFuture; -/// Initializes domRenderer with specific devicePixelRation and physicalSize. +/// Initializes domRenderer with specific devicePixelRatio and physicalSize. Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // Force-initialize DomRenderer so it doesn't overwrite test pixel ratio. engine.domRenderer; diff --git a/lib/web_ui/test/engine/semantics/accessibility_test.dart b/lib/web_ui/test/engine/semantics/accessibility_test.dart index 8c572563c38db8155f1e8f630cd0458aba7290c7..767d4742ad0ea820f02b4ef967a22cb7d180a62d 100644 --- a/lib/web_ui/test/engine/semantics/accessibility_test.dart +++ b/lib/web_ui/test/engine/semantics/accessibility_test.dart @@ -33,7 +33,8 @@ void main() { // Initially there is no accessibility-element expect(document.getElementById('accessibility-element'), isNull); - accessibilityAnnouncements.handleMessage(codec.encodeMessage(testInput)); + accessibilityAnnouncements.handleMessage(codec, + codec.encodeMessage(testInput)); expect( document.getElementById('accessibility-element'), isNotNull, diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index e53d37d3ab08039e9787605dca194935daae4c55..fd7f45e6a00233e048465cc71a6cddb7f3e995ec 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -222,12 +222,15 @@ void main() async { ); final SceneBuilder sb = SceneBuilder(); + sb.pushTransform(Matrix4.diagonal3Values(EngineWindow.browserDevicePixelRatio, + EngineWindow.browserDevicePixelRatio, 1.0).storage); sb.pushTransform(Matrix4.rotationZ(math.pi / 2).storage); sb.pushOffset(0, -500); sb.pushClipRect(canvasSize); sb.pop(); sb.pop(); sb.pop(); + sb.pop(); final SurfaceScene scene = sb.build(); final html.Element sceneElement = scene.webOnlyRootElement; diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 455bceb07551f628cc255e0e865542d6d6a89daa..076fff0ff38531b0472f174a1a92252d719f65b2 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -405,6 +405,11 @@ void _testCullRectComputation() { // enough to fit the rotated clip. test('clips correctly when using 3d transforms', () async { final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + + builder.pushTransform(Matrix4.diagonal3Values( + EngineWindow.browserDevicePixelRatio, + EngineWindow.browserDevicePixelRatio, 1.0).storage); + // TODO(yjbanov): see the TODO below. // final double screenWidth = html.window.innerWidth.toDouble(); // final double screenHeight = html.window.innerHeight.toDouble(); @@ -499,6 +504,7 @@ void _testCullRectComputation() { builder.pop(); // pushClipRect builder.pop(); // pushOffset builder.pop(); // pushTransform scale + builder.pop(); // pushTransform scale devicepixelratio html.document.body.append(builder.build().webOnlyRootElement); await matchGoldenFile('compositing_3d_rotate1.png', region: region); diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index dad9bbdd80af7e2a66cd03ba08857066deb0a507..97360e41357ebd3b9ab13b7e082b104512ed6993 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/text_editing_test.dart @@ -560,7 +560,7 @@ void main() { /// Emulates sending of a message by the framework to the engine. void sendFrameworkMessage(dynamic message) { - textEditing.channel.handleTextInput(message); + textEditing.channel.handleTextInput(message, (ByteData data) {}); } /// Sends the necessary platform messages to activate a text field and show