Flutter Linux Embedder
fl_view_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 
6 
7 #include <memory>
8 
9 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
13 #include "flutter/shell/platform/linux/testing/fl_test.h"
14 #include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
15 #include "flutter/shell/platform/linux/testing/mock_gtk.h"
16 
17 #include "flutter/shell/platform/linux/testing/linux_test.h"
18 #include "gtest/gtest.h"
19 
20 // FIXME(robert-ancell): Disabled, see below.
21 #if 0
22 static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) {
23  *first_frame_emitted = TRUE;
24 }
25 #endif
26 
27 class FlViewTest : public flutter::testing::LinuxTest {};
28 
29 TEST_F(FlViewTest, GetEngine) {
30  FlView* view = fl_view_new(project);
31 
32  // Check the engine is immediately available (i.e. before the widget is
33  // realized).
34  FlEngine* engine = fl_view_get_engine(view);
35  EXPECT_NE(engine, nullptr);
36 }
37 
38 TEST_F(FlViewTest, StateUpdateDoesNotHappenInInit) {
39  FlView* view = fl_view_new(project);
40  // Check that creating a view doesn't try to query the window state in
41  // initialization, causing a critical log to be issued.
42  EXPECT_EQ(
43  flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL,
44  (GLogLevelFlags)0x0);
45 
46  (void)view;
47 }
48 
49 // Disposing a view that holds the text input focus clears the handler's
50 // widget pointer so it does not dangle.
51 // https://github.com/flutter/flutter/issues/188657
52 TEST_F(FlViewTest, DisposeClearsTextInputWidget) {
53  FlView* view = fl_view_new(project);
54  g_object_ref_sink(view);
55  g_autoptr(FlEngine) engine =
56  FL_ENGINE(g_object_ref(fl_view_get_engine(view)));
57  StartEngine(engine);
58  FlTextInputHandler* handler = fl_engine_get_text_input_handler(engine);
59 
60  // Give the view text input focus.
61  fl_text_input_handler_set_widget(handler, GTK_WIDGET(view));
62  EXPECT_EQ(fl_text_input_handler_get_widget(handler), GTK_WIDGET(view));
63 
64  // Destroy the view while the engine and handler persist.
65  g_object_unref(view);
66 
67  EXPECT_EQ(fl_text_input_handler_get_widget(handler), nullptr);
68 }
69 
70 // FIXME(robert-ancell): Disabling this test as it requires the FlView
71 // to be realized to work after some refactoring. This is proving to be
72 // very difficult to mock. Following PRs will change this code so enable the
73 // test again after that.
74 #if 0
75 TEST_F(FlViewTest, FirstFrameSignal) {
76  FlView* view = fl_view_new(project);
77  gboolean first_frame_emitted = FALSE;
78  g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb),
79  &first_frame_emitted);
80 
81  EXPECT_FALSE(first_frame_emitted);
82 
83  fl_renderable_present_layers(FL_RENDERABLE(view), nullptr, 0);
84 
85  // Signal is emitted in idle, clear the main loop.
86  while (g_main_context_iteration(g_main_context_default(), FALSE)) {
87  // Repeat until nothing to iterate on.
88  }
89 
90  // Check view has detected frame.
91  EXPECT_TRUE(first_frame_emitted);
92 }
93 #endif
94 
95 // Check semantics update applied
96 TEST_F(FlViewTest, SemanticsUpdate) {
97  FlView* view = fl_view_new(project);
98 
99  FlEngine* engine = fl_view_get_engine(view);
100  StartEngine(engine);
101 
102  FlutterSemanticsFlags flags = {};
103  FlutterSemanticsNode2 root_node = {
104  .id = 0, .label = "root", .flags2 = &flags};
105  FlutterSemanticsNode2* nodes[1] = {&root_node};
106  FlutterSemanticsUpdate2 update = {
107  .node_count = 1, .nodes = nodes, .view_id = 0};
108  g_signal_emit_by_name(engine, "update-semantics", &update);
109 
110  FlViewAccessible* accessible = fl_view_get_accessible(view);
111  EXPECT_EQ(atk_object_get_n_accessible_children(ATK_OBJECT(accessible)), 1);
112  AtkObject* root_object =
113  atk_object_ref_accessible_child(ATK_OBJECT(accessible), 0);
114  EXPECT_STREQ(atk_object_get_name(root_object), "root");
115 }
116 
117 // Check semantics update ignored for other view
118 TEST_F(FlViewTest, SemanticsUpdateOtherView) {
119  FlView* view = fl_view_new(project);
120 
121  FlEngine* engine = fl_view_get_engine(view);
122  StartEngine(engine);
123 
124  FlutterSemanticsFlags flags = {};
125  FlutterSemanticsNode2 root_node = {
126  .id = 0, .label = "root", .flags2 = &flags};
127  FlutterSemanticsNode2* nodes[1] = {&root_node};
128  FlutterSemanticsUpdate2 update = {
129  .node_count = 1, .nodes = nodes, .view_id = 99};
130  g_signal_emit_by_name(engine, "update-semantics", &update);
131 
132  FlViewAccessible* accessible = fl_view_get_accessible(view);
133  EXPECT_EQ(atk_object_get_n_accessible_children(ATK_OBJECT(accessible)), 0);
134 }
135 
136 // Check secondary view is registered with engine.
137 TEST_F(FlViewTest, SecondaryView) {
138  FlView* implicit_view = fl_view_new(project);
139 
140  FlEngine* engine = fl_view_get_engine(implicit_view);
141 
142  auto view_id = std::make_shared<FlutterViewId>(-1);
143  fl_engine_get_embedder_api(engine)->AddView = MOCK_ENGINE_PROC(
144  AddView, ([view_id](auto engine, const FlutterAddViewInfo* info) {
145  *view_id = info->view_id;
146  FlutterAddViewResult result = {
147  .struct_size = sizeof(FlutterAddViewResult),
148  .added = true,
149  .user_data = info->user_data};
150  info->add_view_callback(&result);
151  return kSuccess;
152  }));
153 
154  StartEngine(engine);
155 
156  FlView* secondary_view = fl_view_new_for_engine(engine);
157  EXPECT_EQ(*view_id, fl_view_get_id(secondary_view));
158 }
159 
160 // Check secondary view that fails registration.
161 TEST_F(FlViewTest, SecondaryViewError) {
162  FlView* implicit_view = fl_view_new(project);
163 
164  FlEngine* engine = fl_view_get_engine(implicit_view);
165 
166  auto view_id = std::make_shared<FlutterViewId>(-1);
167  fl_engine_get_embedder_api(engine)->AddView = MOCK_ENGINE_PROC(
168  AddView, ([view_id](auto engine, const FlutterAddViewInfo* info) {
169  *view_id = info->view_id;
170  return kInvalidArguments;
171  }));
172 
173  StartEngine(engine);
174 
175  FlView* secondary_view = fl_view_new_for_engine(engine);
176  EXPECT_EQ(*view_id, fl_view_get_id(secondary_view));
177 }
178 
179 // Check views are deregistered on destruction.
180 TEST_F(FlViewTest, ViewDestroy) {
181  FlView* implicit_view = fl_view_new(project);
182 
183  FlEngine* engine = fl_view_get_engine(implicit_view);
184 
185  g_autoptr(GPtrArray) removed_views = g_ptr_array_new();
186  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
187  RemoveView,
188  ([removed_views](auto engine, const FlutterRemoveViewInfo* info) {
189  g_ptr_array_add(removed_views, GINT_TO_POINTER(info->view_id));
190  return kSuccess;
191  }));
192 
193  StartEngine(engine);
194 
195  FlView* secondary_view = fl_view_new_for_engine(engine);
196 
197  int64_t implicit_view_id = fl_view_get_id(implicit_view);
198  int64_t secondary_view_id = fl_view_get_id(secondary_view);
199 
200  fl_gtk_widget_destroy(GTK_WIDGET(secondary_view));
201  fl_gtk_widget_destroy(GTK_WIDGET(implicit_view));
202 
203  EXPECT_EQ(removed_views->len, 2u);
204  EXPECT_EQ(GPOINTER_TO_INT(g_ptr_array_index(removed_views, 0)),
205  secondary_view_id);
206  EXPECT_EQ(GPOINTER_TO_INT(g_ptr_array_index(removed_views, 1)),
207  implicit_view_id);
208 }
209 
210 // Check views deregistered with errors works.
211 TEST_F(FlViewTest, ViewDestroyError) {
212  FlView* implicit_view = fl_view_new(project);
213 
214  FlEngine* engine = fl_view_get_engine(implicit_view);
215 
216  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
217  RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
218  return kInvalidArguments;
219  }));
220 
221  StartEngine(engine);
222 
223  FlView* secondary_view = fl_view_new_for_engine(engine);
224 
225  fl_gtk_widget_destroy(GTK_WIDGET(secondary_view));
226  fl_gtk_widget_destroy(GTK_WIDGET(implicit_view));
227 }
228 
229 // Check can create a view that is sized to the content.
230 TEST_F(FlViewTest, SizedToContent) {
231  FlView* implicit_view = fl_view_new(project);
232 
233  FlEngine* engine = fl_view_get_engine(implicit_view);
234 
235  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
236  RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
237  return kInvalidArguments;
238  }));
239 
240  StartEngine(engine);
241 
242  FlView* secondary_view = fl_view_new_sized_to_content(engine);
243 
244  fl_gtk_widget_destroy(GTK_WIDGET(secondary_view));
245  fl_gtk_widget_destroy(GTK_WIDGET(implicit_view));
246 }
FlViewAccessible * accessible
FlView * view
g_autoptr(FlEngine) engine
const char FlTextDirection FlAssertiveness gpointer user_data
return TRUE
FlutterEngineProcTable * fl_engine_get_embedder_api(FlEngine *self)
Definition: fl_engine.cc:920
FlTextInputHandler * fl_engine_get_text_input_handler(FlEngine *self)
Definition: fl_engine.cc:1570
void fl_renderable_present_layers(FlRenderable *self, const FlutterLayer **layers, size_t layers_count)
void fl_text_input_handler_set_widget(FlTextInputHandler *self, GtkWidget *widget)
GtkWidget * fl_text_input_handler_get_widget(FlTextInputHandler *self)
FlViewAccessible * fl_view_get_accessible(FlView *self)
Definition: fl_view.cc:770
G_MODULE_EXPORT FlView * fl_view_new(FlDartProject *project)
Definition: fl_view.cc:705
G_MODULE_EXPORT FlView * fl_view_new_sized_to_content(FlEngine *engine)
Definition: fl_view.cc:735
G_MODULE_EXPORT FlView * fl_view_new_for_engine(FlEngine *engine)
Definition: fl_view.cc:719
G_MODULE_EXPORT FlEngine * fl_view_get_engine(FlView *self)
Definition: fl_view.cc:753
G_MODULE_EXPORT int64_t fl_view_get_id(FlView *self)
Definition: fl_view.cc:759
G_BEGIN_DECLS FlutterViewId view_id
static void first_frame_cb(FlViewMonitor *self)
TEST_F(FlViewTest, GetEngine)
Definition: fl_view_test.cc:29