From aab180c643ab7f466b8c0482afa72f2b2ffd5744 Mon Sep 17 00:00:00 2001 From: a-wallen Date: Thu, 13 Oct 2022 09:37:51 -0400 Subject: [PATCH 1/7] Migrate deprecated calls to new api --- packages/flutter/lib/src/semantics/semantics.dart | 4 +--- packages/flutter_test/lib/src/window.dart | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index d60a72231e6cc..67cd318a78a10 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -3118,9 +3118,7 @@ class SemanticsOwner extends ChangeNotifier { final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!; builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1); } - // TODO(a-wallen): https://github.com/flutter/flutter/issues/112221 - // ignore: deprecated_member_use - SemanticsBinding.instance.platformDispatcher.updateSemantics(builder.build()); + SemanticsBinding.instance.platformDispatcher.views.first.updateSemantics(builder.build()); notifyListeners(); } diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 9207367d494e4..f07f3bcc90477 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -853,9 +853,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { @override void updateSemantics(ui.SemanticsUpdate update) { - // TODO(a-wallen): https://github.com/flutter/flutter/issues/112221 - // ignore: deprecated_member_use - _platformDispatcher.updateSemantics(update); + _platformDispatcher.views.first.updateSemantics(update); } @override From e1bc066d0b7edead42fb5df336dbf7472bcdb202 Mon Sep 17 00:00:00 2001 From: a-wallen Date: Thu, 13 Oct 2022 11:40:51 -0400 Subject: [PATCH 2/7] Migrate updateSemantics in packages/ --- packages/flutter/lib/src/semantics/semantics.dart | 2 +- packages/flutter_test/lib/src/window.dart | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 67cd318a78a10..b05b27a0ee3b3 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -3069,7 +3069,7 @@ class SemanticsOwner extends ChangeNotifier { super.dispose(); } - /// Update the semantics using [dart:ui.PlatformDispatcher.updateSemantics]. + /// Update the semantics using [dart:ui.FlutterView.updateSemantics]. void sendSemanticsUpdate() { if (_dirtyNodes.isEmpty) { return; diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index f07f3bcc90477..9fdaf2319feab 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -463,11 +463,6 @@ class TestWindow implements ui.SingletonFlutterWindow { platformDispatcher.onAccessibilityFeaturesChanged = callback; } - @override - void updateSemantics(ui.SemanticsUpdate update) { - platformDispatcher.updateSemantics(update); - } - @override void setIsolateDebugName(String name) { platformDispatcher.setIsolateDebugName(name); @@ -851,11 +846,6 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { _platformDispatcher.onAccessibilityFeaturesChanged = callback; } - @override - void updateSemantics(ui.SemanticsUpdate update) { - _platformDispatcher.views.first.updateSemantics(update); - } - @override void setIsolateDebugName(String name) { _platformDispatcher.setIsolateDebugName(name); From a77e948d3f3266bd129a3b8f23c85f5f0083ae32 Mon Sep 17 00:00:00 2001 From: a-wallen Date: Mon, 17 Oct 2022 13:55:54 -1000 Subject: [PATCH 3/7] create onSemanticsUpdate callback to update views --- packages/flutter/lib/src/rendering/binding.dart | 6 ++++++ packages/flutter/lib/src/rendering/object.dart | 11 ++++++++++- packages/flutter/lib/src/rendering/view.dart | 8 +++++++- packages/flutter/lib/src/semantics/semantics.dart | 14 +++++++++++++- packages/flutter/test/rendering/object_test.dart | 11 ++++++++--- .../flutter/test/semantics/semantics_test.dart | 6 +++++- 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index a9e0196063822..95472d9859837 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:developer'; +import 'dart:ui' as ui show SemanticsUpdate; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -31,6 +32,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, + onSemanticsUpdate: _handleSemanticsUpdate, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); platformDispatcher @@ -367,6 +369,10 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture renderView.scheduleInitialSemantics(); } + void _handleSemanticsUpdate(ui.SemanticsUpdate update) { + renderView.updateSemantics(update); + } + void _handleSemanticsOwnerDisposed() { renderView.clearSemantics(); } diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index dcae4692d7051..167e6b332a3a6 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'dart:ui' as ui show PictureRecorder; +import 'dart:ui'; import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; @@ -894,6 +895,7 @@ class PipelineOwner { PipelineOwner({ this.onNeedVisualUpdate, this.onSemanticsOwnerCreated, + this.onSemanticsUpdate, this.onSemanticsOwnerDisposed, }); @@ -912,6 +914,12 @@ class PipelineOwner { /// semantics tree. final VoidCallback? onSemanticsOwnerCreated; + /// Called whenever this pipeline owner's semantics owner emits a [SemanticsUpdate]. + /// + /// Typical implementations will delegate the [SemanticsUpdate] to a [FlutterView] + /// that can handle the [SemanticsUpdate]. + final SemanticsUpdateCallback? onSemanticsUpdate; + /// Called whenever this pipeline owner disposes its semantics owner. /// /// Typical implementations will tear down the semantics tree. @@ -1183,7 +1191,8 @@ class PipelineOwner { _outstandingSemanticsHandles += 1; if (_outstandingSemanticsHandles == 1) { assert(_semanticsOwner == null); - _semanticsOwner = SemanticsOwner(); + assert(onSemanticsUpdate != null, 'Attempted to open a semantics handle without an onSemanticsUpdate callback.'); + _semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!); onSemanticsOwnerCreated?.call(); } return SemanticsHandle._(this, listener); diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index da3bf44d4f9e1..bebc9fc9aa5ad 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -4,7 +4,7 @@ import 'dart:developer'; import 'dart:io' show Platform; -import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder; +import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder, SemanticsUpdate; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -247,6 +247,12 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin } } + /// Sends a [SemanticsUpdate] to the semantics tree contained within the + /// window of this [RenderView]. + void updateSemantics(ui.SemanticsUpdate update) { + _window.updateSemantics(update); + } + void _updateSystemChrome() { // Take overlay style from the place where a system status bar and system // navigation bar are placed to update system style overlay. diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index b05b27a0ee3b3..358279c3d3720 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -48,6 +48,9 @@ typedef SetTextHandler = void Function(String text); /// Returned by [SemanticsConfiguration.getActionHandler]. typedef SemanticsActionHandler = void Function(Object? args); +/// Signature for a function that receives a semantics update and returns no result. +typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update); + /// A tag for a [SemanticsNode]. /// /// Tags can be interpreted by the parent of a [SemanticsNode] @@ -3052,6 +3055,15 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> { /// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if /// necessary. class SemanticsOwner extends ChangeNotifier { + /// Creates a [SemanticsOwner] manages zero or more [SemanticsNode] objects. + SemanticsOwner({ + required this.onSemanticsUpdate, + }); + + /// Updates to the internal state of the [SemanticsOwner]'s [SemanticsNode]s + /// are accumulted into one [SemanticsUpdate] that can be delegated with the + /// onSemanticsUpdate callback. + final SemanticsUpdateCallback onSemanticsUpdate; final Set _dirtyNodes = {}; final Map _nodes = {}; final Set _detachedNodes = {}; @@ -3118,7 +3130,7 @@ class SemanticsOwner extends ChangeNotifier { final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!; builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1); } - SemanticsBinding.instance.platformDispatcher.views.first.updateSemantics(builder.build()); + onSemanticsUpdate(builder.build()); notifyListeners(); } diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index 5049405560ac3..6fdbc870dc71e 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -18,9 +18,14 @@ void main() { // Initialize all bindings because owner.flushSemantics() requires a window final TestRenderObject renderObject = TestRenderObject(); int onNeedVisualUpdateCallCount = 0; - final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () { - onNeedVisualUpdateCallCount +=1; - }); + final PipelineOwner owner = PipelineOwner( + onNeedVisualUpdate: () { + onNeedVisualUpdateCallCount +=1; + }, + onSemanticsUpdate: (ui.SemanticsUpdate update) { + + }, + ); owner.ensureSemantics(); renderObject.attach(owner); renderObject.layout(const BoxConstraints.tightForFinite()); // semantics are only calculated if layout information is up to date. diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index 89f6d73339101..85916b0594235 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -2,6 +2,8 @@ // 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/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -698,7 +700,9 @@ void main() { }); test('Semantics id does not repeat', () { - final SemanticsOwner owner = SemanticsOwner(); + final SemanticsOwner owner = SemanticsOwner( + onSemanticsUpdate: (SemanticsUpdate update) {}, + ); const int expectId = 1400; SemanticsNode? nodeToRemove; for (int i = 0; i < kMaxFrameworkAccessibilityIdentifier; i++) { From c0bb1dc4baf6c0d6c8d2177ea90f23ba21311743 Mon Sep 17 00:00:00 2001 From: a-wallen Date: Tue, 25 Oct 2022 13:14:13 -1000 Subject: [PATCH 4/7] Address semantic comments. --- packages/flutter/lib/src/rendering/view.dart | 5 +++-- .../flutter/lib/src/semantics/semantics.dart | 16 +++++++++++----- packages/flutter/test/rendering/object_test.dart | 5 ++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index bebc9fc9aa5ad..836ff8ed44dd8 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -247,8 +247,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin } } - /// Sends a [SemanticsUpdate] to the semantics tree contained within the - /// window of this [RenderView]. + /// Sends the provided [SemanticsUpdate] to the [FlutterView] associated with this [RenderView]. + /// + /// A [SemanticsUpdate] is produced by a [SemanticsOwner] during the [EnginePhase.flushSemantics] phase. void updateSemantics(ui.SemanticsUpdate update) { _window.updateSemantics(update); } diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 358279c3d3720..0ada4ca7c5e9b 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -49,6 +49,8 @@ typedef SetTextHandler = void Function(String text); typedef SemanticsActionHandler = void Function(Object? args); /// Signature for a function that receives a semantics update and returns no result. +/// +/// Used by [SemanticsOwner.onSemanticsUpdate]. typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update); /// A tag for a [SemanticsNode]. @@ -3055,14 +3057,18 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> { /// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if /// necessary. class SemanticsOwner extends ChangeNotifier { - /// Creates a [SemanticsOwner] manages zero or more [SemanticsNode] objects. + /// Creates a [SemanticsOwner] that manages zero or more [SemanticsNode] objects. SemanticsOwner({ required this.onSemanticsUpdate, }); - /// Updates to the internal state of the [SemanticsOwner]'s [SemanticsNode]s - /// are accumulted into one [SemanticsUpdate] that can be delegated with the - /// onSemanticsUpdate callback. + /// The [onSemanticsUpdate] callback is expected to dispatch [SemanticsUpdate]s + /// to the [FlutterView] that is associated with this [PipelineOwner] and/or + /// [SemanticsOwner]. + /// + /// A [SemanticsOwner] calls [onSemanticsUpdate] during [sendSemanticsUpdate] + /// after the [SemanticsUpdate] has been build, but before the [SemanticsOwner]'s + /// listeners have been notified. final SemanticsUpdateCallback onSemanticsUpdate; final Set _dirtyNodes = {}; final Map _nodes = {}; @@ -3081,7 +3087,7 @@ class SemanticsOwner extends ChangeNotifier { super.dispose(); } - /// Update the semantics using [dart:ui.FlutterView.updateSemantics]. + /// Update the semantics using [onSemanticsUpdate]. void sendSemanticsUpdate() { if (_dirtyNodes.isEmpty) { return; diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index 6fdbc870dc71e..2015fd8c17457 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -18,18 +18,21 @@ void main() { // Initialize all bindings because owner.flushSemantics() requires a window final TestRenderObject renderObject = TestRenderObject(); int onNeedVisualUpdateCallCount = 0; + int onSemanticsUpdateCallCount = 0; final PipelineOwner owner = PipelineOwner( onNeedVisualUpdate: () { onNeedVisualUpdateCallCount +=1; }, onSemanticsUpdate: (ui.SemanticsUpdate update) { - + onNeedVisualUpdateCallCount +=1; }, ); owner.ensureSemantics(); renderObject.attach(owner); renderObject.layout(const BoxConstraints.tightForFinite()); // semantics are only calculated if layout information is up to date. + expect(onSemanticsUpdateCallCount, 0); owner.flushSemantics(); + expect(onSemanticsUpdateCallCount, 1); expect(onNeedVisualUpdateCallCount, 1); renderObject.markNeedsSemanticsUpdate(); From 6e1a135750170ae4358ca4c808a2ce99959e13eb Mon Sep 17 00:00:00 2001 From: a-wallen Date: Tue, 25 Oct 2022 16:34:58 -1000 Subject: [PATCH 5/7] Add tests for PipelineOwner --- .../flutter/test/rendering/object_test.dart | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index 2015fd8c17457..136c3e915874b 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -18,27 +18,46 @@ void main() { // Initialize all bindings because owner.flushSemantics() requires a window final TestRenderObject renderObject = TestRenderObject(); int onNeedVisualUpdateCallCount = 0; - int onSemanticsUpdateCallCount = 0; final PipelineOwner owner = PipelineOwner( onNeedVisualUpdate: () { onNeedVisualUpdateCallCount +=1; }, - onSemanticsUpdate: (ui.SemanticsUpdate update) { - onNeedVisualUpdateCallCount +=1; - }, + onSemanticsUpdate: (ui.SemanticsUpdate update) {} ); owner.ensureSemantics(); renderObject.attach(owner); renderObject.layout(const BoxConstraints.tightForFinite()); // semantics are only calculated if layout information is up to date. - expect(onSemanticsUpdateCallCount, 0); owner.flushSemantics(); - expect(onSemanticsUpdateCallCount, 1); expect(onNeedVisualUpdateCallCount, 1); renderObject.markNeedsSemanticsUpdate(); expect(onNeedVisualUpdateCallCount, 2); }); + test('onSemanticsUpdate is called during flushSemantics.', () { + final TestRenderObject renderObject = TestRenderObject(); + bool onSemanticsUpdateCallCount = false; + final PipelineOwner owner = PipelineOwner( + onSemanticsUpdate: (ui.SemanticsUpdate update) { + onSemanticsUpdateCallCount = true; + }, + ); + owner.ensureSemantics(); + + expect(onSemanticsUpdateCallCount, false); + + renderObject.attach(owner); + renderObject.layout(const BoxConstraints.tightForFinite()); + owner.flushSemantics(); + + expect(onSemanticsUpdateCallCount, true); + }); + + test('Enabling semantics without configuring onSemanticsUpdate is invalid.', () { + final PipelineOwner pipelineOwner = PipelineOwner(); + expect(() => pipelineOwner.ensureSemantics(), throwsAssertionError); + }); + test('detached RenderObject does not do semantics', () { final TestRenderObject renderObject = TestRenderObject(); expect(renderObject.attached, isFalse); From 1f67cd6e54ab0218f568ebfedf4edb07fd172c88 Mon Sep 17 00:00:00 2001 From: a-wallen Date: Tue, 25 Oct 2022 16:47:41 -1000 Subject: [PATCH 6/7] SemanticsOwner sendSemanticsUpdate test --- .../flutter/test/rendering/object_test.dart | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index 136c3e915874b..150d07c6625d1 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -35,22 +35,22 @@ void main() { }); test('onSemanticsUpdate is called during flushSemantics.', () { - final TestRenderObject renderObject = TestRenderObject(); - bool onSemanticsUpdateCallCount = false; + int onSemanticsUpdateCallCount = 0; final PipelineOwner owner = PipelineOwner( onSemanticsUpdate: (ui.SemanticsUpdate update) { - onSemanticsUpdateCallCount = true; + onSemanticsUpdateCallCount += 1; }, ); owner.ensureSemantics(); - expect(onSemanticsUpdateCallCount, false); + expect(onSemanticsUpdateCallCount, 0); + final TestRenderObject renderObject = TestRenderObject(); renderObject.attach(owner); renderObject.layout(const BoxConstraints.tightForFinite()); owner.flushSemantics(); - expect(onSemanticsUpdateCallCount, true); + expect(onSemanticsUpdateCallCount, 1); }); test('Enabling semantics without configuring onSemanticsUpdate is invalid.', () { @@ -58,6 +58,25 @@ void main() { expect(() => pipelineOwner.ensureSemantics(), throwsAssertionError); }); + + test('onSemanticsUpdate during sendSemanticsUpdate.', () { + int onSemanticsUpdateCallCount = 0; + final SemanticsOwner owner = SemanticsOwner( + onSemanticsUpdate: (ui.SemanticsUpdate update) { + onSemanticsUpdateCallCount += 1; + }, + ); + + final SemanticsNode node = SemanticsNode.root(owner: owner); + node.rect = Rect.largest; + + expect(onSemanticsUpdateCallCount, 0); + + owner.sendSemanticsUpdate(); + + expect(onSemanticsUpdateCallCount, 1); + }); + test('detached RenderObject does not do semantics', () { final TestRenderObject renderObject = TestRenderObject(); expect(renderObject.attached, isFalse); From bc3a2da06431bd543be85dfee36f3b5f7a6d55ab Mon Sep 17 00:00:00 2001 From: a-wallen Date: Wed, 26 Oct 2022 07:39:39 -1000 Subject: [PATCH 7/7] Reflow doc comments to 80 chars --- packages/flutter/lib/src/rendering/view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index 836ff8ed44dd8..45be360983cb6 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -247,9 +247,11 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin } } - /// Sends the provided [SemanticsUpdate] to the [FlutterView] associated with this [RenderView]. + /// Sends the provided [SemanticsUpdate] to the [FlutterView] associated with + /// this [RenderView]. /// - /// A [SemanticsUpdate] is produced by a [SemanticsOwner] during the [EnginePhase.flushSemantics] phase. + /// A [SemanticsUpdate] is produced by a [SemanticsOwner] during the + /// [EnginePhase.flushSemantics] phase. void updateSemantics(ui.SemanticsUpdate update) { _window.updateSemantics(update); }