forked from firka/flutter
Move semantic-related bindings to SemanticsBinding (#121289)
Move semantic-related bindings to SemanticsBinding
This commit is contained in:
committed by
GitHub
parent
cb67ecd97d
commit
b1b7284a72
@@ -184,8 +184,8 @@ void main() {
|
||||
);
|
||||
|
||||
testWidgets('Flutter Gallery app smoke test with semantics', (WidgetTester tester) async {
|
||||
RendererBinding.instance.setSemanticsEnabled(true);
|
||||
final SemanticsHandle handle = SemanticsBinding.instance.ensureSemantics();
|
||||
await smokeGallery(tester);
|
||||
RendererBinding.instance.setSemanticsEnabled(false);
|
||||
handle.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,19 +3,16 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
VoidCallback? originalSemanticsListener;
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Disconnects semantics listener for testing purposes.
|
||||
originalSemanticsListener = WidgetsBinding.instance.platformDispatcher.onSemanticsEnabledChanged;
|
||||
RendererBinding.instance.platformDispatcher.onSemanticsEnabledChanged = null;
|
||||
RendererBinding.instance.setSemanticsEnabled(false);
|
||||
// If the test passes, LifeCycleSpy will rewire the semantics listener back.
|
||||
SwitchableSemanticsBinding.ensureInitialized();
|
||||
assert(!SwitchableSemanticsBinding.instance.semanticsEnabled);
|
||||
|
||||
runApp(const LifeCycleSpy());
|
||||
}
|
||||
|
||||
@@ -68,8 +65,7 @@ class _LifeCycleSpyState extends State<LifeCycleSpy> with WidgetsBindingObserver
|
||||
Widget build(BuildContext context) {
|
||||
if (const ListEquality<AppLifecycleState?>().equals(_actualLifeCycleSequence, _expectedLifeCycleSequence)) {
|
||||
// Rewires the semantics harness if test passes.
|
||||
RendererBinding.instance.setSemanticsEnabled(true);
|
||||
RendererBinding.instance.platformDispatcher.onSemanticsEnabledChanged = originalSemanticsListener;
|
||||
SwitchableSemanticsBinding.instance.semanticsEnabled = true;
|
||||
}
|
||||
return const MaterialApp(
|
||||
title: 'Flutter View',
|
||||
@@ -77,3 +73,52 @@ class _LifeCycleSpyState extends State<LifeCycleSpy> with WidgetsBindingObserver
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchableSemanticsBinding extends WidgetsFlutterBinding {
|
||||
static SwitchableSemanticsBinding get instance => BindingBase.checkInstance(_instance);
|
||||
static SwitchableSemanticsBinding? _instance;
|
||||
|
||||
static SwitchableSemanticsBinding ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
SwitchableSemanticsBinding();
|
||||
}
|
||||
return SwitchableSemanticsBinding.instance;
|
||||
}
|
||||
|
||||
VoidCallback? _originalSemanticsListener;
|
||||
|
||||
@override
|
||||
void initInstances() {
|
||||
super.initInstances();
|
||||
_instance = this;
|
||||
_updateHandler();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get semanticsEnabled => _semanticsEnabled.value;
|
||||
final ValueNotifier<bool> _semanticsEnabled = ValueNotifier<bool>(false);
|
||||
set semanticsEnabled(bool value) {
|
||||
_semanticsEnabled.value = value;
|
||||
_updateHandler();
|
||||
}
|
||||
|
||||
void _updateHandler() {
|
||||
if (_semanticsEnabled.value) {
|
||||
platformDispatcher.onSemanticsEnabledChanged = _originalSemanticsListener;
|
||||
_originalSemanticsListener = null;
|
||||
} else {
|
||||
_originalSemanticsListener = platformDispatcher.onSemanticsEnabledChanged;
|
||||
platformDispatcher.onSemanticsEnabledChanged = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addSemanticsEnabledListener(VoidCallback listener) {
|
||||
_semanticsEnabled.addListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeSemanticsEnabledListener(VoidCallback listener) {
|
||||
_semanticsEnabled.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,15 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
platformDispatcher
|
||||
..onMetricsChanged = handleMetricsChanged
|
||||
..onTextScaleFactorChanged = handleTextScaleFactorChanged
|
||||
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
|
||||
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
|
||||
..onSemanticsAction = _handleSemanticsAction;
|
||||
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;
|
||||
initRenderView();
|
||||
_handleSemanticsEnabledChanged();
|
||||
addPersistentFrameCallback(_handlePersistentFrameCallback);
|
||||
initMouseTracker();
|
||||
if (kIsWeb) {
|
||||
addPostFrameCallback(_handleWebFirstFrame);
|
||||
}
|
||||
addSemanticsEnabledListener(_handleSemanticsEnabledChanged);
|
||||
_handleSemanticsEnabledChanged();
|
||||
}
|
||||
|
||||
/// The current [RendererBinding], if one has been created.
|
||||
@@ -308,8 +307,6 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
);
|
||||
}
|
||||
|
||||
SemanticsHandle? _semanticsHandle;
|
||||
|
||||
/// Creates a [MouseTracker] which manages state about currently connected
|
||||
/// mice, for hover notification.
|
||||
///
|
||||
@@ -333,14 +330,10 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
super.dispatchEvent(event, hitTestResult);
|
||||
}
|
||||
|
||||
void _handleSemanticsEnabledChanged() {
|
||||
setSemanticsEnabled(platformDispatcher.semanticsEnabled);
|
||||
}
|
||||
SemanticsHandle? _semanticsHandle;
|
||||
|
||||
/// Whether the render tree associated with this binding should produce a tree
|
||||
/// of [SemanticsNode] objects.
|
||||
void setSemanticsEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
void _handleSemanticsEnabledChanged() {
|
||||
if (semanticsEnabled) {
|
||||
_semanticsHandle ??= _pipelineOwner.ensureSemantics();
|
||||
} else {
|
||||
_semanticsHandle?.dispose();
|
||||
@@ -348,18 +341,9 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
}
|
||||
}
|
||||
|
||||
void _handleWebFirstFrame(Duration _) {
|
||||
assert(kIsWeb);
|
||||
const MethodChannel methodChannel = MethodChannel('flutter/service_worker');
|
||||
methodChannel.invokeMethod<void>('first-frame');
|
||||
}
|
||||
|
||||
void _handleSemanticsAction(int id, SemanticsAction action, ByteData? args) {
|
||||
_pipelineOwner.semanticsOwner?.performAction(
|
||||
id,
|
||||
action,
|
||||
args != null ? const StandardMessageCodec().decodeMessage(args) : null,
|
||||
);
|
||||
@override
|
||||
void performSemanticsAction(SemanticsActionEvent action) {
|
||||
_pipelineOwner.semanticsOwner?.performAction(action.nodeId, action.type, action.arguments);
|
||||
}
|
||||
|
||||
void _handleSemanticsOwnerCreated() {
|
||||
@@ -374,6 +358,12 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
renderView.clearSemantics();
|
||||
}
|
||||
|
||||
void _handleWebFirstFrame(Duration _) {
|
||||
assert(kIsWeb);
|
||||
const MethodChannel methodChannel = MethodChannel('flutter/service_worker');
|
||||
methodChannel.invokeMethod<void>('first-frame');
|
||||
}
|
||||
|
||||
void _handlePersistentFrameCallback(Duration timeStamp) {
|
||||
drawFrame();
|
||||
_scheduleMouseTrackerUpdate();
|
||||
|
||||
@@ -808,24 +808,8 @@ typedef RenderObjectVisitor = void Function(RenderObject child);
|
||||
/// Used by [RenderObject.invokeLayoutCallback].
|
||||
typedef LayoutCallback<T extends Constraints> = void Function(T constraints);
|
||||
|
||||
/// A reference to the semantics tree.
|
||||
///
|
||||
/// The framework maintains the semantics tree (used for accessibility and
|
||||
/// indexing) only when there is at least one client holding an open
|
||||
/// [SemanticsHandle].
|
||||
///
|
||||
/// The framework notifies the client that it has updated the semantics tree by
|
||||
/// calling the [listener] callback. When the client no longer needs the
|
||||
/// semantics tree, the client can call [dispose] on the [SemanticsHandle],
|
||||
/// which stops these callbacks and closes the [SemanticsHandle]. When all the
|
||||
/// outstanding [SemanticsHandle] objects are closed, the framework stops
|
||||
/// updating the semantics tree.
|
||||
///
|
||||
/// To obtain a [SemanticsHandle], call [PipelineOwner.ensureSemantics] on the
|
||||
/// [PipelineOwner] for the render tree from which you wish to read semantics.
|
||||
/// You can obtain the [PipelineOwner] using the [RenderObject.owner] property.
|
||||
class SemanticsHandle {
|
||||
SemanticsHandle._(PipelineOwner owner, this.listener)
|
||||
class _LocalSemanticsHandle implements SemanticsHandle {
|
||||
_LocalSemanticsHandle._(PipelineOwner owner, this.listener)
|
||||
: _owner = owner {
|
||||
if (listener != null) {
|
||||
_owner.semanticsOwner!.addListener(listener!);
|
||||
@@ -837,13 +821,7 @@ class SemanticsHandle {
|
||||
/// The callback that will be notified when the semantics tree updates.
|
||||
final VoidCallback? listener;
|
||||
|
||||
/// Closes the semantics handle and stops calling [listener] when the
|
||||
/// semantics updates.
|
||||
///
|
||||
/// When all the outstanding [SemanticsHandle] objects for a given
|
||||
/// [PipelineOwner] are closed, the [PipelineOwner] will stop updating the
|
||||
/// semantics tree.
|
||||
@mustCallSuper
|
||||
@override
|
||||
void dispose() {
|
||||
if (listener != null) {
|
||||
_owner.semanticsOwner!.removeListener(listener!);
|
||||
@@ -1171,7 +1149,12 @@ class PipelineOwner {
|
||||
int _outstandingSemanticsHandles = 0;
|
||||
|
||||
/// Opens a [SemanticsHandle] and calls [listener] whenever the semantics tree
|
||||
/// updates.
|
||||
/// generated from the render tree owned by this [PipelineOwner] updates.
|
||||
///
|
||||
/// Calling this method only ensures that this particular [PipelineOwner] will
|
||||
/// generate a semantics tree. Consider calling
|
||||
/// [SemanticsBinding.ensureSemantics] instead to turn on semantics globally
|
||||
/// for the entire app.
|
||||
///
|
||||
/// The [PipelineOwner] updates the semantics tree only when there are clients
|
||||
/// that wish to use the semantics tree. These clients express their interest
|
||||
@@ -1190,7 +1173,7 @@ class PipelineOwner {
|
||||
_semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!);
|
||||
onSemanticsOwnerCreated?.call();
|
||||
}
|
||||
return SemanticsHandle._(this, listener);
|
||||
return _LocalSemanticsHandle._(this, listener);
|
||||
}
|
||||
|
||||
void _didDisposeSemanticsHandle() {
|
||||
|
||||
@@ -2,22 +2,27 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' as ui show AccessibilityFeatures, SemanticsUpdateBuilder;
|
||||
import 'dart:ui' as ui show AccessibilityFeatures, SemanticsAction, SemanticsUpdateBuilder;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'debug.dart';
|
||||
|
||||
export 'dart:ui' show AccessibilityFeatures, SemanticsUpdateBuilder;
|
||||
|
||||
/// The glue between the semantics layer and the Flutter engine.
|
||||
// TODO(zanderso): move the remaining semantic related bindings here.
|
||||
mixin SemanticsBinding on BindingBase {
|
||||
@override
|
||||
void initInstances() {
|
||||
super.initInstances();
|
||||
_instance = this;
|
||||
_accessibilityFeatures = platformDispatcher.accessibilityFeatures;
|
||||
platformDispatcher
|
||||
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
|
||||
..onSemanticsAction = _handleSemanticsAction
|
||||
..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
|
||||
_handleSemanticsEnabledChanged();
|
||||
}
|
||||
|
||||
/// The current [SemanticsBinding], if one has been created.
|
||||
@@ -28,10 +33,118 @@ mixin SemanticsBinding on BindingBase {
|
||||
static SemanticsBinding get instance => BindingBase.checkInstance(_instance);
|
||||
static SemanticsBinding? _instance;
|
||||
|
||||
/// Whether semantics information must be collected.
|
||||
///
|
||||
/// Returns true if either the platform has requested semantics information
|
||||
/// to be generated or if [ensureSemantics] has been called otherwise.
|
||||
///
|
||||
/// To get notified when this value changes register a listener with
|
||||
/// [addSemanticsEnabledListener].
|
||||
bool get semanticsEnabled {
|
||||
assert(_semanticsEnabled.value == (_outstandingHandles > 0));
|
||||
return _semanticsEnabled.value;
|
||||
}
|
||||
late final ValueNotifier<bool> _semanticsEnabled = ValueNotifier<bool>(platformDispatcher.semanticsEnabled);
|
||||
|
||||
/// Adds a `listener` to be called when [semanticsEnabled] changes.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [removeSemanticsEnabledListener] to remove the listener again.
|
||||
/// * [ValueNotifier.addListener], which documents how and when listeners are
|
||||
/// called.
|
||||
void addSemanticsEnabledListener(VoidCallback listener) {
|
||||
_semanticsEnabled.addListener(listener);
|
||||
}
|
||||
|
||||
/// Removes a `listener` added by [addSemanticsEnabledListener].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ValueNotifier.removeListener], which documents how listeners are
|
||||
/// removed.
|
||||
void removeSemanticsEnabledListener(VoidCallback listener) {
|
||||
_semanticsEnabled.removeListener(listener);
|
||||
}
|
||||
|
||||
int _outstandingHandles = 0;
|
||||
|
||||
/// Creates a new [SemanticsHandle] and requests the collection of semantics
|
||||
/// information.
|
||||
///
|
||||
/// Semantics information are only collected when there are clients interested
|
||||
/// in them. These clients express their interest by holding a
|
||||
/// [SemanticsHandle].
|
||||
///
|
||||
/// Clients can close their [SemanticsHandle] by calling
|
||||
/// [SemanticsHandle.dispose]. Once all outstanding [SemanticsHandle] objects
|
||||
/// are closed, semantics information are no longer collected.
|
||||
SemanticsHandle ensureSemantics() {
|
||||
assert(_outstandingHandles >= 0);
|
||||
_outstandingHandles++;
|
||||
assert(_outstandingHandles > 0);
|
||||
_semanticsEnabled.value = true;
|
||||
return SemanticsHandle._(_didDisposeSemanticsHandle);
|
||||
}
|
||||
|
||||
void _didDisposeSemanticsHandle() {
|
||||
assert(_outstandingHandles > 0);
|
||||
_outstandingHandles--;
|
||||
assert(_outstandingHandles >= 0);
|
||||
_semanticsEnabled.value = _outstandingHandles > 0;
|
||||
}
|
||||
|
||||
// Handle for semantics request from the platform.
|
||||
SemanticsHandle? _semanticsHandle;
|
||||
|
||||
void _handleSemanticsEnabledChanged() {
|
||||
if (platformDispatcher.semanticsEnabled) {
|
||||
_semanticsHandle ??= ensureSemantics();
|
||||
} else {
|
||||
_semanticsHandle?.dispose();
|
||||
_semanticsHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSemanticsAction(int id, ui.SemanticsAction action, ByteData? args) {
|
||||
performSemanticsAction(SemanticsActionEvent(
|
||||
nodeId: id,
|
||||
type: action,
|
||||
arguments: args != null ? const StandardMessageCodec().decodeMessage(args) : null,
|
||||
));
|
||||
}
|
||||
|
||||
/// Called whenever the platform requests an action to be performed on a
|
||||
/// [SemanticsNode].
|
||||
///
|
||||
/// This callback is invoked when a user interacts with the app via an
|
||||
/// accessibility service (e.g. TalkBack and VoiceOver) and initiates an
|
||||
/// action on the focused node.
|
||||
///
|
||||
/// Bindings that mixin the [SemanticsBinding] must implement this method and
|
||||
/// perform the given `action` on the [SemanticsNode] specified by
|
||||
/// [SemanticsActionEvent.nodeId].
|
||||
///
|
||||
/// See [dart:ui.PlatformDispatcher.onSemanticsAction].
|
||||
@protected
|
||||
void performSemanticsAction(SemanticsActionEvent action);
|
||||
|
||||
/// The currently active set of [AccessibilityFeatures].
|
||||
///
|
||||
/// This is set when the binding is first initialized and updated whenever a
|
||||
/// flag is changed.
|
||||
///
|
||||
/// To listen to changes to accessibility features, create a
|
||||
/// [WidgetsBindingObserver] and listen to
|
||||
/// [WidgetsBindingObserver.didChangeAccessibilityFeatures].
|
||||
ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
|
||||
late ui.AccessibilityFeatures _accessibilityFeatures;
|
||||
|
||||
/// Called when the platform accessibility features change.
|
||||
///
|
||||
/// See [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void handleAccessibilityFeaturesChanged() {
|
||||
_accessibilityFeatures = platformDispatcher.accessibilityFeatures;
|
||||
}
|
||||
@@ -46,17 +159,6 @@ mixin SemanticsBinding on BindingBase {
|
||||
return ui.SemanticsUpdateBuilder();
|
||||
}
|
||||
|
||||
/// The currently active set of [AccessibilityFeatures].
|
||||
///
|
||||
/// This is initialized the first time [runApp] is called and updated whenever
|
||||
/// a flag is changed.
|
||||
///
|
||||
/// To listen to changes to accessibility features, create a
|
||||
/// [WidgetsBindingObserver] and listen to
|
||||
/// [WidgetsBindingObserver.didChangeAccessibilityFeatures].
|
||||
ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
|
||||
late ui.AccessibilityFeatures _accessibilityFeatures;
|
||||
|
||||
/// The platform is requesting that animations be disabled or simplified.
|
||||
///
|
||||
/// This setting can be overridden for testing or debugging by setting
|
||||
@@ -72,3 +174,49 @@ mixin SemanticsBinding on BindingBase {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// An event to request a [SemanticsAction] of [type] to be performed on the
|
||||
/// [SemanticsNode] identified by [nodeId].
|
||||
///
|
||||
/// Used by [SemanticsBinding.performSemanticsAction].
|
||||
@immutable
|
||||
class SemanticsActionEvent {
|
||||
/// Creates a [SemanticsActionEvent].
|
||||
///
|
||||
/// The [type] and [nodeId] are required.
|
||||
const SemanticsActionEvent({required this.type, required this.nodeId, this.arguments});
|
||||
|
||||
/// The type of action to be performed.
|
||||
final ui.SemanticsAction type;
|
||||
|
||||
/// The id of the [SemanticsNode] on which the action is to be performed.
|
||||
final int nodeId;
|
||||
|
||||
/// Optional arguments for the action.
|
||||
final Object? arguments;
|
||||
}
|
||||
|
||||
/// A reference to the semantics information generated by the framework.
|
||||
///
|
||||
/// Semantics information are only collected when there are clients interested
|
||||
/// in them. These clients express their interest by holding a
|
||||
/// [SemanticsHandle]. When the client no longer needs the
|
||||
/// semantics information, it must call [dispose] on the [SemanticsHandle] to
|
||||
/// close it. When all open [SemanticsHandle]s are disposed, the framework will
|
||||
/// stop updating the semantics information.
|
||||
///
|
||||
/// To obtain a [SemanticsHandle], call [SemanticsBinding.ensureSemantics].
|
||||
class SemanticsHandle {
|
||||
SemanticsHandle._(this._onDispose);
|
||||
|
||||
final VoidCallback _onDispose;
|
||||
|
||||
/// Closes the semantics handle.
|
||||
///
|
||||
/// When all the outstanding [SemanticsHandle] objects are closed, the
|
||||
/// framework will stop generating semantics information.
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
_onDispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3129,9 +3129,9 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> {
|
||||
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
|
||||
/// render tree semantics.
|
||||
///
|
||||
/// To listen for semantic updates, call [PipelineOwner.ensureSemantics] to
|
||||
/// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if
|
||||
/// necessary.
|
||||
/// To listen for semantic updates, call [SemanticsBinding.ensureSemantics] or
|
||||
/// [PipelineOwner.ensureSemantics] to obtain a [SemanticsHandle]. This will
|
||||
/// create a [SemanticsOwner] if necessary.
|
||||
class SemanticsOwner extends ChangeNotifier {
|
||||
/// Creates a [SemanticsOwner] that manages zero or more [SemanticsNode] objects.
|
||||
SemanticsOwner({
|
||||
|
||||
@@ -260,7 +260,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
_buildOwner = BuildOwner();
|
||||
buildOwner!.onBuildScheduled = _handleBuildScheduled;
|
||||
platformDispatcher.onLocaleChanged = handleLocaleChanged;
|
||||
platformDispatcher.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
|
||||
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
|
||||
assert(() {
|
||||
FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
|
||||
|
||||
84
packages/flutter/test/semantics/semantics_binding_test.dart
Normal file
84
packages/flutter/test/semantics/semantics_binding_test.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
// 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/semantics.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Listeners are called when semantics are turned on with ensureSemantics', (WidgetTester tester) async {
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
|
||||
final List<bool> status = <bool>[];
|
||||
void listener() {
|
||||
status.add(SemanticsBinding.instance.semanticsEnabled);
|
||||
}
|
||||
|
||||
SemanticsBinding.instance.addSemanticsEnabledListener(listener);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
|
||||
final SemanticsHandle handle1 = SemanticsBinding.instance.ensureSemantics();
|
||||
expect(status.single, isTrue);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
status.clear();
|
||||
|
||||
final SemanticsHandle handle2 = SemanticsBinding.instance.ensureSemantics();
|
||||
expect(status, isEmpty); // Listener didn't fire again.
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
|
||||
expect(tester.binding.platformDispatcher.semanticsEnabled, isFalse);
|
||||
tester.binding.platformDispatcher.semanticsEnabledTestValue = true;
|
||||
expect(tester.binding.platformDispatcher.semanticsEnabled, isTrue);
|
||||
tester.binding.platformDispatcher.clearSemanticsEnabledTestValue();
|
||||
expect(tester.binding.platformDispatcher.semanticsEnabled, isFalse);
|
||||
expect(status, isEmpty); // Listener didn't fire again.
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
|
||||
handle1.dispose();
|
||||
expect(status, isEmpty); // Listener didn't fire.
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
|
||||
handle2.dispose();
|
||||
expect(status.single, isFalse);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
testWidgets('Listeners are called when semantics are turned on by platform', (WidgetTester tester) async {
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
|
||||
final List<bool> status = <bool>[];
|
||||
void listener() {
|
||||
status.add(SemanticsBinding.instance.semanticsEnabled);
|
||||
}
|
||||
|
||||
SemanticsBinding.instance.addSemanticsEnabledListener(listener);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
|
||||
tester.binding.platformDispatcher.semanticsEnabledTestValue = true;
|
||||
expect(status.single, isTrue);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
status.clear();
|
||||
|
||||
final SemanticsHandle handle = SemanticsBinding.instance.ensureSemantics();
|
||||
handle.dispose();
|
||||
expect(status, isEmpty); // Listener didn't fire.
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
|
||||
tester.binding.platformDispatcher.clearSemanticsEnabledTestValue();
|
||||
expect(status.single, isFalse);
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
testWidgets('SemanticsBinding.ensureSemantics triggers creation of semantics owner.', (WidgetTester tester) async {
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
|
||||
|
||||
final SemanticsHandle handle = SemanticsBinding.instance.ensureSemantics();
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isTrue);
|
||||
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
|
||||
|
||||
handle.dispose();
|
||||
expect(SemanticsBinding.instance.semanticsEnabled, isFalse);
|
||||
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
|
||||
}, semanticsEnabled: false);
|
||||
}
|
||||
@@ -18,7 +18,9 @@ void main() {
|
||||
|
||||
// Enables the semantics should not schedule any frames if the root widget
|
||||
// has not been attached.
|
||||
binding.setSemanticsEnabled(true);
|
||||
expect(binding.semanticsEnabled, isFalse);
|
||||
binding.ensureSemantics();
|
||||
expect(binding.semanticsEnabled, isTrue);
|
||||
expect(SchedulerBinding.instance.framesEnabled, isFalse);
|
||||
expect(SchedulerBinding.instance.hasScheduledFrame, isFalse);
|
||||
|
||||
|
||||
@@ -443,13 +443,13 @@ mixin CommandHandlerFactory {
|
||||
}
|
||||
|
||||
SemanticsHandle? _semantics;
|
||||
bool get _semanticsIsEnabled => RendererBinding.instance.pipelineOwner.semanticsOwner != null;
|
||||
bool get _semanticsIsEnabled => SemanticsBinding.instance.semanticsEnabled;
|
||||
|
||||
Future<SetSemanticsResult> _setSemantics(Command command) async {
|
||||
final SetSemantics setSemanticsCommand = command as SetSemantics;
|
||||
final bool semanticsWasEnabled = _semanticsIsEnabled;
|
||||
if (setSemanticsCommand.enabled && _semantics == null) {
|
||||
_semantics = RendererBinding.instance.pipelineOwner.ensureSemantics();
|
||||
_semantics = SemanticsBinding.instance.ensureSemantics();
|
||||
if (!semanticsWasEnabled) {
|
||||
// wait for the first frame where semantics is enabled.
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
@@ -72,7 +72,7 @@ class SemanticsController {
|
||||
/// if no semantics are found or are not enabled.
|
||||
SemanticsNode find(Finder finder) {
|
||||
TestAsyncUtils.guardSync();
|
||||
if (_binding.pipelineOwner.semanticsOwner == null) {
|
||||
if (!_binding.semanticsEnabled) {
|
||||
throw StateError('Semantics are not enabled.');
|
||||
}
|
||||
final Iterable<Element> candidates = finder.evaluate();
|
||||
@@ -241,7 +241,7 @@ abstract class WidgetController {
|
||||
/// use of the [Semantics] tree to determine the meaning of an application.
|
||||
/// If semantics has been disabled for the test, this will throw a [StateError].
|
||||
SemanticsController get semantics {
|
||||
if (binding.pipelineOwner.semanticsOwner == null) {
|
||||
if (!binding.semanticsEnabled) {
|
||||
throw StateError(
|
||||
'Semantics are not enabled. Enable them by passing '
|
||||
'`semanticsEnabled: true` to `testWidgets`, or by manually creating a '
|
||||
@@ -1491,7 +1491,7 @@ abstract class WidgetController {
|
||||
///
|
||||
/// The handle must be disposed at the end of the test.
|
||||
SemanticsHandle ensureSemantics() {
|
||||
return binding.pipelineOwner.ensureSemantics();
|
||||
return binding.ensureSemantics();
|
||||
}
|
||||
|
||||
/// Given a widget `W` specified by [finder] and a [Scrollable] widget `S` in
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' show Tooltip;
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'all_elements.dart';
|
||||
@@ -435,7 +435,7 @@ class CommonFinders {
|
||||
/// If the `skipOffstage` argument is true (the default), then this skips
|
||||
/// nodes that are [Offstage] or that are from inactive [Route]s.
|
||||
Finder bySemanticsLabel(Pattern label, { bool skipOffstage = true }) {
|
||||
if (WidgetsBinding.instance.pipelineOwner.semanticsOwner == null) {
|
||||
if (!SemanticsBinding.instance.semanticsEnabled) {
|
||||
throw StateError('Semantics are not enabled. '
|
||||
'Make sure to call tester.ensureSemantics() before using '
|
||||
'this finder, and call dispose on its return value after.');
|
||||
|
||||
Reference in New Issue
Block a user