wait_conditions.dart 8.42 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
  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 {
65
  /// Creates an [_InternalNoPendingFrameCondition] instance.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
  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 {
93
  /// Creates an [_InternalFirstFrameRasterizedCondition] instance.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  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);
  }
}

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
/// 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 {
135
    final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger as TestDefaultBinaryMessenger;
136 137 138 139 140
    return binaryMessenger.pendingMessageCount == 0;
  }

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

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

158
  /// Factory constructor to parse an [_InternalCombinedCondition] instance from
159 160 161 162 163 164 165
  /// 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()}');
166
    final CombinedCondition combinedCondition = condition as CombinedCondition;
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    if (combinedCondition.conditions == null) {
      return const _InternalCombinedCondition(<WaitCondition>[]);
    }

    final List<WaitCondition> conditions = combinedCondition.conditions.map(
        (SerializableWaitCondition serializableCondition) => deserializeCondition(serializableCondition)
      ).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) {
188
      for (final WaitCondition condition in conditions) {
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        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);
210 211
    case 'NoPendingPlatformMessagesCondition':
      return _InternalNoPendingPlatformMessagesCondition.deserialize(waitCondition);
212 213 214 215 216 217
    case 'CombinedCondition':
      return _InternalCombinedCondition.deserialize(waitCondition);
  }
  throw SerializationException(
      'Unsupported wait condition $conditionName in ${waitCondition.serialize()}');
}