LibWeb: Protect audio codec callbacks against its own destruction

The plugin may go out of scope before the callbacks we've queued have
fired. It is undefined behavior for these callbacks to access the plugin
data after that has occurred.

This patch protects these callbacks with weak pointers. Note that the
plugin is not ref-counted, so we cannot use `strong_ref` here.
This commit is contained in:
Timothy Flynn 2025-03-14 09:19:03 -04:00 committed by Andreas Kling
commit 9586a7500c
Notes: github-actions[bot] 2025-03-14 17:03:21 +00:00
2 changed files with 48 additions and 25 deletions

View file

@ -87,8 +87,9 @@ AudioCodecPluginAgnostic::AudioCodecPluginAgnostic(NonnullRefPtr<Audio::Loader>
, m_main_thread_event_loop(Core::EventLoop::current())
, m_update_timer(move(update_timer))
{
m_update_timer->on_timeout = [this]() {
update_timestamp();
m_update_timer->on_timeout = [self = make_weak_ptr<AudioCodecPluginAgnostic>()]() {
if (self)
self->update_timestamp();
};
}
@ -96,10 +97,16 @@ void AudioCodecPluginAgnostic::resume_playback()
{
m_paused = false;
m_output->resume()
->when_resolved([this](AK::Duration new_device_time) {
m_main_thread_event_loop.deferred_invoke([this, new_device_time]() {
m_last_resume_in_device_time = new_device_time;
m_update_timer->start();
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>()](AK::Duration new_device_time) {
if (!self)
return;
self->m_main_thread_event_loop.deferred_invoke([self, new_device_time]() {
if (!self)
return;
self->m_last_resume_in_device_time = new_device_time;
self->m_update_timer->start();
});
})
.when_rejected([](Error&&) {
@ -111,15 +118,23 @@ void AudioCodecPluginAgnostic::pause_playback()
{
m_paused = true;
m_output->drain_buffer_and_suspend()
->when_resolved([this]() -> ErrorOr<void> {
auto new_media_time = timestamp_from_samples(m_loader->loaded_samples(), m_loader->sample_rate());
auto new_device_time = TRY(m_output->total_time_played());
m_main_thread_event_loop.deferred_invoke([this, new_media_time, new_device_time]() {
m_last_resume_in_media_time = new_media_time;
m_last_resume_in_device_time = new_device_time;
m_update_timer->stop();
update_timestamp();
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>()]() -> ErrorOr<void> {
if (!self)
return {};
auto new_media_time = timestamp_from_samples(self->m_loader->loaded_samples(), self->m_loader->sample_rate());
auto new_device_time = TRY(self->m_output->total_time_played());
self->m_main_thread_event_loop.deferred_invoke([self, new_media_time, new_device_time]() {
if (!self)
return;
self->m_last_resume_in_media_time = new_media_time;
self->m_last_resume_in_device_time = new_device_time;
self->m_update_timer->stop();
self->update_timestamp();
});
return {};
})
.when_rejected([](Error&&) {
@ -137,22 +152,29 @@ void AudioCodecPluginAgnostic::set_volume(double volume)
void AudioCodecPluginAgnostic::seek(double position)
{
m_output->discard_buffer_and_suspend()
->when_resolved([this, position, was_paused = m_paused]() -> ErrorOr<void> {
auto sample_position = static_cast<i32>(position * m_loader->sample_rate());
auto seek_result = m_loader->seek(sample_position);
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>(), position, was_paused = m_paused]() -> ErrorOr<void> {
if (!self)
return {};
auto sample_position = static_cast<i32>(position * self->m_loader->sample_rate());
auto seek_result = self->m_loader->seek(sample_position);
if (seek_result.is_error())
return Error::from_string_literal("Seeking in audio loader failed");
auto new_media_time = get_loader_timestamp(m_loader);
auto new_device_time = m_output->total_time_played().release_value_but_fixme_should_propagate_errors();
auto new_media_time = get_loader_timestamp(self->m_loader);
auto new_device_time = self->m_output->total_time_played().release_value_but_fixme_should_propagate_errors();
self->m_main_thread_event_loop.deferred_invoke([self, was_paused, new_device_time, new_media_time]() {
if (!self)
return;
self->m_last_resume_in_device_time = new_device_time;
self->m_last_resume_in_media_time = new_media_time;
m_main_thread_event_loop.deferred_invoke([this, was_paused, new_device_time, new_media_time]() {
m_last_resume_in_device_time = new_device_time;
m_last_resume_in_media_time = new_media_time;
if (was_paused) {
update_timestamp();
self->update_timestamp();
} else {
m_output->resume()->when_rejected([](Error&&) {
self->m_output->resume()->when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}