Skip to content

Commit

Permalink
Everywhere: Transition ImageDecoder to be single-instance, owned by UI
Browse files Browse the repository at this point in the history
This is the same behavior as RequestServer, with the added benefit that
we know how to gracefully reconnect ImageDecoder to all WebContent
processes on restart.
  • Loading branch information
ADKaster committed Jun 26, 2024
1 parent 25512a0 commit 2957b59
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 31 deletions.
20 changes: 20 additions & 0 deletions Ladybird/HelperProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
WebView::ViewImplementation& view,
ReadonlySpan<ByteString> candidate_web_content_paths,
Ladybird::WebContentOptions const& web_content_options,
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket)
{
Vector<ByteString> arguments {
Expand Down Expand Up @@ -113,6 +114,9 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
arguments.append(ByteString::number(request_server_socket->fd()));
}

arguments.append("--image-decoder-socket"sv);
arguments.append(ByteString::number(image_decoder_socket.fd()));

return launch_server_process<WebView::WebContentClient>("WebContent"sv, candidate_web_content_paths, move(arguments), RegisterWithProcessManager::No, web_content_options.enable_callgrind_profiling, view);
}

Expand Down Expand Up @@ -164,3 +168,19 @@ ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient& cl

return socket;
}

ErrorOr<IPC::File> connect_new_image_decoder_client(ImageDecoderClient::Client& client)
{
auto new_socket = client.send_sync_but_allow_failure<Messages::ImageDecoderServer::ConnectNewClients>(1);
if (!new_socket)
return Error::from_string_literal("Failed to connect to ImageDecoder");

auto sockets = new_socket->take_sockets();
if (sockets.size() != 1)
return Error::from_string_literal("Failed to connect to ImageDecoder");

auto socket = sockets.take_last();
TRY(socket.clear_close_on_exec());

return socket;
}
2 changes: 2 additions & 0 deletions Ladybird/HelperProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
WebView::ViewImplementation& view,
ReadonlySpan<ByteString> candidate_web_content_paths,
Ladybird::WebContentOptions const&,
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket = {});

ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(ReadonlySpan<ByteString> candidate_image_decoder_paths);
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(ReadonlySpan<ByteString> candidate_web_worker_paths, NonnullRefPtr<Protocol::RequestClient>);
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates);

ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient&);
ErrorOr<IPC::File> connect_new_image_decoder_client(ImageDecoderClient::Client&);
38 changes: 21 additions & 17 deletions Ladybird/ImageCodecPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,44 @@
*/

#include "ImageCodecPlugin.h"
#ifdef AK_OS_ANDROID
# include <Ladybird/Android/src/main/cpp/WebContentService.h>
#else
# include "HelperProcess.h"
#endif
#include "Utilities.h"
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
#include <LibImageDecoderClient/Client.h>

namespace Ladybird {

ImageCodecPlugin::ImageCodecPlugin(NonnullRefPtr<ImageDecoderClient::Client> client)
: m_client(move(client))
{
m_client->on_death = [this] {
m_client = nullptr;
};
}

void ImageCodecPlugin::set_client(NonnullRefPtr<ImageDecoderClient::Client> client)
{
m_client = move(client);
m_client->on_death = [this] {
m_client = nullptr;
};
}

ImageCodecPlugin::~ImageCodecPlugin() = default;

NonnullRefPtr<Core::Promise<Web::Platform::DecodedImage>> ImageCodecPlugin::decode_image(ReadonlyBytes bytes, Function<ErrorOr<void>(Web::Platform::DecodedImage&)> on_resolved, Function<void(Error&)> on_rejected)
{
if (!m_client) {
#ifdef AK_OS_ANDROID
m_client = MUST(bind_service<ImageDecoderClient::Client>(&bind_image_decoder_java));
#else
auto candidate_image_decoder_paths = get_paths_for_helper_process("ImageDecoder"sv).release_value_but_fixme_should_propagate_errors();
m_client = launch_image_decoder_process(candidate_image_decoder_paths).release_value_but_fixme_should_propagate_errors();
#endif
m_client->on_death = [&] {
m_client = nullptr;
};
}

auto promise = Core::Promise<Web::Platform::DecodedImage>::construct();
if (on_resolved)
promise->on_resolution = move(on_resolved);
if (on_rejected)
promise->on_rejection = move(on_rejected);

if (!m_client) {
promise->reject(Error::from_string_literal("ImageDecoderClient is disconnected"));
return promise;
}

auto image_decoder_promise = m_client->decode_image(
bytes,
[promise](ImageDecoderClient::DecodedImage& result) -> ErrorOr<void> {
Expand Down
4 changes: 3 additions & 1 deletion Ladybird/ImageCodecPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ namespace Ladybird {

class ImageCodecPlugin final : public Web::Platform::ImageCodecPlugin {
public:
ImageCodecPlugin() = default;
explicit ImageCodecPlugin(NonnullRefPtr<ImageDecoderClient::Client>);
virtual ~ImageCodecPlugin() override;

virtual NonnullRefPtr<Core::Promise<Web::Platform::DecodedImage>> decode_image(ReadonlyBytes, Function<ErrorOr<void>(Web::Platform::DecodedImage&)> on_resolved, Function<void(Error&)> on_rejected) override;

void set_client(NonnullRefPtr<ImageDecoderClient::Client>);

private:
RefPtr<ImageDecoderClient::Client> m_client;
};
Expand Down
34 changes: 34 additions & 0 deletions Ladybird/Qt/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "Application.h"
#include "StringUtils.h"
#include "TaskManagerWindow.h"
#include <Ladybird/HelperProcess.h>
#include <Ladybird/Utilities.h>
#include <LibWebView/URL.h>
#include <QFileOpenEvent>

Expand Down Expand Up @@ -43,6 +45,38 @@ bool Application::event(QEvent* event)
return QApplication::event(event);
}

static ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_new_image_decoder()
{
auto paths = TRY(get_paths_for_helper_process("ImageDecoder"sv));
return launch_image_decoder_process(paths);
}

ErrorOr<void> Application::initialize_image_decoder()
{
m_image_decoder_client = TRY(launch_new_image_decoder());

m_image_decoder_client->on_death = [this] {
m_image_decoder_client = nullptr;
if (auto err = this->initialize_image_decoder(); err.is_error()) {
dbgln("Failed to restart image decoder: {}", err.error());
return;
}

auto num_clients = WebView::WebContentClient::client_count();
auto new_sockets = m_image_decoder_client->send_sync_but_allow_failure<Messages::ImageDecoderServer::ConnectNewClients>(num_clients);
if (!new_sockets || new_sockets->sockets().size() == 0) {
dbgln("Failed to connect {} new clients to ImageDecoder", num_clients);
return;
}

WebView::WebContentClient::for_each_client([sockets = new_sockets->take_sockets()](WebContentClient& client) mutable {
client.async_connect_to_image_decoder(sockets.take_last());
return IterationDecision::Continue;
});
};
return {};
}

void Application::show_task_manager_window()
{
if (!m_task_manager_window) {
Expand Down
6 changes: 6 additions & 0 deletions Ladybird/Qt/Application.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <AK/Function.h>
#include <AK/HashTable.h>
#include <Ladybird/Qt/BrowserWindow.h>
#include <LibImageDecoderClient/Client.h>
#include <LibProtocol/RequestClient.h>
#include <LibURL/URL.h>
#include <QApplication>
Expand All @@ -27,6 +28,9 @@ class Application : public QApplication {
Function<void(URL::URL)> on_open_file;
RefPtr<Protocol::RequestClient> request_server_client;

NonnullRefPtr<ImageDecoderClient::Client> image_decoder_client() const { return *m_image_decoder_client; }
ErrorOr<void> initialize_image_decoder();

BrowserWindow& new_window(Vector<URL::URL> const& initial_urls, WebView::CookieJar&, WebContentOptions const&, StringView webdriver_content_ipc_path, bool allow_popups, Tab* parent_tab = nullptr, Optional<u64> page_index = {});

void show_task_manager_window();
Expand All @@ -38,6 +42,8 @@ class Application : public QApplication {
private:
TaskManagerWindow* m_task_manager_window { nullptr };
BrowserWindow* m_active_window { nullptr };

RefPtr<ImageDecoderClient::Client> m_image_decoder_client;
};

}
5 changes: 4 additions & 1 deletion Ladybird/Qt/WebContentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,11 @@ void WebContentView::initialize_client(WebView::ViewImplementation::CreateNewCli
request_server_socket = AK::move(socket);
}

auto image_decoder = static_cast<Ladybird::Application*>(QApplication::instance())->image_decoder_client();
auto image_decoder_socket = connect_new_image_decoder_client(*image_decoder).release_value_but_fixme_should_propagate_errors();

auto candidate_web_content_paths = get_paths_for_helper_process("WebContent"sv).release_value_but_fixme_should_propagate_errors();
auto new_client = launch_web_content_process(*this, candidate_web_content_paths, m_web_content_options, AK::move(request_server_socket)).release_value_but_fixme_should_propagate_errors();
auto new_client = launch_web_content_process(*this, candidate_web_content_paths, m_web_content_options, AK::move(image_decoder_socket), AK::move(request_server_socket)).release_value_but_fixme_should_propagate_errors();

m_client_state.client = new_client;
} else {
Expand Down
4 changes: 3 additions & 1 deletion Ladybird/Qt/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
// FIXME: Create an abstraction to re-spawn the RequestServer and re-hook up its client hooks to each tab on crash
auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv));
auto protocol_client = TRY(launch_request_server_process(request_server_paths, s_serenity_resource_root, certificates));
app.request_server_client = protocol_client;
app.request_server_client = move(protocol_client);

TRY(app.initialize_image_decoder());

StringBuilder command_line_builder;
command_line_builder.join(' ', arguments.strings);
Expand Down
40 changes: 36 additions & 4 deletions Ladybird/WebContent/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include <LibCore/LocalServer.h>
#include <LibCore/Process.h>
#include <LibCore/Resource.h>
#include <LibCore/System.h>
#include <LibCore/SystemServerTakeover.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibJS/Bytecode/Interpreter.h>
Expand All @@ -28,9 +27,7 @@
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
#include <LibWeb/WebSockets/WebSocket.h>
#include <LibWebView/RequestServerAdapter.h>
#include <LibWebView/WebSocketClientAdapter.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/WebDriverConnection.h>
Expand All @@ -52,6 +49,8 @@
static ErrorOr<void> load_content_filters();
static ErrorOr<void> load_autoplay_allowlist();
static ErrorOr<void> initialize_lagom_networking(int request_server_socket);
static ErrorOr<void> initialize_image_decoder(int image_decoder_socket);
static ErrorOr<void> reinitialize_image_decoder(IPC::File const& image_decoder_socket);

namespace JS {
extern bool g_log_all_js_exceptions;
Expand Down Expand Up @@ -79,7 +78,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
platform_init();

Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPlugin);

Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
#if defined(HAVE_QT_MULTIMEDIA)
Expand All @@ -97,6 +95,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
StringView mach_server_name {};
Vector<ByteString> certificates;
int request_server_socket { -1 };
int image_decoder_socket { -1 };
bool is_layout_test_mode = false;
bool expose_internals_object = false;
bool use_lagom_networking = false;
Expand All @@ -111,6 +110,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.add_option(command_line, "Chrome process command line", "command-line", 0, "command_line");
args_parser.add_option(executable_path, "Chrome process executable path", "executable-path", 0, "executable_path");
args_parser.add_option(request_server_socket, "File descriptor of the socket for the RequestServer connection", "request-server-socket", 'r', "request_server_socket");
args_parser.add_option(image_decoder_socket, "File descriptor of the socket for the ImageDecoder connection", "image-decoder-socket", 'i', "image_decoder_socket");
args_parser.add_option(is_layout_test_mode, "Is layout test mode", "layout-test-mode");
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
args_parser.add_option(use_lagom_networking, "Enable Lagom servers for networking", "use-lagom-networking");
Expand Down Expand Up @@ -160,6 +160,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
#endif
TRY(initialize_lagom_networking(request_server_socket));

TRY(initialize_image_decoder(image_decoder_socket));

Web::HTML::Window::set_internals_object_exposed(expose_internals_object);

Web::Platform::FontPlugin::install(*new Ladybird::FontPlugin(is_layout_test_mode));
Expand All @@ -185,6 +187,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto webcontent_socket = TRY(Core::take_over_socket_from_system_server("WebContent"sv));
auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(move(webcontent_socket)));

webcontent_client->on_image_decoder_connection = [&](auto& socket_file) {
auto maybe_error = reinitialize_image_decoder(socket_file);
if (maybe_error.is_error())
dbgln("Failed to reinitialize image decoder: {}", maybe_error.error());
};

return event_loop.exec();
}

Expand Down Expand Up @@ -246,3 +254,27 @@ ErrorOr<void> initialize_lagom_networking(int request_server_socket)
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(new_client))));
return {};
}

ErrorOr<void> initialize_image_decoder(int image_decoder_socket)
{
auto socket = TRY(Core::LocalSocket::adopt_fd(image_decoder_socket));
TRY(socket->set_blocking(true));

auto new_client = TRY(try_make_ref_counted<ImageDecoderClient::Client>(move(socket)));

Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPlugin(move(new_client)));

return {};
}

ErrorOr<void> reinitialize_image_decoder(IPC::File const& image_decoder_socket)
{
auto socket = TRY(Core::LocalSocket::adopt_fd(image_decoder_socket.take_fd()));
TRY(socket->set_blocking(true));

auto new_client = TRY(try_make_ref_counted<ImageDecoderClient::Client>(move(socket)));

static_cast<Ladybird::ImageCodecPlugin&>(Web::Platform::ImageCodecPlugin::the()).set_client(move(new_client));

return {};
}
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWebView/WebContentClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace WebView {

static HashTable<WebContentClient*> s_clients;
HashTable<WebContentClient*> WebContentClient::s_clients;

Optional<ViewImplementation&> WebContentClient::view_for_pid_and_page_id(pid_t pid, u64 page_id)
{
Expand Down
16 changes: 16 additions & 0 deletions Userland/Libraries/LibWebView/WebContentClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class WebContentClient final
public:
static Optional<ViewImplementation&> view_for_pid_and_page_id(pid_t pid, u64 page_id);

template<CallableAs<IterationDecision, WebContentClient&> Callback>
static void for_each_client(Callback callback);

static size_t client_count() { return s_clients.size(); }

WebContentClient(NonnullOwnPtr<Core::LocalSocket>, ViewImplementation&);
~WebContentClient();

Expand Down Expand Up @@ -121,6 +126,17 @@ class WebContentClient final
HashMap<u64, ViewImplementation*> m_views;

ProcessHandle m_process_handle;

static HashTable<WebContentClient*> s_clients;
};

template<CallableAs<IterationDecision, WebContentClient&> Callback>
void WebContentClient::for_each_client(Callback callback)
{
for (auto& it : s_clients) {
if (callback(*it) == IterationDecision::Break)
return;
}
}

}
Loading

0 comments on commit 2957b59

Please sign in to comment.