From 1eb653115b3bd9d73d46f49e8f0e2f6b7ae64d70 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 3 Jul 2022 20:36:07 +0200 Subject: [PATCH] Ladybird: Initial import :^) --- Ladybird/.gitignore | 8 + Ladybird/WebView.cpp | 727 ++++++++++++++++++++++++++++++++++++++++++ Ladybird/WebView.h | 24 ++ Ladybird/ladybird.pro | 39 +++ Ladybird/main.cpp | 43 +++ 5 files changed, 841 insertions(+) create mode 100644 Ladybird/.gitignore create mode 100644 Ladybird/WebView.cpp create mode 100644 Ladybird/WebView.h create mode 100644 Ladybird/ladybird.pro create mode 100644 Ladybird/main.cpp diff --git a/Ladybird/.gitignore b/Ladybird/.gitignore new file mode 100644 index 00000000000..671819ee4f1 --- /dev/null +++ b/Ladybird/.gitignore @@ -0,0 +1,8 @@ +.qmake.stash +Makefile +WebView.o +ladybird +main.o +moc_WebView.cpp +moc_WebView.o +moc_predefs.h diff --git a/Ladybird/WebView.cpp b/Ladybird/WebView.cpp new file mode 100644 index 00000000000..f0c1f903e77 --- /dev/null +++ b/Ladybird/WebView.cpp @@ -0,0 +1,727 @@ +/* + * Copyright (c) 2022, Dex♪ + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include "WebView.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class HeadlessBrowserPageClient final : public Web::PageClient { +public: + static NonnullOwnPtr create(WebView& view) + { + return adopt_own(*new HeadlessBrowserPageClient(view)); + } + + Web::Page& page() { return *m_page; } + Web::Page const& page() const { return *m_page; } + + Web::Layout::InitialContainingBlock* layout_root() + { + auto* document = page().top_level_browsing_context().active_document(); + if (!document) + return nullptr; + return document->layout_node(); + } + + void load(AK::URL const& url) + { + page().load(url); + } + + void paint(Gfx::IntRect const& content_rect, Gfx::Bitmap& target) + { + Gfx::Painter painter(target); + + if (auto* document = page().top_level_browsing_context().active_document()) + document->update_layout(); + + painter.fill_rect({ {}, content_rect.size() }, palette().base()); + + auto* layout_root = this->layout_root(); + if (!layout_root) { + return; + } + + Web::PaintContext context(painter, palette(), content_rect.top_left()); + context.set_should_show_line_box_borders(false); + context.set_viewport_rect(content_rect); + context.set_has_focus(true); + layout_root->paint_all_phases(context); + } + + void setup_palette(Core::AnonymousBuffer theme_buffer) + { + m_palette_impl = Gfx::PaletteImpl::create_with_anonymous_buffer(theme_buffer); + } + + void set_viewport_rect(Gfx::IntRect rect) + { + m_viewport_rect = rect; + page().top_level_browsing_context().set_viewport_rect(rect); + } + + // ^Web::PageClient + virtual Gfx::Palette palette() const override + { + return Gfx::Palette(*m_palette_impl); + } + + virtual Gfx::IntRect screen_rect() const override + { + // FIXME: Return the actual screen rect. + return m_viewport_rect; + } + + Gfx::IntRect viewport_rect() const + { + return m_viewport_rect; + } + + virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override + { + return m_preferred_color_scheme; + } + + virtual void page_did_change_title(String const&) override + { + } + + virtual void page_did_set_document_in_top_level_browsing_context(Web::DOM::Document*) override + { + } + + virtual void page_did_start_loading(AK::URL const&) override + { + } + + virtual void page_did_finish_loading(AK::URL const&) override + { + } + + virtual void page_did_change_selection() override + { + } + + virtual void page_did_request_cursor_change(Gfx::StandardCursor) override + { + } + + virtual void page_did_request_context_menu(Gfx::IntPoint const&) override + { + } + + virtual void page_did_request_link_context_menu(Gfx::IntPoint const&, AK::URL const&, String const&, unsigned) override + { + } + + virtual void page_did_request_image_context_menu(Gfx::IntPoint const&, AK::URL const&, String const&, unsigned, Gfx::Bitmap const*) override + { + } + + virtual void page_did_click_link(AK::URL const&, String const&, unsigned) override + { + } + + virtual void page_did_middle_click_link(AK::URL const&, String const&, unsigned) override + { + } + + virtual void page_did_enter_tooltip_area(Gfx::IntPoint const&, String const&) override + { + } + + virtual void page_did_leave_tooltip_area() override + { + } + + virtual void page_did_hover_link(AK::URL const&) override + { + } + + virtual void page_did_unhover_link() override + { + } + + virtual void page_did_invalidate(Gfx::IntRect const&) override + { + m_view.update(); + } + + virtual void page_did_change_favicon(Gfx::Bitmap const&) override + { + } + + virtual void page_did_layout() override + { + } + + virtual void page_did_request_scroll_into_view(Gfx::IntRect const&) override + { + } + + virtual void page_did_request_alert(String const&) override + { + } + + virtual bool page_did_request_confirm(String const&) override + { + return false; + } + + virtual String page_did_request_prompt(String const&, String const&) override + { + return String::empty(); + } + + virtual String page_did_request_cookie(AK::URL const&, Web::Cookie::Source) override + { + return String::empty(); + } + + virtual void page_did_set_cookie(AK::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override + { + } + + void request_file(NonnullRefPtr& request) override + { + auto const file = Core::System::open(request->path(), O_RDONLY); + request->on_file_request_finish(file); + } + +private: + HeadlessBrowserPageClient(WebView& view) + : m_view(view) + , m_page(make(*this)) + { + } + + WebView& m_view; + NonnullOwnPtr m_page; + + RefPtr m_palette_impl; + Gfx::IntRect m_viewport_rect { 0, 0, 800, 600 }; + Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto }; +}; + +WebView::WebView() +{ + m_page_client = HeadlessBrowserPageClient::create(*this); + + m_page_client->setup_palette(Gfx::load_system_theme("/home/kling/src/serenity/Base/res/themes/Default.ini")); + + // FIXME: Allow passing these values as arguments + m_page_client->set_viewport_rect({ 0, 0, 800, 600 }); +} + +WebView::~WebView() +{ +} + +void WebView::load(String const& url) +{ + m_page_client->load(AK::URL(url)); +} + +void WebView::paintEvent(QPaintEvent* event) +{ + QPainter painter(viewport()); + painter.setClipRect(event->rect()); + + auto output_rect = m_page_client->viewport_rect(); + auto output_bitmap = MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, output_rect.size())); + + dbgln("paintEvent: {}", output_rect); + + m_page_client->paint(output_rect, output_bitmap); + + QImage q_image(output_bitmap->scanline_u8(0), output_bitmap->width(), output_bitmap->height(), QImage::Format_RGB32); + painter.drawImage(QPoint(0, 0), q_image); +} + +void WebView::resizeEvent(QResizeEvent* event) +{ + Gfx::IntRect rect(0, 0, event->size().width(), event->size().height()); + m_page_client->set_viewport_rect(rect); +} + +class HeadlessImageDecoderClient : public Web::ImageDecoding::Decoder { +public: + static NonnullRefPtr create() + { + return adopt_ref(*new HeadlessImageDecoderClient()); + } + + virtual ~HeadlessImageDecoderClient() override = default; + + virtual Optional decode_image(ReadonlyBytes data) override + { + auto decoder = Gfx::ImageDecoder::try_create(data); + + if (!decoder) + return Web::ImageDecoding::DecodedImage { false, 0, Vector {} }; + + if (!decoder->frame_count()) + return Web::ImageDecoding::DecodedImage { false, 0, Vector {} }; + + Vector frames; + for (size_t i = 0; i < decoder->frame_count(); ++i) { + auto frame_or_error = decoder->frame(i); + if (frame_or_error.is_error()) { + frames.append({ {}, 0 }); + } else { + auto frame = frame_or_error.release_value(); + frames.append({ move(frame.image), static_cast(frame.duration) }); + } + } + + return Web::ImageDecoding::DecodedImage { + decoder->is_animated(), + static_cast(decoder->loop_count()), + frames, + }; + } + +private: + explicit HeadlessImageDecoderClient() = default; +}; + +static HashTable> s_all_requests; + +class HeadlessRequestServer : public Web::ResourceLoaderConnector { +public: + class HTTPHeadlessRequest + : public Web::ResourceLoaderConnectorRequest + , public Weakable { + public: + static ErrorOr> create(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) + { + auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); + auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80))); + TRY(underlying_socket->set_blocking(false)); + auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); + + HTTP::HttpRequest request; + if (method.equals_ignoring_case("head"sv)) + request.set_method(HTTP::HttpRequest::HEAD); + else if (method.equals_ignoring_case("get"sv)) + request.set_method(HTTP::HttpRequest::GET); + else if (method.equals_ignoring_case("post"sv)) + request.set_method(HTTP::HttpRequest::POST); + else + request.set_method(HTTP::HttpRequest::Invalid); + request.set_url(move(url)); + request.set_headers(request_headers); + request.set_body(TRY(ByteBuffer::copy(request_body))); + + return adopt_ref(*new HTTPHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); + } + + virtual ~HTTPHeadlessRequest() override + { + } + + virtual void set_should_buffer_all_input(bool) override + { + } + + virtual bool stop() override + { + return false; + } + + virtual void stream_into(Core::Stream::Stream&) override + { + } + + private: + HTTPHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) + : m_stream_backing_buffer(move(stream_backing_buffer)) + , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) + , m_socket(move(socket)) + , m_job(HTTP::Job::construct(move(request), *m_output_stream)) + { + m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { + if (auto strong_this = weak_this.strong_ref()) { + strong_this->m_response_code = response_code; + for (auto& header : response_headers) { + strong_this->m_response_headers.set(header.key, header.value); + } + } + }; + m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { + Core::deferred_invoke([weak_this, success]() mutable { + if (auto strong_this = weak_this.strong_ref()) { + ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; + auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); + strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); + } }); + }; + m_job->start(*m_socket); + } + + Optional m_response_code; + ByteBuffer m_stream_backing_buffer; + NonnullOwnPtr m_output_stream; + NonnullOwnPtr m_socket; + NonnullRefPtr m_job; + HashMap m_response_headers; + }; + + class HTTPSHeadlessRequest + : public Web::ResourceLoaderConnectorRequest + , public Weakable { + public: + static ErrorOr> create(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) + { + auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); + auto underlying_socket = TRY(TLS::TLSv12::connect(url.host(), url.port().value_or(80))); + TRY(underlying_socket->set_blocking(false)); + auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); + + HTTP::HttpRequest request; + if (method.equals_ignoring_case("head"sv)) + request.set_method(HTTP::HttpRequest::HEAD); + else if (method.equals_ignoring_case("get"sv)) + request.set_method(HTTP::HttpRequest::GET); + else if (method.equals_ignoring_case("post"sv)) + request.set_method(HTTP::HttpRequest::POST); + else + request.set_method(HTTP::HttpRequest::Invalid); + request.set_url(move(url)); + request.set_headers(request_headers); + request.set_body(TRY(ByteBuffer::copy(request_body))); + + return adopt_ref(*new HTTPSHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); + } + + virtual ~HTTPSHeadlessRequest() override + { + } + + virtual void set_should_buffer_all_input(bool) override + { + } + + virtual bool stop() override + { + return false; + } + + virtual void stream_into(Core::Stream::Stream&) override + { + } + + private: + HTTPSHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) + : m_stream_backing_buffer(move(stream_backing_buffer)) + , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) + , m_socket(move(socket)) + , m_job(HTTP::HttpsJob::construct(move(request), *m_output_stream)) + { + m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { + if (auto strong_this = weak_this.strong_ref()) { + strong_this->m_response_code = response_code; + for (auto& header : response_headers) { + strong_this->m_response_headers.set(header.key, header.value); + } + } + }; + m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { + Core::deferred_invoke([weak_this, success]() mutable { + if (auto strong_this = weak_this.strong_ref()) { + ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; + auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); + strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); + } }); + }; + m_job->start(*m_socket); + } + + Optional m_response_code; + ByteBuffer m_stream_backing_buffer; + NonnullOwnPtr m_output_stream; + NonnullOwnPtr m_socket; + NonnullRefPtr m_job; + HashMap m_response_headers; + }; + + class GeminiHeadlessRequest + : public Web::ResourceLoaderConnectorRequest + , public Weakable { + public: + static ErrorOr> create(String const&, AK::URL const& url, HashMap const&, ReadonlyBytes, Core::ProxyData const&) + { + auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); + auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80))); + TRY(underlying_socket->set_blocking(false)); + auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); + + Gemini::GeminiRequest request; + request.set_url(url); + + return adopt_ref(*new GeminiHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); + } + + virtual ~GeminiHeadlessRequest() override + { + } + + virtual void set_should_buffer_all_input(bool) override + { + } + + virtual bool stop() override + { + return false; + } + + virtual void stream_into(Core::Stream::Stream&) override + { + } + + private: + GeminiHeadlessRequest(Gemini::GeminiRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) + : m_stream_backing_buffer(move(stream_backing_buffer)) + , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) + , m_socket(move(socket)) + , m_job(Gemini::Job::construct(move(request), *m_output_stream)) + { + m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { + if (auto strong_this = weak_this.strong_ref()) { + strong_this->m_response_code = response_code; + for (auto& header : response_headers) { + strong_this->m_response_headers.set(header.key, header.value); + } + } + }; + m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { + Core::deferred_invoke([weak_this, success]() mutable { + if (auto strong_this = weak_this.strong_ref()) { + ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; + auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); + strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); + } }); + }; + m_job->start(*m_socket); + } + + Optional m_response_code; + ByteBuffer m_stream_backing_buffer; + NonnullOwnPtr m_output_stream; + NonnullOwnPtr m_socket; + NonnullRefPtr m_job; + HashMap m_response_headers; + }; + + static NonnullRefPtr create() + { + return adopt_ref(*new HeadlessRequestServer()); + } + + virtual ~HeadlessRequestServer() override { } + + virtual void prefetch_dns(AK::URL const&) override { } + virtual void preconnect(AK::URL const&) override { } + + virtual RefPtr start_request(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const& proxy) override + { + RefPtr request; + if (url.protocol().equals_ignoring_case("http"sv)) { + auto request_or_error = HTTPHeadlessRequest::create(method, url, request_headers, request_body, proxy); + if (request_or_error.is_error()) + return {}; + request = request_or_error.release_value(); + } + if (url.protocol().equals_ignoring_case("https"sv)) { + auto request_or_error = HTTPSHeadlessRequest::create(method, url, request_headers, request_body, proxy); + if (request_or_error.is_error()) + return {}; + request = request_or_error.release_value(); + } + if (url.protocol().equals_ignoring_case("gemini"sv)) { + auto request_or_error = GeminiHeadlessRequest::create(method, url, request_headers, request_body, proxy); + if (request_or_error.is_error()) + return {}; + request = request_or_error.release_value(); + } + if (request) + s_all_requests.set(request); + return request; + } + +private: + HeadlessRequestServer() { } +}; + +class HeadlessWebSocketClientManager : public Web::WebSockets::WebSocketClientManager { +public: + class HeadlessWebSocket + : public Web::WebSockets::WebSocketClientSocket + , public Weakable { + public: + static NonnullRefPtr create(NonnullRefPtr underlying_socket) + { + return adopt_ref(*new HeadlessWebSocket(move(underlying_socket))); + } + + virtual ~HeadlessWebSocket() override + { + } + + virtual Web::WebSockets::WebSocket::ReadyState ready_state() override + { + switch (m_websocket->ready_state()) { + case WebSocket::ReadyState::Connecting: + return Web::WebSockets::WebSocket::ReadyState::Connecting; + case WebSocket::ReadyState::Open: + return Web::WebSockets::WebSocket::ReadyState::Open; + case WebSocket::ReadyState::Closing: + return Web::WebSockets::WebSocket::ReadyState::Closing; + case WebSocket::ReadyState::Closed: + return Web::WebSockets::WebSocket::ReadyState::Closed; + } + VERIFY_NOT_REACHED(); + } + + virtual void send(ByteBuffer binary_or_text_message, bool is_text) override + { + m_websocket->send(WebSocket::Message(binary_or_text_message, is_text)); + } + + virtual void send(StringView message) override + { + m_websocket->send(WebSocket::Message(message)); + } + + virtual void close(u16 code, String reason) override + { + m_websocket->close(code, reason); + } + + private: + HeadlessWebSocket(NonnullRefPtr underlying_socket) + : m_websocket(move(underlying_socket)) + { + m_websocket->on_open = [weak_this = make_weak_ptr()] { + if (auto strong_this = weak_this.strong_ref()) + if (strong_this->on_open) + strong_this->on_open(); + }; + m_websocket->on_message = [weak_this = make_weak_ptr()](auto message) { + if (auto strong_this = weak_this.strong_ref()) { + if (strong_this->on_message) { + strong_this->on_message(Web::WebSockets::WebSocketClientSocket::Message { + .data = move(message.data()), + .is_text = message.is_text(), + }); + } + } + }; + m_websocket->on_error = [weak_this = make_weak_ptr()](auto error) { + if (auto strong_this = weak_this.strong_ref()) { + if (strong_this->on_error) { + switch (error) { + case WebSocket::WebSocket::Error::CouldNotEstablishConnection: + strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::CouldNotEstablishConnection); + return; + case WebSocket::WebSocket::Error::ConnectionUpgradeFailed: + strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::ConnectionUpgradeFailed); + return; + case WebSocket::WebSocket::Error::ServerClosedSocket: + strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::ServerClosedSocket); + return; + } + VERIFY_NOT_REACHED(); + } + } + }; + m_websocket->on_close = [weak_this = make_weak_ptr()](u16 code, String reason, bool was_clean) { + if (auto strong_this = weak_this.strong_ref()) + if (strong_this->on_close) + strong_this->on_close(code, move(reason), was_clean); + }; + } + + NonnullRefPtr m_websocket; + }; + + static NonnullRefPtr create() + { + return adopt_ref(*new HeadlessWebSocketClientManager()); + } + + virtual ~HeadlessWebSocketClientManager() override { } + + virtual RefPtr connect(AK::URL const& url, String const& origin) override + { + WebSocket::ConnectionInfo connection_info(url); + connection_info.set_origin(origin); + + auto connection = HeadlessWebSocket::create(WebSocket::WebSocket::create(move(connection_info))); + return connection; + } + +private: + HeadlessWebSocketClientManager() { } +}; + +void initialize_web_engine() +{ + Web::ImageDecoding::Decoder::initialize(HeadlessImageDecoderClient::create()); + Web::ResourceLoader::initialize(HeadlessRequestServer::create()); + Web::WebSockets::WebSocketClientManager::initialize(HeadlessWebSocketClientManager::create()); + + Web::FrameLoader::set_default_favicon_path("/home/kling/src/serenity/Base/res/icons/16x16/app-browser.png"); + + Gfx::FontDatabase::set_default_fonts_lookup_path("/home/kling/src/serenity/Base/res/fonts"); + + Gfx::FontDatabase::set_default_font_query("Katica 10 400 0"); + Gfx::FontDatabase::set_fixed_width_font_query("Csilla 10 400 0"); + + Web::FrameLoader::set_error_page_url("file:///home/kling/src/serenity/Base/res/html/error.html"); +} diff --git a/Ladybird/WebView.h b/Ladybird/WebView.h new file mode 100644 index 00000000000..d6a242aad03 --- /dev/null +++ b/Ladybird/WebView.h @@ -0,0 +1,24 @@ +#pragma once + +#define AK_DONT_REPLACE_STD + +#include +#include +#include + +class HeadlessBrowserPageClient; + +class WebView final : public QAbstractScrollArea { + Q_OBJECT +public: + WebView(); + virtual ~WebView() override; + + void load(String const& url); + + virtual void paintEvent(QPaintEvent*) override; + virtual void resizeEvent(QResizeEvent*) override; + +private: + OwnPtr m_page_client; +}; diff --git a/Ladybird/ladybird.pro b/Ladybird/ladybird.pro new file mode 100644 index 00000000000..571c343de5e --- /dev/null +++ b/Ladybird/ladybird.pro @@ -0,0 +1,39 @@ +###################################################################### +# Automatically generated by qmake (3.1) Sun Jul 3 19:48:59 2022 +###################################################################### + +QT += widgets +TEMPLATE = app +TARGET = ladybird +INCLUDEPATH += \ + /home/kling/src/serenity/ \ + /home/kling/src/serenity/Userland/Libraries/ \ + /home/kling/src/serenity/Build/x86_64/Userland/Libraries/ \ + /home/kling/src/serenity/Build/x86_64/ \ + . + +# You can make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# Please consult the documentation of the deprecated API in order to know +# how to port your code away from it. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +# Input +HEADERS += WebView.h +SOURCES += main.cpp WebView.cpp + +QMAKE_LIBDIR += /home/kling/src/serenity/Build/lagom/ + +LIBS += \ + -llagom-web \ + -llagom-gemini \ + -llagom-gfx \ + -llagom-crypto \ + -llagom-core \ + -llagom-http \ + -llagom-tls \ + -llagom-websocket \ + -llagom-main + +QMAKE_CXXFLAGS += -std=c++20 -Wno-expansion-to-defined -Wno-literal-suffix -Wno-deprecated-enum-enum-conversion diff --git a/Ladybird/main.cpp b/Ladybird/main.cpp new file mode 100644 index 00000000000..d2f59f2e022 --- /dev/null +++ b/Ladybird/main.cpp @@ -0,0 +1,43 @@ +#include "WebView.h" +#include +#include +#include +#include +#include +#include +#include + +extern void initialize_web_engine(); + +ErrorOr serenity_main(Main::Arguments arguments) +{ + initialize_web_engine(); + + String url; + Core::ArgsParser args_parser; + args_parser.set_general_help("The Ladybird web browser :^)"); + args_parser.add_positional_argument(url, "URL to open", "url", Core::ArgsParser::Required::No); + args_parser.parse(arguments); + + Core::EventLoop event_loop; + + QApplication app(arguments.argc, arguments.argv); + QMainWindow window; + window.setWindowTitle("Ladybird"); + window.resize(800, 600); + window.show(); + + WebView view; + window.setCentralWidget(&view); + + auto qt_event_loop_driver = Core::Timer::create_repeating(50, [&] { + app.processEvents(); + }); + qt_event_loop_driver->start(); + + if (!url.is_empty()) { + view.load(url); + } + + return event_loop.exec(); +}