Commit ab707aca authored by K. P. Sroka's avatar K. P. Sroka Committed by Dan Field

Prevent exception being thrown on hasScrolledBody (#31485)

Convert scheduling to use `scheduleMicrotask` instead of `Timer.run`
parent 1b87f558
...@@ -763,12 +763,21 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { ...@@ -763,12 +763,21 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
_warmUpFrame = true; _warmUpFrame = true;
Timeline.startSync('Warm-up frame'); Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame; final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between. final Completer<void> _warmUpFrameCompleter = Completer<void>();
Timer.run(() {
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await _warmUpFrameCompleter.future;
Timeline.finishSync();
});
// We use scheduleMicrotask here to ensure that microtasks flush in between.
scheduleMicrotask(() {
assert(_warmUpFrame); assert(_warmUpFrame);
handleBeginFrame(null); handleBeginFrame(null);
});
Timer.run(() { scheduleMicrotask(() {
assert(_warmUpFrame); assert(_warmUpFrame);
handleDrawFrame(); handleDrawFrame();
// We call resetEpoch after this frame so that, in the hot reload case, // We call resetEpoch after this frame so that, in the hot reload case,
...@@ -781,15 +790,10 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { ...@@ -781,15 +790,10 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
// then skipping every frame and finishing in the new time. // then skipping every frame and finishing in the new time.
resetEpoch(); resetEpoch();
_warmUpFrame = false; _warmUpFrame = false;
_warmUpFrameCompleter.complete();
if (hadScheduledFrame) if (hadScheduledFrame)
scheduleFrame(); scheduleFrame();
}); });
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
}); });
} }
......
...@@ -19,6 +19,26 @@ class TestStrategy { ...@@ -19,6 +19,26 @@ class TestStrategy {
} }
} }
List<VoidCallback> runWithMicrotaskQueueSpy(VoidCallback callback) {
final List<VoidCallback> microtaskQueue = <VoidCallback>[];
runZoned<void>(
() {
callback();
},
zoneSpecification: ZoneSpecification(
scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void f()) {
// Don't actually run the tasks, just record that it was scheduled.
microtaskQueue.add(f);
self.parent.scheduleMicrotask(() {
f();
microtaskQueue.remove(f);
});
},
),
);
return microtaskQueue;
}
void main() { void main() {
SchedulerBinding scheduler; SchedulerBinding scheduler;
setUpAll(() { setUpAll(() {
...@@ -92,27 +112,27 @@ void main() { ...@@ -92,27 +112,27 @@ void main() {
}); });
test('2 calls to scheduleWarmUpFrame just schedules it once', () { test('2 calls to scheduleWarmUpFrame just schedules it once', () {
final List<VoidCallback> timerQueueTasks = <VoidCallback>[]; final List<VoidCallback> microtaskQueue = runWithMicrotaskQueueSpy(() {
bool taskExecuted = false;
runZoned<void>(
() {
// Run it twice without processing the queued tasks.
scheduler.scheduleWarmUpFrame(); scheduler.scheduleWarmUpFrame();
scheduler.scheduleWarmUpFrame(); scheduler.scheduleWarmUpFrame();
});
// scheduleWarmUpFrame scheduled 1 microtask
expect(microtaskQueue.length, 1);
});
test('Tasks are not executed before scheduleWarmUpFrame finishes', () async {
bool taskExecuted = false;
final List<VoidCallback> microtaskQueue = runWithMicrotaskQueueSpy(() {
scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch); scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch);
}, Timer.run(() { taskExecuted = true; });
zoneSpecification: ZoneSpecification( scheduler.scheduleWarmUpFrame();
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) { });
// Don't actually run the tasks, just record that it was scheduled.
timerQueueTasks.add(f); expect(microtaskQueue.isNotEmpty, true);
return null; await scheduler.endOfFrame;
},
),
);
// scheduleWarmUpFrame scheduled 2 Timers, scheduleTask scheduled 0 because expect(scheduler.schedulerPhase, SchedulerPhase.idle);
// events are locked.
expect(timerQueueTasks.length, 2);
expect(taskExecuted, false); expect(taskExecuted, false);
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment