binding.dart 45.4 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 'dart:async';
6
import 'dart:collection';
7
import 'dart:developer' show Flow, Timeline;
8
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, TimingsCallback, PlatformDispatcher;
9

10
import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue;
11
import 'package:flutter/foundation.dart';
12

13
import 'debug.dart';
14 15
import 'priority.dart';

16
export 'dart:ui' show AppLifecycleState, VoidCallback, FrameTiming;
17

18
/// Slows down animations by this factor to help in development.
19 20
double get timeDilation => _timeDilation;
double _timeDilation = 1.0;
21 22 23
/// Setting the time dilation automatically calls [SchedulerBinding.resetEpoch]
/// to ensure that time stamps seen by consumers of the scheduler binding are
/// always increasing.
24
set timeDilation(double value) {
25
  assert(value > 0.0);
26 27 28 29 30 31 32
  if (_timeDilation == value)
    return;
  // We need to resetEpoch first so that we capture start of the epoch with the
  // current time dilation.
  SchedulerBinding.instance?.resetEpoch();
  _timeDilation = value;
}
33

34
/// Signature for frame-related callbacks from the scheduler.
35
///
36
/// The `timeStamp` is the number of milliseconds since the beginning of the
37 38 39
/// scheduler's epoch. Use timeStamp to determine how far to advance animation
/// timelines so that all the animations in the system are synchronized to a
/// common time base.
40
typedef FrameCallback = void Function(Duration timeStamp);
41

42
/// Signature for [SchedulerBinding.scheduleTask] callbacks.
43
///
44
/// The type argument `T` is the task's return value. Consider `void` if the
45
/// task does not return a value.
46
typedef TaskCallback<T> = T Function();
47

48
/// Signature for the [SchedulerBinding.schedulingStrategy] callback. Called
49 50 51
/// whenever the system needs to decide whether a task at a given
/// priority needs to be run.
///
52 53
/// Return true if a task with the given priority should be executed at this
/// time, false otherwise.
54
///
55 56 57
/// See also:
///
///  * [defaultSchedulingStrategy], the default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
58
typedef SchedulingStrategy = bool Function({ required int priority, required SchedulerBinding scheduler });
Ian Hickson's avatar
Ian Hickson committed
59

60 61
class _TaskEntry<T> {
  _TaskEntry(this.task, this.priority, this.debugLabel, this.flow) {
62 63 64 65 66
    assert(() {
      debugStack = StackTrace.current;
      return true;
    }());
  }
67
  final TaskCallback<T> task;
68
  final int priority;
69 70
  final String? debugLabel;
  final Flow? flow;
71

72 73
  late StackTrace debugStack;
  final Completer<T> completer = Completer<T>();
74 75

  void run() {
76 77 78 79 80 81
    if (!kReleaseMode) {
      Timeline.timeSync(
        debugLabel ?? 'Scheduled Task',
        () {
          completer.complete(task());
        },
82
        flow: flow != null ? Flow.step(flow!.id) : null,
83 84 85 86
      );
    } else {
      completer.complete(task());
    }
87
  }
88
}
89

90
class _FrameCallbackEntry {
91
  _FrameCallbackEntry(this.callback, { bool rescheduling = false }) {
92 93
    assert(() {
      if (rescheduling) {
94
        assert(() {
95
          if (debugCurrentCallbackStack == null) {
96 97 98 99 100
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('scheduleFrameCallback called with rescheduling true, but no callback is in scope.'),
              ErrorDescription(
                'The "rescheduling" argument should only be set to true if the '
                'callback is being reregistered from within the callback itself, '
101
                'and only then if the callback itself is entirely synchronous.',
102 103 104 105
              ),
              ErrorHint(
                'If this is the initial registration of the callback, or if the '
                'callback is asynchronous, then do not use the "rescheduling" '
106 107
                'argument.',
              ),
108
            ]);
109 110
          }
          return true;
111
        }());
112
        debugStack = debugCurrentCallbackStack;
113
      } else {
114
        // TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
115
        debugStack = StackTrace.current;
116 117
      }
      return true;
118
    }());
119
  }
120

121
  final FrameCallback callback;
122

123 124
  static StackTrace? debugCurrentCallbackStack;
  StackTrace? debugStack;
125 126
}

127 128 129 130 131 132 133 134
/// The various phases that a [SchedulerBinding] goes through during
/// [SchedulerBinding.handleBeginFrame].
///
/// This is exposed by [SchedulerBinding.schedulerPhase].
///
/// The values of this enum are ordered in the same order as the phases occur,
/// so their relative index values can be compared to each other.
///
135 136 137 138
/// See also:
///
///  * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
///    to generate a frame.
139 140
enum SchedulerPhase {
  /// No frame is being processed. Tasks (scheduled by
141
  /// [SchedulerBinding.scheduleTask]), microtasks (scheduled by
142 143 144 145 146 147
  /// [scheduleMicrotask]), [Timer] callbacks, event handlers (e.g. from user
  /// input), and other callbacks (e.g. from [Future]s, [Stream]s, and the like)
  /// may be executing.
  idle,

  /// The transient callbacks (scheduled by
148
  /// [SchedulerBinding.scheduleFrameCallback]) are currently executing.
149
  ///
150 151 152
  /// Typically, these callbacks handle updating objects to new animation
  /// states.
  ///
153
  /// See [SchedulerBinding.handleBeginFrame].
154 155
  transientCallbacks,

156 157 158
  /// Microtasks scheduled during the processing of transient callbacks are
  /// current executing.
  ///
159
  /// This may include, for instance, callbacks from futures resolved during the
160 161 162
  /// [transientCallbacks] phase.
  midFrameMicrotasks,

163
  /// The persistent callbacks (scheduled by
164
  /// [SchedulerBinding.addPersistentFrameCallback]) are currently executing.
165 166
  ///
  /// Typically, this is the build/layout/paint pipeline. See
167
  /// [WidgetsBinding.drawFrame] and [SchedulerBinding.handleDrawFrame].
168 169 170
  persistentCallbacks,

  /// The post-frame callbacks (scheduled by
171
  /// [SchedulerBinding.addPostFrameCallback]) are currently executing.
172 173 174
  ///
  /// Typically, these callbacks handle cleanup and scheduling of work for the
  /// next frame.
175
  ///
176
  /// See [SchedulerBinding.handleDrawFrame].
177 178 179
  postFrameCallbacks,
}

180
/// Scheduler for running the following:
181
///
182 183 184 185
/// * _Transient callbacks_, triggered by the system's
///   [dart:ui.PlatformDispatcher.onBeginFrame] callback, for synchronizing the
///   application's behavior to the system's display. For example, [Ticker]s and
///   [AnimationController]s trigger from these.
186
///
187 188 189 190
/// * _Persistent callbacks_, triggered by the system's
///   [dart:ui.PlatformDispatcher.onDrawFrame] callback, for updating the
///   system's display after transient callbacks have executed. For example, the
///   rendering layer uses this to drive its rendering pipeline.
191 192
///
/// * _Post-frame callbacks_, which are run after persistent callbacks, just
193
///   before returning from the [dart:ui.PlatformDispatcher.onDrawFrame] callback.
194
///
195 196 197
/// * Non-rendering tasks, to be run between frames. These are given a
///   priority and are executed in priority order according to a
///   [schedulingStrategy].
198
mixin SchedulerBinding on BindingBase {
199
  @override
Ian Hickson's avatar
Ian Hickson committed
200 201 202
  void initInstances() {
    super.initInstances();
    _instance = this;
203 204

    if (!kReleaseMode) {
205
      addTimingsCallback((List<FrameTiming> timings) {
206
        timings.forEach(_profileFramePostEvent);
207 208 209 210 211 212
      });
    }
  }

  final List<TimingsCallback> _timingsCallbacks = <TimingsCallback>[];

213 214
  /// Add a [TimingsCallback] that receives [FrameTiming] sent from
  /// the engine.
215
  ///
216 217 218 219 220 221 222
  /// This API enables applications to monitor their graphics
  /// performance. Data from the engine is batched into lists of
  /// [FrameTiming] objects which are reported approximately once a
  /// second in release mode and approximately once every 100ms in
  /// debug and profile builds. The list is sorted in ascending
  /// chronological order (earliest frame first). The timing of the
  /// first frame is sent immediately without batching.
223
  ///
224 225 226 227 228 229 230 231 232 233 234 235 236
  /// The data returned can be used to catch missed frames (by seeing
  /// if [FrameTiming.buildDuration] or [FrameTiming.rasterDuration]
  /// exceed the frame budget, e.g. 16ms at 60Hz), and to catch high
  /// latency (by seeing if [FrameTiming.totalSpan] exceeds the frame
  /// budget). It is possible for no frames to be missed but for the
  /// latency to be more than one frame in the case where the Flutter
  /// engine is pipelining the graphics updates, e.g. because the sum
  /// of the [FrameTiming.buildDuration] and the
  /// [FrameTiming.rasterDuration] together exceed the frame budget.
  /// In those cases, animations will be smooth but touch input will
  /// feel more sluggish.
  ///
  /// Using [addTimingsCallback] is preferred over using
237 238 239 240 241 242 243 244 245 246 247 248 249 250
  /// [dart:ui.PlatformDispatcher.onReportTimings] directly because the
  /// [dart:ui.PlatformDispatcher.onReportTimings] API only allows one callback,
  /// which prevents multiple libraries from registering listeners
  /// simultaneously, while this API allows multiple callbacks to be registered
  /// independently.
  ///
  /// This API is implemented in terms of
  /// [dart:ui.PlatformDispatcher.onReportTimings]. In release builds, when no
  /// libraries have registered with this API, the
  /// [dart:ui.PlatformDispatcher.onReportTimings] callback is not set, which
  /// disables the performance tracking and reduces the runtime overhead to
  /// approximately zero. The performance overhead of the performance tracking
  /// when one or more callbacks are registered (i.e. when it is enabled) is
  /// very approximately 0.01% CPU usage per second (measured on an iPhone 6s).
251 252 253
  ///
  /// In debug and profile builds, the [SchedulerBinding] itself
  /// registers a timings callback to update the [Timeline].
254 255
  ///
  /// If the same callback is added twice, it will be executed twice.
256 257 258 259 260
  ///
  /// See also:
  ///
  ///  * [removeTimingsCallback], which can be used to remove a callback
  ///    added using this method.
261 262 263
  void addTimingsCallback(TimingsCallback callback) {
    _timingsCallbacks.add(callback);
    if (_timingsCallbacks.length == 1) {
264 265
      assert(window.onReportTimings == null);
      window.onReportTimings = _executeTimingsCallbacks;
266
    }
267
    assert(window.onReportTimings == _executeTimingsCallbacks);
268 269 270 271 272 273 274
  }

  /// Removes a callback that was earlier added by [addTimingsCallback].
  void removeTimingsCallback(TimingsCallback callback) {
    assert(_timingsCallbacks.contains(callback));
    _timingsCallbacks.remove(callback);
    if (_timingsCallbacks.isEmpty) {
275
      window.onReportTimings = null;
276 277 278
    }
  }

279
  @pragma('vm:notify-debugger-on-exception')
280 281 282
  void _executeTimingsCallbacks(List<FrameTiming> timings) {
    final List<TimingsCallback> clonedCallbacks =
        List<TimingsCallback>.from(_timingsCallbacks);
283
    for (final TimingsCallback callback in clonedCallbacks) {
284 285 286 287 288
      try {
        if (_timingsCallbacks.contains(callback)) {
          callback(timings);
        }
      } catch (exception, stack) {
289
        InformationCollector? collector;
290 291
        assert(() {
          collector = () sync* {
292 293 294 295 296
            yield DiagnosticsProperty<TimingsCallback>(
              'The TimingsCallback that gets executed was',
              callback,
              style: DiagnosticsTreeStyle.errorProperty,
            );
297 298 299 300 301 302 303
          };
          return true;
        }());
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          context: ErrorDescription('while executing callbacks for FrameTiming'),
304
          informationCollector: collector,
305 306
        ));
      }
307
    }
308
  }
309

310
  /// The current [SchedulerBinding], if one has been created.
311 312
  static SchedulerBinding? get instance => _instance;
  static SchedulerBinding? _instance;
Ian Hickson's avatar
Ian Hickson committed
313

314 315 316
  @override
  void initServiceExtensions() {
    super.initServiceExtensions();
317

318
    if (!kReleaseMode) {
319 320 321 322 323 324 325 326
      registerNumericServiceExtension(
        name: 'timeDilation',
        getter: () async => timeDilation,
        setter: (double value) async {
          timeDilation = value;
        },
      );
    }
327 328
  }

329 330 331 332 333 334 335 336
  /// Whether the application is visible, and if so, whether it is currently
  /// interactive.
  ///
  /// This is set by [handleAppLifecycleStateChanged] when the
  /// [SystemChannels.lifecycle] notification is dispatched.
  ///
  /// The preferred way to watch for changes to this value is using
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
337 338
  AppLifecycleState? get lifecycleState => _lifecycleState;
  AppLifecycleState? _lifecycleState;
339 340 341 342 343 344 345

  /// Called when the application lifecycle state changes.
  ///
  /// Notifies all the observers using
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  ///
  /// This method exposes notifications from [SystemChannels.lifecycle].
346 347
  @protected
  @mustCallSuper
348 349 350 351 352 353 354 355 356
  void handleAppLifecycleStateChanged(AppLifecycleState state) {
    assert(state != null);
    _lifecycleState = state;
    switch (state) {
      case AppLifecycleState.resumed:
      case AppLifecycleState.inactive:
        _setFramesEnabledState(true);
        break;
      case AppLifecycleState.paused:
357
      case AppLifecycleState.detached:
358 359 360 361 362
        _setFramesEnabledState(false);
        break;
    }
  }

363
  /// The strategy to use when deciding whether to run a task or not.
364
  ///
365
  /// Defaults to [defaultSchedulingStrategy].
Ian Hickson's avatar
Ian Hickson committed
366
  SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
367

368
  static int _taskSorter (_TaskEntry<dynamic> e1, _TaskEntry<dynamic> e2) {
Hixie's avatar
Hixie committed
369 370
    return -e1.priority.compareTo(e2.priority);
  }
371
  final PriorityQueue<_TaskEntry<dynamic>> _taskQueue = HeapPriorityQueue<_TaskEntry<dynamic>>(_taskSorter);
372

373 374 375 376 377 378 379
  /// Schedules the given `task` with the given `priority` and returns a
  /// [Future] that completes to the `task`'s eventual return value.
  ///
  /// The `debugLabel` and `flow` are used to report the task to the [Timeline],
  /// for use when profiling.
  ///
  /// ## Processing model
380 381 382 383 384 385
  ///
  /// Tasks will be executed between frames, in priority order,
  /// excluding tasks that are skipped by the current
  /// [schedulingStrategy]. Tasks should be short (as in, up to a
  /// millisecond), so as to not cause the regular frame callbacks to
  /// get delayed.
386 387 388 389 390 391
  ///
  /// If an animation is running, including, for instance, a [ProgressIndicator]
  /// indicating that there are pending tasks, then tasks with a priority below
  /// [Priority.animation] won't run (at least, not with the
  /// [defaultSchedulingStrategy]; this can be configured using
  /// [schedulingStrategy]).
392 393 394
  Future<T> scheduleTask<T>(
    TaskCallback<T> task,
    Priority priority, {
395 396
    String? debugLabel,
    Flow? flow,
397
  }) {
398
    final bool isFirstTask = _taskQueue.isEmpty;
399
    final _TaskEntry<T> entry = _TaskEntry<T>(
400 401 402 403 404 405
      task,
      priority.value,
      debugLabel,
      flow,
    );
    _taskQueue.add(entry);
406 407
    if (isFirstTask && !locked)
      _ensureEventLoopCallback();
408
    return entry.completer.future;
409 410 411 412 413 414
  }

  @override
  void unlocked() {
    super.unlocked();
    if (_taskQueue.isNotEmpty)
415
      _ensureEventLoopCallback();
416 417
  }

418 419 420
  // Whether this scheduler already requested to be called from the event loop.
  bool _hasRequestedAnEventLoopCallback = false;

Dan Field's avatar
Dan Field committed
421 422
  // Ensures that the scheduler services a task scheduled by
  // [SchedulerBinding.scheduleTask].
423
  void _ensureEventLoopCallback() {
424
    assert(!locked);
425
    assert(_taskQueue.isNotEmpty);
426 427 428
    if (_hasRequestedAnEventLoopCallback)
      return;
    _hasRequestedAnEventLoopCallback = true;
429
    Timer.run(_runTasks);
430 431
  }

432 433
  // Scheduled by _ensureEventLoopCallback.
  void _runTasks() {
434
    _hasRequestedAnEventLoopCallback = false;
435 436
    if (handleEventLoopCallback())
      _ensureEventLoopCallback(); // runs next task when there's time
437 438
  }

439 440 441 442 443 444 445 446 447 448 449
  /// Execute the highest-priority task, if it is of a high enough priority.
  ///
  /// Returns true if a task was executed and there are other tasks remaining
  /// (even if they are not high-enough priority).
  ///
  /// Returns false if no task was executed, which can occur if there are no
  /// tasks scheduled, if the scheduler is [locked], or if the highest-priority
  /// task is of too low a priority given the current [schedulingStrategy].
  ///
  /// Also returns false if there are no tasks remaining.
  @visibleForTesting
450
  @pragma('vm:notify-debugger-on-exception')
451
  bool handleEventLoopCallback() {
452
    if (_taskQueue.isEmpty || locked)
453
      return false;
454
    final _TaskEntry<dynamic> entry = _taskQueue.first;
Ian Hickson's avatar
Ian Hickson committed
455
    if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
456
      try {
457 458
        _taskQueue.removeFirst();
        entry.run();
459
      } catch (exception, exceptionStack) {
460
        StackTrace? callbackStack;
461 462 463 464
        assert(() {
          callbackStack = entry.debugStack;
          return true;
        }());
465
        FlutterError.reportError(FlutterErrorDetails(
466 467 468
          exception: exception,
          stack: exceptionStack,
          library: 'scheduler library',
469 470 471 472 473 474 475
          context: ErrorDescription('during a task callback'),
          informationCollector: (callbackStack == null) ? null : () sync* {
            yield DiagnosticsStackTrace(
              '\nThis exception was thrown in the context of a scheduler callback. '
              'When the scheduler callback was _registered_ (as opposed to when the '
              'exception was thrown), this was the stack',
              callbackStack,
476
            );
477
          },
478
        ));
479
      }
480
      return _taskQueue.isNotEmpty;
481
    }
482
    return false;
483 484
  }

485
  int _nextFrameCallbackId = 0; // positive
486
  Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
487
  final Set<int> _removedIds = HashSet<int>();
488

489 490 491
  /// The current number of transient frame callbacks scheduled.
  ///
  /// This is reset to zero just before all the currently scheduled
492
  /// transient callbacks are called, at the start of a frame.
493 494 495 496
  ///
  /// This number is primarily exposed so that tests can verify that
  /// there are no unexpected transient callbacks still registered
  /// after a test's resources have been gracefully disposed.
497 498
  int get transientCallbackCount => _transientCallbacks.length;

499
  /// Schedules the given transient frame callback.
500
  ///
501
  /// Adds the given callback to the list of frame callbacks and ensures that a
502
  /// frame is scheduled.
503
  ///
504 505
  /// If this is a one-off registration, ignore the `rescheduling` argument.
  ///
506 507 508 509 510 511 512 513 514
  /// If this is a callback that will be re-registered each time it fires, then
  /// when you re-register the callback, set the `rescheduling` argument to
  /// true. This has no effect in release builds, but in debug builds, it
  /// ensures that the stack trace that is stored for this callback is the
  /// original stack trace for when the callback was _first_ registered, rather
  /// than the stack trace for when the callback is re-registered. This makes it
  /// easier to track down the original reason that a particular callback was
  /// called. If `rescheduling` is true, the call must be in the context of a
  /// frame callback.
515 516 517
  ///
  /// Callbacks registered with this method can be canceled using
  /// [cancelFrameCallbackWithId].
518
  int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
519
    scheduleFrame();
520
    _nextFrameCallbackId += 1;
521
    _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
522 523 524
    return _nextFrameCallbackId;
  }

525
  /// Cancels the transient frame callback with the given [id].
526 527
  ///
  /// Removes the given callback from the list of frame callbacks. If a frame
528 529
  /// has been requested, this does not also cancel that request.
  ///
530
  /// Transient frame callbacks are those registered using
531
  /// [scheduleFrameCallback].
532
  void cancelFrameCallbackWithId(int id) {
533 534 535 536 537
    assert(id > 0);
    _transientCallbacks.remove(id);
    _removedIds.add(id);
  }

538 539 540
  /// Asserts that there are no registered transient callbacks; if
  /// there are, prints their locations and throws an exception.
  ///
541
  /// A transient frame callback is one that was registered with
542
  /// [scheduleFrameCallback].
543
  ///
544 545 546
  /// This is expected to be called at the end of tests (the
  /// flutter_test framework does it automatically in normal cases).
  ///
547
  /// Call this method when you expect there to be no transient
548 549 550 551
  /// callbacks registered, in an assert statement with a message that
  /// you want printed when a transient callback is registered:
  ///
  /// ```dart
552
  /// assert(SchedulerBinding.instance.debugAssertNoTransientCallbacks(
553 554 555 556 557 558 559 560
  ///   'A leak of transient callbacks was detected while doing foo.'
  /// ));
  /// ```
  ///
  /// Does nothing if asserts are disabled. Always returns true.
  bool debugAssertNoTransientCallbacks(String reason) {
    assert(() {
      if (transientCallbackCount > 0) {
561 562 563 564
        // We cache the values so that we can produce them later
        // even if the information collector is called after
        // the problem has been resolved.
        final int count = transientCallbackCount;
565 566
        final Map<int, _FrameCallbackEntry> callbacks = Map<int, _FrameCallbackEntry>.from(_transientCallbacks);
        FlutterError.reportError(FlutterErrorDetails(
567 568
          exception: reason,
          library: 'scheduler library',
569
          informationCollector: () sync* {
570
            if (count == 1) {
571 572
              // TODO(jacobr): I have added an extra line break in this case.
              yield ErrorDescription(
573
                'There was one transient callback left. '
574
                'The stack trace for when it was registered is as follows:',
575 576
              );
            } else {
577
              yield ErrorDescription(
578
                'There were $count transient callbacks left. '
579
                'The stack traces for when they were registered are as follows:',
580 581
              );
            }
582
            for (final int id in callbacks.keys) {
583
              final _FrameCallbackEntry entry = callbacks[id]!;
584
              yield DiagnosticsStackTrace('── callback $id ──', entry.debugStack, showSeparator: false);
585
            }
586
          },
587 588 589
        ));
      }
      return true;
590
    }());
591 592 593
    return true;
  }

594 595 596
  /// Prints the stack for where the current transient callback was registered.
  ///
  /// A transient frame callback is one that was registered with
597
  /// [scheduleFrameCallback].
598 599 600
  ///
  /// When called in debug more and in the context of a transient callback, this
  /// function prints the stack trace from where the current transient callback
601
  /// was registered (i.e. where it first called [scheduleFrameCallback]).
602 603 604 605 606 607 608 609 610
  ///
  /// When called in debug mode in other contexts, it prints a message saying
  /// that this function was not called in the context a transient callback.
  ///
  /// In release mode, this function does nothing.
  ///
  /// To call this function, use the following code:
  ///
  /// ```dart
611
  /// SchedulerBinding.debugPrintTransientCallbackRegistrationStack();
612 613 614 615 616 617 618
  /// ```
  static void debugPrintTransientCallbackRegistrationStack() {
    assert(() {
      if (_FrameCallbackEntry.debugCurrentCallbackStack != null) {
        debugPrint('When the current transient callback was registered, this was the stack:');
        debugPrint(
          FlutterError.defaultStackFilter(
619
            FlutterError.demangleStackTrace(
620
              _FrameCallbackEntry.debugCurrentCallbackStack!,
621 622
            ).toString().trimRight().split('\n'),
          ).join('\n'),
623 624 625 626 627
        );
      } else {
        debugPrint('No transient callback is currently executing.');
      }
      return true;
628
    }());
629 630
  }

631
  final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
632

633 634
  /// Adds a persistent frame callback.
  ///
635
  /// Persistent callbacks are called after transient
636
  /// (non-persistent) frame callbacks.
637
  ///
638 639 640 641
  /// Does *not* request a new frame. Conceptually, persistent frame
  /// callbacks are observers of "begin frame" events. Since they are
  /// executed after the transient frame callbacks they can drive the
  /// rendering pipeline.
642 643 644
  ///
  /// Persistent frame callbacks cannot be unregistered. Once registered, they
  /// are called for every frame for the lifetime of the application.
645
  void addPersistentFrameCallback(FrameCallback callback) {
646 647 648
    _persistentCallbacks.add(callback);
  }

649
  final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];
650 651 652

  /// Schedule a callback for the end of this frame.
  ///
653
  /// Does *not* request a new frame.
654
  ///
655 656 657 658 659 660
  /// This callback is run during a frame, just after the persistent
  /// frame callbacks (which is when the main rendering pipeline has
  /// been flushed). If a frame is in progress and post-frame
  /// callbacks haven't been executed yet, then the registered
  /// callback is still executed during the frame. Otherwise, the
  /// registered callback is executed during the next frame.
661
  ///
662 663
  /// The callbacks are executed in the order in which they have been
  /// added.
664 665
  ///
  /// Post-frame callbacks cannot be unregistered. They are called exactly once.
666 667 668 669 670
  ///
  /// See also:
  ///
  ///  * [scheduleFrameCallback], which registers a callback for the start of
  ///    the next frame.
671
  void addPostFrameCallback(FrameCallback callback) {
672 673 674
    _postFrameCallbacks.add(callback);
  }

675
  Completer<void>? _nextFrameCompleter;
676 677 678 679 680 681 682 683 684 685

  /// Returns a Future that completes after the frame completes.
  ///
  /// If this is called between frames, a frame is immediately scheduled if
  /// necessary. If this is called during a frame, the Future completes after
  /// the current frame.
  ///
  /// If the device's screen is currently turned off, this may wait a very long
  /// time, since frames are not scheduled while the device's screen is turned
  /// off.
686
  Future<void> get endOfFrame {
687 688 689
    if (_nextFrameCompleter == null) {
      if (schedulerPhase == SchedulerPhase.idle)
        scheduleFrame();
690
      _nextFrameCompleter = Completer<void>();
691
      addPostFrameCallback((Duration timeStamp) {
692
        _nextFrameCompleter!.complete();
693 694 695
        _nextFrameCompleter = null;
      });
    }
696
    return _nextFrameCompleter!.future;
697 698
  }

699
  /// Whether this scheduler has requested that [handleBeginFrame] be called soon.
700
  bool get hasScheduledFrame => _hasScheduledFrame;
701
  bool _hasScheduledFrame = false;
702

703 704 705
  /// The phase that the scheduler is currently operating under.
  SchedulerPhase get schedulerPhase => _schedulerPhase;
  SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
706

707 708 709 710 711 712 713 714 715 716 717 718 719 720
  /// Whether frames are currently being scheduled when [scheduleFrame] is called.
  ///
  /// This value depends on the value of the [lifecycleState].
  bool get framesEnabled => _framesEnabled;

  bool _framesEnabled = true;
  void _setFramesEnabledState(bool enabled) {
    if (_framesEnabled == enabled)
      return;
    _framesEnabled = enabled;
    if (enabled)
      scheduleFrame();
  }

721 722
  /// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and
  /// [PlatformDispatcher.onDrawFrame] are registered.
723 724
  @protected
  void ensureFrameCallbacksRegistered() {
725 726
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
727 728
  }

729 730
  /// Schedules a new frame using [scheduleFrame] if this object is not
  /// currently producing a frame.
731
  ///
732 733 734 735 736 737 738 739 740 741
  /// Calling this method ensures that [handleDrawFrame] will eventually be
  /// called, unless it's already in progress.
  ///
  /// This has no effect if [schedulerPhase] is
  /// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
  /// (because a frame is already being prepared in that case), or
  /// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
  /// rendered in that case). It will schedule a frame if the [schedulerPhase]
  /// is [SchedulerPhase.idle] (in between frames) or
  /// [SchedulerPhase.postFrameCallbacks] (after a frame).
742
  void ensureVisualUpdate() {
743 744 745 746 747 748 749 750 751 752
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
753
  }
754

755
  /// If necessary, schedules a new frame by calling
756
  /// [dart:ui.PlatformDispatcher.scheduleFrame].
757 758
  ///
  /// After this is called, the engine will (eventually) call
759 760 761 762 763 764
  /// [handleBeginFrame]. (This call might be delayed, e.g. if the device's
  /// screen is turned off it will typically be delayed until the screen is on
  /// and the application is visible.) Calling this during a frame forces
  /// another frame to be scheduled, even if the current frame has not yet
  /// completed.
  ///
765 766 767 768 769 770 771 772
  /// Scheduled frames are serviced when triggered by a "Vsync" signal provided
  /// by the operating system. The "Vsync" signal, or vertical synchronization
  /// signal, was historically related to the display refresh, at a time when
  /// hardware physically moved a beam of electrons vertically between updates
  /// of the display. The operation of contemporary hardware is somewhat more
  /// subtle and complicated, but the conceptual "Vsync" refresh signal continue
  /// to be used to indicate when applications should update their rendering.
  ///
773 774
  /// To have a stack trace printed to the console any time this function
  /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
775 776 777 778 779 780 781
  ///
  /// See also:
  ///
  ///  * [scheduleForcedFrame], which ignores the [lifecycleState] when
  ///    scheduling a frame.
  ///  * [scheduleWarmUpFrame], which ignores the "Vsync" signal entirely and
  ///    triggers a frame immediately.
782
  void scheduleFrame() {
783
    if (_hasScheduledFrame || !framesEnabled)
784
      return;
785 786 787 788
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      return true;
789
    }());
790
    ensureFrameCallbacksRegistered();
791
    window.scheduleFrame();
792 793
    _hasScheduledFrame = true;
  }
794

795 796
  /// Schedules a new frame by calling
  /// [dart:ui.PlatformDispatcher.scheduleFrame].
797 798 799 800 801 802 803 804 805 806 807 808 809
  ///
  /// After this is called, the engine will call [handleBeginFrame], even if
  /// frames would normally not be scheduled by [scheduleFrame] (e.g. even if
  /// the device's screen is turned off).
  ///
  /// The framework uses this to force a frame to be rendered at the correct
  /// size when the phone is rotated, so that a correctly-sized rendering is
  /// available when the screen is turned back on.
  ///
  /// To have a stack trace printed to the console any time this function
  /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
  ///
  /// Prefer using [scheduleFrame] unless it is imperative that a frame be
810
  /// scheduled immediately, since using [scheduleForcedFrame] will cause
811 812 813 814 815
  /// significantly higher battery usage when the device should be idle.
  ///
  /// Consider using [scheduleWarmUpFrame] instead if the goal is to update the
  /// rendering as soon as possible (e.g. at application startup).
  void scheduleForcedFrame() {
816 817
    // TODO(chunhtai): Removes the if case once the issue is fixed
    // https://github.com/flutter/flutter/issues/45131
818
    if (!framesEnabled)
819 820
      return;

821 822 823 824 825 826 827
    if (_hasScheduledFrame)
      return;
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleForcedFrame() called. Current phase is $schedulerPhase.');
      return true;
    }());
828
    window.scheduleFrame();
829 830 831 832 833 834 835 836 837 838 839
    _hasScheduledFrame = true;
  }

  bool _warmUpFrame = false;

  /// Schedule a frame to run as soon as possible, rather than waiting for
  /// the engine to request a frame in response to a system "Vsync" signal.
  ///
  /// This is used during application startup so that the first frame (which is
  /// likely to be quite expensive) gets a few extra milliseconds to run.
  ///
840 841
  /// Locks events dispatching until the scheduled frame has completed.
  ///
842 843 844
  /// If a frame has already been scheduled with [scheduleFrame] or
  /// [scheduleForcedFrame], this call may delay that frame.
  ///
845 846 847
  /// If any scheduled frame has already begun or if another
  /// [scheduleWarmUpFrame] was already called, this call will be ignored.
  ///
848 849
  /// Prefer [scheduleFrame] to update the display in normal operation.
  void scheduleWarmUpFrame() {
850 851 852
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

853
    _warmUpFrame = true;
854 855
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });
877 878 879 880

    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
881
      await endOfFrame;
882 883
      Timeline.finishSync();
    });
884 885
  }

886
  Duration? _firstRawTimeStampInEpoch;
887 888
  Duration _epochStart = Duration.zero;
  Duration _lastRawTimeStamp = Duration.zero;
889

890
  /// Prepares the scheduler for a non-monotonic change to how time stamps are
891
  /// calculated.
892 893 894 895 896 897 898 899
  ///
  /// Callbacks received from the scheduler assume that their time stamps are
  /// monotonically increasing. The raw time stamp passed to [handleBeginFrame]
  /// is monotonic, but the scheduler might adjust those time stamps to provide
  /// [timeDilation]. Without careful handling, these adjusts could cause time
  /// to appear to run backwards.
  ///
  /// The [resetEpoch] function ensures that the time stamps are monotonic by
900
  /// resetting the base time stamp used for future time stamp adjustments to the
901 902 903 904
  /// current value. For example, if the [timeDilation] decreases, rather than
  /// scaling down the [Duration] since the beginning of time, [resetEpoch] will
  /// ensure that we only scale down the duration since [resetEpoch] was called.
  ///
905 906
  /// Setting [timeDilation] calls [resetEpoch] automatically. You don't need to
  /// call [resetEpoch] yourself.
907 908 909 910 911 912 913 914 915 916 917 918 919 920
  void resetEpoch() {
    _epochStart = _adjustForEpoch(_lastRawTimeStamp);
    _firstRawTimeStampInEpoch = null;
  }

  /// Adjusts the given time stamp into the current epoch.
  ///
  /// This both offsets the time stamp to account for when the epoch started
  /// (both in raw time and in the epoch's own time line) and scales the time
  /// stamp to reflect the time dilation in the current epoch.
  ///
  /// These mechanisms together combine to ensure that the durations we give
  /// during frame callbacks are monotonically increasing.
  Duration _adjustForEpoch(Duration rawTimeStamp) {
921
    final Duration rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null ? Duration.zero : rawTimeStamp - _firstRawTimeStampInEpoch!;
922
    return Duration(microseconds: (rawDurationSinceEpoch.inMicroseconds / timeDilation).round() + _epochStart.inMicroseconds);
923 924
  }

925 926
  /// The time stamp for the frame currently being processed.
  ///
927 928 929
  /// This is only valid while between the start of [handleBeginFrame] and the
  /// end of the corresponding [handleDrawFrame], i.e. while a frame is being
  /// produced.
930 931
  Duration get currentFrameTimeStamp {
    assert(_currentFrameTimeStamp != null);
932
    return _currentFrameTimeStamp!;
933
  }
934
  Duration? _currentFrameTimeStamp;
935

936 937 938
  /// The raw time stamp as provided by the engine to
  /// [dart:ui.PlatformDispatcher.onBeginFrame] for the frame currently being
  /// processed.
939 940 941 942 943 944 945 946 947 948 949 950 951 952
  ///
  /// Unlike [currentFrameTimeStamp], this time stamp is neither adjusted to
  /// offset when the epoch started nor scaled to reflect the [timeDilation] in
  /// the current epoch.
  ///
  /// On most platforms, this is a more or less arbitrary value, and should
  /// generally be ignored. On Fuchsia, this corresponds to the system-provided
  /// presentation time, and can be used to ensure that animations running in
  /// different processes are synchronized.
  Duration get currentSystemFrameTimeStamp {
    assert(_lastRawTimeStamp != null);
    return _lastRawTimeStamp;
  }

953
  int _debugFrameNumber = 0;
954
  String? _debugBanner;
955 956 957 958 959 960 961 962 963 964

  // Whether the current engine frame needs to be postponed till after the
  // warm-up frame.
  //
  // Engine may begin a frame in the middle of the warm-up frame because the
  // warm-up frame is scheduled by timers while the engine frame is scheduled
  // by platform specific frame scheduler (e.g. `requestAnimationFrame` on the
  // web). When this happens, we let the warm-up frame finish, and postpone the
  // engine frame.
  bool _rescheduleAfterWarmUpFrame = false;
965 966 967

  void _handleBeginFrame(Duration rawTimeStamp) {
    if (_warmUpFrame) {
968 969 970 971 972
      // "begin frame" and "draw frame" must strictly alternate. Therefore
      // _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
      // reset by _handleDrawFrame.
      assert(!_rescheduleAfterWarmUpFrame);
      _rescheduleAfterWarmUpFrame = true;
973 974 975 976 977 978
      return;
    }
    handleBeginFrame(rawTimeStamp);
  }

  void _handleDrawFrame() {
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
    if (_rescheduleAfterWarmUpFrame) {
      _rescheduleAfterWarmUpFrame = false;
      // Reschedule in a post-frame callback to allow the draw-frame phase of
      // the warm-up frame to finish.
      addPostFrameCallback((Duration timeStamp) {
        // Force an engine frame.
        //
        // We need to reset _hasScheduledFrame here because we cancelled the
        // original engine frame, and therefore did not run handleBeginFrame
        // who is responsible for resetting it. So if a frame callback set this
        // to true in the "begin frame" part of the warm-up frame, it will
        // still be true here and cause us to skip scheduling an engine frame.
        _hasScheduledFrame = false;
        scheduleFrame();
      });
994 995 996 997
      return;
    }
    handleDrawFrame();
  }
998

999
  /// Called by the engine to prepare the framework to produce a new frame.
1000
  ///
1001 1002 1003 1004
  /// This function calls all the transient frame callbacks registered by
  /// [scheduleFrameCallback]. It then returns, any scheduled microtasks are run
  /// (e.g. handlers for any [Future]s resolved by transient frame callbacks),
  /// and [handleDrawFrame] is called to continue the frame.
1005 1006 1007 1008 1009 1010 1011 1012 1013
  ///
  /// If the given time stamp is null, the time stamp from the last frame is
  /// reused.
  ///
  /// To have a banner shown at the start of every frame in debug mode, set
  /// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
  /// console using [debugPrint] and will contain the frame number (which
  /// increments by one for each frame), and the time stamp of the frame. If the
  /// given time stamp was null, then the string "warm-up frame" is shown
1014 1015 1016
  /// instead of the time stamp. This allows frames eagerly pushed by the
  /// framework to be distinguished from those requested by the engine in
  /// response to the "Vsync" signal from the operating system.
1017 1018 1019 1020 1021
  ///
  /// You can also show a banner at the end of every frame by setting
  /// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
  /// statements printed during a frame from those printed between frames (e.g.
  /// in response to events or timers).
1022
  void handleBeginFrame(Duration? rawTimeStamp) {
1023
    Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
1024
    _firstRawTimeStampInEpoch ??= rawTimeStamp;
1025 1026 1027 1028
    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
    if (rawTimeStamp != null)
      _lastRawTimeStamp = rawTimeStamp;

1029
    assert(() {
1030 1031
      _debugFrameNumber += 1;

1032
      if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
1033
        final StringBuffer frameTimeStampDescription = StringBuffer();
1034
        if (rawTimeStamp != null) {
1035
          _debugDescribeTimeStamp(_currentFrameTimeStamp!, frameTimeStampDescription);
1036 1037 1038
        } else {
          frameTimeStampDescription.write('(warm-up frame)');
        }
1039
        _debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)}   ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
1040
        if (debugPrintBeginFrameBanner)
1041
          debugPrint(_debugBanner);
1042
      }
1043
      return true;
1044
    }());
1045

1046
    assert(schedulerPhase == SchedulerPhase.idle);
1047
    _hasScheduledFrame = false;
1048 1049
    try {
      // TRANSIENT FRAME CALLBACKS
1050
      Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
1051
      _schedulerPhase = SchedulerPhase.transientCallbacks;
1052 1053 1054 1055
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
1056
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
1057 1058 1059 1060 1061 1062
      });
      _removedIds.clear();
    } finally {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }
1063

1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
  /// Called by the engine to produce a new frame.
  ///
  /// This method is called immediately after [handleBeginFrame]. It calls all
  /// the callbacks registered by [addPersistentFrameCallback], which typically
  /// drive the rendering pipeline, and then calls the callbacks registered by
  /// [addPostFrameCallback].
  ///
  /// See [handleBeginFrame] for a discussion about debugging hooks that may be
  /// useful when working with frame callbacks.
  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
1075
    Timeline.finishSync(); // end the "Animate" phase
1076
    try {
1077
      // PERSISTENT FRAME CALLBACKS
1078
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
1079
      for (final FrameCallback callback in _persistentCallbacks)
1080
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
1081 1082

      // POST-FRAME CALLBACKS
1083
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
1084
      final List<FrameCallback> localPostFrameCallbacks =
1085
          List<FrameCallback>.from(_postFrameCallbacks);
1086
      _postFrameCallbacks.clear();
1087
      for (final FrameCallback callback in localPostFrameCallbacks)
1088
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
1089
    } finally {
1090
      _schedulerPhase = SchedulerPhase.idle;
1091
      Timeline.finishSync(); // end the Frame
1092 1093
      assert(() {
        if (debugPrintEndFrameBanner)
1094
          debugPrint('▀' * _debugBanner!.length);
1095
        _debugBanner = null;
1096
        return true;
1097
      }());
1098
      _currentFrameTimeStamp = null;
1099
    }
1100 1101
  }

1102
  void _profileFramePostEvent(FrameTiming frameTiming) {
1103
    postEvent('Flutter.Frame', <String, dynamic>{
1104
      'number': frameTiming.frameNumber,
1105 1106 1107 1108
      'startTime': frameTiming.timestampInMicroseconds(FramePhase.buildStart),
      'elapsed': frameTiming.totalSpan.inMicroseconds,
      'build': frameTiming.buildDuration.inMicroseconds,
      'raster': frameTiming.rasterDuration.inMicroseconds,
1109
      'vsyncOverhead': frameTiming.vsyncOverhead.inMicroseconds,
1110 1111 1112
    });
  }

1113 1114 1115 1116
  static void _debugDescribeTimeStamp(Duration timeStamp, StringBuffer buffer) {
    if (timeStamp.inDays > 0)
      buffer.write('${timeStamp.inDays}d ');
    if (timeStamp.inHours > 0)
1117
      buffer.write('${timeStamp.inHours - timeStamp.inDays * Duration.hoursPerDay}h ');
1118
    if (timeStamp.inMinutes > 0)
1119
      buffer.write('${timeStamp.inMinutes - timeStamp.inHours * Duration.minutesPerHour}m ');
1120
    if (timeStamp.inSeconds > 0)
1121 1122 1123
      buffer.write('${timeStamp.inSeconds - timeStamp.inMinutes * Duration.secondsPerMinute}s ');
    buffer.write('${timeStamp.inMilliseconds - timeStamp.inSeconds * Duration.millisecondsPerSecond}');
    final int microseconds = timeStamp.inMicroseconds - timeStamp.inMilliseconds * Duration.microsecondsPerMillisecond;
1124 1125 1126 1127 1128
    if (microseconds > 0)
      buffer.write('.${microseconds.toString().padLeft(3, "0")}');
    buffer.write('ms');
  }

1129
  // Calls the given [callback] with [timestamp] as argument.
1130 1131 1132 1133
  //
  // Wraps the callback in a try/catch and forwards any error to
  // [debugSchedulerExceptionHandler], if set. If not set, then simply prints
  // the error.
1134
  @pragma('vm:notify-debugger-on-exception')
1135
  void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
1136
    assert(callback != null);
1137
    assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
1138 1139 1140 1141
    assert(() {
      _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
      return true;
    }());
1142 1143
    try {
      callback(timeStamp);
1144
    } catch (exception, exceptionStack) {
1145
      FlutterError.reportError(FlutterErrorDetails(
1146
        exception: exception,
1147
        stack: exceptionStack,
1148
        library: 'scheduler library',
1149 1150 1151
        context: ErrorDescription('during a scheduler callback'),
        informationCollector: (callbackStack == null) ? null : () sync* {
          yield DiagnosticsStackTrace(
1152 1153
            '\nThis exception was thrown in the context of a scheduler callback. '
            'When the scheduler callback was _registered_ (as opposed to when the '
1154 1155
            'exception was thrown), this was the stack',
            callbackStack,
1156
          );
1157
        },
1158
      ));
1159
    }
1160 1161 1162 1163
    assert(() {
      _FrameCallbackEntry.debugCurrentCallbackStack = null;
      return true;
    }());
1164
  }
1165 1166
}

1167
/// The default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
1168 1169 1170 1171
///
/// If there are any frame callbacks registered, only runs tasks with
/// a [Priority] of [Priority.animation] or higher. Otherwise, runs
/// all tasks.
1172
bool defaultSchedulingStrategy({ required int priority, required SchedulerBinding scheduler }) {
Ian Hickson's avatar
Ian Hickson committed
1173
  if (scheduler.transientCallbackCount > 0)
1174
    return priority >= Priority.animation.value;
Ian Hickson's avatar
Ian Hickson committed
1175
  return true;
1176
}