wait_conditions.dart 7.62 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
  const _InternalNoTransientCallbacksCondition();

  /// Factory constructor to parse an [InternalNoTransientCallbacksCondition]
  /// instance from the given [SerializableWaitCondition] instance.
  factory _InternalNoTransientCallbacksCondition.deserialize(SerializableWaitCondition condition) {
43
    if (condition.conditionName != 'NoTransientCallbacksCondition') {
44
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
45
    }
46 47 48 49
    return const _InternalNoTransientCallbacksCondition();
  }

  @override
50
  bool get condition => SchedulerBinding.instance.transientCallbackCount == 0;
51 52 53 54

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

/// A condition that waits until no pending frame is scheduled.
class _InternalNoPendingFrameCondition implements WaitCondition {
63
  /// Creates an [_InternalNoPendingFrameCondition] instance.
64 65 66 67 68
  const _InternalNoPendingFrameCondition();

  /// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
  /// from the given [SerializableWaitCondition] instance.
  factory _InternalNoPendingFrameCondition.deserialize(SerializableWaitCondition condition) {
69
    if (condition.conditionName != 'NoPendingFrameCondition') {
70
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
71
    }
72 73 74 75
    return const _InternalNoPendingFrameCondition();
  }

  @override
76
  bool get condition => !SchedulerBinding.instance.hasScheduledFrame;
77 78 79 80

  @override
  Future<void> wait() async {
    while (!condition) {
81
      await SchedulerBinding.instance.endOfFrame;
82 83 84 85 86 87 88
    }
    assert(condition);
  }
}

/// A condition that waits until the Flutter engine has rasterized the first frame.
class _InternalFirstFrameRasterizedCondition implements WaitCondition {
89
  /// Creates an [_InternalFirstFrameRasterizedCondition] instance.
90 91 92 93 94
  const _InternalFirstFrameRasterizedCondition();

  /// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
  /// from the given [SerializableWaitCondition] instance.
  factory _InternalFirstFrameRasterizedCondition.deserialize(SerializableWaitCondition condition) {
95
    if (condition.conditionName != 'FirstFrameRasterizedCondition') {
96
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
97
    }
98 99 100 101
    return const _InternalFirstFrameRasterizedCondition();
  }

  @override
102
  bool get condition => WidgetsBinding.instance.firstFrameRasterized;
103 104 105

  @override
  Future<void> wait() async {
106
    await WidgetsBinding.instance.waitUntilFirstFrameRasterized;
107 108 109 110
    assert(condition);
  }
}

111 112 113 114 115 116 117 118
/// 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.
  factory _InternalNoPendingPlatformMessagesCondition.deserialize(SerializableWaitCondition condition) {
119
    if (condition.conditionName != 'NoPendingPlatformMessagesCondition') {
120
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
121
    }
122 123 124 125 126
    return const _InternalNoPendingPlatformMessagesCondition();
  }

  @override
  bool get condition {
127
    final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger as TestDefaultBinaryMessenger;
128 129 130 131 132
    return binaryMessenger.pendingMessageCount == 0;
  }

  @override
  Future<void> wait() async {
133
    final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger as TestDefaultBinaryMessenger;
134 135 136 137 138 139 140
    while (!condition) {
      await binaryMessenger.platformMessagesFinished;
    }
    assert(condition);
  }
}

141 142
/// A combined condition that waits until all the given [conditions] are met.
class _InternalCombinedCondition implements WaitCondition {
143
  /// Creates an [_InternalCombinedCondition] instance with the given list of
144
  /// [conditions].
145
  const _InternalCombinedCondition(this.conditions);
146

147
  /// Factory constructor to parse an [_InternalCombinedCondition] instance from
148 149
  /// the given [SerializableWaitCondition] instance.
  factory _InternalCombinedCondition.deserialize(SerializableWaitCondition condition) {
150
    if (condition.conditionName != 'CombinedCondition') {
151
      throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
152
    }
153
    final CombinedCondition combinedCondition = condition as CombinedCondition;
154
    final List<WaitCondition> conditions = combinedCondition.conditions.map(deserializeCondition).toList();
155 156 157 158 159 160 161 162 163 164 165 166 167 168
    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) {
169
      for (final WaitCondition condition in conditions) {
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
        await condition.wait();
      }
    }
    assert(condition);
  }
}

/// Parses a [WaitCondition] or its subclass from the given serializable [waitCondition].
WaitCondition deserializeCondition(SerializableWaitCondition waitCondition) {
  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);
187 188
    case 'NoPendingPlatformMessagesCondition':
      return _InternalNoPendingPlatformMessagesCondition.deserialize(waitCondition);
189 190 191 192 193 194
    case 'CombinedCondition':
      return _InternalCombinedCondition.deserialize(waitCondition);
  }
  throw SerializationException(
      'Unsupported wait condition $conditionName in ${waitCondition.serialize()}');
}