123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- /*
- * Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com>
- * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #define AK_DONT_REPLACE_STD
- #include "WebView.h"
- #include "RequestManagerQt.h"
- #include <AK/Assertions.h>
- #include <AK/ByteBuffer.h>
- #include <AK/Format.h>
- #include <AK/HashTable.h>
- #include <AK/NonnullOwnPtr.h>
- #include <AK/StringBuilder.h>
- #include <AK/Types.h>
- #include <LibCore/ArgsParser.h>
- #include <LibCore/EventLoop.h>
- #include <LibCore/File.h>
- #include <LibCore/IODevice.h>
- #include <LibCore/MemoryStream.h>
- #include <LibCore/Stream.h>
- #include <LibCore/System.h>
- #include <LibCore/Timer.h>
- #include <LibGfx/Bitmap.h>
- #include <LibGfx/Font/FontDatabase.h>
- #include <LibGfx/ImageDecoder.h>
- #include <LibGfx/PNGWriter.h>
- #include <LibGfx/Rect.h>
- #include <LibMain/Main.h>
- #include <LibWeb/Cookie/ParsedCookie.h>
- #include <LibWeb/DOM/Document.h>
- #include <LibWeb/HTML/BrowsingContext.h>
- #include <LibWeb/ImageDecoding.h>
- #include <LibWeb/Layout/InitialContainingBlock.h>
- #include <LibWeb/Loader/ResourceLoader.h>
- #include <LibWeb/Page/Page.h>
- #include <LibWeb/Painting/PaintableBox.h>
- #include <LibWeb/WebSockets/WebSocket.h>
- #include <LibWebSocket/ConnectionInfo.h>
- #include <LibWebSocket/Message.h>
- #include <LibWebSocket/WebSocket.h>
- #include <QCursor>
- #include <QIcon>
- #include <QMouseEvent>
- #include <QPaintEvent>
- #include <QPainter>
- #include <QScrollBar>
- #include <stdlib.h>
- String s_serenity_resource_root = [] {
- auto const* source_dir = getenv("SERENITY_SOURCE_DIR");
- if (source_dir) {
- return String::formatted("{}/Base", source_dir);
- }
- auto* home = getenv("XDG_CONFIG_HOME") ?: getenv("HOME");
- VERIFY(home);
- return String::formatted("{}/.lagom", home);
- }();
- class HeadlessBrowserPageClient final : public Web::PageClient {
- public:
- static NonnullOwnPtr<HeadlessBrowserPageClient> 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& title) override
- {
- emit m_view.title_changed(title.characters());
- }
- virtual void page_did_set_document_in_top_level_browsing_context(Web::DOM::Document*) override
- {
- }
- virtual void page_did_start_loading(AK::URL const& url) override
- {
- emit m_view.loadStarted(url);
- }
- 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 cursor) override
- {
- switch (cursor) {
- case Gfx::StandardCursor::Hand:
- m_view.setCursor(Qt::PointingHandCursor);
- break;
- case Gfx::StandardCursor::IBeam:
- m_view.setCursor(Qt::IBeamCursor);
- break;
- case Gfx::StandardCursor::Arrow:
- default:
- m_view.setCursor(Qt::ArrowCursor);
- break;
- }
- }
- 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& url) override
- {
- emit m_view.linkHovered(url.to_string().characters());
- }
- virtual void page_did_unhover_link() override
- {
- emit m_view.linkUnhovered();
- }
- virtual void page_did_invalidate(Gfx::IntRect const&) override
- {
- m_view.viewport()->update();
- }
- virtual void page_did_change_favicon(Gfx::Bitmap const& bitmap) override
- {
- auto qimage = QImage(bitmap.scanline_u8(0), bitmap.width(), bitmap.height(), QImage::Format_ARGB32);
- if (qimage.isNull())
- return;
- auto qpixmap = QPixmap::fromImage(qimage);
- if (qpixmap.isNull())
- return;
- emit m_view.favicon_changed(QIcon(qpixmap));
- }
- virtual void page_did_layout() override
- {
- auto* layout_root = this->layout_root();
- VERIFY(layout_root);
- Gfx::IntSize content_size;
- if (layout_root->paint_box()->has_overflow())
- content_size = enclosing_int_rect(layout_root->paint_box()->scrollable_overflow_rect().value()).size();
- else
- content_size = enclosing_int_rect(layout_root->paint_box()->absolute_rect()).size();
- m_view.verticalScrollBar()->setMaximum(content_size.height() - m_viewport_rect.height());
- m_view.horizontalScrollBar()->setMaximum(content_size.width() - m_viewport_rect.width());
- }
- 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<Web::FileRequest>& 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<Web::Page>(*this))
- {
- }
- WebView& m_view;
- NonnullOwnPtr<Web::Page> m_page;
- RefPtr<Gfx::PaletteImpl> 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()
- {
- setMouseTracking(true);
- m_page_client = HeadlessBrowserPageClient::create(*this);
- m_page_client->setup_palette(Gfx::load_system_theme(String::formatted("{}/res/themes/Default.ini", s_serenity_resource_root)));
- // FIXME: Allow passing these values as arguments
- m_page_client->set_viewport_rect({ 0, 0, 800, 600 });
- m_inverse_pixel_scaling_ratio = 1.0 / devicePixelRatio();
- }
- WebView::~WebView()
- {
- }
- void WebView::reload()
- {
- auto url = m_page_client->page().top_level_browsing_context().active_document()->url();
- m_page_client->load(url);
- }
- void WebView::load(String const& url)
- {
- m_page_client->load(AK::URL(url));
- }
- unsigned get_button_from_qt_event(QMouseEvent const& event)
- {
- if (event.button() == Qt::MouseButton::LeftButton)
- return 1;
- if (event.button() == Qt::MouseButton::RightButton)
- return 2;
- if (event.button() == Qt::MouseButton::MiddleButton)
- return 4;
- return 0;
- }
- unsigned get_buttons_from_qt_event(QMouseEvent const& event)
- {
- unsigned buttons = 0;
- if (event.buttons() & Qt::MouseButton::LeftButton)
- buttons |= 1;
- if (event.buttons() & Qt::MouseButton::RightButton)
- buttons |= 2;
- if (event.buttons() & Qt::MouseButton::MiddleButton)
- buttons |= 4;
- return buttons;
- }
- unsigned get_modifiers_from_qt_event(QMouseEvent const& event)
- {
- unsigned modifiers = 0;
- if (event.modifiers() & Qt::Modifier::ALT)
- modifiers |= 1;
- if (event.modifiers() & Qt::Modifier::CTRL)
- modifiers |= 2;
- if (event.modifiers() & Qt::Modifier::SHIFT)
- modifiers |= 4;
- return modifiers;
- }
- void WebView::mouseMoveEvent(QMouseEvent* event)
- {
- Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio);
- auto buttons = get_buttons_from_qt_event(*event);
- auto modifiers = get_modifiers_from_qt_event(*event);
- m_page_client->page().handle_mousemove(to_content(position), buttons, modifiers);
- }
- void WebView::mousePressEvent(QMouseEvent* event)
- {
- Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio);
- auto button = get_button_from_qt_event(*event);
- auto modifiers = get_modifiers_from_qt_event(*event);
- m_page_client->page().handle_mousedown(to_content(position), button, modifiers);
- }
- void WebView::mouseReleaseEvent(QMouseEvent* event)
- {
- Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio);
- auto button = get_button_from_qt_event(*event);
- auto modifiers = get_modifiers_from_qt_event(*event);
- m_page_client->page().handle_mouseup(to_content(position), button, modifiers);
- }
- Gfx::IntPoint WebView::to_content(Gfx::IntPoint viewport_position) const
- {
- return viewport_position.translated(horizontalScrollBar()->value(), verticalScrollBar()->value());
- }
- void WebView::paintEvent(QPaintEvent* event)
- {
- QPainter painter(viewport());
- painter.setClipRect(event->rect());
- painter.scale(m_inverse_pixel_scaling_ratio, m_inverse_pixel_scaling_ratio);
- auto output_rect = m_page_client->viewport_rect();
- output_rect.set_x(horizontalScrollBar()->value());
- output_rect.set_y(verticalScrollBar()->value());
- auto output_bitmap = MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, output_rect.size()));
- 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)
- {
- auto scaled_width = int(event->size().width() / m_inverse_pixel_scaling_ratio);
- auto scaled_height = int(event->size().height() / m_inverse_pixel_scaling_ratio);
- Gfx::IntRect rect(horizontalScrollBar()->value(), verticalScrollBar()->value(), scaled_width, scaled_height);
- if (verticalScrollBar()->isVisible())
- rect.set_width(rect.width() - verticalScrollBar()->width());
- if (horizontalScrollBar()->isVisible())
- rect.set_height(rect.height() - horizontalScrollBar()->height());
- m_page_client->set_viewport_rect(rect);
- }
- class HeadlessImageDecoderClient : public Web::ImageDecoding::Decoder {
- public:
- static NonnullRefPtr<HeadlessImageDecoderClient> create()
- {
- return adopt_ref(*new HeadlessImageDecoderClient());
- }
- virtual ~HeadlessImageDecoderClient() override = default;
- virtual Optional<Web::ImageDecoding::DecodedImage> decode_image(ReadonlyBytes data) override
- {
- auto decoder = Gfx::ImageDecoder::try_create(data);
- if (!decoder)
- return Web::ImageDecoding::DecodedImage { false, 0, Vector<Web::ImageDecoding::Frame> {} };
- if (!decoder->frame_count())
- return Web::ImageDecoding::DecodedImage { false, 0, Vector<Web::ImageDecoding::Frame> {} };
- Vector<Web::ImageDecoding::Frame> 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<size_t>(frame.duration) });
- }
- }
- return Web::ImageDecoding::DecodedImage {
- decoder->is_animated(),
- static_cast<u32>(decoder->loop_count()),
- frames,
- };
- }
- private:
- explicit HeadlessImageDecoderClient() = default;
- };
- class HeadlessWebSocketClientManager : public Web::WebSockets::WebSocketClientManager {
- public:
- class HeadlessWebSocket
- : public Web::WebSockets::WebSocketClientSocket
- , public Weakable<HeadlessWebSocket> {
- public:
- static NonnullRefPtr<HeadlessWebSocket> create(NonnullRefPtr<WebSocket::WebSocket> 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<WebSocket::WebSocket> 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<WebSocket::WebSocket> m_websocket;
- };
- static NonnullRefPtr<HeadlessWebSocketClientManager> create()
- {
- return adopt_ref(*new HeadlessWebSocketClientManager());
- }
- virtual ~HeadlessWebSocketClientManager() override { }
- virtual RefPtr<Web::WebSockets::WebSocketClientSocket> 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(RequestManagerQt::create());
- Web::WebSockets::WebSocketClientManager::initialize(HeadlessWebSocketClientManager::create());
- Web::FrameLoader::set_default_favicon_path(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root));
- dbgln("Set favoicon path to {}", String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root));
- Gfx::FontDatabase::set_default_fonts_lookup_path(String::formatted("{}/res/fonts", s_serenity_resource_root));
- 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(String::formatted("file://{}/res/html/error.html", s_serenity_resource_root));
- }
|