Commit 744921fa authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Ensure that a frame is scheduled when a RenderObject calls markNeedsSemanticsUpdate (#11207)

* Ensure that a frame is allways scheduled when a RenderObject marks itself as needing a semantics update

Fixes issue 2 described in https://github.com/flutter/flutter/issues/10971

Previously, an object could call `markNeedsSemanticsUpdate` and its wish would never be granted because no frame was scheduled that would actually update the semantics. This caused an issue during scrolling on Android where at the end of the scroll the `RenderIgnorePointer` would stop blocking the semantics of the scrolled view, call `markNeedsSemanticsUpdate`, but then no frame was scheduled to actually put the semantics of the scrolled view back into the semantics tree. That made the scrolled view unusable for a11y users.

At first I was a bit wary to call `requestVisualUpdate` within `markNeedsSemanticsUpdate` because technically the visual is fine, we only need the frame it schedules to update the semantics. However, it seems like we are using `requestVisualUpdate` for exactly that purpose in other places already where we just need an update to the semantics (e.g. https://github.com/flutter/flutter/blob/76a50fe0ca9641a835517ca020f9089453989011/packages/flutter/lib/src/rendering/object.dart#L2408).

* add tests
parent 16037e33
...@@ -2512,8 +2512,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -2512,8 +2512,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
if (owner != null) if (owner != null) {
owner._nodesNeedingSemantics.add(node); owner._nodesNeedingSemantics.add(node);
owner.requestVisualUpdate();
}
} }
} else { } else {
// The shape of the semantics tree around us may have changed. // The shape of the semantics tree around us may have changed.
...@@ -2532,8 +2534,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -2532,8 +2534,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
node._semantics?.reset(); node._semantics?.reset();
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
if (owner != null) if (owner != null) {
owner._nodesNeedingSemantics.add(node); owner._nodesNeedingSemantics.add(node);
owner.requestVisualUpdate();
}
} }
} }
} }
......
// Copyright 2017 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:test/test.dart';
void main() {
test("ensure frame is scheduled for markNeedsSemanticsUpdate", () {
final TestRenderObject renderObject = new TestRenderObject();
int onNeedVisualUpdateCallCount = 0;
final PipelineOwner owner = new PipelineOwner(onNeedVisualUpdate: () {
onNeedVisualUpdateCallCount +=1;
});
owner.ensureSemantics();
renderObject.attach(owner);
owner.flushSemantics();
expect(onNeedVisualUpdateCallCount, 1);
renderObject.markNeedsSemanticsUpdate();
expect(onNeedVisualUpdateCallCount, 2);
});
test("ensure frame is scheduled for markNeedsSemanticsUpdate with onlyChanges: true", () {
final TestRenderObject renderObject = new TestRenderObject();
int onNeedVisualUpdateCallCount = 0;
final PipelineOwner owner = new PipelineOwner(onNeedVisualUpdate: () {
onNeedVisualUpdateCallCount +=1;
});
owner.ensureSemantics();
renderObject.attach(owner);
owner.flushSemantics();
expect(onNeedVisualUpdateCallCount, 1);
renderObject.markNeedsSemanticsUpdate(onlyChanges: true);
expect(onNeedVisualUpdateCallCount, 2);
});
}
class TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() {}
@override
Rect get paintBounds => null;
@override
void performLayout() {}
@override
void performResize() {}
@override
Rect get semanticBounds => new Rect.fromLTWH(0.0, 0.0, 10.0, 20.0);
@override
bool get isSemanticBoundary => true;
}
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