wait_conditions.dart 8.25 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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';
6
import 'package:flutter/services.dart';
7
import 'package:flutter/widgets.dart';
8
import 'package:flutter_test/flutter_test.dart';
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

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 {
37
  /// Creates an [_InternalNoTransientCallbacksCondition] instance.
38 39 40 41 42 43 44 45
  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);
46
    if (condition.conditionName != 'NoTransientCallbacksCondition') {
47
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
48
    }
49 50 51 52
    return const _InternalNoTransientCallbacksCondition();
  }

  @override
53
  bool get condition => SchedulerBinding.instance.transientCallbackCount == 0;
54 55 56 57

  @override
  Future<void> wait() async {
    while (!condition) {
58
      await SchedulerBinding.instance.endOfFrame;
59 60 61 62 63 64 65
    }
    assert(condition);
  }
}

/// A condition that waits until no pending frame is scheduled.
class _InternalNoPendingFrameCondition implements WaitCondition {
66
  /// Creates an [_InternalNoPendingFrameCondition] instance.
67 68 69 70 71 72 73 74
  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);
75
    if (condition.conditionName != 'NoPendingFrameCondition') {
76
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
77
    }
78 79 80 81
    return const _InternalNoPendingFrameCondition();
  }

  @override
82
  bool get condition => !SchedulerBinding.instance.hasScheduledFrame;
83 84 85 86

  @override
  Future<void> wait() async {
    while (!condition) {
87
      await SchedulerBinding.instance.endOfFrame;
88 89 90 91 92 93 94
    }
    assert(condition);
  }
}

/// A condition that waits until the Flutter engine has rasterized the first frame.
class _InternalFirstFrameRasterizedCondition implements WaitCondition {
95
  /// Creates an [_InternalFirstFrameRasterizedCondition] instance.
96 97 98 99 100 101 102 103
  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);
104
    if (condition.conditionName != 'FirstFrameRasterizedCondition') {
105
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
106
    }
107 108 109 110
    return const _InternalFirstFrameRasterizedCondition();
  }

  @override
111
  bool get condition => WidgetsBinding.instance.firstFrameRasterized;
112 113 114

  @override
  Future<void> wait() async {
115
    await WidgetsBinding.instance.waitUntilFirstFrameRasterized;
116 117 118 119
    assert(condition);
  }
}

120 121 122 123 124 125 126 127 128 129 130
/// 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);
131
    if (condition.conditionName != 'NoPendingPlatformMessagesCondition') {
132
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
133
    }
134 135 136 137 138
    return const _InternalNoPendingPlatformMessagesCondition();
  }

  @override
  bool get condition {
139
    final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger as TestDefaultBinaryMessenger;
140 141 142 143 144
    return binaryMessenger.pendingMessageCount == 0;
  }

  @override
  Future<void> wait() async {
145
    final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger as TestDefaultBinaryMessenger;
146 147 148 149 150 151 152
    while (!condition) {
      await binaryMessenger.platformMessagesFinished;
    }
    assert(condition);
  }
}

153 154
/// A combined condition that waits until all the given [conditions] are met.
class _InternalCombinedCondition implements WaitCondition {
155
  /// Creates an [_InternalCombinedCondition] instance with the given list of
156 157 158 159 160 161
  /// [conditions].
  ///
  /// The [conditions] argument must not be null.
  const _InternalCombinedCondition(this.conditions)
      : assert(conditions != null);

162
  /// Factory constructor to parse an [_InternalCombinedCondition] instance from
163 164 165 166 167
  /// the given [SerializableWaitCondition] instance.
  ///
  /// The [condition] argument must not be null.
  factory _InternalCombinedCondition.deserialize(SerializableWaitCondition condition) {
    assert(condition != null);
168
    if (condition.conditionName != 'CombinedCondition') {
169
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
170
    }
171
    final CombinedCondition combinedCondition = condition as CombinedCondition;
172
    final List<WaitCondition> conditions = combinedCondition.conditions.map(deserializeCondition).toList();
173 174 175 176 177 178 179 180 181 182 183 184 185 186
    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) {
187
      for (final WaitCondition condition in conditions) {
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
        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);
209 210
    case 'NoPendingPlatformMessagesCondition':
      return _InternalNoPendingPlatformMessagesCondition.deserialize(waitCondition);
211 212 213 214 215 216
    case 'CombinedCondition':
      return _InternalCombinedCondition.deserialize(waitCondition);
  }
  throw SerializationException(
      'Unsupported wait condition $conditionName in ${waitCondition.serialize()}');
}