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