Flutter Engine
The Flutter Engine
portable_ui_test.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 "portable_ui_test.h"
6
7#include <fuchsia/inspect/cpp/fidl.h>
8#include <fuchsia/logger/cpp/fidl.h>
9#include <fuchsia/sysmem/cpp/fidl.h>
10#include <fuchsia/sysmem2/cpp/fidl.h>
11#include <fuchsia/tracing/provider/cpp/fidl.h>
12#include <fuchsia/ui/app/cpp/fidl.h>
13#include <lib/async/cpp/task.h>
14#include <lib/sys/component/cpp/testing/realm_builder.h>
15#include <lib/sys/component/cpp/testing/realm_builder_types.h>
16
17#include "check_view.h"
18#include "flutter/fml/logging.h"
19
20namespace fuchsia_test_utils {
21namespace {
22
23// Types imported for the realm_builder library.
24using component_testing::ChildOptions;
25using component_testing::ChildRef;
26using component_testing::ParentRef;
27using component_testing::Protocol;
28using component_testing::RealmRoot;
29using component_testing::Route;
30
32
33} // namespace
34
35void PortableUITest::SetUp(bool build_realm) {
36 SetUpRealmBase();
38
39 if (build_realm) {
40 BuildRealm();
41 }
42}
43
45 realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
46}
47
48void PortableUITest::SetUpRealmBase() {
49 FML_LOG(INFO) << "Setting up realm base";
50
51 // Add Flutter JIT runner as a child of the RealmBuilder
52 realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl);
53
54 // Add environment providing the Flutter JIT runner
55 fuchsia::component::decl::Environment flutter_runner_environment;
56 flutter_runner_environment.set_name(kFlutterRunnerEnvironment);
57 flutter_runner_environment.set_extends(
58 fuchsia::component::decl::EnvironmentExtends::REALM);
59 flutter_runner_environment.set_runners({});
60 auto environment_runners = flutter_runner_environment.mutable_runners();
61
62 // Add Flutter JIT runner to the environment
63 fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg;
64 flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
65 fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner}));
66 flutter_jit_runner_reg.set_source_name(kFlutterJitRunner);
67 flutter_jit_runner_reg.set_target_name(kFlutterJitRunner);
68 environment_runners->push_back(std::move(flutter_jit_runner_reg));
69 auto realm_decl = realm_builder_.GetRealmDecl();
70 if (!realm_decl.has_environments()) {
71 realm_decl.set_environments({});
72 }
73 auto realm_environments = realm_decl.mutable_environments();
74 realm_environments->push_back(std::move(flutter_runner_environment));
75 realm_builder_.ReplaceRealmDecl(std::move(realm_decl));
76
77 // Add test UI stack component.
78 realm_builder_.AddChild(kTestUIStack, GetTestUIStackUrl());
79
80 // // Route base system services to flutter and the test UI stack.
81 realm_builder_.AddRoute(Route{
82 .capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
83 Protocol{fuchsia::inspect::InspectSink::Name_},
84 Protocol{fuchsia::sysmem::Allocator::Name_},
85
86 // Replace string with
87 // fuchsia::sysmem2::Allocator::Name_
88 // when available (fuchsia SDK >= 19).
89 Protocol{"fuchsia.sysmem2.Allocator"},
90 Protocol{fuchsia::tracing::provider::Registry::Name_},
91 Protocol{fuchsia::ui::input::ImeService::Name_},
94 component_testing::Directory{"config-data"}},
95 .source = ParentRef(),
97
98 // Route UI capabilities to test driver and Flutter runner
99 realm_builder_.AddRoute(Route{
100 .capabilities = {Protocol{fuchsia::ui::composition::Allocator::Name_},
101 Protocol{fuchsia::ui::composition::Flatland::Name_},
102 Protocol{fuchsia::ui::test::input::Registry::Name_},
103 Protocol{fuchsia::ui::test::scene::Controller::Name_},
104 Protocol{fuchsia::ui::display::singleton::Info::Name_},
106 .source = kTestUIStackRef,
107 .targets = {ParentRef(), kFlutterJitRunnerRef}});
108}
109
110void PortableUITest::ProcessViewGeometryResponse(
111 fuchsia::ui::observation::geometry::WatchResponse response) {
112 // Process update if no error
113 if (!response.has_error()) {
114 std::vector<fuchsia::ui::observation::geometry::ViewTreeSnapshot>* updates =
115 response.mutable_updates();
116 if (updates && !updates->empty()) {
117 last_view_tree_snapshot_ = std::move(updates->back());
118 }
119 } else {
120 // Otherwise process error
121 const auto& error = response.error();
122 if (error | fuchsia::ui::observation::geometry::Error::CHANNEL_OVERFLOW) {
123 FML_LOG(INFO) << "View Tree watcher channel overflowed";
124 } else if (error |
125 fuchsia::ui::observation::geometry::Error::BUFFER_OVERFLOW) {
126 FML_LOG(INFO) << "View Tree watcher buffer overflowed";
127 } else if (error |
128 fuchsia::ui::observation::geometry::Error::VIEWS_OVERFLOW) {
129 // This one indicates some possible data loss, so we log with a high
130 // severity
131 FML_LOG(WARNING)
132 << "View Tree watcher attempted to report too many views";
133 }
134 }
135}
136
137void PortableUITest::WatchViewGeometry() {
138 FML_CHECK(view_tree_watcher_)
139 << "View Tree watcher must be registered before calling Watch()";
140
141 view_tree_watcher_->Watch([this](auto response) {
142 ProcessViewGeometryResponse(std::move(response));
143 WatchViewGeometry();
144 });
145}
146
147bool PortableUITest::HasViewConnected(zx_koid_t view_ref_koid) {
148 return last_view_tree_snapshot_.has_value() &&
149 CheckViewExistsInSnapshot(*last_view_tree_snapshot_, view_ref_koid);
150}
151
153 scene_provider_ =
154 realm_->component().Connect<fuchsia::ui::test::scene::Controller>();
155 scene_provider_.set_error_handler([](auto) {
156 FML_LOG(ERROR) << "Error from test scene provider: "
157 << &zx_status_get_string;
158 });
159
160 fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
161 request.set_view_provider(
162 realm_->component().Connect<fuchsia::ui::app::ViewProvider>());
163 scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
164 []() {});
165 scene_provider_->AttachClientView(
166 std::move(request), [this](auto client_view_ref_koid) {
167 client_root_view_ref_koid_ = client_view_ref_koid;
168 });
169
170 FML_LOG(INFO) << "Waiting for client view ref koid";
171 RunLoopUntil([this] { return client_root_view_ref_koid_.has_value(); });
172
173 WatchViewGeometry();
174
175 FML_LOG(INFO) << "Waiting for client view to connect";
176 // Wait for the client view to get attached to the view tree.
177 RunLoopUntil(
178 [this] { return HasViewConnected(*client_root_view_ref_koid_); });
179 FML_LOG(INFO) << "Client view has rendered";
180}
181
183 LaunchClient();
184 // At this point, the parent view must have rendered, so we just need to wait
185 // for the embedded view.
186 RunLoopUntil([this] {
187 if (!last_view_tree_snapshot_.has_value() ||
188 !last_view_tree_snapshot_->has_views()) {
189 return false;
190 }
191
192 if (!client_root_view_ref_koid_.has_value()) {
193 return false;
194 }
195
196 for (const auto& view : last_view_tree_snapshot_->views()) {
197 if (!view.has_view_ref_koid() ||
198 view.view_ref_koid() != *client_root_view_ref_koid_) {
199 continue;
200 }
201
202 if (view.children().empty()) {
203 return false;
204 }
205
206 // NOTE: We can't rely on the presence of the child view in
207 // `view.children()` to guarantee that it has rendered. The child view
208 // also needs to be present in `last_view_tree_snapshot_->views`.
209 return std::count_if(
210 last_view_tree_snapshot_->views().begin(),
211 last_view_tree_snapshot_->views().end(),
212 [view_to_find =
213 view.children().back()](const auto& view_to_check) {
214 return view_to_check.has_view_ref_koid() &&
215 view_to_check.view_ref_koid() == view_to_find;
216 }) > 0;
217 }
218
219 return false;
220 });
221
222 FML_LOG(INFO) << "Embedded view has rendered";
223}
224
225void PortableUITest::RegisterTouchScreen() {
226 FML_LOG(INFO) << "Registering fake touch screen";
227 input_registry_ =
228 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
229 input_registry_.set_error_handler([](auto) {
230 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
231 });
232
233 bool touchscreen_registered = false;
234 fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request;
235 request.set_device(fake_touchscreen_.NewRequest());
236 input_registry_->RegisterTouchScreen(
237 std::move(request),
238 [&touchscreen_registered]() { touchscreen_registered = true; });
239
240 RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; });
241 FML_LOG(INFO) << "Touchscreen registered";
242}
243
244void PortableUITest::RegisterMouse() {
245 FML_LOG(INFO) << "Registering fake mouse";
246 input_registry_ =
247 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
248 input_registry_.set_error_handler([](auto) {
249 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
250 });
251
252 bool mouse_registered = false;
253 fuchsia::ui::test::input::RegistryRegisterMouseRequest request;
254 request.set_device(fake_mouse_.NewRequest());
255 input_registry_->RegisterMouse(
256 std::move(request), [&mouse_registered]() { mouse_registered = true; });
257
258 RunLoopUntil([&mouse_registered] { return mouse_registered; });
259 FML_LOG(INFO) << "Mouse registered";
260}
261
262void PortableUITest::RegisterKeyboard() {
263 FML_LOG(INFO) << "Registering fake keyboard";
264 input_registry_ =
265 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
266 input_registry_.set_error_handler([](auto) {
267 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
268 });
269
270 bool keyboard_registered = false;
271 fuchsia::ui::test::input::RegistryRegisterKeyboardRequest request;
272 request.set_device(fake_keyboard_.NewRequest());
273 input_registry_->RegisterKeyboard(
274 std::move(request),
275 [&keyboard_registered]() { keyboard_registered = true; });
276
277 RunLoopUntil([&keyboard_registered] { return keyboard_registered; });
278 FML_LOG(INFO) << "Keyboard registered";
279}
280
281void PortableUITest::InjectTap(int32_t x, int32_t y) {
282 fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request;
283 tap_request.mutable_tap_location()->x = x;
284 tap_request.mutable_tap_location()->y = y;
285
286 FML_LOG(INFO) << "Injecting tap at (" << tap_request.tap_location().x << ", "
287 << tap_request.tap_location().y << ")";
288 fake_touchscreen_->SimulateTap(std::move(tap_request), [this]() {
289 ++touch_injection_request_count_;
290 FML_LOG(INFO) << "*** Tap injected, count: "
291 << touch_injection_request_count_;
292 });
293}
294
295void PortableUITest::SimulateMouseEvent(
296 std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
297 int movement_x,
298 int movement_y) {
299 fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
300 request.set_pressed_buttons(std::move(pressed_buttons));
301 request.set_movement_x(movement_x);
302 request.set_movement_y(movement_y);
303
304 FML_LOG(INFO) << "Injecting mouse input";
305
306 fake_mouse_->SimulateMouseEvent(
307 std::move(request), [] { FML_LOG(INFO) << "Mouse event injected"; });
308}
309
310void PortableUITest::SimulateMouseScroll(
311 std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
312 int scroll_x,
313 int scroll_y,
314 bool use_physical_units) {
315 FML_LOG(INFO) << "Requesting mouse scroll";
316 fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
317 request.set_pressed_buttons(std::move(pressed_buttons));
318 if (use_physical_units) {
319 request.set_scroll_h_physical_pixel(scroll_x);
320 request.set_scroll_v_physical_pixel(scroll_y);
321 } else {
322 request.set_scroll_h_detent(scroll_x);
323 request.set_scroll_v_detent(scroll_y);
324 }
325
326 fake_mouse_->SimulateMouseEvent(std::move(request), [] {
327 FML_LOG(INFO) << "Mouse scroll event injected";
328 });
329}
330
331void PortableUITest::SimulateTextEntry(std::string text) {
332 FML_LOG(INFO) << "Sending text request";
333 bool done = false;
334
335 fuchsia::ui::test::input::KeyboardSimulateUsAsciiTextEntryRequest request;
336 request.set_text(text);
337 fake_keyboard_->SimulateUsAsciiTextEntry(std::move(request),
338 [&done]() { done = true; });
339
340 RunLoopUntil([&] { return done; });
341 FML_LOG(INFO) << "Text request sent";
342}
343
344} // namespace fuchsia_test_utils
static void done(const char *config, const char *src, const char *srcOptions, const char *name)
Definition: DM.cpp:263
for(const auto glyph :glyphs)
Definition: FontMgrTest.cpp:52
static constexpr auto kTestUIStack
static constexpr auto kFlutterRunnerEnvironment
static constexpr auto kFlutterJitRunnerRef
static constexpr auto kPointerInjectorRegistryName
virtual std::string GetTestUIStackUrl()=0
bool HasViewConnected(zx_koid_t view_ref_koid)
static constexpr auto kTestUIStackRef
static constexpr auto kFlutterJitRunner
static constexpr auto kFlutterJitRunnerUrl
static constexpr auto kVulkanLoaderServiceName
static constexpr auto kPosixSocketProviderName
void SetUp(bool build_realm=true)
if(end==-1)
const uint8_t uint32_t uint32_t GError ** error
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_CHECK(condition)
Definition: logging.h:85
std::u16string text
double y
double x
bool CheckViewExistsInSnapshot(const fuchsia::ui::observation::geometry::ViewTreeSnapshot &snapshot, zx_koid_t view_ref_koid)
Definition: check_view.cc:11
#define ERROR(message)
Definition: elf_loader.cc:260