Unverified Commit b1b7284a authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Move semantic-related bindings to SemanticsBinding (#121289)

Move semantic-related bindings to SemanticsBinding
parent cb67ecd9
......@@ -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);
......
// 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.');
......
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