| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" |
| #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" |
| #include "flutter/shell/platform/linux/fl_engine_private.h" |
| #include "flutter/shell/platform/linux/fl_view_private.h" |
| #include "flutter/shell/platform/linux/testing/fl_test.h" |
| #include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h" |
| |
| #include "gtest/gtest.h" |
| |
| static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) { |
| *first_frame_emitted = TRUE; |
| } |
| |
| TEST(FlViewTest, GetEngine) { |
| flutter::testing::fl_ensure_gtk_init(); |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* view = fl_view_new(project); |
| |
| // Check the engine is immediately available (i.e. before the widget is |
| // realized). |
| FlEngine* engine = fl_view_get_engine(view); |
| EXPECT_NE(engine, nullptr); |
| } |
| |
| TEST(FlViewTest, StateUpdateDoesNotHappenInInit) { |
| flutter::testing::fl_ensure_gtk_init(); |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* view = fl_view_new(project); |
| // Check that creating a view doesn't try to query the window state in |
| // initialization, causing a critical log to be issued. |
| EXPECT_EQ( |
| flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL, |
| (GLogLevelFlags)0x0); |
| |
| (void)view; |
| } |
| |
| TEST(FlViewTest, FirstFrameSignal) { |
| flutter::testing::fl_ensure_gtk_init(); |
| |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* view = fl_view_new(project); |
| gboolean first_frame_emitted = FALSE; |
| g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb), |
| &first_frame_emitted); |
| |
| EXPECT_FALSE(first_frame_emitted); |
| |
| fl_renderable_redraw(FL_RENDERABLE(view)); |
| |
| // Signal is emitted in idle, clear the main loop. |
| while (g_main_context_iteration(g_main_context_default(), FALSE)) { |
| // Repeat until nothing to iterate on. |
| } |
| |
| // Check view has detected frame. |
| EXPECT_TRUE(first_frame_emitted); |
| } |
| |
| // Check secondary view is registered with engine. |
| TEST(FlViewTest, SecondaryView) { |
| flutter::testing::fl_ensure_gtk_init(); |
| |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* implicit_view = fl_view_new(project); |
| |
| FlEngine* engine = fl_view_get_engine(implicit_view); |
| FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); |
| |
| FlutterViewId view_id = -1; |
| embedder_api->AddView = MOCK_ENGINE_PROC( |
| AddView, ([&view_id](auto engine, const FlutterAddViewInfo* info) { |
| view_id = info->view_id; |
| FlutterAddViewResult result = { |
| .struct_size = sizeof(FlutterAddViewResult), |
| .added = true, |
| .user_data = info->user_data}; |
| info->add_view_callback(&result); |
| return kSuccess; |
| })); |
| |
| FlView* secondary_view = fl_view_new_for_engine(engine); |
| EXPECT_EQ(view_id, fl_view_get_id(secondary_view)); |
| } |
| |
| // Check secondary view that fails registration. |
| TEST(FlViewTest, SecondaryViewError) { |
| flutter::testing::fl_ensure_gtk_init(); |
| |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* implicit_view = fl_view_new(project); |
| |
| FlEngine* engine = fl_view_get_engine(implicit_view); |
| FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); |
| |
| FlutterViewId view_id = -1; |
| embedder_api->AddView = MOCK_ENGINE_PROC( |
| AddView, ([&view_id](auto engine, const FlutterAddViewInfo* info) { |
| view_id = info->view_id; |
| return kInvalidArguments; |
| })); |
| |
| FlView* secondary_view = fl_view_new_for_engine(engine); |
| EXPECT_EQ(view_id, fl_view_get_id(secondary_view)); |
| } |
| |
| // Check views are deregistered on destruction. |
| TEST(FlViewTest, ViewDestroy) { |
| flutter::testing::fl_ensure_gtk_init(); |
| |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* implicit_view = fl_view_new(project); |
| |
| FlEngine* engine = fl_view_get_engine(implicit_view); |
| FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); |
| |
| g_autoptr(GPtrArray) removed_views = g_ptr_array_new(); |
| embedder_api->RemoveView = MOCK_ENGINE_PROC( |
| RemoveView, |
| ([removed_views](auto engine, const FlutterRemoveViewInfo* info) { |
| g_ptr_array_add(removed_views, GINT_TO_POINTER(info->view_id)); |
| return kSuccess; |
| })); |
| |
| FlView* secondary_view = fl_view_new_for_engine(engine); |
| |
| int64_t implicit_view_id = fl_view_get_id(implicit_view); |
| int64_t secondary_view_id = fl_view_get_id(secondary_view); |
| |
| gtk_widget_destroy(GTK_WIDGET(secondary_view)); |
| gtk_widget_destroy(GTK_WIDGET(implicit_view)); |
| |
| EXPECT_EQ(removed_views->len, 2u); |
| EXPECT_EQ(GPOINTER_TO_INT(g_ptr_array_index(removed_views, 0)), |
| secondary_view_id); |
| EXPECT_EQ(GPOINTER_TO_INT(g_ptr_array_index(removed_views, 1)), |
| implicit_view_id); |
| } |
| |
| // Check views deregistered with errors works. |
| TEST(FlViewTest, ViewDestroyError) { |
| flutter::testing::fl_ensure_gtk_init(); |
| |
| g_autoptr(FlDartProject) project = fl_dart_project_new(); |
| FlView* implicit_view = fl_view_new(project); |
| |
| FlEngine* engine = fl_view_get_engine(implicit_view); |
| FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); |
| |
| embedder_api->RemoveView = MOCK_ENGINE_PROC( |
| RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) { |
| return kInvalidArguments; |
| })); |
| |
| FlView* secondary_view = fl_view_new_for_engine(engine); |
| |
| gtk_widget_destroy(GTK_WIDGET(secondary_view)); |
| gtk_widget_destroy(GTK_WIDGET(implicit_view)); |
| } |