From 65fb5d322849e76ad6c9b659b1dec4d4d4b56562 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 18 Mar 2020 11:52:32 -0700 Subject: [PATCH 01/29] Switch ui.window.devicePixelRatio to browser dpi --- lib/web_ui/lib/src/engine/dom_renderer.dart | 6 ------ .../lib/src/engine/surface/scene_builder.dart | 8 ++++++++ lib/web_ui/lib/src/engine/window.dart | 16 ++++------------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8e2f585b83f20..a5d630dd59de7 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/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index 4de1fe779e55c..dc7c1918d9906 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -97,6 +97,14 @@ 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 web browser + // renders using logical device pixels. + assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && + matrix4[5] == EngineWindow.browserDevicePixelRatio); + matrix4 = Matrix4.identity().storage; + } return _pushSurface(PersistedTransform(oldLayer, matrix4)); } diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index e2004cda29286..58a40d29f8bdc 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; - } - - if (experimentalUseSkia) { - return browserDevicePixelRatio; - } else { - return 1.0; - } - } + double get devicePixelRatio => _debugDevicePixelRatio != null + ? _debugDevicePixelRatio + : browserDevicePixelRatio; - /// 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. From bd58d35001917261d95c2f1856f93848e14879b6 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 18 Mar 2020 17:17:07 -0700 Subject: [PATCH 02/29] Rename debugOverrideDevicePixelRation to override. Add integration test for multi res image loading --- .../assets/images/1.5x/sample_image1.png | Bin 0 -> 28340 bytes .../assets/images/2.0x/sample_image1.png | Bin 0 -> 41558 bytes .../assets/images/sample_image1.png | Bin 0 -> 14746 bytes .../lib/image_loading_main.dart | 38 ++++++++++++++++++ .../regular_integration_tests/pubspec.yaml | 4 ++ .../test_driver/image_loading_e2e.dart | 24 +++++++++++ .../test_driver/image_loading_e2e_test.dart | 18 +++++++++ lib/web_ui/lib/src/engine/window.dart | 26 +++++++++--- lib/web_ui/lib/src/ui/test_embedding.dart | 2 +- lib/web_ui/test/path_test.dart | 4 +- 10 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 e2etests/web/regular_integration_tests/assets/images/1.5x/sample_image1.png create mode 100644 e2etests/web/regular_integration_tests/assets/images/2.0x/sample_image1.png create mode 100644 e2etests/web/regular_integration_tests/assets/images/sample_image1.png create mode 100644 e2etests/web/regular_integration_tests/lib/image_loading_main.dart create mode 100644 e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart create mode 100644 e2etests/web/regular_integration_tests/test_driver/image_loading_e2e_test.dart 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 GIT binary patch literal 28340 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 e(}mphinJVDosk2bwfW} MyAppState(); +} + +class MyAppState extends State { + + @override + void initState() { + super.initState(); + const MethodChannel channel = + OptionalMethodChannel('flutter/web_test_e2e', JSONMethodCodec()); + channel.invokeMethod( + 'setDevicePixelRatio', + '1.5', + ); + } + + @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 98fa40773834e..7f70f838183db 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 0000000000000..3c858da3a9d08 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart @@ -0,0 +1,24 @@ +// 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_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 0000000000000..7a60dcaa9ec31 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e_test.dart @@ -0,0 +1,18 @@ +// 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)); + print('result $dataRequest'); + await driver.close(); + + exit(dataRequest == 'pass' ? 0 : 1); +} diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 58a40d29f8bdc..df2919e8399a7 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -28,12 +28,10 @@ class EngineWindow extends ui.Window { /// Overrides the default device pixel ratio. /// - /// This is useful in tests to emulate screens of different dimensions. - void debugOverrideDevicePixelRatio(double value) { - assert(() { - _debugDevicePixelRatio = value; - return true; - }()); + /// This is useful in tests to emulate screens of different dimensions + /// including integration tests. + void overrideDevicePixelRatio(double value) { + _debugDevicePixelRatio = value; } double _debugDevicePixelRatio; @@ -174,6 +172,9 @@ class EngineWindow extends ui.Window { textEditing.channel.handleTextInput(data); return; + case 'flutter/web_test_e2e': + _handleWebTestEnd2EndMessage(data); + return; case 'flutter/platform_views': if (experimentalUseSkia) { rasterizer.viewEmbedder.handlePlatformViewCall(data, callback); @@ -307,6 +308,19 @@ class EngineWindow extends ui.Window { Rasterizer rasterizer = experimentalUseSkia ? Rasterizer(Surface()) : null; } +void _handleWebTestEnd2EndMessage(ByteData data) { + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map message = decoded.arguments; + double ratio = double.parse(decoded.arguments); + switch(decoded.method) { + case 'setDevicePixelRatio': + window.overrideDevicePixelRatio(ratio); + window.onMetricsChanged(); + break; + } +} + /// 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 5952b19ed8c2f..be18bd69dbd05 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -37,7 +37,7 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // The following parameters are hard-coded in Flutter's test embedder. Since // we don't have an embedder yet this is the lowest-most layer we can put // this stuff in. - engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); + engine.window.overrideDevicePixelRatio(devicePixelRatio); engine.window.webOnlyDebugPhysicalSizeOverride = Size(800 * devicePixelRatio, 600 * devicePixelRatio); webOnlyScheduleFrameCallback = () {}; diff --git a/lib/web_ui/test/path_test.dart b/lib/web_ui/test/path_test.dart index 91aa622359012..488c10b71a573 100644 --- a/lib/web_ui/test/path_test.dart +++ b/lib/web_ui/test/path_test.dart @@ -259,7 +259,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/44470 test('Should handle contains for devicepixelratio != 1.0', () { js_util.setProperty(html.window, 'devicePixelRatio', 4.0); - window.debugOverrideDevicePixelRatio(4.0); + window.overrideDevicePixelRatio(4.0); final path = Path() ..moveTo(50, 0) ..lineTo(100, 100) @@ -268,7 +268,7 @@ void main() { ..close(); expect(path.contains(Offset(50, 50)), isTrue); js_util.setProperty(html.window, 'devicePixelRatio', 1.0); - window.debugOverrideDevicePixelRatio(1.0); + window.overrideDevicePixelRatio(1.0); // TODO: Investigate failure on CI. Locally this passes. // [Exception... "Failure" nsresult: "0x80004005 (NS_ERROR_FAILURE)" }, skip: browserEngine == BrowserEngine.firefox); From 542be23f8124600fc988ca860c58fcb2545cf65d Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 19 Mar 2020 09:28:49 -0700 Subject: [PATCH 03/29] Update cirrus for new integration test --- .cirrus.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cirrus.yml b/.cirrus.yml index 95bc2e9cc5384..fdf6a4b39e791 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: | From aab46b41e550569a8ad76c45da15eed587e92aca Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 19 Mar 2020 13:37:02 -0700 Subject: [PATCH 04/29] update canvas golden test root transform --- lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart | 3 +++ 1 file changed, 3 insertions(+) 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 e53d37d3ab080..fd7f45e6a0023 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; From 101083db09c7813439f2961eda4b13828c6df722 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 19 Mar 2020 14:48:27 -0700 Subject: [PATCH 05/29] Update compositing golden test with dpr transform --- .../test/golden_tests/engine/compositing_golden_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) 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 455bceb07551f..076fff0ff3853 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); From a328874a4f261739e9d604cf4e1280475354e542 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 19 Mar 2020 14:52:51 -0700 Subject: [PATCH 06/29] Address review comment --- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 dc7c1918d9906..cf705e21019fa 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -99,8 +99,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { } if (_surfaceStack.length == 1) { // Top level transform contains view configuration to scale - // scene to devicepixelratio. Use identity instead since web browser - // renders using logical device pixels. + // scene to devicepixelratio. Use identity instead since CSS uses + // logical device pixels. assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && matrix4[5] == EngineWindow.browserDevicePixelRatio); matrix4 = Matrix4.identity().storage; From 3af31efcd1a78bbf4684886cdf5eb58791b145e8 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 19 Mar 2020 14:55:34 -0700 Subject: [PATCH 07/29] remove print --- .../test_driver/image_loading_e2e_test.dart | 1 - 1 file changed, 1 deletion(-) 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 index 7a60dcaa9ec31..02edbe5679179 100644 --- 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 @@ -11,7 +11,6 @@ Future main() async { final String dataRequest = await driver.requestData(null, timeout: const Duration(seconds: 1)); - print('result $dataRequest'); await driver.close(); exit(dataRequest == 'pass' ? 0 : 1); From 7d310691820da37178ae1e843d1f411b8158df91 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 20 Mar 2020 15:32:24 -0700 Subject: [PATCH 08/29] update test --- .../lib/text_editing_main.dart | 2 +- .../lib/treeshaking_main.dart | 45 +++++++++++++++++++ .../test_driver/treeshaking_e2e.dart | 41 +++++++++++++++++ .../test_driver/treeshaking_e2e_test.dart | 17 +++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 e2etests/web/regular_integration_tests/lib/treeshaking_main.dart create mode 100644 e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart create mode 100644 e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart diff --git a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart index 1962900718551..8fc67869c5de3 100644 --- a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart +++ b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart @@ -28,7 +28,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final TextEditingController _controller = - TextEditingController(text: 'Text1'); + TextEditingController(text: 'Text1'); @override Widget build(BuildContext context) { diff --git a/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart b/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart new file mode 100644 index 0000000000000..0df900d0597b4 --- /dev/null +++ b/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart @@ -0,0 +1,45 @@ + +// 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'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + key: const Key('mainapp'), + title: 'Integration Test App', + home: MyHomePage(title: 'Integration Test App'), + ); + } +} + +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: 'TreeshakingThings1'); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: const Center( + child: Text('TreeshakingThings'), + ), + ); + } +} diff --git a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart b/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart new file mode 100644 index 0000000000000..03115a97af993 --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart @@ -0,0 +1,41 @@ +// 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_test/flutter_test.dart'; +import 'package:regular_integration_tests/treeshaking_main.dart' as app; +import 'package:flutter/material.dart'; + +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding; + + testWidgets('debug+Fill+Properties is tree shaken', + (WidgetTester tester) async { + await testIsTreeShaken(tester, '${debugPrefix}FillProperties'); + }); +} + +// Used to prevent compiler optimization that will generate const string. +String get debugPrefix => ['d','e','b','u','g'].join(''); + +Future testIsTreeShaken(WidgetTester tester, String methodName) async { + app.main(); + await tester.pumpAndSettle(); + + // Make sure app loaded. + final Finder finder = find.byKey(const Key('mainapp')); + expect(finder, findsOneWidget); + + await _loadBundleAndCheck(methodName); +} + +String fileContents; + +Future _loadBundleAndCheck(String methodName) async { + fileContents ??= await html.HttpRequest.getString('main.dart.js'); + expect(fileContents, contains('RenderObjectToWidgetElement')); + expect(fileContents.contains(methodName), false); +} diff --git a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart b/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart new file mode 100644 index 0000000000000..5907b5e35884c --- /dev/null +++ b/e2etests/web/regular_integration_tests/test_driver/treeshaking_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: 3)); + await driver.close(); + + exit(dataRequest == 'pass' ? 0 : 1); +} From a2aaafb9405bdf964c6aa9192392096b55fe18b4 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 23 Mar 2020 12:41:12 -0700 Subject: [PATCH 09/29] Addressed review comments/replyChannel --- .../lib/image_loading_main.dart | 20 +++++++------------ .../test_driver/image_loading_e2e.dart | 1 + lib/web_ui/lib/src/engine/window.dart | 18 +++++++++++++---- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/e2etests/web/regular_integration_tests/lib/image_loading_main.dart b/e2etests/web/regular_integration_tests/lib/image_loading_main.dart index bd5e9d73bde40..78991d9652eb2 100644 --- a/e2etests/web/regular_integration_tests/lib/image_loading_main.dart +++ b/e2etests/web/regular_integration_tests/lib/image_loading_main.dart @@ -5,7 +5,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -void main() { +void main() async { + const MethodChannel channel = + OptionalMethodChannel('flutter/web_test_e2e', JSONMethodCodec()); + await channel.invokeMethod( + 'setDevicePixelRatio', + '1.5', + ); runApp(MyApp()); } @@ -15,18 +21,6 @@ class MyApp extends StatefulWidget { } class MyAppState extends State { - - @override - void initState() { - super.initState(); - const MethodChannel channel = - OptionalMethodChannel('flutter/web_test_e2e', JSONMethodCodec()); - channel.invokeMethod( - 'setDevicePixelRatio', - '1.5', - ); - } - @override Widget build(BuildContext context) { return MaterialApp( 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 index 3c858da3a9d08..bc6ebb9dba887 100644 --- a/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart +++ b/e2etests/web/regular_integration_tests/test_driver/image_loading_e2e.dart @@ -3,6 +3,7 @@ // 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; diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index df2919e8399a7..4f82bc4735636 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -150,20 +150,25 @@ 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; @@ -173,8 +178,12 @@ class EngineWindow extends ui.Window { return; case 'flutter/web_test_e2e': - _handleWebTestEnd2EndMessage(data); + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(codec, data) + )); return; + case 'flutter/platform_views': if (experimentalUseSkia) { rasterizer.viewEmbedder.handlePlatformViewCall(data, callback); @@ -308,17 +317,18 @@ class EngineWindow extends ui.Window { Rasterizer rasterizer = experimentalUseSkia ? Rasterizer(Surface()) : null; } -void _handleWebTestEnd2EndMessage(ByteData data) { - const MethodCodec codec = JSONMethodCodec(); +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.overrideDevicePixelRatio(ratio); window.onMetricsChanged(); - break; + return true; } + return false; } /// The window singleton. From 2b999629a02958ea3221b5af65b97c60c7468f04 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 23 Mar 2020 13:05:06 -0700 Subject: [PATCH 10/29] Fix missing platform replymessage --- .../lib/treeshaking_main.dart | 45 ------------------- .../test_driver/treeshaking_e2e.dart | 41 ----------------- .../test_driver/treeshaking_e2e_test.dart | 17 ------- .../src/engine/compositor/embedded_views.dart | 2 + lib/web_ui/lib/src/engine/platform_views.dart | 2 + .../src/engine/semantics/accessibility.dart | 6 ++- .../src/engine/text_editing/text_editing.dart | 8 +++- lib/web_ui/lib/src/engine/window.dart | 8 +++- lib/web_ui/test/text_editing_test.dart | 2 +- 9 files changed, 21 insertions(+), 110 deletions(-) delete mode 100644 e2etests/web/regular_integration_tests/lib/treeshaking_main.dart delete mode 100644 e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart delete mode 100644 e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart diff --git a/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart b/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart deleted file mode 100644 index 0df900d0597b4..0000000000000 --- a/e2etests/web/regular_integration_tests/lib/treeshaking_main.dart +++ /dev/null @@ -1,45 +0,0 @@ - -// 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'; - -void main() => runApp(MyApp()); - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - key: const Key('mainapp'), - title: 'Integration Test App', - home: MyHomePage(title: 'Integration Test App'), - ); - } -} - -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: 'TreeshakingThings1'); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: const Center( - child: Text('TreeshakingThings'), - ), - ); - } -} diff --git a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart b/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart deleted file mode 100644 index 03115a97af993..0000000000000 --- a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e.dart +++ /dev/null @@ -1,41 +0,0 @@ -// 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_test/flutter_test.dart'; -import 'package:regular_integration_tests/treeshaking_main.dart' as app; -import 'package:flutter/material.dart'; - -import 'package:e2e/e2e.dart'; - -void main() { - E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding; - - testWidgets('debug+Fill+Properties is tree shaken', - (WidgetTester tester) async { - await testIsTreeShaken(tester, '${debugPrefix}FillProperties'); - }); -} - -// Used to prevent compiler optimization that will generate const string. -String get debugPrefix => ['d','e','b','u','g'].join(''); - -Future testIsTreeShaken(WidgetTester tester, String methodName) async { - app.main(); - await tester.pumpAndSettle(); - - // Make sure app loaded. - final Finder finder = find.byKey(const Key('mainapp')); - expect(finder, findsOneWidget); - - await _loadBundleAndCheck(methodName); -} - -String fileContents; - -Future _loadBundleAndCheck(String methodName) async { - fileContents ??= await html.HttpRequest.getString('main.dart.js'); - expect(fileContents, contains('RenderObjectToWidgetElement')); - expect(fileContents.contains(methodName), false); -} diff --git a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart b/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart deleted file mode 100644 index 5907b5e35884c..0000000000000 --- a/e2etests/web/regular_integration_tests/test_driver/treeshaking_e2e_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -// 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: 3)); - 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 31f9dc176bafe..9deee4f7b60f5 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/platform_views.dart b/lib/web_ui/lib/src/engine/platform_views.dart index d422f9a3ecf84..2454cd44edb1d 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 a858ebc7ab668..c36f13bcd7e89 100644 --- a/lib/web_ui/lib/src/engine/semantics/accessibility.dart +++ b/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -52,9 +52,10 @@ class AccessibilityAnnouncements { html.HtmlElement get _domElement => _element ??= _createElement(); /// Decodes the message coming from the 'flutter/accessibility' channel. - void handleMessage(ByteData data) { + StandardMessageCodec handleMessage(ByteData data) { + final StandardMessageCodec codec = StandardMessageCodec(); 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) { @@ -63,6 +64,7 @@ class AccessibilityAnnouncements { _element.remove(); }); } + return codec; } void _initLiveRegion(String message) { 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 369d711e59f38..b13b0a168e604 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 4f82bc4735636..77fb4ef213800 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -174,7 +174,7 @@ class EngineWindow extends ui.Window { break; case 'flutter/textinput': - textEditing.channel.handleTextInput(data); + textEditing.channel.handleTextInput(data, callback); return; case 'flutter/web_test_e2e': @@ -194,7 +194,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 = + accessibilityAnnouncements.handleMessage(data); + _replyToPlatformMessage(callback, codec.encodeMessage(true)); return; case 'flutter/navigation': @@ -206,9 +208,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; diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index dad9bbdd80af7..97360e41357eb 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 From 6ff40c226a9ebc898de94428f161ac29af5e2ec9 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 23 Mar 2020 13:21:50 -0700 Subject: [PATCH 11/29] Don't reply when callback is null --- lib/web_ui/lib/src/engine/window.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 77fb4ef213800..2d8d639675f74 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -256,7 +256,9 @@ class EngineWindow extends ui.Window { ByteData data, ) { Future.delayed(Duration.zero).then((_) { - callback(data); + if (callback != null) { + callback(data); + } }); } From e456f3d1271325c0a46d51dcfceb94b94b7644d5 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 10:25:48 -0700 Subject: [PATCH 12/29] update test embedding for flutter framework tests --- lib/web_ui/lib/src/ui/test_embedding.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index be18bd69dbd05..ce7c1139b2b6b 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -29,17 +29,20 @@ 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; - // The following parameters are hard-coded in Flutter's test embedder. Since - // we don't have an embedder yet this is the lowest-most layer we can put - // this stuff in. - engine.window.overrideDevicePixelRatio(devicePixelRatio); - engine.window.webOnlyDebugPhysicalSizeOverride = - Size(800 * devicePixelRatio, 600 * devicePixelRatio); + assert(() { + // The following parameters are hard-coded in Flutter's test embedder. Since + // we don't have an embedder yet this is the lowest-most layer we can put + // this stuff in. + engine.window.overrideDevicePixelRatio(devicePixelRatio); + engine.window.webOnlyDebugPhysicalSizeOverride = + Size(800 * devicePixelRatio, 600 * devicePixelRatio); + }()); + webOnlyScheduleFrameCallback = () {}; debugEmulateFlutterTesterEnvironment = true; From c34111badabb3a73faa766efc97df92455ca7385 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 10:51:38 -0700 Subject: [PATCH 13/29] fix assert return --- lib/web_ui/lib/src/ui/test_embedding.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index ce7c1139b2b6b..b4ce30cd93f58 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -41,6 +41,7 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { engine.window.overrideDevicePixelRatio(devicePixelRatio); engine.window.webOnlyDebugPhysicalSizeOverride = Size(800 * devicePixelRatio, 600 * devicePixelRatio); + return true; }()); webOnlyScheduleFrameCallback = () {}; From 47da9cede9418f92087ed4fe5813471b71d71f82 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 11:28:55 -0700 Subject: [PATCH 14/29] CI test --- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 cf705e21019fa..4d0944ed2abda 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -101,8 +101,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. - assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && - matrix4[5] == EngineWindow.browserDevicePixelRatio); +// assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && +// matrix4[5] == EngineWindow.browserDevicePixelRatio); matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); From 81fd91709cda5569a10e690ad34dd60637f1e693 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 12:06:37 -0700 Subject: [PATCH 15/29] CI test --- lib/web_ui/lib/src/engine/dom_renderer.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index a5d630dd59de7..8e2f585b83f20 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -419,6 +419,12 @@ 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 From 940e44dea56c3289ac5d7e74a4e277b91b988448 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 12:08:12 -0700 Subject: [PATCH 16/29] CI test --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8e2f585b83f20..7407152e6980b 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -423,7 +423,7 @@ flt-glass-pane * { // 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); + window.overrideDevicePixelRatio(1.0); if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the From 1d864dcbbe928d937d8c2dc11f9846f0d203fec9 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 12:24:32 -0700 Subject: [PATCH 17/29] CI test --- lib/web_ui/lib/src/engine/window.dart | 46 ++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 2d8d639675f74..b1a1d59f40d69 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -14,12 +14,40 @@ class EngineWindow extends ui.Window { _addBrightnessMediaQueryListener(); } +// @override +// double get devicePixelRatio => _debugDevicePixelRatio != null +// ? _debugDevicePixelRatio +// : browserDevicePixelRatio; +// +// /// Returns device pixel ratio returned by browser. +// static double get browserDevicePixelRatio { +// double ratio = html.window.devicePixelRatio; +// // Guard against WebOS returning 0. +// return (ratio == null || ratio == 0.0) ? 1.0 : ratio; +// } +// +// /// Overrides the default device pixel ratio. +// /// +// /// This is useful in tests to emulate screens of different dimensions +// /// including integration tests. +// void overrideDevicePixelRatio(double value) { +// _debugDevicePixelRatio = value; +// } + @override - double get devicePixelRatio => _debugDevicePixelRatio != null - ? _debugDevicePixelRatio - : browserDevicePixelRatio; + double get devicePixelRatio { + if (_debugDevicePixelRatio != null) { + return _debugDevicePixelRatio; + } + + if (experimentalUseSkia) { + return browserDevicePixelRatio; + } else { + return 1.0; + } + } - /// Returns device pixel ratio returned by browser. + /// Returns device pixel ratio returns by browser. static double get browserDevicePixelRatio { double ratio = html.window.devicePixelRatio; // Guard against WebOS returning 0. @@ -28,10 +56,12 @@ class EngineWindow extends ui.Window { /// Overrides the default device pixel ratio. /// - /// This is useful in tests to emulate screens of different dimensions - /// including integration tests. - void overrideDevicePixelRatio(double value) { - _debugDevicePixelRatio = value; + /// This is useful in tests to emulate screens of different dimensions. + void debugOverrideDevicePixelRatio(double value) { + assert(() { + _debugDevicePixelRatio = value; + return true; + }()); } double _debugDevicePixelRatio; From 2419fdb9c608f03f3763e3610e6ff5131a042697 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 13:07:37 -0700 Subject: [PATCH 18/29] CI Test --- lib/web_ui/lib/src/ui/test_embedding.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index b4ce30cd93f58..1bf062ed3ff3b 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -38,7 +38,7 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // The following parameters are hard-coded in Flutter's test embedder. Since // we don't have an embedder yet this is the lowest-most layer we can put // this stuff in. - engine.window.overrideDevicePixelRatio(devicePixelRatio); + engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); engine.window.webOnlyDebugPhysicalSizeOverride = Size(800 * devicePixelRatio, 600 * devicePixelRatio); return true; From d455d74f1bc9750265c45824fdf3c51bedb53b89 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 13:16:40 -0700 Subject: [PATCH 19/29] CI Test --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- lib/web_ui/lib/src/engine/window.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 7407152e6980b..8e2f585b83f20 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -423,7 +423,7 @@ flt-glass-pane * { // 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.overrideDevicePixelRatio(1.0); + window.debugOverrideDevicePixelRatio(1.0); if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index b1a1d59f40d69..ef4e382901e9e 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -360,7 +360,7 @@ bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData data) { bool result = false; switch(decoded.method) { case 'setDevicePixelRatio': - window.overrideDevicePixelRatio(ratio); + window.debugOverrideDevicePixelRatio(ratio); window.onMetricsChanged(); return true; } From be2113d88ed10cbb2da7de8993c90b242f753630 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 13:54:42 -0700 Subject: [PATCH 20/29] Test CI --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- lib/web_ui/lib/src/engine/window.dart | 9 +++------ lib/web_ui/lib/src/ui/test_embedding.dart | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8e2f585b83f20..7407152e6980b 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -423,7 +423,7 @@ flt-glass-pane * { // 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); + window.overrideDevicePixelRatio(1.0); if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index ef4e382901e9e..d02a766403601 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -57,11 +57,8 @@ class EngineWindow extends ui.Window { /// Overrides the default device pixel ratio. /// /// This is useful in tests to emulate screens of different dimensions. - void debugOverrideDevicePixelRatio(double value) { - assert(() { - _debugDevicePixelRatio = value; - return true; - }()); + void overrideDevicePixelRatio(double value) { + _debugDevicePixelRatio = value; } double _debugDevicePixelRatio; @@ -360,7 +357,7 @@ bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData data) { bool result = false; switch(decoded.method) { case 'setDevicePixelRatio': - window.debugOverrideDevicePixelRatio(ratio); + window.overrideDevicePixelRatio(ratio); window.onMetricsChanged(); return true; } diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index 1bf062ed3ff3b..b4ce30cd93f58 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -38,7 +38,7 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // The following parameters are hard-coded in Flutter's test embedder. Since // we don't have an embedder yet this is the lowest-most layer we can put // this stuff in. - engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); + engine.window.overrideDevicePixelRatio(devicePixelRatio); engine.window.webOnlyDebugPhysicalSizeOverride = Size(800 * devicePixelRatio, 600 * devicePixelRatio); return true; From 6b227a74fe8eebccaa6e0ba2062de9e526446027 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 15:03:09 -0700 Subject: [PATCH 21/29] Test CI --- .../lib/src/engine/semantics/accessibility.dart | 4 +--- lib/web_ui/lib/src/engine/window.dart | 11 ++++++++--- .../test/engine/semantics/accessibility_test.dart | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/engine/semantics/accessibility.dart b/lib/web_ui/lib/src/engine/semantics/accessibility.dart index c36f13bcd7e89..6bc7b0987df6c 100644 --- a/lib/web_ui/lib/src/engine/semantics/accessibility.dart +++ b/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -52,8 +52,7 @@ class AccessibilityAnnouncements { html.HtmlElement get _domElement => _element ??= _createElement(); /// Decodes the message coming from the 'flutter/accessibility' channel. - StandardMessageCodec handleMessage(ByteData data) { - final StandardMessageCodec codec = StandardMessageCodec(); + void handleMessage(StandardMessageCodec codec, ByteData data) { final Map inputMap = codec.decodeMessage(data); final Map dataMap = inputMap['data']; @@ -64,7 +63,6 @@ class AccessibilityAnnouncements { _element.remove(); }); } - return codec; } void _initLiveRegion(String message) { diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index d02a766403601..9ca6efe140e33 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -58,7 +58,12 @@ class EngineWindow extends ui.Window { /// /// This is useful in tests to emulate screens of different dimensions. void overrideDevicePixelRatio(double value) { - _debugDevicePixelRatio = value; + void overrideDevicePixelRatio(double value) { + assert(() { + _debugDevicePixelRatio = value; + return true; + }()); + } } double _debugDevicePixelRatio; @@ -221,8 +226,8 @@ class EngineWindow extends ui.Window { case 'flutter/accessibility': // In widget tests we want to bypass processing of platform messages. - final StandardMessageCodec codec = - accessibilityAnnouncements.handleMessage(data); + final StandardMessageCodec codec = StandardMessageCodec(); + accessibilityAnnouncements.handleMessage(codec, data); _replyToPlatformMessage(callback, codec.encodeMessage(true)); return; diff --git a/lib/web_ui/test/engine/semantics/accessibility_test.dart b/lib/web_ui/test/engine/semantics/accessibility_test.dart index 8c572563c38db..767d4742ad0ea 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, From 5f21300ec162f56d5adaeb3196da68fc06b1d905 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 16:35:11 -0700 Subject: [PATCH 22/29] rename override to debugOverride --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- lib/web_ui/lib/src/engine/window.dart | 4 ++-- lib/web_ui/lib/src/ui/test_embedding.dart | 2 +- lib/web_ui/test/path_test.dart | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 7407152e6980b..8e2f585b83f20 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -423,7 +423,7 @@ flt-glass-pane * { // 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.overrideDevicePixelRatio(1.0); + window.debugOverrideDevicePixelRatio(1.0); if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 9ca6efe140e33..dc84e28b8838b 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -57,7 +57,7 @@ class EngineWindow extends ui.Window { /// Overrides the default device pixel ratio. /// /// This is useful in tests to emulate screens of different dimensions. - void overrideDevicePixelRatio(double value) { + void debugOverrideDevicePixelRatio(double value) { void overrideDevicePixelRatio(double value) { assert(() { _debugDevicePixelRatio = value; @@ -362,7 +362,7 @@ bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData data) { bool result = false; switch(decoded.method) { case 'setDevicePixelRatio': - window.overrideDevicePixelRatio(ratio); + window.debugOverrideDevicePixelRatio(ratio); window.onMetricsChanged(); return true; } diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index b4ce30cd93f58..1bf062ed3ff3b 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -38,7 +38,7 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // The following parameters are hard-coded in Flutter's test embedder. Since // we don't have an embedder yet this is the lowest-most layer we can put // this stuff in. - engine.window.overrideDevicePixelRatio(devicePixelRatio); + engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); engine.window.webOnlyDebugPhysicalSizeOverride = Size(800 * devicePixelRatio, 600 * devicePixelRatio); return true; diff --git a/lib/web_ui/test/path_test.dart b/lib/web_ui/test/path_test.dart index 488c10b71a573..212e6fd52f2ae 100644 --- a/lib/web_ui/test/path_test.dart +++ b/lib/web_ui/test/path_test.dart @@ -259,7 +259,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/44470 test('Should handle contains for devicepixelratio != 1.0', () { js_util.setProperty(html.window, 'devicePixelRatio', 4.0); - window.overrideDevicePixelRatio(4.0); + window.debugoverrideDevicePixelRatio(4.0); final path = Path() ..moveTo(50, 0) ..lineTo(100, 100) @@ -268,7 +268,7 @@ void main() { ..close(); expect(path.contains(Offset(50, 50)), isTrue); js_util.setProperty(html.window, 'devicePixelRatio', 1.0); - window.overrideDevicePixelRatio(1.0); + window.debugOverrideDevicePixelRatio(1.0); // TODO: Investigate failure on CI. Locally this passes. // [Exception... "Failure" nsresult: "0x80004005 (NS_ERROR_FAILURE)" }, skip: browserEngine == BrowserEngine.firefox); From 2eaae7ebd946b71c05d1da3d033090151a01e453 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 16:45:50 -0700 Subject: [PATCH 23/29] Revert to pre CI test code that was reviewed --- lib/web_ui/lib/src/engine/dom_renderer.dart | 6 --- .../lib/src/engine/surface/scene_builder.dart | 4 +- lib/web_ui/lib/src/engine/window.dart | 37 +++---------------- 3 files changed, 7 insertions(+), 40 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8e2f585b83f20..a5d630dd59de7 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/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index 4d0944ed2abda..cf705e21019fa 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -101,8 +101,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. -// assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && -// matrix4[5] == EngineWindow.browserDevicePixelRatio); + assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && + matrix4[5] == EngineWindow.browserDevicePixelRatio); matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index dc84e28b8838b..c183f6d7cf861 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -14,46 +14,19 @@ class EngineWindow extends ui.Window { _addBrightnessMediaQueryListener(); } -// @override -// double get devicePixelRatio => _debugDevicePixelRatio != null -// ? _debugDevicePixelRatio -// : browserDevicePixelRatio; -// -// /// Returns device pixel ratio returned by browser. -// static double get browserDevicePixelRatio { -// double ratio = html.window.devicePixelRatio; -// // Guard against WebOS returning 0. -// return (ratio == null || ratio == 0.0) ? 1.0 : ratio; -// } -// -// /// Overrides the default device pixel ratio. -// /// -// /// This is useful in tests to emulate screens of different dimensions -// /// including integration tests. -// void overrideDevicePixelRatio(double value) { -// _debugDevicePixelRatio = value; -// } - @override - double get devicePixelRatio { - if (_debugDevicePixelRatio != null) { - return _debugDevicePixelRatio; - } - - if (experimentalUseSkia) { - return browserDevicePixelRatio; - } else { - return 1.0; - } - } + double get devicePixelRatio => _debugDevicePixelRatio != null + ? _debugDevicePixelRatio + : browserDevicePixelRatio; - /// 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. return (ratio == null || ratio == 0.0) ? 1.0 : ratio; } + /// Overrides the default device pixel ratio. /// /// This is useful in tests to emulate screens of different dimensions. From 158533c5c37254911238909039ae767e50c9c6fd Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 16:55:26 -0700 Subject: [PATCH 24/29] fix typo and address review comment --- .../regular_integration_tests/lib/text_editing_main.dart | 2 +- lib/web_ui/lib/src/engine/window.dart | 7 +------ lib/web_ui/test/path_test.dart | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart index 8fc67869c5de3..1962900718551 100644 --- a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart +++ b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart @@ -28,7 +28,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final TextEditingController _controller = - TextEditingController(text: 'Text1'); + TextEditingController(text: 'Text1'); @override Widget build(BuildContext context) { diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index c183f6d7cf861..ffbb47fb7569e 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -31,12 +31,7 @@ class EngineWindow extends ui.Window { /// /// This is useful in tests to emulate screens of different dimensions. void debugOverrideDevicePixelRatio(double value) { - void overrideDevicePixelRatio(double value) { - assert(() { - _debugDevicePixelRatio = value; - return true; - }()); - } + _debugDevicePixelRatio = value; } double _debugDevicePixelRatio; diff --git a/lib/web_ui/test/path_test.dart b/lib/web_ui/test/path_test.dart index 212e6fd52f2ae..91aa622359012 100644 --- a/lib/web_ui/test/path_test.dart +++ b/lib/web_ui/test/path_test.dart @@ -259,7 +259,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/44470 test('Should handle contains for devicepixelratio != 1.0', () { js_util.setProperty(html.window, 'devicePixelRatio', 4.0); - window.debugoverrideDevicePixelRatio(4.0); + window.debugOverrideDevicePixelRatio(4.0); final path = Path() ..moveTo(50, 0) ..lineTo(100, 100) From de3b6a70d5add56d3faef692eed4d2a39f3e52f6 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Mar 2020 17:41:08 -0700 Subject: [PATCH 25/29] Fix check for top level matrix --- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 cf705e21019fa..a9e2e28747eb9 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -101,8 +101,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. - assert(matrix4[0] == EngineWindow.browserDevicePixelRatio && - matrix4[5] == EngineWindow.browserDevicePixelRatio); + assert(matrix4[0] == window.devicePixelRatio && + matrix4[5] == window.devicePixelRatio); matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); From b710b870097c95492ceb6f0320877b388ff7644f Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 25 Mar 2020 09:36:20 -0700 Subject: [PATCH 26/29] Update assert for top level matrix to allow subclassing of window in flutter_test --- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a9e2e28747eb9..b0506259322e9 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -101,8 +101,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. - assert(matrix4[0] == window.devicePixelRatio && - matrix4[5] == window.devicePixelRatio); + assert(matrix4[0] == window._debugDevicePixelRatio && + matrix4[5] == window._debugDevicePixelRatio); matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); From 01c5bf51f175024ef07f8a74bd46bee963124365 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 25 Mar 2020 10:00:29 -0700 Subject: [PATCH 27/29] update assert to handle null _debugDevicePixels --- .../lib/src/engine/surface/scene_builder.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) 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 b0506259322e9..98de1f2c00715 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); @@ -101,8 +104,12 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. - assert(matrix4[0] == window._debugDevicePixelRatio && - matrix4[5] == window._debugDevicePixelRatio); + assert(matrix4[0] == + (window._debugDevicePixelRatio ?? + EngineWindow.browserDevicePixelRatio) && + matrix4[5] == + (window._debugDevicePixelRatio ?? + EngineWindow.browserDevicePixelRatio)); matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); @@ -284,7 +291,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); @@ -327,7 +335,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 @@ -345,7 +354,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'); } } @@ -385,7 +395,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'); From 47a11e5ec36644d01635e874441f51d3d1fa358d Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 25 Mar 2020 10:22:03 -0700 Subject: [PATCH 28/29] Restrict top level transform check --- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 98de1f2c00715..823a9963acddc 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -104,12 +104,10 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // Top level transform contains view configuration to scale // scene to devicepixelratio. Use identity instead since CSS uses // logical device pixels. - assert(matrix4[0] == - (window._debugDevicePixelRatio ?? - EngineWindow.browserDevicePixelRatio) && - matrix4[5] == - (window._debugDevicePixelRatio ?? - EngineWindow.browserDevicePixelRatio)); + if (!ui.debugEmulateFlutterTesterEnvironment) { + assert(matrix4[0] == window.devicePixelRatio && + matrix4[5] == window.devicePixelRatio); + } matrix4 = Matrix4.identity().storage; } return _pushSurface(PersistedTransform(oldLayer, matrix4)); From ea15a3c7d61a34d19e0e535ee766bf3dc004303f Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 25 Mar 2020 11:32:52 -0700 Subject: [PATCH 29/29] address review comments --- .../lib/src/engine/semantics/accessibility.dart | 2 +- lib/web_ui/lib/src/engine/window.dart | 1 - lib/web_ui/lib/src/ui/test_embedding.dart | 16 ++++++---------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/web_ui/lib/src/engine/semantics/accessibility.dart b/lib/web_ui/lib/src/engine/semantics/accessibility.dart index 6bc7b0987df6c..2d874f8cf01da 100644 --- a/lib/web_ui/lib/src/engine/semantics/accessibility.dart +++ b/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -52,7 +52,7 @@ class AccessibilityAnnouncements { html.HtmlElement get _domElement => _element ??= _createElement(); /// Decodes the message coming from the 'flutter/accessibility' channel. - void handleMessage(StandardMessageCodec codec, ByteData data) { + void handleMessage(StandardMessageCodec codec, ByteData data) { final Map inputMap = codec.decodeMessage(data); final Map dataMap = inputMap['data']; diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index ffbb47fb7569e..8bfb18039b5fb 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -26,7 +26,6 @@ class EngineWindow extends ui.Window { return (ratio == null || ratio == 0.0) ? 1.0 : ratio; } - /// Overrides the default device pixel ratio. /// /// This is useful in tests to emulate screens of different dimensions. diff --git a/lib/web_ui/lib/src/ui/test_embedding.dart b/lib/web_ui/lib/src/ui/test_embedding.dart index 1bf062ed3ff3b..471876706c0f4 100644 --- a/lib/web_ui/lib/src/ui/test_embedding.dart +++ b/lib/web_ui/lib/src/ui/test_embedding.dart @@ -34,16 +34,12 @@ Future webOnlyInitializeTestDomRenderer({double devicePixelRatio = 3.0}) { // Force-initialize DomRenderer so it doesn't overwrite test pixel ratio. engine.domRenderer; - assert(() { - // The following parameters are hard-coded in Flutter's test embedder. Since - // we don't have an embedder yet this is the lowest-most layer we can put - // this stuff in. - engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); - engine.window.webOnlyDebugPhysicalSizeOverride = - Size(800 * devicePixelRatio, 600 * devicePixelRatio); - return true; - }()); - + // The following parameters are hard-coded in Flutter's test embedder. Since + // we don't have an embedder yet this is the lowest-most layer we can put + // this stuff in. + engine.window.debugOverrideDevicePixelRatio(devicePixelRatio); + engine.window.webOnlyDebugPhysicalSizeOverride = + Size(800 * devicePixelRatio, 600 * devicePixelRatio); webOnlyScheduleFrameCallback = () {}; debugEmulateFlutterTesterEnvironment = true;