Unverified Commit 670f9d20 authored by Jim Graham's avatar Jim Graham Committed by GitHub

Revert "Introduce the PipelineOwner tree (#122231)" (#122425)

This reverts commit f73c358e.
parent f73c358e
......@@ -30,6 +30,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsUpdate: _handleSemanticsUpdate,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
......@@ -44,7 +45,8 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
if (kIsWeb) {
addPostFrameCallback(_handleWebFirstFrame);
}
_pipelineOwner.attach(_manifold);
addSemanticsEnabledListener(_handleSemanticsEnabledChanged);
_handleSemanticsEnabledChanged();
}
/// The current [RendererBinding], if one has been created.
......@@ -199,8 +201,6 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
}
}
late final PipelineManifold _manifold = _BindingPipelineManifold(this);
/// Creates a [RenderView] object to be the root of the
/// [RenderObject] rendering tree, and initializes it so that it
/// will be rendered when the next frame is requested.
......@@ -330,6 +330,17 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
super.dispatchEvent(event, hitTestResult);
}
SemanticsHandle? _semanticsHandle;
void _handleSemanticsEnabledChanged() {
if (semanticsEnabled) {
_semanticsHandle ??= _pipelineOwner.ensureSemantics();
} else {
_semanticsHandle?.dispose();
_semanticsHandle = null;
}
}
@override
void performSemanticsAction(SemanticsActionEvent action) {
_pipelineOwner.semanticsOwner?.performAction(action.nodeId, action.type, action.arguments);
......@@ -610,26 +621,3 @@ class RenderingFlutterBinding extends BindingBase with GestureBinding, Scheduler
return RendererBinding.instance;
}
}
/// A [PipelineManifold] implementation that is backed by the [RendererBinding].
class _BindingPipelineManifold extends ChangeNotifier implements PipelineManifold {
_BindingPipelineManifold(this._binding) {
_binding.addSemanticsEnabledListener(notifyListeners);
}
final RendererBinding _binding;
@override
void requestVisualUpdate() {
_binding.ensureVisualUpdate();
}
@override
bool get semanticsEnabled => _binding.semanticsEnabled;
@override
void dispose() {
_binding.removeSemanticsEnabledListener(notifyListeners);
super.dispose();
}
}
......@@ -859,20 +859,6 @@ class _LocalSemanticsHandle implements SemanticsHandle {
/// are visible on screen. You can create other pipeline owners to manage
/// off-screen objects, which can flush their pipelines independently of the
/// on-screen render objects.
///
/// [PipelineOwner]s can be organized in a tree to manage multiple render trees,
/// where each [PipelineOwner] is responsible for one of the render trees. To
/// build or modify the tree, call [adoptChild] or [dropChild]. During each of
/// the different flush phases described above, a [PipelineOwner] will first
/// perform the phase on the nodes it manages in its own render tree before
/// calling the same flush method on its children. No assumption must be made
/// about the order in which child [PipelineOwner]s are flushed.
///
/// A [PipelineOwner] may also be [attach]ed to a [PipelineManifold], which
/// gives it access to platform functionality usually exposed by the bindings
/// without tying it to a specific binding implementation. All [PipelineOwner]s
/// in a given tree must be attached to the same [PipelineManifold]. This
/// happens automatically during [adoptChild].
class PipelineOwner {
/// Creates a pipeline owner.
///
......@@ -893,10 +879,6 @@ class PipelineOwner {
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// When the [PipelineOwner] is attached to a [PipelineManifold] and
/// [onNeedVisualUpdate] is provided, the [onNeedVisualUpdate] callback is
/// invoked instead of calling [PipelineManifold.requestVisualUpdate].
final VoidCallback? onNeedVisualUpdate;
/// Called whenever this pipeline owner creates a semantics object.
......@@ -921,11 +903,7 @@ class PipelineOwner {
/// Used to notify the pipeline owner that an associated render object wishes
/// to update its visual appearance.
void requestVisualUpdate() {
if (onNeedVisualUpdate != null) {
onNeedVisualUpdate!();
} else {
_manifold?.requestVisualUpdate();
}
onNeedVisualUpdate?.call();
}
/// The unique object managed by this pipeline that has no parent.
......@@ -967,7 +945,6 @@ class PipelineOwner {
/// always returns false.
bool get debugDoingLayout => _debugDoingLayout;
bool _debugDoingLayout = false;
bool _debugDoingChildLayout = false;
/// Update the layout information for all dirty render objects.
///
......@@ -1020,20 +997,10 @@ class PipelineOwner {
// relayout boundary back.
_shouldMergeDirtyNodes = false;
}
assert(() {
_debugDoingChildLayout = true;
return true;
}());
for (final PipelineOwner child in _children) {
child.flushLayout();
}
assert(_nodesNeedingLayout.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
} finally {
_shouldMergeDirtyNodes = false;
assert(() {
_debugDoingLayout = false;
_debugDoingChildLayout = false;
return true;
}());
if (!kReleaseMode) {
......@@ -1085,10 +1052,6 @@ class PipelineOwner {
}
}
_nodesNeedingCompositingBitsUpdate.clear();
for (final PipelineOwner child in _children) {
child.flushCompositingBits();
}
assert(_nodesNeedingCompositingBitsUpdate.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
if (!kReleaseMode) {
Timeline.finishSync();
}
......@@ -1153,10 +1116,7 @@ class PipelineOwner {
}
}
}
for (final PipelineOwner child in _children) {
child.flushPaint();
}
assert(_nodesNeedingPaint.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
assert(_nodesNeedingPaint.isEmpty);
} finally {
assert(() {
_debugDoingPaint = false;
......@@ -1170,13 +1130,11 @@ class PipelineOwner {
/// The object that is managing semantics for this pipeline owner, if any.
///
/// An owner is created by [ensureSemantics] or when the [PipelineManifold] to
/// which this owner is connected has [PipelineManifold.semanticsEnabled] set
/// to true. The owner is valid for as long as
/// [PipelineManifold.semanticsEnabled] remains true or while there are
/// outstanding [SemanticsHandle]s from calls to [ensureSemantics]. The
/// [semanticsOwner] field will revert to null once both conditions are no
/// longer met.
/// An owner is created by [ensureSemantics]. The owner is valid for as long
/// there are [SemanticsHandle]s returned by [ensureSemantics] that have not
/// yet been disposed. Once the last handle has been disposed, the
/// [semanticsOwner] field will revert to null, and the previous owner will be
/// disposed.
///
/// When [semanticsOwner] is null, the [PipelineOwner] skips all steps
/// relating to semantics.
......@@ -1209,28 +1167,23 @@ class PipelineOwner {
/// maintaining the semantics tree.
SemanticsHandle ensureSemantics({ VoidCallback? listener }) {
_outstandingSemanticsHandles += 1;
_updateSemanticsOwner();
return _LocalSemanticsHandle._(this, listener);
}
void _updateSemanticsOwner() {
if ((_manifold?.semanticsEnabled ?? false) || _outstandingSemanticsHandles > 0) {
if (_semanticsOwner == null) {
assert(onSemanticsUpdate != null, 'Attempted to enable semantics without configuring an onSemanticsUpdate callback.');
_semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!);
onSemanticsOwnerCreated?.call();
}
} else if (_semanticsOwner != null) {
_semanticsOwner?.dispose();
_semanticsOwner = null;
onSemanticsOwnerDisposed?.call();
if (_outstandingSemanticsHandles == 1) {
assert(_semanticsOwner == null);
assert(onSemanticsUpdate != null, 'Attempted to open a semantics handle without an onSemanticsUpdate callback.');
_semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!);
onSemanticsOwnerCreated?.call();
}
return _LocalSemanticsHandle._(this, listener);
}
void _didDisposeSemanticsHandle() {
assert(_semanticsOwner != null);
_outstandingSemanticsHandles -= 1;
_updateSemanticsOwner();
if (_outstandingSemanticsHandles == 0) {
_semanticsOwner!.dispose();
_semanticsOwner = null;
onSemanticsOwnerDisposed?.call();
}
}
bool _debugDoingSemantics = false;
......@@ -1269,11 +1222,8 @@ class PipelineOwner {
}
}
_semanticsOwner!.sendSemanticsUpdate();
for (final PipelineOwner child in _children) {
child.flushSemantics();
}
assert(_nodesNeedingSemantics.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
} finally {
assert(_nodesNeedingSemantics.isEmpty);
assert(() {
_debugDoingSemantics = false;
return true;
......@@ -1283,176 +1233,6 @@ class PipelineOwner {
}
}
}
// TREE MANAGEMENT
final Set<PipelineOwner> _children = <PipelineOwner>{};
PipelineManifold? _manifold;
PipelineOwner? _debugParent;
bool _debugSetParent(PipelineOwner child, PipelineOwner? parent) {
child._debugParent = parent;
return true;
}
/// Mark this [PipelineOwner] as attached to the given [PipelineManifold].
///
/// Typically, this is only called directly on the root [PipelineOwner].
/// Children are automatically attached to their parent's [PipelineManifold]
/// when [adoptChild] is called.
void attach(PipelineManifold manifold) {
assert(_manifold == null);
_manifold = manifold;
_manifold!.addListener(_updateSemanticsOwner);
_updateSemanticsOwner();
// If onNeedVisualUpdate is specified, it has already been called when the node was dirtied in the first place.
if (onNeedVisualUpdate == null && (
_nodesNeedingLayout.isNotEmpty ||
_nodesNeedingCompositingBitsUpdate.isNotEmpty ||
_nodesNeedingPaint.isNotEmpty ||
_nodesNeedingSemantics.isNotEmpty
)) {
requestVisualUpdate();
}
for (final PipelineOwner child in _children) {
child.attach(manifold);
}
}
/// Mark this [PipelineOwner] as detached.
///
/// Typically, this is only called directly on the root [PipelineOwner].
/// Children are automatically detached from their parent's [PipelineManifold]
/// when [dropChild] is called.
void detach() {
assert(_manifold != null);
_manifold!.removeListener(_updateSemanticsOwner);
_manifold = null;
_updateSemanticsOwner();
for (final PipelineOwner child in _children) {
child.detach();
}
}
// In theory, child list modifications are also disallowed between
// _debugDoingChildrenLayout and _debugDoingPaint as well as between
// _debugDoingPaint and _debugDoingSemantics. However, since the associated
// flush methods are usually called back to back, this gets us close enough.
bool get _debugAllowChildListModifications => !_debugDoingChildLayout && !_debugDoingPaint && !_debugDoingSemantics;
/// Adds `child` to this [PipelineOwner].
///
/// During the phases of frame production (see [RendererBinding.drawFrame]),
/// the parent [PipelineOwner] will complete a phase for the nodes it owns
/// directly before invoking the flush method corresponding to the current
/// phase on its child [PipelineOwner]s. For example, during layout, the
/// parent [PipelineOwner] will first lay out its own nodes before calling
/// [flushLayout] on its children. During paint, it will first paint its own
/// nodes before calling [flushPaint] on its children. This order also applies
/// for all the other phases.
///
/// No assumptions must be made about the order in which child
/// [PipelineOwner]s are flushed.
///
/// No new children may be added after the [PipelineOwner] has started calling
/// [flushLayout] on any of its children until the end of the current frame.
///
/// To remove a child, call [dropChild].
void adoptChild(PipelineOwner child) {
assert(child._debugParent == null);
assert(!_children.contains(child));
assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.');
_children.add(child);
assert(_debugSetParent(child, this));
if (_manifold != null) {
child.attach(_manifold!);
}
}
/// Removes a child [PipelineOwner] previously added via [adoptChild].
///
/// This node will cease to call the flush methods on the `child` during frame
/// production.
///
/// No children may be removed after the [PipelineOwner] has started calling
/// [flushLayout] on any of its children until the end of the current frame.
void dropChild(PipelineOwner child) {
assert(child._debugParent == this);
assert(_children.contains(child));
assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.');
_children.remove(child);
assert(_debugSetParent(child, null));
if (_manifold != null) {
child.detach();
}
}
/// Calls `visitor` for each immediate child of this [PipelineOwner].
///
/// See also:
///
/// * [adoptChild] to add a child.
/// * [dropChild] to remove a child.
void visitChildren(PipelineOwnerVisitor visitor) {
_children.forEach(visitor);
}
}
/// Signature for the callback to [PipelineOwner.visitChildren].
///
/// The argument is the child being visited.
typedef PipelineOwnerVisitor = void Function(PipelineOwner child);
/// Manages a tree of [PipelineOwner]s.
///
/// All [PipelineOwner]s within a tree are attached to the same
/// [PipelineManifold], which gives them access to shared functionality such
/// as requesting a visual update (by calling [requestVisualUpdate]). As such,
/// the [PipelineManifold] gives the [PipelineOwner]s access to functionality
/// usually provided by the bindings without tying the [PipelineOwner]s to a
/// particular binding implementation.
///
/// The root of the [PipelineOwner] tree is attached to a [PipelineManifold] by
/// passing the manifold to [PipelineOwner.attach]. Children are attached to the
/// same [PipelineManifold] as their parent when they are adopted via
/// [PipelineOwner.adoptChild].
///
/// [PipelineOwner]s can register listeners with the [PipelineManifold] to be
/// informed when certain values provided by the [PipelineManifold] change.
abstract class PipelineManifold implements Listenable {
/// Whether [PipelineOwner]s connected to this [PipelineManifold] should
/// collect semantics information and produce a semantics tree.
///
/// The [PipelineManifold] notifies its listeners (managed with [addListener]
/// and [removeListener]) when this property changes its value.
///
/// See also:
///
/// * [SemanticsBinding.semanticsEnabled], which [PipelineManifold]
/// implementations typically use to back this property.
bool get semanticsEnabled;
/// Called by a [PipelineOwner] connected to this [PipelineManifold] when a
/// [RenderObject] associated with that pipeline owner wishes to update its
/// visual appearance.
///
/// Typical implementations of this function will schedule a task to flush the
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// A [PipelineOwner] connected to this [PipelineManifold] will call
/// [PipelineOwner.onNeedVisualUpdate] instead of this method if it has been
/// configured with a non-null [PipelineOwner.onNeedVisualUpdate] callback.
///
/// See also:
///
/// * [SchedulerBinding.ensureVisualUpdate], which [PipelineManifold]
/// implementations typically call to implement this method.
void requestVisualUpdate();
}
const String _flutterRenderingLibrary = 'package:flutter/rendering.dart';
......
// Copyright 2014 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/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Initializing the RendererBinding does not crash when semantics is enabled', () {
try {
MyRenderingFlutterBinding();
} catch (e) {
fail('Initializing the RenderingBinding threw an unexpected error:\n$e');
}
expect(RendererBinding.instance, isA<MyRenderingFlutterBinding>());
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
});
}
// Binding that pretends the platform had semantics enabled before the binding
// is initialized.
class MyRenderingFlutterBinding extends RenderingFlutterBinding {
@override
bool get semanticsEnabled => true;
}
// Copyright 2014 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'rendering_tester.dart';
void main() {
MyTestRenderingFlutterBinding.ensureInitialized();
tearDown(() {
final List<PipelineOwner> children = <PipelineOwner>[];
RendererBinding.instance.pipelineOwner.visitChildren((PipelineOwner child) {
children.add(child);
});
children.forEach(RendererBinding.instance.pipelineOwner.dropChild);
});
test("BindingPipelineManifold notifies binding if render object managed by binding's PipelineOwner tree needs visual update", () {
final PipelineOwner child = PipelineOwner();
RendererBinding.instance.pipelineOwner.adoptChild(child);
final RenderObject renderObject = TestRenderObject();
child.rootNode = renderObject;
renderObject.scheduleInitialLayout();
RendererBinding.instance.pipelineOwner.flushLayout();
MyTestRenderingFlutterBinding.instance.ensureVisualUpdateCount = 0;
renderObject.markNeedsLayout();
expect(MyTestRenderingFlutterBinding.instance.ensureVisualUpdateCount, 1);
});
test('Turning global semantics on/off creates semantics owners in PipelineOwner tree', () {
final PipelineOwner child = PipelineOwner(
onSemanticsUpdate: (_) { },
);
RendererBinding.instance.pipelineOwner.adoptChild(child);
expect(child.semanticsOwner, isNull);
expect(RendererBinding.instance.pipelineOwner.semanticsOwner, isNull);
final SemanticsHandle handle = SemanticsBinding.instance.ensureSemantics();
expect(child.semanticsOwner, isNotNull);
expect(RendererBinding.instance.pipelineOwner.semanticsOwner, isNotNull);
handle.dispose();
expect(child.semanticsOwner, isNull);
expect(RendererBinding.instance.pipelineOwner.semanticsOwner, isNull);
});
}
class MyTestRenderingFlutterBinding extends TestRenderingFlutterBinding {
static MyTestRenderingFlutterBinding get instance => BindingBase.checkInstance(_instance);
static MyTestRenderingFlutterBinding? _instance;
static MyTestRenderingFlutterBinding ensureInitialized() {
if (_instance != null) {
return _instance!;
}
return MyTestRenderingFlutterBinding();
}
@override
void initInstances() {
super.initInstances();
_instance = this;
}
int ensureVisualUpdateCount = 0;
@override
void ensureVisualUpdate() {
super.ensureVisualUpdate();
ensureVisualUpdateCount++;
}
}
class TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() { }
@override
Rect get paintBounds => Rect.zero;
@override
void performLayout() { }
@override
void performResize() { }
@override
Rect get semanticBounds => Rect.zero;
}
// Copyright 2014 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:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
FlutterError.presentError = (FlutterErrorDetails details) {
// Make tests fail on FlutterErrors.
throw details.exception;
};
test('onNeedVisualUpdate takes precedence over manifold', () {
final TestPipelineManifold manifold = TestPipelineManifold();
int rootOnNeedVisualUpdateCallCount = 0;
final TestRenderObject rootRenderObject = TestRenderObject();
final PipelineOwner root = PipelineOwner(
onNeedVisualUpdate: () {
rootOnNeedVisualUpdateCallCount += 1;
},
);
root.rootNode = rootRenderObject;
rootRenderObject.scheduleInitialLayout();
int child1OnNeedVisualUpdateCallCount = 0;
final TestRenderObject child1RenderObject = TestRenderObject();
final PipelineOwner child1 = PipelineOwner(
onNeedVisualUpdate: () {
child1OnNeedVisualUpdateCallCount += 1;
},
);
child1.rootNode = child1RenderObject;
child1RenderObject.scheduleInitialLayout();
final TestRenderObject child2RenderObject = TestRenderObject();
final PipelineOwner child2 = PipelineOwner();
child2.rootNode = child2RenderObject;
child2RenderObject.scheduleInitialLayout();
root.adoptChild(child1);
root.adoptChild(child2);
root.attach(manifold);
root.flushLayout();
manifold.requestVisualUpdateCount = 0;
rootRenderObject.markNeedsLayout();
expect(manifold.requestVisualUpdateCount, 0);
expect(rootOnNeedVisualUpdateCallCount, 1);
expect(child1OnNeedVisualUpdateCallCount, 0);
child1RenderObject.markNeedsLayout();
expect(manifold.requestVisualUpdateCount, 0);
expect(rootOnNeedVisualUpdateCallCount, 1);
expect(child1OnNeedVisualUpdateCallCount, 1);
child2RenderObject.markNeedsLayout();
expect(manifold.requestVisualUpdateCount, 1);
expect(rootOnNeedVisualUpdateCallCount, 1);
expect(child1OnNeedVisualUpdateCallCount, 1);
});
test("parent's render objects are laid out before child's render objects", () {
final TestPipelineManifold manifold = TestPipelineManifold();
final List<String> log = <String>[];
final TestRenderObject rootRenderObject = TestRenderObject(
onLayout: () {
log.add('layout parent');
},
);
final PipelineOwner root = PipelineOwner();
root.rootNode = rootRenderObject;
rootRenderObject.scheduleInitialLayout();
final TestRenderObject childRenderObject = TestRenderObject(
onLayout: () {
log.add('layout child');
},
);
final PipelineOwner child = PipelineOwner();
child.rootNode = childRenderObject;
childRenderObject.scheduleInitialLayout();
root.adoptChild(child);
root.attach(manifold);
expect(log, isEmpty);
root.flushLayout();
expect(log, <String>['layout parent', 'layout child']);
});
test("child cannot dirty parent's render object during flushLayout", () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject rootRenderObject = TestRenderObject();
final PipelineOwner root = PipelineOwner();
root.rootNode = rootRenderObject;
rootRenderObject.scheduleInitialLayout();
bool childLayoutExecuted = false;
final TestRenderObject childRenderObject = TestRenderObject(
onLayout: () {
childLayoutExecuted = true;
expect(() => rootRenderObject.markNeedsLayout(), throwsFlutterError);
},
);
final PipelineOwner child = PipelineOwner();
child.rootNode = childRenderObject;
childRenderObject.scheduleInitialLayout();
root.adoptChild(child);
root.attach(manifold);
root.flushLayout();
expect(childLayoutExecuted, isTrue);
});
test('updates compositing bits on children', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject rootRenderObject = TestRenderObject();
final PipelineOwner root = PipelineOwner();
root.rootNode = rootRenderObject;
rootRenderObject.markNeedsCompositingBitsUpdate();
final TestRenderObject childRenderObject = TestRenderObject();
final PipelineOwner child = PipelineOwner();
child.rootNode = childRenderObject;
childRenderObject.markNeedsCompositingBitsUpdate();
root.adoptChild(child);
root.attach(manifold);
expect(() => rootRenderObject.needsCompositing, throwsAssertionError);
expect(() => childRenderObject.needsCompositing, throwsAssertionError);
root.flushCompositingBits();
expect(rootRenderObject.needsCompositing, isTrue);
expect(childRenderObject.needsCompositing, isTrue);
});
test("parent's render objects are painted before child's render objects", () {
final TestPipelineManifold manifold = TestPipelineManifold();
final List<String> log = <String>[];
final TestRenderObject rootRenderObject = TestRenderObject(
onPaint: () {
log.add('paint parent');
},
);
final PipelineOwner root = PipelineOwner();
root.rootNode = rootRenderObject;
final OffsetLayer rootLayer = OffsetLayer();
rootLayer.attach(rootRenderObject);
rootRenderObject.scheduleInitialLayout();
rootRenderObject.scheduleInitialPaint(rootLayer);
final TestRenderObject childRenderObject = TestRenderObject(
onPaint: () {
log.add('paint child');
},
);
final PipelineOwner child = PipelineOwner();
child.rootNode = childRenderObject;
final OffsetLayer childLayer = OffsetLayer();
childLayer.attach(childRenderObject);
childRenderObject.scheduleInitialLayout();
childRenderObject.scheduleInitialPaint(childLayer);
root.adoptChild(child);
root.attach(manifold);
root.flushLayout(); // Can't paint with invalid layout.
expect(log, isEmpty);
root.flushPaint();
expect(log, <String>['paint parent', 'paint child']);
});
test("child paint cannot dirty parent's render object", () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject rootRenderObject = TestRenderObject();
final PipelineOwner root = PipelineOwner();
root.rootNode = rootRenderObject;
final OffsetLayer rootLayer = OffsetLayer();
rootLayer.attach(rootRenderObject);
rootRenderObject.scheduleInitialLayout();
rootRenderObject.scheduleInitialPaint(rootLayer);
bool childPaintExecuted = false;
final TestRenderObject childRenderObject = TestRenderObject(
onPaint: () {
childPaintExecuted = true;
expect(() => rootRenderObject.markNeedsPaint(), throwsAssertionError);
},
);
final PipelineOwner child = PipelineOwner();
child.rootNode = childRenderObject;
final OffsetLayer childLayer = OffsetLayer();
childLayer.attach(childRenderObject);
childRenderObject.scheduleInitialLayout();
childRenderObject.scheduleInitialPaint(childLayer);
root.adoptChild(child);
root.attach(manifold);
root.flushLayout(); // Can't paint with invalid layout.
root.flushPaint();
expect(childPaintExecuted, isTrue);
});
test("parent's render objects do semantics before child's render objects", () {
final TestPipelineManifold manifold = TestPipelineManifold()
..semanticsEnabled = true;
final List<String> log = <String>[];
final TestRenderObject rootRenderObject = TestRenderObject(
onSemantics: () {
log.add('semantics parent');
},
);
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootRenderObject.scheduleInitialSemantics();
},
onSemanticsUpdate: (SemanticsUpdate update) { },
);
root.rootNode = rootRenderObject;
final TestRenderObject childRenderObject = TestRenderObject(
onSemantics: () {
log.add('semantics child');
},
);
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childRenderObject.scheduleInitialSemantics();
},
onSemanticsUpdate: (SemanticsUpdate update) { },
);
child.rootNode = childRenderObject;
root.adoptChild(child);
root.attach(manifold);
log.clear();
rootRenderObject.markNeedsSemanticsUpdate();
childRenderObject.markNeedsSemanticsUpdate();
root.flushSemantics();
expect(log, <String>['semantics parent', 'semantics child']);
});
test("child cannot mark parent's render object dirty during flushSemantics", () {
final TestPipelineManifold manifold = TestPipelineManifold()
..semanticsEnabled = true;
final TestRenderObject rootRenderObject = TestRenderObject();
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootRenderObject.scheduleInitialSemantics();
},
onSemanticsUpdate: (SemanticsUpdate update) { },
);
root.rootNode = rootRenderObject;
bool childSemanticsCalled = false;
final TestRenderObject childRenderObject = TestRenderObject(
onSemantics: () {
childSemanticsCalled = true;
rootRenderObject.markNeedsSemanticsUpdate();
},
);
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childRenderObject.scheduleInitialSemantics();
},
onSemanticsUpdate: (SemanticsUpdate update) { },
);
child.rootNode = childRenderObject;
root.adoptChild(child);
root.attach(manifold);
rootRenderObject.markNeedsSemanticsUpdate();
childRenderObject.markNeedsSemanticsUpdate();
root.flushSemantics();
expect(childSemanticsCalled, isTrue);
});
test('when manifold enables semantics all PipelineOwners in tree create SemanticsOwner', () {
final TestPipelineManifold manifold = TestPipelineManifold();
int rootOnSemanticsOwnerCreatedCount = 0;
int rootOnSemanticsOwnerDisposed = 0;
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
rootOnSemanticsOwnerDisposed++;
},
);
int childOnSemanticsOwnerCreatedCount = 0;
int childOnSemanticsOwnerDisposed = 0;
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
childOnSemanticsOwnerDisposed++;
},
);
root.adoptChild(child);
root.attach(manifold);
expect(rootOnSemanticsOwnerCreatedCount, 0);
expect(childOnSemanticsOwnerCreatedCount, 0);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
manifold.semanticsEnabled = true;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = false;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 1);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
});
test('when manifold enables semantics all PipelineOwners in tree that did not have a SemanticsOwner create one', () {
final TestPipelineManifold manifold = TestPipelineManifold();
int rootOnSemanticsOwnerCreatedCount = 0;
int rootOnSemanticsOwnerDisposed = 0;
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
rootOnSemanticsOwnerDisposed++;
},
);
int childOnSemanticsOwnerCreatedCount = 0;
int childOnSemanticsOwnerDisposed = 0;
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
childOnSemanticsOwnerDisposed++;
},
);
root.adoptChild(child);
root.attach(manifold);
final SemanticsHandle childSemantics = child.ensureSemantics();
expect(rootOnSemanticsOwnerCreatedCount, 0);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = true;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = false;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNotNull);
childSemantics.dispose();
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 1);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
});
test('PipelineOwner can dispose local handle even when manifold forces semantics to on', () {
final TestPipelineManifold manifold = TestPipelineManifold();
int rootOnSemanticsOwnerCreatedCount = 0;
int rootOnSemanticsOwnerDisposed = 0;
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
rootOnSemanticsOwnerDisposed++;
},
);
int childOnSemanticsOwnerCreatedCount = 0;
int childOnSemanticsOwnerDisposed = 0;
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
childOnSemanticsOwnerDisposed++;
},
);
root.adoptChild(child);
root.attach(manifold);
final SemanticsHandle childSemantics = child.ensureSemantics();
expect(rootOnSemanticsOwnerCreatedCount, 0);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = true;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
childSemantics.dispose();
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = false;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 1);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
});
test('can hold on to local handle when manifold turns off semantics', () {
final TestPipelineManifold manifold = TestPipelineManifold();
int rootOnSemanticsOwnerCreatedCount = 0;
int rootOnSemanticsOwnerDisposed = 0;
final PipelineOwner root = PipelineOwner(
onSemanticsOwnerCreated: () {
rootOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
rootOnSemanticsOwnerDisposed++;
},
);
int childOnSemanticsOwnerCreatedCount = 0;
int childOnSemanticsOwnerDisposed = 0;
final PipelineOwner child = PipelineOwner(
onSemanticsOwnerCreated: () {
childOnSemanticsOwnerCreatedCount++;
},
onSemanticsUpdate: (SemanticsUpdate update) { },
onSemanticsOwnerDisposed: () {
childOnSemanticsOwnerDisposed++;
},
);
root.adoptChild(child);
root.attach(manifold);
expect(rootOnSemanticsOwnerCreatedCount, 0);
expect(childOnSemanticsOwnerCreatedCount, 0);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
manifold.semanticsEnabled = true;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
final SemanticsHandle childSemantics = child.ensureSemantics();
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 0);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
manifold.semanticsEnabled = false;
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 0);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNotNull);
childSemantics.dispose();
expect(rootOnSemanticsOwnerCreatedCount, 1);
expect(childOnSemanticsOwnerCreatedCount, 1);
expect(rootOnSemanticsOwnerDisposed, 1);
expect(childOnSemanticsOwnerDisposed, 1);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
});
test('cannot attach when already attached', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final PipelineOwner owner = PipelineOwner();
owner.attach(manifold);
expect(() => owner.attach(manifold), throwsAssertionError);
});
test('attach update semanticsOwner', () {
final TestPipelineManifold manifold = TestPipelineManifold()
..semanticsEnabled = true;
final PipelineOwner owner = PipelineOwner(
onSemanticsUpdate: (_) { },
);
expect(owner.semanticsOwner, isNull);
owner.attach(manifold);
expect(owner.semanticsOwner, isNotNull);
});
test('attach does not request visual update if nothing is dirty', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
final PipelineOwner owner = PipelineOwner();
owner.rootNode = renderObject;
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 0);
});
test('attach does not request visual update if onNeedVisualUpdate is specified', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
int onNeedVisualUpdateCount = 0;
final PipelineOwner owner = PipelineOwner(
onNeedVisualUpdate: () {
onNeedVisualUpdateCount++;
},
onSemanticsUpdate: (_) { },
);
owner.ensureSemantics();
owner.rootNode = renderObject;
final OffsetLayer rootLayer = OffsetLayer();
rootLayer.attach(renderObject);
renderObject.scheduleInitialLayout();
renderObject.scheduleInitialPaint(rootLayer);
renderObject.scheduleInitialSemantics();
renderObject.markNeedsSemanticsUpdate();
onNeedVisualUpdateCount = 0;
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 0);
expect(onNeedVisualUpdateCount, 0);
});
test('attach requests visual update if onNeedVisualUpdate was not specified and nodes are dirty for layout', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
final PipelineOwner owner = PipelineOwner();
owner.rootNode = renderObject;
renderObject.scheduleInitialLayout();
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 1);
});
test('attach requests visual update if onNeedVisualUpdate was not specified and nodes are dirty for compositing bits', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
final PipelineOwner owner = PipelineOwner();
owner.rootNode = renderObject;
renderObject.markNeedsCompositingBitsUpdate();
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 1);
});
test('attach requests visual update if onNeedVisualUpdate was not specified and nodes are dirty for paint', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
final PipelineOwner owner = PipelineOwner();
owner.rootNode = renderObject;
final OffsetLayer rootLayer = OffsetLayer();
rootLayer.attach(renderObject);
renderObject.scheduleInitialLayout();
renderObject.scheduleInitialPaint(rootLayer);
owner.flushLayout();
owner.flushPaint();
renderObject.markNeedsPaint();
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 1);
});
test('attach requests visual update if onNeedVisualUpdate was not specified and nodes are dirty for semantics', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final TestRenderObject renderObject = TestRenderObject();
final PipelineOwner owner = PipelineOwner(
onSemanticsUpdate: (_) { },
);
final SemanticsHandle handle = owner.ensureSemantics();
owner.rootNode = renderObject;
renderObject.scheduleInitialLayout();
renderObject.scheduleInitialSemantics();
owner.flushLayout();
owner.flushSemantics();
renderObject.markNeedsSemanticsUpdate();
expect(manifold.requestVisualUpdateCount, 0);
owner.attach(manifold);
expect(manifold.requestVisualUpdateCount, 1);
handle.dispose();
});
test('cannot detach when not attached', () {
final PipelineOwner owner = PipelineOwner();
expect(() => owner.detach(), throwsAssertionError);
});
test('cannot adopt twice', () {
final PipelineOwner root = PipelineOwner();
final PipelineOwner child = PipelineOwner();
root.adoptChild(child);
expect(() => root.adoptChild(child), throwsAssertionError);
});
test('cannot adopt child of other parent', () {
final PipelineOwner root = PipelineOwner();
final PipelineOwner child = PipelineOwner();
final PipelineOwner otherRoot = PipelineOwner();
root.adoptChild(child);
expect(() => otherRoot.adoptChild(child), throwsAssertionError);
});
test('adopting creates semantics owner if necessary', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final PipelineOwner root = PipelineOwner(
onSemanticsUpdate: (_) { },
);
final PipelineOwner child = PipelineOwner(
onSemanticsUpdate: (_) { },
);
final PipelineOwner childOfChild = PipelineOwner(
onSemanticsUpdate: (_) { },
);
root.attach(manifold);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
expect(childOfChild.semanticsOwner, isNull);
root.adoptChild(child);
expect(root.semanticsOwner, isNull);
expect(child.semanticsOwner, isNull);
expect(childOfChild.semanticsOwner, isNull);
manifold.semanticsEnabled = true;
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
expect(childOfChild.semanticsOwner, isNull);
child.adoptChild(childOfChild);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
expect(childOfChild.semanticsOwner, isNotNull);
});
test('cannot drop unattached child', () {
final PipelineOwner root = PipelineOwner();
final PipelineOwner child = PipelineOwner();
expect(() => root.dropChild(child), throwsAssertionError);
});
test('cannot drop child attached to other parent', () {
final PipelineOwner root = PipelineOwner();
final PipelineOwner child = PipelineOwner();
final PipelineOwner otherRoot = PipelineOwner();
otherRoot.adoptChild(child);
expect(() => root.dropChild(child), throwsAssertionError);
});
test('dropping destroys semantics owner if necessary', () {
final TestPipelineManifold manifold = TestPipelineManifold()
..semanticsEnabled = true;
final PipelineOwner root = PipelineOwner(
onSemanticsUpdate: (_) { },
);
final PipelineOwner child = PipelineOwner(
onSemanticsUpdate: (_) { },
);
final PipelineOwner childOfChild = PipelineOwner(
onSemanticsUpdate: (_) { },
);
root.attach(manifold);
root.adoptChild(child);
child.adoptChild(childOfChild);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
expect(childOfChild.semanticsOwner, isNotNull);
child.dropChild(childOfChild);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
expect(childOfChild.semanticsOwner, isNull);
final SemanticsHandle childSemantics = child.ensureSemantics();
root.dropChild(child);
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNotNull);
expect(childOfChild.semanticsOwner, isNull);
childSemantics.dispose();
expect(root.semanticsOwner, isNotNull);
expect(child.semanticsOwner, isNull);
expect(childOfChild.semanticsOwner, isNull);
});
test('can adopt/drop children during own layout', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final PipelineOwner root = PipelineOwner();
final PipelineOwner child1 = PipelineOwner();
final PipelineOwner child2 = PipelineOwner();
final TestRenderObject rootRenderObject = TestRenderObject(
onLayout: () {
child1.dropChild(child2);
root.dropChild(child1);
root.adoptChild(child2);
child2.adoptChild(child1);
},
);
root.rootNode = rootRenderObject;
rootRenderObject.scheduleInitialLayout();
root.adoptChild(child1);
child1.adoptChild(child2);
root.attach(manifold);
expect(_treeWalk(root), <PipelineOwner>[root, child1, child2]);
root.flushLayout();
expect(_treeWalk(root), <PipelineOwner>[root, child2, child1]);
});
test('cannot adopt/drop children during child layout', () {
final TestPipelineManifold manifold = TestPipelineManifold();
final PipelineOwner root = PipelineOwner();
final PipelineOwner child1 = PipelineOwner();
final PipelineOwner child2 = PipelineOwner();
final PipelineOwner child3 = PipelineOwner();
Object? droppingError;
Object? adoptingError;
final TestRenderObject childRenderObject = TestRenderObject(
onLayout: () {
child1.dropChild(child2);
child1.adoptChild(child3);
try {
root.dropChild(child1);
} catch (e) {
droppingError = e;
}
try {
root.adoptChild(child2);
} catch (e) {
adoptingError = e;
}
},
);
child1.rootNode = childRenderObject;
childRenderObject.scheduleInitialLayout();
root.adoptChild(child1);
child1.adoptChild(child2);
root.attach(manifold);
expect(_treeWalk(root), <PipelineOwner>[root, child1, child2]);
root.flushLayout();
expect(adoptingError, isAssertionError.having((AssertionError e) => e.message, 'message', contains('Cannot modify child list after layout.')));
expect(droppingError, isAssertionError.having((AssertionError e) => e.message, 'message', contains('Cannot modify child list after layout.')));
});
test('visitChildren visits all children', () {
final PipelineOwner root = PipelineOwner();
final PipelineOwner child1 = PipelineOwner();
final PipelineOwner child2 = PipelineOwner();
final PipelineOwner child3 = PipelineOwner();
final PipelineOwner childOfChild3 = PipelineOwner();
root.adoptChild(child1);
root.adoptChild(child2);
root.adoptChild(child3);
child3.adoptChild(childOfChild3);
final List<PipelineOwner> children = <PipelineOwner>[];
root.visitChildren((PipelineOwner child) {
children.add(child);
});
expect(children, <PipelineOwner>[child1, child2, child3]);
children.clear();
child3.visitChildren((PipelineOwner child) {
children.add(child);
});
expect(children.single, childOfChild3);
});
}
class TestPipelineManifold extends ChangeNotifier implements PipelineManifold {
int requestVisualUpdateCount = 0;
@override
void requestVisualUpdate() {
requestVisualUpdateCount++;
}
@override
bool get semanticsEnabled => _semanticsEnabled;
bool _semanticsEnabled = false;
set semanticsEnabled(bool value) {
if (value == _semanticsEnabled) {
return;
}
_semanticsEnabled = value;
notifyListeners();
}
}
class TestRenderObject extends RenderObject {
TestRenderObject({this.onLayout, this.onPaint, this.onSemantics});
final VoidCallback? onLayout;
final VoidCallback? onPaint;
final VoidCallback? onSemantics;
@override
bool get isRepaintBoundary => true;
@override
void debugAssertDoesMeetConstraints() { }
@override
Rect get paintBounds => Rect.zero;
@override
void performLayout() {
onLayout?.call();
}
@override
void paint(PaintingContext context, Offset offset) {
onPaint?.call();
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
onSemantics?.call();
}
@override
void performResize() { }
@override
Rect get semanticBounds => Rect.zero;
}
List<PipelineOwner> _treeWalk(PipelineOwner root) {
final List<PipelineOwner> results = <PipelineOwner>[root];
void visitor(PipelineOwner child) {
results.add(child);
child.visitChildren(visitor);
}
root.visitChildren(visitor);
return results;
}
......@@ -1045,7 +1045,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
void _verifySemanticsHandlesWereDisposed() {
assert(_lastRecordedSemanticsHandles != null);
// TODO(goderbauer): Fix known leak in web engine when running integration tests and remove this "correction", https://github.com/flutter/flutter/issues/121640.
final int knownWebEngineLeakForLiveTestsCorrection = kIsWeb && binding is LiveTestWidgetsFlutterBinding ? 1 : 0;
final int knownWebEngineLeakForLiveTestsCorrection = kIsWeb && binding is LiveTestWidgetsFlutterBinding ? 2 : 0;
if (_currentSemanticsHandles - knownWebEngineLeakForLiveTestsCorrection > _lastRecordedSemanticsHandles!) {
throw FlutterError.fromParts(<DiagnosticsNode>[
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment