Flutter Engine
The Flutter Engine
input_events_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/shell/common/shell_test.h"
6#include "flutter/testing/testing.h"
7
8// CREATE_NATIVE_ENTRY is leaky by design
9// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
10
11namespace flutter {
12namespace testing {
13
14// Throughout these tests, the choice of time unit is irrelevant as long as all
15// times have the same units.
17
18// Signature of a generator function that takes the frame index as input and
19// returns the time of that frame.
21
22namespace {
23
24constexpr int64_t kImplicitViewId = 0;
25
26}
27
28//----------------------------------------------------------------------------
29/// Simulate n input events where the i-th one is delivered at delivery_time(i).
30///
31/// Simulation results will be written into events_consumed_at_frame whose
32/// length will be equal to the number of frames drawn. Each element in the
33/// vector is the number of input events consumed up to that frame. (We can't
34/// return such vector because ASSERT_TRUE requires return type of void.)
35///
36/// We assume (and check) that the delivery latency is some base latency plus a
37/// random latency where the random latency must be within one frame:
38///
39/// 1. latency = delivery_time(i) - j * frame_time = base_latency +
40/// random_latency
41/// 2. 0 <= base_latency, 0 <= random_latency < frame_time
42///
43/// We also assume that there will be at least one input event per frame if
44/// there were no latency. Let j = floor( (delivery_time(i) - base_latency) /
45/// frame_time ) be the frame index if there were no latency. Then the set of j
46/// should be all integers from 0 to continuous_frame_count - 1 for some
47/// integer continuous_frame_count.
48///
49/// (Note that there coulds be multiple input events within one frame.)
50///
51/// The test here is insensitive to the choice of time unit as long as
52/// delivery_time and frame_time are in the same unit.
54 ShellTest* fixture,
55 int num_events,
56 UnitlessTime base_latency,
57 Generator delivery_time,
58 UnitlessTime frame_time,
59 std::vector<UnitlessTime>& events_consumed_at_frame,
60 bool restart_engine = false) {
61 ///// Begin constructing shell ///////////////////////////////////////////////
62 auto settings = fixture->CreateSettingsForFixture();
63 std::unique_ptr<Shell> shell = fixture->CreateShell({
64 .settings = settings,
65 .platform_view_create_callback = ShellTestPlatformViewBuilder({
66 .simulate_vsync = true,
67 }),
68 });
69
71 configuration.SetEntrypoint("onPointerDataPacketMain");
72
73 // The following 4 variables are only accessed in the UI thread by
74 // nativeOnPointerDataPacket and nativeOnBeginFrame between their
75 // initializations and `shell.reset()`.
76 events_consumed_at_frame.clear();
77 bool will_draw_new_frame = true;
78 int events_consumed = 0;
79 int frame_drawn = 0;
80 auto nativeOnPointerDataPacket = [&events_consumed_at_frame,
81 &will_draw_new_frame, &events_consumed,
82 &frame_drawn](Dart_NativeArguments args) {
83 events_consumed += 1;
84 if (will_draw_new_frame) {
85 frame_drawn += 1;
86 will_draw_new_frame = false;
87 events_consumed_at_frame.push_back(events_consumed);
88 } else {
89 events_consumed_at_frame.back() = events_consumed;
90 }
91 };
92 fixture->AddNativeCallback("NativeOnPointerDataPacket",
93 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
94
95 ASSERT_TRUE(configuration.IsValid());
96 fixture->RunEngine(shell.get(), std::move(configuration));
97
98 if (restart_engine) {
99 auto new_configuration = RunConfiguration::InferFromSettings(settings);
100 new_configuration.SetEntrypoint("onPointerDataPacketMain");
101 ASSERT_TRUE(new_configuration.IsValid());
102 fixture->RestartEngine(shell.get(), std::move(new_configuration));
103 }
104 ///// End constructing shell /////////////////////////////////////////////////
105
106 ASSERT_GE(base_latency, 0);
107
108 // Check that delivery_time satisfies our assumptions.
109 int continuous_frame_count = 0;
110 for (int i = 0; i < num_events; i += 1) {
111 // j is the frame index of event i if there were no latency.
112 int j = static_cast<int>((delivery_time(i) - base_latency) / frame_time);
113 if (j == continuous_frame_count) {
114 continuous_frame_count += 1;
115 }
116 double random_latency = delivery_time(i) - j * frame_time - base_latency;
117 ASSERT_GE(random_latency, 0);
118 ASSERT_LT(random_latency, frame_time);
119
120 // If there were no latency, there should be at least one event per frame.
121 // Hence j should never skip any integer less than continuous_frame_count.
122 ASSERT_LT(j, continuous_frame_count);
123 }
124
125 // This has to be running on a different thread than Platform thread to avoid
126 // dead locks.
127 auto simulation = std::async(std::launch::async, [&]() {
128 // i is the input event's index.
129 // j is the frame's index.
130 for (int i = 0, j = 0; i < num_events; j += 1) {
131 double t = j * frame_time;
132 while (i < num_events && delivery_time(i) <= t) {
133 // Use a different x every time for the pointer data converter to
134 // generate non-empty events.
136 i += 1;
137 }
138 ShellTest::VSyncFlush(shell.get(), &will_draw_new_frame);
139 }
140 // Finally, issue a vsync for the pending event that may be generated duing
141 // the last vsync.
142 ShellTest::VSyncFlush(shell.get(), &will_draw_new_frame);
143 });
144
145 simulation.wait();
146
147 TaskRunners task_runners = fixture->GetTaskRunnersForFixture();
149 task_runners.GetPlatformTaskRunner()->PostTask([&shell, &latch]() mutable {
150 shell.reset();
151 latch.Signal();
152 });
153 latch.Wait();
154
155 // Make sure that all events have been consumed so
156 // https://github.com/flutter/flutter/issues/40863 won't happen again.
157 ASSERT_GT(events_consumed_at_frame.size(), 0u);
158 ASSERT_EQ(events_consumed_at_frame.back(), num_events);
159}
160
162 PointerData::Change change,
163 double dx,
164 double dy) {
165 data.time_stamp = 0;
166 data.change = change;
169 data.device = 0;
170 data.pointer_identifier = 0;
171 data.physical_x = dx;
172 data.physical_y = dy;
173 data.physical_delta_x = 0.0;
174 data.physical_delta_y = 0.0;
175 data.buttons = 0;
176 data.obscured = 0;
177 data.synthesized = 0;
178 data.pressure = 0.0;
179 data.pressure_min = 0.0;
180 data.pressure_max = 0.0;
181 data.distance = 0.0;
182 data.distance_max = 0.0;
183 data.size = 0.0;
184 data.radius_major = 0.0;
185 data.radius_minor = 0.0;
186 data.radius_min = 0.0;
187 data.radius_max = 0.0;
188 data.orientation = 0.0;
189 data.tilt = 0.0;
190 data.platformData = 0;
191 data.scroll_delta_x = 0.0;
192 data.scroll_delta_y = 0.0;
193 data.view_id = kImplicitViewId;
194}
195
196TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) {
197 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
198 // it well with lambda capture.
199 UnitlessTime frame_time = 10;
200 UnitlessTime base_latency = 0.5 * frame_time;
201 Generator extreme = [frame_time, base_latency](int i) {
202 return static_cast<UnitlessTime>(
203 i * frame_time + base_latency +
204 (i % 2 == 0 ? 0.1 * frame_time : 0.9 * frame_time));
205 };
206 constexpr int n = 40;
207 std::vector<int> events_consumed_at_frame;
208 TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
209 events_consumed_at_frame);
210 int frame_drawn = events_consumed_at_frame.size();
211 ASSERT_GE(frame_drawn, n - 1);
212
213 // Make sure that it also works after an engine restart.
214 TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
215 events_consumed_at_frame, true /* restart_engine */);
216 int frame_drawn_after_restart = events_consumed_at_frame.size();
217 ASSERT_GE(frame_drawn_after_restart, n - 1);
218}
219
220TEST_F(ShellTest, DelayAtMostOneEventForFasterThanVSyncInputEvents) {
221 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
222 // it well with lambda capture.
223 UnitlessTime frame_time = 10;
224 UnitlessTime base_latency = 0.2 * frame_time;
225 Generator double_sampling = [frame_time, base_latency](int i) {
226 return static_cast<UnitlessTime>(i * 0.5 * frame_time + base_latency);
227 };
228 constexpr int n = 40;
229 std::vector<int> events_consumed_at_frame;
230 TestSimulatedInputEvents(this, n, base_latency, double_sampling, frame_time,
231 events_consumed_at_frame);
232
233 // Draw one extra frame due to delaying a pending packet for the next frame.
234 int frame_drawn = events_consumed_at_frame.size();
235 ASSERT_EQ(frame_drawn, n / 2 + 1);
236
237 for (int i = 0; i < n / 2; i += 1) {
238 ASSERT_GE(events_consumed_at_frame[i], 2 * i - 1);
239 }
240}
241
242TEST_F(ShellTest, HandlesActualIphoneXsInputEvents) {
243 // Actual delivery times measured on iPhone Xs, in the unit of frame_time
244 // (16.67ms for 60Hz).
245 static constexpr double iphone_xs_times[] = {0.15,
246 1.0773046874999999,
247 2.1738720703124996,
248 3.0579052734374996,
249 4.0890087890624995,
250 5.0952685546875,
251 6.1251708984375,
252 7.1253076171875,
253 8.125927734374999,
254 9.37248046875,
255 10.133950195312499,
256 11.161201171875,
257 12.226992187499999,
258 13.1443798828125,
259 14.440327148437499,
260 15.091684570312498,
261 16.138681640625,
262 17.126469726562497,
263 18.1592431640625,
264 19.371372070312496,
265 20.033774414062496,
266 21.021782226562497,
267 22.070053710937497,
268 23.325541992187496,
269 24.119648437499997,
270 25.084262695312496,
271 26.077866210937497,
272 27.036547851562496,
273 28.035073242187497,
274 29.081411132812498,
275 30.066064453124998,
276 31.089360351562497,
277 32.086142578125,
278 33.4618798828125,
279 34.14697265624999,
280 35.0513525390625,
281 36.136025390624994,
282 37.1618408203125,
283 38.144472656249995,
284 39.201123046875,
285 40.4339501953125,
286 41.1552099609375,
287 42.102128906249995,
288 43.0426318359375,
289 44.070131835937495,
290 45.08862304687499,
291 46.091469726562494};
292 constexpr int n = sizeof(iphone_xs_times) / sizeof(iphone_xs_times[0]);
293 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
294 // it well with lambda capture.
295 UnitlessTime frame_time = 10000;
296 double base_latency_f = 0.0;
297 for (int i = 0; i < 10; i++) {
298 base_latency_f += 0.1;
299 // Everything is converted to int to avoid floating point error in
300 // TestSimulatedInputEvents.
301 UnitlessTime base_latency =
302 static_cast<UnitlessTime>(base_latency_f * frame_time);
303 Generator iphone_xs_generator = [frame_time, base_latency](int i) {
304 return base_latency +
305 static_cast<UnitlessTime>(iphone_xs_times[i] * frame_time);
306 };
307 std::vector<int> events_consumed_at_frame;
308 TestSimulatedInputEvents(this, n, base_latency, iphone_xs_generator,
309 frame_time, events_consumed_at_frame);
310 int frame_drawn = events_consumed_at_frame.size();
311 ASSERT_GE(frame_drawn, n - 1);
312 }
313}
314
315TEST_F(ShellTest, CanCorrectlyPipePointerPacket) {
316 // Sets up shell with test fixture.
317 auto settings = CreateSettingsForFixture();
318 std::unique_ptr<Shell> shell = CreateShell({
319 .settings = settings,
320 .platform_view_create_callback = ShellTestPlatformViewBuilder({
321 .simulate_vsync = true,
322 }),
323 });
324
325 auto configuration = RunConfiguration::InferFromSettings(settings);
326 configuration.SetEntrypoint("onPointerDataPacketMain");
327 // Sets up native handler.
328 fml::AutoResetWaitableEvent reportLatch;
329 std::vector<int64_t> result_sequence;
330 auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
332 Dart_Handle exception = nullptr;
333 result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
334 args, 0, exception);
335 reportLatch.Signal();
336 };
337 // Starts engine.
338 AddNativeCallback("NativeOnPointerDataPacket",
339 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
340 ASSERT_TRUE(configuration.IsValid());
341 RunEngine(shell.get(), std::move(configuration));
342 // Starts test.
343 auto packet = std::make_unique<PointerDataPacket>(6);
346 packet->SetPointerData(0, data);
348 packet->SetPointerData(1, data);
350 packet->SetPointerData(2, data);
352 packet->SetPointerData(3, data);
354 packet->SetPointerData(4, data);
356 packet->SetPointerData(5, data);
357 ShellTest::DispatchPointerData(shell.get(), std::move(packet));
359
360 reportLatch.Wait();
361 size_t expect_length = 6;
362 ASSERT_EQ(result_sequence.size(), expect_length);
363 ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
364 ASSERT_EQ(PointerData::Change(result_sequence[1]),
366 ASSERT_EQ(PointerData::Change(result_sequence[2]),
368 ASSERT_EQ(PointerData::Change(result_sequence[3]),
370 ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
371 ASSERT_EQ(PointerData::Change(result_sequence[5]),
373
374 // Cleans up shell.
375 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
376 DestroyShell(std::move(shell));
377 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
378}
379
380TEST_F(ShellTest, CanCorrectlySynthesizePointerPacket) {
381 // Sets up shell with test fixture.
382 auto settings = CreateSettingsForFixture();
383 std::unique_ptr<Shell> shell = CreateShell({
384 .settings = settings,
385 .platform_view_create_callback = ShellTestPlatformViewBuilder({
386 .simulate_vsync = true,
387 }),
388 });
389
390 auto configuration = RunConfiguration::InferFromSettings(settings);
391 configuration.SetEntrypoint("onPointerDataPacketMain");
392 // Sets up native handler.
393 fml::AutoResetWaitableEvent reportLatch;
394 std::vector<int64_t> result_sequence;
395 auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
397 Dart_Handle exception = nullptr;
398 result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
399 args, 0, exception);
400 reportLatch.Signal();
401 };
402 // Starts engine.
403 AddNativeCallback("NativeOnPointerDataPacket",
404 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
405 ASSERT_TRUE(configuration.IsValid());
406 RunEngine(shell.get(), std::move(configuration));
407 // Starts test.
408 auto packet = std::make_unique<PointerDataPacket>(4);
411 packet->SetPointerData(0, data);
413 packet->SetPointerData(1, data);
415 packet->SetPointerData(2, data);
417 packet->SetPointerData(3, data);
418 ShellTest::DispatchPointerData(shell.get(), std::move(packet));
420
421 reportLatch.Wait();
422 size_t expect_length = 6;
423 ASSERT_EQ(result_sequence.size(), expect_length);
424 ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
425 // The pointer data packet converter should synthesize a hover event.
426 ASSERT_EQ(PointerData::Change(result_sequence[1]),
428 ASSERT_EQ(PointerData::Change(result_sequence[2]),
430 // The pointer data packet converter should synthesize a move event.
431 ASSERT_EQ(PointerData::Change(result_sequence[3]),
433 ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
434 ASSERT_EQ(PointerData::Change(result_sequence[5]),
436
437 // Cleans up shell.
438 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
439 DestroyShell(std::move(shell));
440 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
441}
442
443} // namespace testing
444} // namespace flutter
445
446// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static bool IsInstanceRunning()
static RunConfiguration InferFromSettings(const Settings &settings, const fml::RefPtr< fml::TaskRunner > &io_worker=nullptr, IsolateLaunchType launch_type=IsolateLaunchType::kNewGroup)
Attempts to infer a run configuration from the settings object. This tries to create a run configurat...
fml::RefPtr< fml::TaskRunner > GetPlatformTaskRunner() const
Definition: task_runners.cc:30
void AddNativeCallback(const std::string &name, Dart_NativeFunction callback)
Definition: dart_fixture.cc:75
static void RunEngine(Shell *shell, RunConfiguration configuration)
Definition: shell_test.cc:104
Settings CreateSettingsForFixture() override
Definition: shell_test.cc:336
TaskRunners GetTaskRunnersForFixture()
Definition: shell_test.cc:355
static void VSyncFlush(Shell *shell, bool *will_draw_new_frame=nullptr)
Definition: shell_test.cc:128
std::unique_ptr< Shell > CreateShell(const Settings &settings, std::optional< TaskRunners > task_runners={})
Definition: shell_test.cc:369
static void DispatchPointerData(Shell *shell, std::unique_ptr< PointerDataPacket > packet)
Definition: shell_test.cc:270
static void RestartEngine(Shell *shell, RunConfiguration configuration)
Definition: shell_test.cc:118
static void DispatchFakePointerData(Shell *shell, double x)
Definition: shell_test.cc:261
virtual void PostTask(const fml::closure &task) override
Definition: task_runner.cc:24
struct _Dart_Handle * Dart_Handle
Definition: dart_api.h:258
struct _Dart_NativeArguments * Dart_NativeArguments
Definition: dart_api.h:3019
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
Dart_NativeFunction function
Definition: fuchsia.cc:51
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
TEST_F(DisplayListTest, Defaults)
std::function< UnitlessTime(int)> Generator
void CreateSimulatedPointerData(PointerData &data, PointerData::Change change, int64_t device, double dx, double dy, int64_t buttons)
static void TestSimulatedInputEvents(ShellTest *fixture, int num_events, UnitlessTime base_latency, Generator delivery_time, UnitlessTime frame_time, std::vector< UnitlessTime > &events_consumed_at_frame, bool restart_engine=false)
constexpr int64_t kImplicitViewId
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition: switches.h:41
#define CREATE_NATIVE_ENTRY(native_entry)