Skip to content

Commit

Permalink
WindowServer: Fix animation crash
Browse files Browse the repository at this point in the history
If an Animation's on_stop handler cleared itself inside the on_stop
event handler, it would remove itself from the animation map that was
still being iterated, leading to a crash.

To solve this, we'll iterate over the animations using the
remove_all_matching function, which enables us to delete it by simply
returning true when the animation finished. In the event that the
Animation is kept alive elsewhere and the on_stop event clears its own
reference, we need to temporarily bump the reference count. Another
advantage is that we only need to bump the reference count when an
animation is finished, whereas before this we bumped it
unconditionally.
  • Loading branch information
tomuta authored and awesomekling committed Mar 18, 2022
1 parent 56046d3 commit 9f59d7d
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 8 deletions.
13 changes: 9 additions & 4 deletions Userland/Services/WindowServer/Animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Animation::Animation()

Animation::~Animation()
{
Compositor::the().unregister_animation({}, *this);
if (!m_was_removed)
Compositor::the().unregister_animation({}, *this);
}

void Animation::set_duration(int duration_in_ms)
Expand All @@ -39,16 +40,20 @@ void Animation::stop()
on_stop();
}

void Animation::update(Badge<Compositor>, Gfx::Painter& painter, Screen& screen, Gfx::DisjointRectSet& flush_rects)
void Animation::was_removed(Badge<Compositor>)
{
m_was_removed = true;
}

bool Animation::update(Badge<Compositor>, Gfx::Painter& painter, Screen& screen, Gfx::DisjointRectSet& flush_rects)
{
int elapsed_ms = m_timer.elapsed();
float progress = min((float)elapsed_ms / (float)m_duration, 1.0f);

if (on_update)
on_update(progress, painter, screen, flush_rects);

if (progress >= 1.0f)
stop();
return progress < 1.0f;
}

}
4 changes: 3 additions & 1 deletion Userland/Services/WindowServer/Animation.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ class Animation : public RefCounted<Animation> {

void start();
void stop();
void was_removed(Badge<Compositor>);

void set_duration(int duration_in_ms);
int duration() const { return m_duration; }

void update(Badge<Compositor>, Gfx::Painter&, Screen&, Gfx::DisjointRectSet& flush_rects);
bool update(Badge<Compositor>, Gfx::Painter&, Screen&, Gfx::DisjointRectSet& flush_rects);

Function<void(float progress, Gfx::Painter&, Screen&, Gfx::DisjointRectSet& flush_rects)> on_update;
Function<void()> on_stop;
Expand All @@ -43,6 +44,7 @@ class Animation : public RefCounted<Animation> {
Core::ElapsedTimer m_timer;
int m_duration { 0 };
bool m_running { false };
bool m_was_removed { false };
};

}
22 changes: 19 additions & 3 deletions Userland/Services/WindowServer/Compositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1460,9 +1460,25 @@ void Compositor::unregister_animation(Badge<Animation>, Animation& animation)
void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_rects)
{
auto& painter = *screen.compositor_screen_data().m_back_painter;
for (RefPtr<Animation> animation : m_animations) {
animation->update({}, painter, screen, flush_rects);
}
// Iterating over the animations using remove_all_matching we can iterate
// and immediately remove finished animations without having to keep track
// of them in a separate container.
m_animations.remove_all_matching([&](auto* animation) {
if (!animation->update({}, painter, screen, flush_rects)) {
// Mark it as removed so that the Animation::on_stop handler doesn't
// trigger the Animation object from being destroyed, causing it to
// unregister while we still loop over them.
animation->was_removed({});

// Temporarily bump the ref count so that if the Animation::on_stop
// handler clears its own reference, it doesn't immediately destroy
// itself while we're still in the Function<> call
NonnullRefPtr<Animation> protect_animation(*animation);
animation->stop();
return true;
}
return false;
});
}

void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack)
Expand Down

0 comments on commit 9f59d7d

Please sign in to comment.