diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 915fdb1bdac..9cda3bf601e 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -3196,7 +3196,12 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { } node.updateWith(config: null, childrenInInversePaintOrder: children); - assert(!node.isInvisible); + // The root node is the only semantics node allowed to be invisible. This + // can happen when the canvas the app is drawn on has a size of 0 by 0 + // pixel. If this happens, the root node must not have any children (because + // these would be invisible as well and are therefore excluded from the + // tree). + assert(!node.isInvisible || children.isEmpty); yield node; } diff --git a/packages/flutter/test/widgets/semantics_zero_surface_size_test.dart b/packages/flutter/test/widgets/semantics_zero_surface_size_test.dart new file mode 100644 index 00000000000..a7426964560 --- /dev/null +++ b/packages/flutter/test/widgets/semantics_zero_surface_size_test.dart @@ -0,0 +1,47 @@ +// Copyright 2018 The Chromium 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/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'semantics_tester.dart'; + +void main() { + testWidgets('has only root node if surface size is 0x0', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget(new Semantics( + selected: true, + )); + + expect(semantics, hasSemantics( + new TestSemantics( + id: 0, + rect: new Rect.fromLTRB(0.0, 0.0, 2400.0, 1800.0), + children: [ + new TestSemantics( + id: 1, + rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0), + flags: [SemanticsFlag.isSelected], + ), + ], + ), ignoreTransform: true, + )); + + await tester.binding.setSurfaceSize(const Size(0.0, 0.0)); + await tester.pumpAndSettle(); + + expect(semantics, hasSemantics( + new TestSemantics( + id: 0, + rect: new Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), + ), ignoreTransform: true, + )); + + await tester.binding.setSurfaceSize(null); + semantics.dispose(); + }); +} diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index a149ceb1052..73859091493 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -235,6 +235,33 @@ abstract class TestWidgetsFlutterBinding extends BindingBase }); } + Size _surfaceSize; + + /// Artificially changes the surface size to `size` on the Widget binding, + /// then flushes microtasks. + /// + /// Set to null to use the default surface size. + Future setSurfaceSize(Size size) { + return TestAsyncUtils.guard(() async { + assert(inTest); + if (_surfaceSize == size) + return null; + _surfaceSize = size; + handleMetricsChanged(); + return null; + }); + } + + @override + ViewConfiguration createViewConfiguration() { + final double devicePixelRatio = ui.window.devicePixelRatio; + final Size size = _surfaceSize ?? ui.window.physicalSize / devicePixelRatio; + return new ViewConfiguration( + size: size, + devicePixelRatio: devicePixelRatio, + ); + } + /// Acts as if the application went idle. /// /// Runs all remaining microtasks, including those scheduled as a result of @@ -1250,7 +1277,7 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { @override ViewConfiguration createViewConfiguration() { - return new TestViewConfiguration(); + return new TestViewConfiguration(size: _surfaceSize ?? _kDefaultTestViewportSize); } @override