WebContent: Give paint requests a chance to happen between input events

Before this patch, we had an issue where the WebContent process could
get backed up with tons of pending input events (especially mouse moves)
and have to work through all of those before responding to a paint
request from the UI process.

This could lead to a situation where we went for a very long time
without seeing any visual updates.

The approach I've taken here is pretty simple, we basically make a queue
of all incoming input events on the WebContent process side, and then
process that queue one event at a time, using a zero timer. This is
basic, but it allows paint requests to come in between the input events
and we do now get more frequent visual updates even during heavy
pressure from input events.
This commit is contained in:
Andreas Kling 2023-03-14 13:26:06 +01:00
parent d1065afdd3
commit 479e269c8b
2 changed files with 151 additions and 8 deletions

View file

@ -43,6 +43,7 @@ ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket> sock
, m_page_host(PageHost::create(*this))
{
m_paint_flush_timer = Web::Platform::Timer::create_single_shot(0, [this] { flush_pending_paint_requests(); });
m_input_event_queue_timer = Web::Platform::Timer::create_single_shot(0, [this] { process_next_input_event(); });
}
void ConnectionFromClient::die()
@ -155,39 +156,145 @@ void ConnectionFromClient::flush_pending_paint_requests()
m_pending_paint_requests.clear();
}
void ConnectionFromClient::process_next_input_event()
{
if (m_input_event_queue.is_empty())
return;
auto event = m_input_event_queue.dequeue();
event.visit(
[&](QueuedMouseEvent const& event) {
switch (event.type) {
case QueuedMouseEvent::Type::MouseDown:
report_finished_handling_input_event(page().handle_mousedown(
event.position.to_type<Web::DevicePixels>(),
event.button, event.buttons, event.modifiers));
break;
case QueuedMouseEvent::Type::MouseUp:
report_finished_handling_input_event(page().handle_mouseup(
event.position.to_type<Web::DevicePixels>(),
event.button, event.buttons, event.modifiers));
break;
case QueuedMouseEvent::Type::MouseMove:
report_finished_handling_input_event(page().handle_mousemove(
event.position.to_type<Web::DevicePixels>(),
event.buttons, event.modifiers));
break;
case QueuedMouseEvent::Type::DoubleClick:
report_finished_handling_input_event(page().handle_doubleclick(
event.position.to_type<Web::DevicePixels>(),
event.button, event.buttons, event.modifiers));
break;
case QueuedMouseEvent::Type::MouseWheel:
report_finished_handling_input_event(page().handle_mousewheel(
event.position.to_type<Web::DevicePixels>(),
event.button, event.buttons, event.modifiers, event.wheel_delta_x, event.wheel_delta_y));
break;
}
},
[&](QueuedKeyboardEvent const& event) {
switch (event.type) {
case QueuedKeyboardEvent::Type::KeyDown:
report_finished_handling_input_event(page().handle_keydown((KeyCode)event.key, event.modifiers, event.code_point));
break;
case QueuedKeyboardEvent::Type::KeyUp:
report_finished_handling_input_event(page().handle_keyup((KeyCode)event.key, event.modifiers, event.code_point));
break;
}
});
if (!m_input_event_queue.is_empty())
m_input_event_queue_timer->start();
}
void ConnectionFromClient::mouse_down(Gfx::IntPoint position, unsigned int button, unsigned int buttons, unsigned int modifiers)
{
report_finished_handling_input_event(page().handle_mousedown(position.to_type<Web::DevicePixels>(), button, buttons, modifiers));
enqueue_input_event(
QueuedMouseEvent {
.type = QueuedMouseEvent::Type::MouseDown,
.position = position,
.button = button,
.buttons = buttons,
.modifiers = modifiers,
});
}
void ConnectionFromClient::mouse_move(Gfx::IntPoint position, [[maybe_unused]] unsigned int button, unsigned int buttons, unsigned int modifiers)
{
report_finished_handling_input_event(page().handle_mousemove(position.to_type<Web::DevicePixels>(), buttons, modifiers));
enqueue_input_event(
QueuedMouseEvent {
.type = QueuedMouseEvent::Type::MouseMove,
.position = position,
.button = button,
.buttons = buttons,
.modifiers = modifiers,
});
}
void ConnectionFromClient::mouse_up(Gfx::IntPoint position, unsigned int button, unsigned int buttons, unsigned int modifiers)
{
report_finished_handling_input_event(page().handle_mouseup(position.to_type<Web::DevicePixels>(), button, buttons, modifiers));
enqueue_input_event(
QueuedMouseEvent {
.type = QueuedMouseEvent::Type::MouseUp,
.position = position,
.button = button,
.buttons = buttons,
.modifiers = modifiers,
});
}
void ConnectionFromClient::mouse_wheel(Gfx::IntPoint position, unsigned int button, unsigned int buttons, unsigned int modifiers, i32 wheel_delta_x, i32 wheel_delta_y)
{
report_finished_handling_input_event(page().handle_mousewheel(position.to_type<Web::DevicePixels>(), button, buttons, modifiers, wheel_delta_x, wheel_delta_y));
enqueue_input_event(
QueuedMouseEvent {
.type = QueuedMouseEvent::Type::MouseWheel,
.position = position,
.button = button,
.buttons = buttons,
.modifiers = modifiers,
.wheel_delta_x = wheel_delta_x,
.wheel_delta_y = wheel_delta_y,
});
}
void ConnectionFromClient::doubleclick(Gfx::IntPoint position, unsigned int button, unsigned int buttons, unsigned int modifiers)
{
report_finished_handling_input_event(page().handle_doubleclick(position.to_type<Web::DevicePixels>(), button, buttons, modifiers));
enqueue_input_event(
QueuedMouseEvent {
.type = QueuedMouseEvent::Type::DoubleClick,
.position = position,
.button = button,
.buttons = buttons,
.modifiers = modifiers,
});
}
void ConnectionFromClient::key_down(i32 key, unsigned int modifiers, u32 code_point)
{
report_finished_handling_input_event(page().handle_keydown((KeyCode)key, modifiers, code_point));
enqueue_input_event(
QueuedKeyboardEvent {
.type = QueuedKeyboardEvent::Type::KeyDown,
.key = key,
.modifiers = modifiers,
.code_point = code_point,
});
}
void ConnectionFromClient::key_up(i32 key, unsigned int modifiers, u32 code_point)
{
report_finished_handling_input_event(page().handle_keyup((KeyCode)key, modifiers, code_point));
enqueue_input_event(
QueuedKeyboardEvent {
.type = QueuedKeyboardEvent::Type::KeyUp,
.key = key,
.modifiers = modifiers,
.code_point = code_point,
});
}
void ConnectionFromClient::enqueue_input_event(Variant<QueuedMouseEvent, QueuedKeyboardEvent> event)
{
m_input_event_queue.enqueue(move(event));
m_input_event_queue_timer->start();
}
void ConnectionFromClient::report_finished_handling_input_event(bool event_was_handled)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
@ -9,6 +9,7 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/Queue.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Handle.h>
@ -119,6 +120,41 @@ private:
HashMap<int, Web::FileRequest> m_requested_files {};
int last_id { 0 };
struct QueuedMouseEvent {
enum class Type {
MouseMove,
MouseDown,
MouseUp,
MouseWheel,
DoubleClick,
};
Type type {};
Gfx::IntPoint position {};
unsigned button {};
unsigned buttons {};
unsigned modifiers {};
int wheel_delta_x {};
int wheel_delta_y {};
};
struct QueuedKeyboardEvent {
enum class Type {
KeyDown,
KeyUp,
};
Type type {};
i32 key {};
unsigned int modifiers {};
u32 code_point {};
};
void enqueue_input_event(Variant<QueuedMouseEvent, QueuedKeyboardEvent>);
void process_next_input_event();
Queue<Variant<QueuedMouseEvent, QueuedKeyboardEvent>> m_input_event_queue;
RefPtr<Web::Platform::Timer> m_input_event_queue_timer;
};
}