blob: c5c6e69029b9023211663c75ebbfa5e3acf596b3 [file] [log] [blame]
// 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/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../common/wait.dart';
/// Base class for a condition that can be waited upon.
///
/// This class defines the wait logic and runs on device, while
/// [SerializableWaitCondition] takes care of the serialization between the
/// driver script running on the host and the extension running on device.
///
/// If you subclass this, you might also want to implement a [SerializableWaitCondition]
/// that takes care of serialization.
abstract class WaitCondition {
/// Gets the current status of the [condition], executed in the context of the
/// Flutter app:
///
/// * True, if the condition is satisfied.
/// * False otherwise.
///
/// The future returned by [wait] will complete when this [condition] is
/// fulfilled.
bool get condition;
/// Returns a future that completes when [condition] turns true.
Future<void> wait();
}
/// A condition that waits until no transient callbacks are scheduled.
class _InternalNoTransientCallbacksCondition implements WaitCondition {
/// Creates an [_InternalNoTransientCallbacksCondition] instance.
const _InternalNoTransientCallbacksCondition();
/// Factory constructor to parse an [InternalNoTransientCallbacksCondition]
/// instance from the given [SerializableWaitCondition] instance.
///
/// The [condition] argument must not be null.
factory _InternalNoTransientCallbacksCondition.deserialize(SerializableWaitCondition condition) {
assert(condition != null);
if (condition.conditionName != 'NoTransientCallbacksCondition')
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
return const _InternalNoTransientCallbacksCondition();
}
@override
bool get condition => SchedulerBinding.instance!.transientCallbackCount == 0;
@override
Future<void> wait() async {
while (!condition) {
await SchedulerBinding.instance!.endOfFrame;
}
assert(condition);
}
}
/// A condition that waits until no pending frame is scheduled.
class _InternalNoPendingFrameCondition implements WaitCondition {
/// Creates an [_InternalNoPendingFrameCondition] instance.
const _InternalNoPendingFrameCondition();
/// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
/// from the given [SerializableWaitCondition] instance.
///
/// The [condition] argument must not be null.
factory _InternalNoPendingFrameCondition.deserialize(SerializableWaitCondition condition) {
assert(condition != null);
if (condition.conditionName != 'NoPendingFrameCondition')
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
return const _InternalNoPendingFrameCondition();
}
@override
bool get condition => !SchedulerBinding.instance!.hasScheduledFrame;
@override
Future<void> wait() async {
while (!condition) {
await SchedulerBinding.instance!.endOfFrame;
}
assert(condition);
}
}
/// A condition that waits until the Flutter engine has rasterized the first frame.
class _InternalFirstFrameRasterizedCondition implements WaitCondition {
/// Creates an [_InternalFirstFrameRasterizedCondition] instance.
const _InternalFirstFrameRasterizedCondition();
/// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
/// from the given [SerializableWaitCondition] instance.
///
/// The [condition] argument must not be null.
factory _InternalFirstFrameRasterizedCondition.deserialize(SerializableWaitCondition condition) {
assert(condition != null);
if (condition.conditionName != 'FirstFrameRasterizedCondition')
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
return const _InternalFirstFrameRasterizedCondition();
}
@override
bool get condition => WidgetsBinding.instance!.firstFrameRasterized;
@override
Future<void> wait() async {
await WidgetsBinding.instance!.waitUntilFirstFrameRasterized;
assert(condition);
}
}
/// A condition that waits until no pending platform messages.
class _InternalNoPendingPlatformMessagesCondition implements WaitCondition {
/// Creates an [_InternalNoPendingPlatformMessagesCondition] instance.
const _InternalNoPendingPlatformMessagesCondition();
/// Factory constructor to parse an [_InternalNoPendingPlatformMessagesCondition] instance
/// from the given [SerializableWaitCondition] instance.
///
/// The [condition] argument must not be null.
factory _InternalNoPendingPlatformMessagesCondition.deserialize(SerializableWaitCondition condition) {
assert(condition != null);
if (condition.conditionName != 'NoPendingPlatformMessagesCondition')
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
return const _InternalNoPendingPlatformMessagesCondition();
}
@override
bool get condition {
final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger as TestDefaultBinaryMessenger;
return binaryMessenger.pendingMessageCount == 0;
}
@override
Future<void> wait() async {
final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger as TestDefaultBinaryMessenger;
while (!condition) {
await binaryMessenger.platformMessagesFinished;
}
assert(condition);
}
}
/// A combined condition that waits until all the given [conditions] are met.
class _InternalCombinedCondition implements WaitCondition {
/// Creates an [_InternalCombinedCondition] instance with the given list of
/// [conditions].
///
/// The [conditions] argument must not be null.
const _InternalCombinedCondition(this.conditions)
: assert(conditions != null);
/// Factory constructor to parse an [_InternalCombinedCondition] instance from
/// the given [SerializableWaitCondition] instance.
///
/// The [condition] argument must not be null.
factory _InternalCombinedCondition.deserialize(SerializableWaitCondition condition) {
assert(condition != null);
if (condition.conditionName != 'CombinedCondition')
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
final CombinedCondition combinedCondition = condition as CombinedCondition;
final List<WaitCondition> conditions = combinedCondition.conditions.map(deserializeCondition).toList();
return _InternalCombinedCondition(conditions);
}
/// A list of conditions it waits for.
final List<WaitCondition> conditions;
@override
bool get condition {
return conditions.every((WaitCondition condition) => condition.condition);
}
@override
Future<void> wait() async {
while (!condition) {
for (final WaitCondition condition in conditions) {
assert (condition != null);
await condition.wait();
}
}
assert(condition);
}
}
/// Parses a [WaitCondition] or its subclass from the given serializable [waitCondition].
///
/// The [waitCondition] argument must not be null.
WaitCondition deserializeCondition(SerializableWaitCondition waitCondition) {
assert(waitCondition != null);
final String conditionName = waitCondition.conditionName;
switch (conditionName) {
case 'NoTransientCallbacksCondition':
return _InternalNoTransientCallbacksCondition.deserialize(waitCondition);
case 'NoPendingFrameCondition':
return _InternalNoPendingFrameCondition.deserialize(waitCondition);
case 'FirstFrameRasterizedCondition':
return _InternalFirstFrameRasterizedCondition.deserialize(waitCondition);
case 'NoPendingPlatformMessagesCondition':
return _InternalNoPendingPlatformMessagesCondition.deserialize(waitCondition);
case 'CombinedCondition':
return _InternalCombinedCondition.deserialize(waitCondition);
}
throw SerializationException(
'Unsupported wait condition $conditionName in ${waitCondition.serialize()}');
}