diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 1b4f180b87..d0053915e5 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -161,21 +161,25 @@ abstract class BindingBase { /// callback's future completes. /// /// This causes input lag and should therefore be avoided when possible. It is - /// primarily intended for development features, in particular to allow - /// [reassembleApplication] to block input while it walks the tree (which it - /// partially does asynchronously). + /// primarily intended for use during non-user-interactive time such as to + /// allow [reassembleApplication] to block input while it walks the tree + /// (which it partially does asynchronously). /// /// The [Future] returned by the `callback` argument is returned by [lockEvents]. @protected Future lockEvents(Future callback()) { + developer.Timeline.startSync('Lock events'); + assert(callback != null); _lockCount += 1; final Future future = callback(); assert(future != null, 'The lockEvents() callback returned null; it should return a Future that completes when the lock is to expire.'); future.whenComplete(() { _lockCount -= 1; - if (!locked) + if (!locked) { + developer.Timeline.finishSync(); unlocked(); + } }); return future; } diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index 035c0c65a6..6af228a7cb 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -666,6 +666,8 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding { /// 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. /// + /// Locks events dispatching until the scheduled frame has completed. + /// /// If a frame has already been scheduled with [scheduleFrame] or /// [scheduleForcedFrame], this call may delay that frame. /// @@ -677,8 +679,9 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding { if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) return; - final bool hadScheduledFrame = _hasScheduledFrame; _warmUpFrame = true; + Timeline.startSync('Warm-up frame'); + final bool hadScheduledFrame = _hasScheduledFrame; // We use timers here to ensure that microtasks flush in between. Timer.run(() { assert(_warmUpFrame); @@ -700,6 +703,13 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding { if (hadScheduledFrame) scheduleFrame(); }); + + // Lock events so touch events etc don't insert themselves until the + // scheduled frame has finished. + lockEvents(() async { + await endOfFrame; + Timeline.finishSync(); + }); } Duration _firstRawTimeStampInEpoch; diff --git a/packages/flutter/test/scheduler/scheduler_test.dart b/packages/flutter/test/scheduler/scheduler_test.dart index 2f7345628c..25b3b29e08 100644 --- a/packages/flutter/test/scheduler/scheduler_test.dart +++ b/packages/flutter/test/scheduler/scheduler_test.dart @@ -92,11 +92,13 @@ void main() { test('2 calls to scheduleWarmUpFrame just schedules it once', () { final List timerQueueTasks = []; + bool taskExecuted = false; runZoned( () { // Run it twice without processing the queued tasks. scheduler.scheduleWarmUpFrame(); scheduler.scheduleWarmUpFrame(); + scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch); }, zoneSpecification: new ZoneSpecification( createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) { @@ -107,7 +109,9 @@ void main() { ), ); - // A single call to scheduleWarmUpFrame queues up 2 Timer tasks. + // scheduleWarmUpFrame scheduled 2 Timers, scheduleTask scheduled 0 because + // events are locked. expect(timerQueueTasks.length, 2); + expect(taskExecuted, false); }); }