mutter/src/tests/stage-view-tests.c

1383 lines
48 KiB
C
Raw Normal View History

/*
* Copyright (C) 2020 Jonas Dreßler
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "clutter/clutter.h"
#include "clutter/clutter-stage-view-private.h"
#include "compositor/meta-window-actor-private.h"
#include "meta-test/meta-context-test.h"
#include "meta/meta-window-actor.h"
#include "tests/meta-backend-test.h"
#include "tests/meta-monitor-test-utils.h"
#include "tests/meta-test-utils.h"
#include "x11/meta-x11-display-private.h"
#define X11_TEST_CLIENT_NAME "x11_test_client"
#define X11_TEST_CLIENT_WINDOW "window1"
static MetaContext *test_context;
static MetaBackend *test_backend;
static MonitorTestCaseSetup initial_test_case_setup = {
.modes = {
{
.width = 1024,
.height = 768,
.refresh_rate = 60.0
}
},
.n_modes = 1,
.outputs = {
{
.crtc = 0,
.modes = { 0 },
.n_modes = 1,
.preferred_mode = 0,
.possible_crtcs = { 0 },
.n_possible_crtcs = 1,
.width_mm = 222,
.height_mm = 125
},
{
.crtc = 1,
.modes = { 0 },
.n_modes = 1,
.preferred_mode = 0,
.possible_crtcs = { 1 },
.n_possible_crtcs = 1,
.width_mm = 220,
.height_mm = 124
}
},
.n_outputs = 2,
.crtcs = {
{
.current_mode = 0
},
{
.current_mode = 0
}
},
.n_crtcs = 2
};
static void
meta_test_stage_views_exist (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage;
GList *stage_views;
stage = meta_backend_get_stage (backend);
g_assert_cmpint (clutter_actor_get_width (stage), ==, 1024 * 2);
g_assert_cmpint (clutter_actor_get_height (stage), ==, 768);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 2);
}
static void
on_after_paint (ClutterStage *stage,
ClutterStageView *view,
gboolean *was_painted)
{
*was_painted = TRUE;
}
static void
wait_for_paint (ClutterActor *stage)
{
gboolean was_painted = FALSE;
gulong was_painted_id;
was_painted_id = g_signal_connect (CLUTTER_STAGE (stage),
"after-paint",
G_CALLBACK (on_after_paint),
&was_painted);
while (!was_painted)
g_main_context_iteration (NULL, TRUE);
g_signal_handler_disconnect (stage, was_painted_id);
}
static void
on_stage_views_changed (ClutterActor *actor,
gboolean *stage_views_changed)
{
*stage_views_changed = TRUE;
}
static void
is_on_stage_views (ClutterActor *actor,
unsigned int n_views,
...)
{
va_list valist;
int i = 0;
GList *stage_views = clutter_actor_peek_stage_views (actor);
va_start (valist, n_views);
for (i = 0; i < n_views; i++)
{
ClutterStageView *view = va_arg (valist, ClutterStageView*);
g_assert_nonnull (g_list_find (stage_views, view));
}
va_end (valist);
g_assert (g_list_length (stage_views) == n_views);
}
static void
meta_test_actor_stage_views (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage, *container, *test_actor;
GList *stage_views;
gboolean stage_views_changed_container = FALSE;
gboolean stage_views_changed_test_actor = FALSE;
gboolean *stage_views_changed_container_ptr =
&stage_views_changed_container;
gboolean *stage_views_changed_test_actor_ptr =
&stage_views_changed_test_actor;
stage = meta_backend_get_stage (backend);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
container = clutter_actor_new ();
clutter_actor_set_size (container, 100, 100);
clutter_actor_add_child (stage, container);
test_actor = clutter_actor_new ();
clutter_actor_set_size (test_actor, 50, 50);
clutter_actor_add_child (container, test_actor);
g_signal_connect (container, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_container_ptr);
g_signal_connect (test_actor, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_test_actor_ptr);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (container, 1, stage_views->data);
is_on_stage_views (test_actor, 1, stage_views->data);
/* The signal was emitted for the initial change */
g_assert (stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_container = FALSE;
stage_views_changed_test_actor = FALSE;
/* Move the container to the second stage view */
clutter_actor_set_x (container, 1040);
wait_for_paint (stage);
is_on_stage_views (container, 1, stage_views->next->data);
is_on_stage_views (test_actor, 1, stage_views->next->data);
/* The signal was emitted again */
g_assert (stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_container = FALSE;
stage_views_changed_test_actor = FALSE;
/* Move the container so it's on both stage views while the test_actor
* is only on the first one.
*/
clutter_actor_set_x (container, 940);
wait_for_paint (stage);
is_on_stage_views (container, 2, stage_views->data, stage_views->next->data);
is_on_stage_views (test_actor, 1, stage_views->data);
/* The signal was emitted again */
g_assert (stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
g_signal_handlers_disconnect_by_func (container, on_stage_views_changed,
stage_views_changed_container_ptr);
g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed,
stage_views_changed_test_actor_ptr);
clutter_actor_destroy (container);
}
static void
on_relayout_actor_frame (ClutterTimeline *timeline,
int msec,
ClutterActor *actor)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage = meta_backend_get_stage (backend);
clutter_stage_clear_stage_views (CLUTTER_STAGE (stage));
}
static void
meta_test_actor_stage_views_relayout (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage, *actor;
ClutterTransition *transition;
GMainLoop *main_loop;
stage = meta_backend_get_stage (backend);
actor = clutter_actor_new ();
clutter_actor_set_size (actor, 100, 100);
clutter_actor_set_easing_duration (actor, 100);
clutter_actor_add_child (stage, actor);
clutter_actor_show (stage);
wait_for_paint (stage);
clutter_actor_set_position (actor, 1000.0, 0.0);
transition = clutter_actor_get_transition (actor, "position");
g_signal_connect_after (transition, "new-frame",
G_CALLBACK (on_relayout_actor_frame),
actor);
main_loop = g_main_loop_new (NULL, FALSE);
g_signal_connect_swapped (transition, "stopped",
G_CALLBACK (g_main_loop_quit),
main_loop);
g_main_loop_run (main_loop);
clutter_actor_destroy (actor);
g_main_loop_unref (main_loop);
}
static void
meta_test_actor_stage_views_reparent (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage, *container, *test_actor;
GList *stage_views;
gboolean stage_views_changed_container = FALSE;
gboolean stage_views_changed_test_actor = FALSE;
gboolean *stage_views_changed_container_ptr =
&stage_views_changed_container;
gboolean *stage_views_changed_test_actor_ptr =
&stage_views_changed_test_actor;
stage = meta_backend_get_stage (backend);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
container = clutter_actor_new ();
clutter_actor_set_size (container, 100, 100);
clutter_actor_set_x (container, 1020);
clutter_actor_add_child (stage, container);
test_actor = clutter_actor_new ();
clutter_actor_set_size (test_actor, 20, 20);
clutter_actor_add_child (container, test_actor);
g_signal_connect (container, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_container_ptr);
g_signal_connect (test_actor, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_test_actor_ptr);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (container, 2, stage_views->data, stage_views->next->data);
is_on_stage_views (test_actor, 2, stage_views->data, stage_views->next->data);
/* The signal was emitted for both actors */
g_assert (stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_container = FALSE;
stage_views_changed_test_actor = FALSE;
/* Remove the test_actor from the scene-graph */
g_object_ref (test_actor);
clutter_actor_remove_child (container, test_actor);
/* While the test_actor is not on stage, it must be on no stage views */
is_on_stage_views (test_actor, 0);
/* When the test_actor left the stage, the signal was emitted */
g_assert (!stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_test_actor = FALSE;
/* Add the test_actor again as a child of the stage */
clutter_actor_add_child (stage, test_actor);
g_object_unref (test_actor);
wait_for_paint (stage);
/* The container is still on both stage views... */
is_on_stage_views (container, 2, stage_views->data, stage_views->next->data);
/* ...while the test_actor is only on the first one now */
is_on_stage_views (test_actor, 1, stage_views->data);
/* The signal was emitted for the test_actor again */
g_assert (!stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_test_actor = FALSE;
/* Move the container out of the stage... */
clutter_actor_set_y (container, 2000);
g_object_ref (test_actor);
clutter_actor_remove_child (stage, test_actor);
/* When the test_actor left the stage, the signal was emitted */
g_assert (!stage_views_changed_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_test_actor = FALSE;
/* ...and reparent the test_actor to the container again */
clutter_actor_add_child (container, test_actor);
g_object_unref (test_actor);
wait_for_paint (stage);
/* Now both actors are on no stage views */
is_on_stage_views (container, 0);
is_on_stage_views (test_actor, 0);
/* The signal was emitted only for the container, the test_actor already
* has no stage-views.
*/
g_assert (stage_views_changed_container);
g_assert (!stage_views_changed_test_actor);
g_signal_handlers_disconnect_by_func (container, on_stage_views_changed,
stage_views_changed_container_ptr);
g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed,
stage_views_changed_test_actor_ptr);
clutter_actor_destroy (container);
}
static void
meta_test_actor_stage_views_hide_parent (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage, *outer_container, *inner_container, *test_actor;
GList *stage_views;
gboolean stage_views_changed_outer_container = FALSE;
gboolean stage_views_changed_inner_container = FALSE;
gboolean stage_views_changed_test_actor = FALSE;
gboolean *stage_views_changed_outer_container_ptr =
&stage_views_changed_outer_container;
gboolean *stage_views_changed_inner_container_ptr =
&stage_views_changed_inner_container;
gboolean *stage_views_changed_test_actor_ptr =
&stage_views_changed_test_actor;
stage = meta_backend_get_stage (backend);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
outer_container = clutter_actor_new ();
clutter_actor_add_child (stage, outer_container);
inner_container = clutter_actor_new ();
clutter_actor_add_child (outer_container, inner_container);
test_actor = clutter_actor_new ();
clutter_actor_set_size (test_actor, 20, 20);
clutter_actor_add_child (inner_container, test_actor);
g_signal_connect (outer_container, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_outer_container_ptr);
g_signal_connect (inner_container, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_inner_container_ptr);
g_signal_connect (test_actor, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
stage_views_changed_test_actor_ptr);
clutter_actor_show (stage);
wait_for_paint (stage);
/* The containers and the test_actor are on all on the first view */
is_on_stage_views (outer_container, 1, stage_views->data);
is_on_stage_views (inner_container, 1, stage_views->data);
is_on_stage_views (test_actor, 1, stage_views->data);
/* The signal was emitted for all three */
g_assert (stage_views_changed_outer_container);
g_assert (stage_views_changed_inner_container);
g_assert (stage_views_changed_test_actor);
stage_views_changed_outer_container = FALSE;
stage_views_changed_inner_container = FALSE;
stage_views_changed_test_actor = FALSE;
/* Hide the inner_container */
clutter_actor_hide (inner_container);
/* Move the outer_container so it's still on the first view */
clutter_actor_set_x (outer_container, 1023);
wait_for_paint (stage);
/* The outer_container is still expanded so it should be on both views */
is_on_stage_views (outer_container, 2,
stage_views->data, stage_views->next->data);
/* The inner_container and test_actor aren't updated because they're hidden */
is_on_stage_views (inner_container, 1, stage_views->data);
is_on_stage_views (test_actor, 1, stage_views->data);
/* The signal was emitted for the outer_container */
g_assert (stage_views_changed_outer_container);
g_assert (!stage_views_changed_inner_container);
g_assert (!stage_views_changed_test_actor);
stage_views_changed_outer_container = FALSE;
/* Show the inner_container again */
clutter_actor_show (inner_container);
wait_for_paint (stage);
/* All actors are on both views now */
is_on_stage_views (outer_container, 2,
stage_views->data, stage_views->next->data);
is_on_stage_views (inner_container, 2,
stage_views->data, stage_views->next->data);
is_on_stage_views (test_actor, 2,
stage_views->data, stage_views->next->data);
/* The signal was emitted for the inner_container and test_actor */
g_assert (!stage_views_changed_outer_container);
g_assert (stage_views_changed_inner_container);
g_assert (stage_views_changed_test_actor);
g_signal_handlers_disconnect_by_func (outer_container, on_stage_views_changed,
stage_views_changed_outer_container_ptr);
g_signal_handlers_disconnect_by_func (inner_container, on_stage_views_changed,
stage_views_changed_inner_container_ptr);
g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed,
stage_views_changed_test_actor_ptr);
clutter_actor_destroy (outer_container);
}
static MetaMonitorTestSetup *
create_stage_view_test_setup (MetaBackend *backend)
{
return meta_create_monitor_test_setup (backend,
&initial_test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
}
static void
assert_is_stage_view (ClutterStageView *stage_view,
int x,
int y,
int width,
int height)
{
cairo_rectangle_int_t layout;
g_assert_nonnull (stage_view);
g_assert_true (CLUTTER_IS_STAGE_VIEW (stage_view));
clutter_stage_view_get_layout (stage_view, &layout);
g_assert_cmpint (layout.x, ==, x);
g_assert_cmpint (layout.y, ==, y);
g_assert_cmpint (layout.width, ==, width);
g_assert_cmpint (layout.height, ==, height);
}
static void
meta_test_actor_stage_views_hot_plug (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
ClutterActor *stage = meta_backend_get_stage (backend);
ClutterActor *actor_1;
ClutterActor *actor_2;
GList *stage_views;
GList *prev_stage_views;
MonitorTestCaseSetup hotplug_test_case_setup = initial_test_case_setup;
MetaMonitorTestSetup *test_setup;
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 2);
assert_is_stage_view (stage_views->data, 0, 0, 1024, 768);
assert_is_stage_view (stage_views->next->data, 1024, 0, 1024, 768);
actor_1 = clutter_actor_new ();
clutter_actor_set_size (actor_1, 100, 100);
clutter_actor_set_position (actor_1, 100, 100);
clutter_actor_add_child (stage, actor_1);
actor_2 = clutter_actor_new ();
clutter_actor_set_size (actor_2, 100, 100);
clutter_actor_set_position (actor_2, 1100, 100);
clutter_actor_add_child (stage, actor_2);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (actor_1, 1, stage_views->data);
is_on_stage_views (actor_2, 1, stage_views->next->data);
prev_stage_views = g_list_copy_deep (stage_views,
(GCopyFunc) g_object_ref, NULL);
test_setup = meta_create_monitor_test_setup (test_backend,
&hotplug_test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert (stage_views != prev_stage_views);
g_assert_cmpint (g_list_length (stage_views), ==, 2);
g_assert (prev_stage_views->data != stage_views->data);
g_assert (prev_stage_views->next->data != stage_views->next->data);
assert_is_stage_view (stage_views->data, 0, 0, 1024, 768);
assert_is_stage_view (stage_views->next->data, 1024, 0, 1024, 768);
g_list_free_full (prev_stage_views, (GDestroyNotify) g_object_unref);
is_on_stage_views (actor_1, 0);
is_on_stage_views (actor_2, 0);
wait_for_paint (stage);
is_on_stage_views (actor_1, 1, stage_views->data);
is_on_stage_views (actor_2, 1, stage_views->next->data);
clutter_actor_destroy (actor_1);
clutter_actor_destroy (actor_2);
}
static void
meta_test_actor_stage_views_frame_clock (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
ClutterActor *stage = meta_backend_get_stage (backend);
ClutterActor *actor_1;
ClutterActor *actor_2;
ClutterActor *actor_3;
GList *stage_views;
MonitorTestCaseSetup frame_clock_test_setup = initial_test_case_setup;
MetaMonitorTestSetup *test_setup;
ClutterFrameClock *frame_clock;
frame_clock_test_setup.modes[1].width = 1024;
frame_clock_test_setup.modes[1].height = 768;
frame_clock_test_setup.modes[1].refresh_rate = 30.0;
frame_clock_test_setup.n_modes = 2;
frame_clock_test_setup.outputs[1].modes[0] = 1;
frame_clock_test_setup.outputs[1].preferred_mode = 1;
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpfloat (clutter_stage_view_get_refresh_rate (stage_views->data),
==,
60.0);
g_assert_cmpfloat (clutter_stage_view_get_refresh_rate (stage_views->next->data),
==,
30.0);
actor_1 = clutter_actor_new ();
clutter_actor_set_size (actor_1, 100, 100);
clutter_actor_set_position (actor_1, 100, 100);
clutter_actor_add_child (stage, actor_1);
actor_2 = clutter_actor_new ();
clutter_actor_set_size (actor_2, 100, 100);
clutter_actor_set_position (actor_2, 1100, 100);
clutter_actor_add_child (stage, actor_2);
actor_3 = clutter_actor_new ();
clutter_actor_set_size (actor_3, 100, 100);
clutter_actor_set_position (actor_3, 1000, 400);
clutter_actor_add_child (stage, actor_3);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (actor_1, 1, stage_views->data);
is_on_stage_views (actor_2, 1, stage_views->next->data);
is_on_stage_views (actor_3, 2,
stage_views->data,
stage_views->next->data);
frame_clock = clutter_actor_pick_frame_clock (actor_1, NULL);
g_assert_cmpfloat (clutter_frame_clock_get_refresh_rate (frame_clock),
==,
60.0);
frame_clock = clutter_actor_pick_frame_clock (actor_2, NULL);
g_assert_cmpfloat (clutter_frame_clock_get_refresh_rate (frame_clock),
==,
30.0);
frame_clock = clutter_actor_pick_frame_clock (actor_3, NULL);
g_assert_cmpfloat (clutter_frame_clock_get_refresh_rate (frame_clock),
==,
60.0);
clutter_actor_destroy (actor_1);
clutter_actor_destroy (actor_2);
clutter_actor_destroy (actor_3);
}
typedef struct _TimelineTest
{
GMainLoop *main_loop;
ClutterFrameClock *frame_clock_1;
ClutterFrameClock *frame_clock_2;
int phase;
int frame_counter[2];
} TimelineTest;
static void
on_transition_stopped (ClutterTransition *transition,
gboolean is_finished,
TimelineTest *test)
{
g_assert_true (is_finished);
g_assert_cmpint (test->phase, ==, 2);
test->phase = 3;
g_main_loop_quit (test->main_loop);
}
static void
on_transition_new_frame (ClutterTransition *transition,
int elapsed_time_ms,
TimelineTest *test)
{
ClutterTimeline *timeline = CLUTTER_TIMELINE (transition);
if (test->phase == 1)
{
g_assert (clutter_timeline_get_frame_clock (timeline) ==
test->frame_clock_1);
test->frame_counter[0]++;
}
else if (test->phase == 2)
{
g_assert (clutter_timeline_get_frame_clock (timeline) ==
test->frame_clock_2);
test->frame_counter[1]++;
}
else
{
g_assert_not_reached ();
}
}
static void
on_transition_frame_clock_changed (ClutterTimeline *timeline,
GParamSpec *pspec,
TimelineTest *test)
{
ClutterFrameClock *frame_clock;
frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert (frame_clock == test->frame_clock_2);
g_assert_cmpint (test->phase, ==, 1);
test->phase = 2;
}
static void
meta_test_actor_stage_views_timeline (void)
{
TimelineTest test = { 0 };
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
ClutterActor *stage = meta_backend_get_stage (backend);
MonitorTestCaseSetup frame_clock_test_setup;
ClutterActor *actor;
GList *stage_views;
ClutterStageView *stage_view_1;
ClutterStageView *stage_view_2;
MetaMonitorTestSetup *test_setup;
ClutterTransition *transition;
frame_clock_test_setup = initial_test_case_setup;
frame_clock_test_setup.modes[1].width = 1024;
frame_clock_test_setup.modes[1].height = 768;
frame_clock_test_setup.modes[1].refresh_rate = 30.0;
frame_clock_test_setup.n_modes = 2;
frame_clock_test_setup.outputs[1].modes[0] = 1;
frame_clock_test_setup.outputs[1].preferred_mode = 1;
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
stage_view_1 = stage_views->data;
stage_view_2 = stage_views->next->data;
g_assert_nonnull (stage_view_1);
g_assert_nonnull (stage_view_2);
test.frame_clock_1 = clutter_stage_view_get_frame_clock (stage_view_1);
test.frame_clock_2 = clutter_stage_view_get_frame_clock (stage_view_2);
g_assert_nonnull (test.frame_clock_1);
g_assert_nonnull (test.frame_clock_2);
actor = clutter_actor_new ();
clutter_actor_set_size (actor, 100, 100);
clutter_actor_set_position (actor, 800, 100);
clutter_actor_add_child (stage, actor);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (actor, 1, stage_views->data);
clutter_actor_set_easing_duration (actor, 1000);
clutter_actor_set_position (actor, 1200, 300);
transition = clutter_actor_get_transition (actor, "position");
g_assert_nonnull (transition);
g_assert (clutter_timeline_get_frame_clock (CLUTTER_TIMELINE (transition)) ==
test.frame_clock_1);
test.main_loop = g_main_loop_new (NULL, FALSE);
g_signal_connect (transition, "stopped",
G_CALLBACK (on_transition_stopped),
&test);
g_signal_connect (transition, "new-frame",
G_CALLBACK (on_transition_new_frame),
&test);
g_signal_connect (transition, "notify::frame-clock",
G_CALLBACK (on_transition_frame_clock_changed),
&test);
test.phase = 1;
g_main_loop_run (test.main_loop);
g_assert_cmpint (test.phase, ==, 3);
g_assert_cmpint (test.frame_counter[0], >, 0);
g_assert_cmpint (test.frame_counter[1], >, 0);
clutter_actor_destroy (actor);
g_main_loop_unref (test.main_loop);
}
static void
meta_test_actor_stage_views_parent_views_rebuilt (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
MonitorTestCaseSetup frame_clock_test_setup;
MetaMonitorTestSetup *test_setup;
ClutterActor *stage, *container, *test_actor;
GList *stage_views;
ClutterTimeline *timeline;
ClutterFrameClock *timeline_frame_clock;
ClutterFrameClock *view_frame_clock;
ClutterStageView *old_stage_view;
ClutterFrameClock *old_frame_clock;
stage = meta_backend_get_stage (backend);
frame_clock_test_setup = initial_test_case_setup;
frame_clock_test_setup.n_outputs = 1;
frame_clock_test_setup.n_crtcs = 1;
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 1);
container = clutter_actor_new ();
clutter_actor_set_size (container, 100, 100);
clutter_actor_set_position (container, 0, 0);
clutter_actor_add_child (stage, container);
test_actor = clutter_actor_new ();
clutter_actor_set_size (test_actor, 0, 0);
clutter_actor_add_child (container, test_actor);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (test_actor, 0);
is_on_stage_views (container, 1, stage_views->data);
is_on_stage_views (stage, 1, stage_views->data);
timeline = clutter_timeline_new_for_actor (test_actor, 100);
clutter_timeline_start (timeline);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
view_frame_clock = clutter_stage_view_get_frame_clock (stage_views->data);
g_assert_nonnull (timeline_frame_clock);
g_assert_nonnull (view_frame_clock);
g_assert (timeline_frame_clock == view_frame_clock);
/* Keep the stage view alive so it can be used to compare with later. */
old_stage_view = g_object_ref (stage_views->data);
old_frame_clock =
g_object_ref (clutter_stage_view_get_frame_clock (old_stage_view));
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
wait_for_paint (stage);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 1);
g_assert (stage_views->data != old_stage_view);
view_frame_clock = clutter_stage_view_get_frame_clock (stage_views->data);
g_assert_nonnull (view_frame_clock);
g_assert (view_frame_clock != old_frame_clock);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_nonnull (timeline_frame_clock);
g_assert (timeline_frame_clock == view_frame_clock);
g_object_unref (old_stage_view);
g_object_unref (old_frame_clock);
clutter_actor_destroy (test_actor);
clutter_actor_destroy (container);
}
static void
meta_test_actor_stage_views_parent_views_changed (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
MonitorTestCaseSetup frame_clock_test_setup;
MetaMonitorTestSetup *test_setup;
ClutterActor *stage, *container, *test_actor;
GList *stage_views;
ClutterTimeline *timeline;
ClutterFrameClock *timeline_frame_clock;
ClutterFrameClock *first_view_frame_clock;
ClutterFrameClock *second_view_frame_clock;
stage = meta_backend_get_stage (backend);
frame_clock_test_setup = initial_test_case_setup;
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 2);
container = clutter_actor_new ();
clutter_actor_set_size (container, 100, 100);
clutter_actor_set_position (container, 0, 0);
clutter_actor_add_child (stage, container);
test_actor = clutter_actor_new ();
clutter_actor_set_size (test_actor, 0, 0);
clutter_actor_add_child (container, test_actor);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 2);
clutter_actor_show (stage);
wait_for_paint (stage);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 2);
is_on_stage_views (test_actor, 0);
is_on_stage_views (container, 1, stage_views->data);
is_on_stage_views (stage, 2,
stage_views->data,
stage_views->next->data);
timeline = clutter_timeline_new_for_actor (test_actor, 100);
clutter_timeline_start (timeline);
first_view_frame_clock =
clutter_stage_view_get_frame_clock (stage_views->data);
second_view_frame_clock =
clutter_stage_view_get_frame_clock (stage_views->next->data);
g_assert_nonnull (first_view_frame_clock);
g_assert_nonnull (second_view_frame_clock);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_nonnull (timeline_frame_clock);
g_assert (timeline_frame_clock == first_view_frame_clock);
clutter_actor_set_x (container, 1200);
wait_for_paint (stage);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_nonnull (timeline_frame_clock);
g_assert (timeline_frame_clock == second_view_frame_clock);
clutter_actor_destroy (test_actor);
clutter_actor_destroy (container);
}
static void
meta_test_actor_stage_views_and_frame_clocks_freed (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
ClutterActor *stage = meta_backend_get_stage (backend);
ClutterActor *actor_1;
ClutterActor *actor_2;
GList *stage_views;
ClutterStageView *first_view;
ClutterStageView *second_view;
ClutterTimeline *timeline;
ClutterFrameClock *timeline_frame_clock;
ClutterFrameClock *first_view_frame_clock;
ClutterFrameClock *second_view_frame_clock;
MonitorTestCaseSetup frame_clock_test_setup;
MetaMonitorTestSetup *test_setup;
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
first_view = stage_views->data;
second_view = stage_views->next->data;
g_object_add_weak_pointer (G_OBJECT (first_view), (gpointer *) &first_view);
g_object_add_weak_pointer (G_OBJECT (second_view), (gpointer *) &second_view);
/* Create two actors, one on the first stage view, another one on the
* second view.
*/
actor_1 = clutter_actor_new ();
clutter_actor_set_size (actor_1, 100, 100);
clutter_actor_set_position (actor_1, 100, 100);
clutter_actor_add_child (stage, actor_1);
actor_2 = clutter_actor_new ();
clutter_actor_set_size (actor_2, 100, 100);
clutter_actor_set_position (actor_2, 1100, 100);
clutter_actor_add_child (stage, actor_2);
clutter_actor_show (stage);
wait_for_paint (stage);
is_on_stage_views (actor_1, 1, first_view);
is_on_stage_views (actor_2, 1, second_view);
/* Now create a timeline for the first actor and make sure its using the
* frame clock of the first view.
*/
timeline = clutter_timeline_new_for_actor (actor_1, 100);
clutter_timeline_start (timeline);
first_view_frame_clock =
clutter_stage_view_get_frame_clock (first_view);
second_view_frame_clock =
clutter_stage_view_get_frame_clock (second_view);
g_assert_nonnull (first_view_frame_clock);
g_assert_nonnull (second_view_frame_clock);
g_object_add_weak_pointer (G_OBJECT (first_view_frame_clock),
(gpointer *) &first_view_frame_clock);
g_object_add_weak_pointer (G_OBJECT (second_view_frame_clock),
(gpointer *) &second_view_frame_clock);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_nonnull (timeline_frame_clock);
g_assert (timeline_frame_clock == first_view_frame_clock);
/* Now set the timeline actor to actor_2 and make sure the timeline is
* using the second frame clock.
*/
clutter_timeline_set_actor (timeline, actor_2);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_nonnull (timeline_frame_clock);
g_assert (timeline_frame_clock == second_view_frame_clock);
/* Trigger a hotplug and remove both monitors, after that the timeline
* should have no frame clock set and both stage views and their
* frame clocks should have been freed.
*/
frame_clock_test_setup = initial_test_case_setup;
frame_clock_test_setup.n_outputs = 0;
frame_clock_test_setup.n_crtcs = 0;
test_setup = meta_create_monitor_test_setup (test_backend,
&frame_clock_test_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
timeline_frame_clock = clutter_timeline_get_frame_clock (timeline);
g_assert_null (timeline_frame_clock);
g_assert_null (first_view);
g_assert_null (first_view_frame_clock);
g_assert_null (second_view);
g_assert_null (second_view_frame_clock);
clutter_actor_destroy (actor_1);
clutter_actor_destroy (actor_2);
}
static void
ensure_view_count (int n_views)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage = meta_backend_get_stage (backend);
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
MonitorTestCaseSetup test_case_setup;
MetaMonitorTestSetup *test_setup;
GList *stage_views;
test_case_setup = initial_test_case_setup;
test_case_setup.n_outputs = n_views;
test_case_setup.n_crtcs = n_views;
test_setup = meta_create_monitor_test_setup (test_backend,
&test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpuint (g_list_length (stage_views), ==, n_views);
}
static gboolean
tests_alarm_filter (MetaX11Display *x11_display,
XSyncAlarmNotifyEvent *event,
gpointer user_data)
{
MetaTestClient *test_client = user_data;
return meta_test_client_process_x11_event (test_client,
x11_display, event);
}
static void
check_test_client_state (MetaTestClient *test_client)
{
GError *error = NULL;
if (!meta_test_client_wait (test_client, &error))
{
g_error ("Failed to sync test client '%s': %s",
meta_test_client_get_id (test_client), error->message);
}
}
static void
meta_test_actor_stage_views_queue_frame_drawn (void)
{
MetaBackend *backend = meta_context_get_backend (test_context);
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
MetaDisplay *display;
MetaX11Display *x11_display;
ClutterActor *stage = meta_backend_get_stage (backend);
MetaTestClient *x11_test_client;
MonitorTestCaseSetup hotplug_test_case_setup = initial_test_case_setup;
MetaMonitorTestSetup *test_setup;
GError *error = NULL;
MetaWindow *test_window;
ClutterActor *window_actor;
x11_test_client = meta_test_client_new (test_context,
X11_TEST_CLIENT_NAME,
META_WINDOW_CLIENT_TYPE_X11,
&error);
if (!x11_test_client)
g_error ("Failed to launch X11 test client: %s", error->message);
display = meta_context_get_display (test_context);
x11_display = meta_display_get_x11_display (display);
meta_x11_display_set_alarm_filter (x11_display,
tests_alarm_filter,
x11_test_client);
if (!meta_test_client_do (x11_test_client, &error,
"create", X11_TEST_CLIENT_WINDOW,
NULL))
g_error ("Failed to create X11 window: %s", error->message);
if (!meta_test_client_do (x11_test_client, &error,
"show", X11_TEST_CLIENT_WINDOW,
NULL))
g_error ("Failed to show the window: %s", error->message);
check_test_client_state (x11_test_client);
/* Make sure we have a single output. */
hotplug_test_case_setup.n_outputs = 1;
hotplug_test_case_setup.n_crtcs = 1;
test_setup = meta_create_monitor_test_setup (test_backend,
&hotplug_test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
wait_for_paint (stage);
g_assert_cmpint (g_list_length (clutter_actor_peek_stage_views (stage)),
==,
1);
/* Find client window actor and ensure it's on a stage view. */
test_window = meta_test_client_find_window (x11_test_client,
X11_TEST_CLIENT_WINDOW,
&error);
if (!test_window)
g_error ("Failed to find the window: %s", error->message);
window_actor = CLUTTER_ACTOR (meta_window_actor_from_window (test_window));
g_assert_nonnull (clutter_actor_peek_stage_views (window_actor));
/* Queue an X11 _NET_WM_FRAME_DRAWN event; this will find the frame clock via
* the actor stage view list.
*/
meta_window_actor_queue_frame_drawn (META_WINDOW_ACTOR (window_actor), TRUE);
/* Hotplug to rebuild the views, will clear the window actor view list. */
test_setup = meta_create_monitor_test_setup (test_backend,
&hotplug_test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
g_assert_null (clutter_actor_peek_stage_views (window_actor));
/* Queue an X11 _NET_WM_FRAME_DRAWN event; this will find the frame clock via
* the stage's frame clock, as the actor hasn't been been through relayout.
*/
meta_window_actor_queue_frame_drawn (META_WINDOW_ACTOR (window_actor), TRUE);
/* Hotplug again to re-rebuild the views, will again clear the window actor
* view list, which will be a no-op. */
test_setup = meta_create_monitor_test_setup (test_backend,
&hotplug_test_case_setup,
MONITOR_TEST_FLAG_NO_STORED);
meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
/* Make sure we're not using some old frame clock when queuing another
* _NET_WM_FRAME_DRAWN event. */
meta_window_actor_queue_frame_drawn (META_WINDOW_ACTOR (window_actor), TRUE);
wait_for_paint (stage);
if (!meta_test_client_quit (x11_test_client, &error))
g_error ("Failed to quit X11 test client: %s", error->message);
meta_test_client_destroy (x11_test_client);
meta_x11_display_set_alarm_filter (x11_display, NULL, NULL);
}
static void
meta_test_timeline_actor_destroyed (void)
{
MetaBackend *backend = meta_get_backend ();
ClutterActor *stage;
GList *stage_views;
ClutterActor *persistent_actor;
ClutterActor *actor;
ClutterTimeline *timeline;
gboolean did_stage_views_changed = FALSE;
ensure_view_count (0);
stage = meta_backend_get_stage (backend);
clutter_actor_show (stage);
persistent_actor = clutter_actor_new ();
clutter_actor_add_child (stage, persistent_actor);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_null (stage_views);
stage_views = clutter_actor_peek_stage_views (stage);
g_assert_null (stage_views);
g_assert_null (clutter_actor_pick_frame_clock (stage, NULL));
actor = clutter_actor_new ();
clutter_actor_add_child (stage, actor);
g_assert_null (clutter_actor_pick_frame_clock (actor, NULL));
timeline = clutter_timeline_new_for_actor (actor, 100);
clutter_timeline_start (timeline);
g_signal_connect (stage, "stage-views-changed",
G_CALLBACK (on_stage_views_changed),
&did_stage_views_changed);
clutter_actor_destroy (actor);
g_object_unref (timeline);
ensure_view_count (1);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
g_assert_cmpint (g_list_length (stage_views), ==, 1);
g_assert_false (did_stage_views_changed);
clutter_actor_queue_redraw (persistent_actor);
clutter_stage_schedule_update (CLUTTER_STAGE (stage));
wait_for_paint (stage);
g_assert_true (did_stage_views_changed);
g_signal_handlers_disconnect_by_func (stage, on_stage_views_changed,
&did_stage_views_changed);
clutter_actor_destroy (persistent_actor);
}
clutter/actor: First clear all stage views before emitting they changed If one would end up with an actor attached to mapped actor, where the attached actor doesn't itself have an up to date stage view list while listening on the stage for updating, when clearing the stage views of the list, anything that would query the stage views list at this time would end up accessing freed memory. This could happen if 1) An actor was added to a newly created container actor attached to the stage 2) The actor got a timeline attached to it 3) The actor was moved to a container that already was mapped 4) A hotplug happened After (1) both the container and actor would not have any stage views. After (2) the timeline would listen on the stage for stage views updates. After (3) the actor would still listen on the stage for stage views updates. When (4) happened, the actor would be signalled when the stage got its stage view cleared, at which point it would traverse up its actor's tree finding an appropriate stage view to base its animation on. The problem here would be that it'd query the already mapped container and its yet-to-be-cleared stage view list, resulting in use-after free, resulting in for example the following backtrace: 0) g_type_check_instance_cast () 1) CLUTTER_STAGE_VIEW () 2) clutter_actor_pick_frame_clock () 3) clutter_actor_pick_frame_clock () 4) update_frame_clock () 5) on_frame_clock_actor_stage_views_changed () 6) g_closure_invoke () 7) signal_emit_unlocked_R () 8) g_signal_emit_valist () 9) g_signal_emit () 10) clear_stage_views_cb () 11) _clutter_actor_traverse_depth () 12) _clutter_actor_traverse () 13) clutter_actor_clear_stage_views_recursive () 14) clutter_stage_clear_stage_views () ... Avoid this issue by making sure that we don't emit 'stage-views-changed' signals while the actor tree is in an invalid state. While we now end up traversing tree twice, it doesn't change the Big-O notation. It has not been measured whether this has any noticible performance impact. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1950 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2025>
2021-09-28 05:26:25 -04:00
static void
meta_test_timeline_actor_tree_clear (void)
{
ClutterActor *stage;
ClutterActor *container1;
ClutterActor *container2;
g_autoptr (ClutterActor) floating = NULL;
g_autoptr (ClutterTimeline) timeline = NULL;
GList *stage_views;
stage = meta_backend_get_stage (meta_context_get_backend (test_context));
ensure_view_count (1);
container1 = clutter_actor_new ();
clutter_actor_set_size (container1, 100, 100);
clutter_actor_add_child (stage, container1);
wait_for_paint (stage);
container2 = clutter_actor_new ();
clutter_actor_set_size (container2, 100, 100);
clutter_actor_add_child (stage, container2);
floating = g_object_ref_sink (clutter_actor_new ());
clutter_actor_set_size (floating, 100, 100);
clutter_actor_add_child (container2, floating);
timeline = clutter_timeline_new_for_actor (floating, 100);
clutter_actor_remove_child (container2, floating);
clutter_actor_add_child (container1, floating);
ensure_view_count (1);
is_on_stage_views (container1, 0);
is_on_stage_views (container2, 0);
is_on_stage_views (floating, 0);
wait_for_paint (stage);
stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
is_on_stage_views (container1, 1, stage_views->data);
is_on_stage_views (container2, 1, stage_views->data);
is_on_stage_views (floating, 1, stage_views->data);
clutter_actor_destroy (floating);
clutter_actor_destroy (container1);
clutter_actor_destroy (container2);
}
static void
on_before_tests (MetaContext *context)
{
test_backend = meta_context_get_backend (context);
}
static void
init_tests (void)
{
meta_init_monitor_test_setup (create_stage_view_test_setup);
g_test_add_func ("/stage-view/stage-views-exist",
meta_test_stage_views_exist);
g_test_add_func ("/stage-views/actor-stage-views",
meta_test_actor_stage_views);
g_test_add_func ("/stage-views/actor-stage-views-relayout",
meta_test_actor_stage_views_relayout);
g_test_add_func ("/stage-views/actor-stage-views-reparent",
meta_test_actor_stage_views_reparent);
g_test_add_func ("/stage-views/actor-stage-views-hide-parent",
meta_test_actor_stage_views_hide_parent);
g_test_add_func ("/stage-views/actor-stage-views-hot-plug",
meta_test_actor_stage_views_hot_plug);
g_test_add_func ("/stage-views/actor-stage-views-frame-clock",
meta_test_actor_stage_views_frame_clock);
g_test_add_func ("/stage-views/actor-stage-views-timeline",
meta_test_actor_stage_views_timeline);
g_test_add_func ("/stage-views/actor-stage-views-parent-rebuilt",
meta_test_actor_stage_views_parent_views_rebuilt);
g_test_add_func ("/stage-views/actor-stage-views-parent-changed",
meta_test_actor_stage_views_parent_views_changed);
g_test_add_func ("/stage-views/actor-stage-views-and-frame-clocks-freed",
meta_test_actor_stage_views_and_frame_clocks_freed);
g_test_add_func ("/stage-views/actor-stage-viwes-queue-frame-drawn",
meta_test_actor_stage_views_queue_frame_drawn);
g_test_add_func ("/stage-views/timeline/actor-destroyed",
meta_test_timeline_actor_destroyed);
clutter/actor: First clear all stage views before emitting they changed If one would end up with an actor attached to mapped actor, where the attached actor doesn't itself have an up to date stage view list while listening on the stage for updating, when clearing the stage views of the list, anything that would query the stage views list at this time would end up accessing freed memory. This could happen if 1) An actor was added to a newly created container actor attached to the stage 2) The actor got a timeline attached to it 3) The actor was moved to a container that already was mapped 4) A hotplug happened After (1) both the container and actor would not have any stage views. After (2) the timeline would listen on the stage for stage views updates. After (3) the actor would still listen on the stage for stage views updates. When (4) happened, the actor would be signalled when the stage got its stage view cleared, at which point it would traverse up its actor's tree finding an appropriate stage view to base its animation on. The problem here would be that it'd query the already mapped container and its yet-to-be-cleared stage view list, resulting in use-after free, resulting in for example the following backtrace: 0) g_type_check_instance_cast () 1) CLUTTER_STAGE_VIEW () 2) clutter_actor_pick_frame_clock () 3) clutter_actor_pick_frame_clock () 4) update_frame_clock () 5) on_frame_clock_actor_stage_views_changed () 6) g_closure_invoke () 7) signal_emit_unlocked_R () 8) g_signal_emit_valist () 9) g_signal_emit () 10) clear_stage_views_cb () 11) _clutter_actor_traverse_depth () 12) _clutter_actor_traverse () 13) clutter_actor_clear_stage_views_recursive () 14) clutter_stage_clear_stage_views () ... Avoid this issue by making sure that we don't emit 'stage-views-changed' signals while the actor tree is in an invalid state. While we now end up traversing tree twice, it doesn't change the Big-O notation. It has not been measured whether this has any noticible performance impact. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1950 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2025>
2021-09-28 05:26:25 -04:00
g_test_add_func ("/stage-views/timeline/tree-clear",
meta_test_timeline_actor_tree_clear);
}
int
main (int argc, char *argv[])
{
g_autoptr (MetaContext) context = NULL;
g_setenv ("MUTTER_DEBUG_DISABLE_ANIMATIONS", "1", TRUE);
context = meta_create_test_context (META_CONTEXT_TEST_TYPE_NESTED,
META_CONTEXT_TEST_FLAG_TEST_CLIENT);
g_assert (meta_context_configure (context, &argc, &argv, NULL));
test_context = context;
init_tests ();
g_signal_connect (context, "before-tests",
G_CALLBACK (on_before_tests), NULL);
return meta_context_test_run_tests (META_CONTEXT_TEST (context),
META_TEST_RUN_FLAG_NONE);
}