From 26a7ea0e0f15d8b7ec673c453950d2f3c20e4400 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 5 Oct 2022 15:23:41 +0200 Subject: [PATCH] Ladybird: Render web content in a separate process :^) This patch brings over the WebContent process over from SerenityOS to Ladybird, along with a new WebContentView widget that renders web content in a separate process. There's a lot of jank and FIXME material here, notably I had to re-add manually pumped Core::EventLoop instances on both sides, in order to get the IPC protocol running. This introduces a lot of latency and we should work towards replacing those loops with improved abstractions. The WebContent process is built separately here (not part of Lagom) and we provide our own main.cpp for it. Like everything, this can be better architected, it's just a starting point. :^) --- Ladybird/BrowserWindow.cpp | 11 +- Ladybird/BrowserWindow.h | 4 +- Ladybird/CMakeLists.txt | 22 +- Ladybird/SimpleWebView.cpp | 668 --------------------- Ladybird/SimpleWebView.h | 89 --- Ladybird/Tab.cpp | 24 +- Ladybird/Tab.h | 6 +- Ladybird/Utilities.cpp | 28 + Ladybird/Utilities.h | 3 + Ladybird/WebContent/CMakeLists.txt | 28 + Ladybird/WebContent/main.cpp | 106 ++++ Ladybird/WebContentView.cpp | 911 +++++++++++++++++++++++++++++ Ladybird/WebContentView.h | 186 ++++++ Ladybird/main.cpp | 22 +- 14 files changed, 1313 insertions(+), 795 deletions(-) delete mode 100644 Ladybird/SimpleWebView.cpp delete mode 100644 Ladybird/SimpleWebView.h create mode 100644 Ladybird/WebContent/CMakeLists.txt create mode 100644 Ladybird/WebContent/main.cpp create mode 100644 Ladybird/WebContentView.cpp create mode 100644 Ladybird/WebContentView.h diff --git a/Ladybird/BrowserWindow.cpp b/Ladybird/BrowserWindow.cpp index be4bf8aa554..f92d66adaa4 100644 --- a/Ladybird/BrowserWindow.cpp +++ b/Ladybird/BrowserWindow.cpp @@ -9,8 +9,8 @@ #include "BrowserWindow.h" #include "Settings.h" #include "SettingsDialog.h" -#include "SimpleWebView.h" #include "Utilities.h" +#include "WebContentView.h" #include #include #include @@ -94,14 +94,7 @@ BrowserWindow::BrowserWindow() inspect_menu->addAction(view_source_action); QObject::connect(view_source_action, &QAction::triggered, this, [this] { if (m_current_tab) { - auto source = m_current_tab->view().source(); - - auto* text_edit = new QPlainTextEdit(this); - text_edit->setWindowFlags(Qt::Window); - text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont)); - text_edit->resize(800, 600); - text_edit->setPlainText(source.characters()); - text_edit->show(); + m_current_tab->view().get_source(); } }); diff --git a/Ladybird/BrowserWindow.h b/Ladybird/BrowserWindow.h index 8e42557ac57..3d985263069 100644 --- a/Ladybird/BrowserWindow.h +++ b/Ladybird/BrowserWindow.h @@ -16,14 +16,14 @@ #pragma once -class SimpleWebView; +class WebContentView; class BrowserWindow : public QMainWindow { Q_OBJECT public: explicit BrowserWindow(); - SimpleWebView& view() const { return m_current_tab->view(); } + WebContentView& view() const { return m_current_tab->view(); } int tab_index(Tab*); diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index a481840c5bb..576e1aa6bc5 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -50,32 +50,26 @@ find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network) set(SOURCES BrowserWindow.cpp - ConsoleClient.cpp - ConsoleGlobalObject.cpp CookieJar.cpp - EventLoopPluginQt.cpp - FontPluginQt.cpp - ImageCodecPluginLadybird.cpp - ModelTranslator.cpp - PageClientLadybird.cpp - RequestManagerQt.cpp - main.cpp + WebContentView.cpp History.cpp + ModelTranslator.cpp Settings.cpp SettingsDialog.cpp - SimpleWebView.cpp Tab.cpp - TimerQt.cpp Utilities.cpp - WebSocketClientManagerLadybird.cpp - WebSocketLadybird.cpp + main.cpp ) qt_add_executable(ladybird ${SOURCES} MANUAL_FINALIZATION ) +#target_link_libraries(ladybird PRIVATE Qt::Widgets Qt::Network LibWeb LibGUI LibWeb LibWebView LibGL LibSoftGPU LibMain) target_link_libraries(ladybird PRIVATE Qt::Widgets Qt::Network LibWeb LibWebSocket LibGUI LibWebView LibGL LibSoftGPU LibMain) +target_include_directories(ladybird PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(ladybird PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/) + set_target_properties(ladybird PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER org.serenityos.ladybird MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} @@ -104,3 +98,5 @@ qt_finalize_executable(ladybird) if(NOT CMAKE_SKIP_INSTALL_RULES) include(cmake/InstallRules.cmake) endif() + +add_subdirectory(WebContent) diff --git a/Ladybird/SimpleWebView.cpp b/Ladybird/SimpleWebView.cpp deleted file mode 100644 index 9a41a26683b..00000000000 --- a/Ladybird/SimpleWebView.cpp +++ /dev/null @@ -1,668 +0,0 @@ -/* - * Copyright (c) 2022, Dex♪ - * Copyright (c) 2022, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#define AK_DONT_REPLACE_STD - -#include "SimpleWebView.h" -#include "ConsoleClient.h" -#include "CookieJar.h" -#include "EventLoopPluginQt.h" -#include "FontPluginQt.h" -#include "ImageCodecPluginLadybird.h" -#include "ModelTranslator.h" -#include "PageClientLadybird.h" -#include "RequestManagerQt.h" -#include "Utilities.h" -#include "WebSocketClientManagerLadybird.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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -String s_serenity_resource_root; - -SimpleWebView::SimpleWebView() -{ - setMouseTracking(true); - - m_page_client = Ladybird::PageClientLadybird::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(); - - verticalScrollBar()->setSingleStep(24); - horizontalScrollBar()->setSingleStep(24); - - QObject::connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) { - update_viewport_rect(); - }); - QObject::connect(horizontalScrollBar(), &QScrollBar::valueChanged, [this](int) { - update_viewport_rect(); - }); -} - -SimpleWebView::~SimpleWebView() -{ -} - -void SimpleWebView::reload() -{ - auto url = m_page_client->page().top_level_browsing_context().active_document()->url(); - m_page_client->load(url); -} - -void SimpleWebView::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; - if (event.button() == Qt::MouseButton::BackButton) - return 8; - if (event.buttons() == Qt::MouseButton::ForwardButton) - return 16; - 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; - if (event.buttons() & Qt::MouseButton::BackButton) - buttons |= 8; - if (event.buttons() & Qt::MouseButton::ForwardButton) - buttons |= 16; - return buttons; -} - -unsigned get_modifiers_from_qt_mouse_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; -} - -unsigned get_modifiers_from_qt_keyboard_event(QKeyEvent const& event) -{ - auto modifiers = 0; - if (event.modifiers().testFlag(Qt::AltModifier)) - modifiers |= KeyModifier::Mod_Alt; - if (event.modifiers().testFlag(Qt::ControlModifier)) - modifiers |= KeyModifier::Mod_Ctrl; - if (event.modifiers().testFlag(Qt::MetaModifier)) - modifiers |= KeyModifier::Mod_Super; - if (event.modifiers().testFlag(Qt::ShiftModifier)) - modifiers |= KeyModifier::Mod_Shift; - if (event.modifiers().testFlag(Qt::AltModifier)) - modifiers |= KeyModifier::Mod_AltGr; - return modifiers; -} - -KeyCode get_keycode_from_qt_keyboard_event(QKeyEvent const& event) -{ - struct Mapping { - constexpr Mapping(Qt::Key q, KeyCode s) - : qt_key(q) - , serenity_key(s) - { - } - - Qt::Key qt_key; - KeyCode serenity_key; - }; - - constexpr Mapping mappings[] = { - { Qt::Key_0, Key_0 }, - { Qt::Key_1, Key_1 }, - { Qt::Key_2, Key_2 }, - { Qt::Key_3, Key_3 }, - { Qt::Key_4, Key_4 }, - { Qt::Key_5, Key_5 }, - { Qt::Key_6, Key_6 }, - { Qt::Key_7, Key_7 }, - { Qt::Key_8, Key_8 }, - { Qt::Key_9, Key_9 }, - { Qt::Key_A, Key_A }, - { Qt::Key_Alt, Key_Alt }, - { Qt::Key_Ampersand, Key_Ampersand }, - { Qt::Key_Apostrophe, Key_Apostrophe }, - { Qt::Key_AsciiCircum, Key_Circumflex }, - { Qt::Key_AsciiTilde, Key_Tilde }, - { Qt::Key_Asterisk, Key_Asterisk }, - { Qt::Key_At, Key_AtSign }, - { Qt::Key_B, Key_B }, - { Qt::Key_Backslash, Key_Backslash }, - { Qt::Key_Backspace, Key_Backspace }, - { Qt::Key_Bar, Key_Pipe }, - { Qt::Key_BraceLeft, Key_LeftBrace }, - { Qt::Key_BraceRight, Key_RightBrace }, - { Qt::Key_BracketLeft, Key_LeftBracket }, - { Qt::Key_BracketRight, Key_RightBracket }, - { Qt::Key_C, Key_C }, - { Qt::Key_CapsLock, Key_CapsLock }, - { Qt::Key_Colon, Key_Colon }, - { Qt::Key_Comma, Key_Comma }, - { Qt::Key_Control, Key_Control }, - { Qt::Key_D, Key_D }, - { Qt::Key_Delete, Key_Delete }, - { Qt::Key_Dollar, Key_Dollar }, - { Qt::Key_Down, Key_Down }, - { Qt::Key_E, Key_E }, - { Qt::Key_End, Key_End }, - { Qt::Key_Equal, Key_Equal }, - { Qt::Key_Escape, Key_Escape }, - { Qt::Key_exclamdown, Key_ExclamationPoint }, - { Qt::Key_F, Key_F }, - { Qt::Key_F1, Key_F1 }, - { Qt::Key_F10, Key_F10 }, - { Qt::Key_F11, Key_F11 }, - { Qt::Key_F12, Key_F12 }, - { Qt::Key_F2, Key_F2 }, - { Qt::Key_F3, Key_F3 }, - { Qt::Key_F4, Key_F4 }, - { Qt::Key_F5, Key_F5 }, - { Qt::Key_F6, Key_F6 }, - { Qt::Key_F7, Key_F7 }, - { Qt::Key_F8, Key_F8 }, - { Qt::Key_F9, Key_F9 }, - { Qt::Key_G, Key_G }, - { Qt::Key_Greater, Key_GreaterThan }, - { Qt::Key_H, Key_H }, - { Qt::Key_Home, Key_Home }, - { Qt::Key_I, Key_I }, - { Qt::Key_Insert, Key_Insert }, - { Qt::Key_J, Key_J }, - { Qt::Key_K, Key_K }, - { Qt::Key_L, Key_L }, - { Qt::Key_Left, Key_Left }, - { Qt::Key_Less, Key_LessThan }, - { Qt::Key_M, Key_M }, - { Qt::Key_Menu, Key_Menu }, - { Qt::Key_Minus, Key_Minus }, - { Qt::Key_N, Key_N }, - { Qt::Key_NumLock, Key_NumLock }, - { Qt::Key_O, Key_O }, - { Qt::Key_P, Key_P }, - { Qt::Key_PageDown, Key_PageDown }, - { Qt::Key_PageUp, Key_PageUp }, - { Qt::Key_ParenLeft, Key_LeftParen }, - { Qt::Key_ParenRight, Key_RightParen }, - { Qt::Key_Percent, Key_Percent }, - { Qt::Key_Period, Key_Period }, - { Qt::Key_Plus, Key_Plus }, - { Qt::Key_Print, Key_PrintScreen }, - { Qt::Key_Q, Key_Q }, - { Qt::Key_Question, Key_QuestionMark }, - { Qt::Key_QuoteDbl, Key_DoubleQuote }, - { Qt::Key_R, Key_R }, - { Qt::Key_Return, Key_Return }, - { Qt::Key_Right, Key_Right }, - { Qt::Key_S, Key_S }, - { Qt::Key_ScrollLock, Key_ScrollLock }, - { Qt::Key_Semicolon, Key_Semicolon }, - { Qt::Key_Shift, Key_LeftShift }, - { Qt::Key_Slash, Key_Slash }, - { Qt::Key_Space, Key_Space }, - { Qt::Key_Super_L, Key_Super }, - { Qt::Key_SysReq, Key_SysRq }, - { Qt::Key_T, Key_T }, - { Qt::Key_Tab, Key_Tab }, - { Qt::Key_U, Key_U }, - { Qt::Key_Underscore, Key_Underscore }, - { Qt::Key_Up, Key_Up }, - { Qt::Key_V, Key_V }, - { Qt::Key_W, Key_W }, - { Qt::Key_X, Key_X }, - { Qt::Key_Y, Key_Y }, - { Qt::Key_Z, Key_Z }, - }; - - for (auto const& mapping : mappings) { - if (event.key() == mapping.qt_key) - return mapping.serenity_key; - } - return Key_Invalid; -} - -void SimpleWebView::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_mouse_event(*event); - m_page_client->page().handle_mousemove(to_content(position), buttons, modifiers); -} - -void SimpleWebView::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); - if (button == 0) { - // We could not convert Qt buttons to something that Lagom can - // recognize - don't even bother propagating this to the web engine - // as it will not handle it anyway, and it will (currently) assert - return; - } - auto modifiers = get_modifiers_from_qt_mouse_event(*event); - m_page_client->page().handle_mousedown(to_content(position), button, modifiers); -} - -void SimpleWebView::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); - if (button == 0) { - // We could not convert Qt buttons to something that Lagom can - // recognize - don't even bother propagating this to the web engine - // as it will not handle it anyway, and it will (currently) assert - return; - } - auto modifiers = get_modifiers_from_qt_mouse_event(*event); - m_page_client->page().handle_mouseup(to_content(position), button, modifiers); -} - -void SimpleWebView::keyPressEvent(QKeyEvent* event) -{ - switch (event->key()) { - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - QAbstractScrollArea::keyPressEvent(event); - break; - default: - break; - } - - auto text = event->text(); - if (text.isEmpty()) { - return; - } - auto point = event->text()[0].unicode(); - auto keycode = get_keycode_from_qt_keyboard_event(*event); - auto modifiers = get_modifiers_from_qt_keyboard_event(*event); - m_page_client->page().handle_keydown(keycode, modifiers, point); -} - -void SimpleWebView::keyReleaseEvent(QKeyEvent* event) -{ - auto text = event->text(); - if (text.isEmpty()) { - return; - } - auto point = event->text()[0].unicode(); - auto keycode = get_keycode_from_qt_keyboard_event(*event); - auto modifiers = get_modifiers_from_qt_keyboard_event(*event); - m_page_client->page().handle_keyup(keycode, modifiers, point); -} - -Gfx::IntPoint SimpleWebView::to_content(Gfx::IntPoint viewport_position) const -{ - return viewport_position.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()); -} - -Gfx::IntPoint SimpleWebView::to_widget(Gfx::IntPoint content_position) const -{ - return content_position.translated(-horizontalScrollBar()->value(), -verticalScrollBar()->value()); -} - -void SimpleWebView::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 SimpleWebView::resizeEvent(QResizeEvent* event) -{ - QAbstractScrollArea::resizeEvent(event); - update_viewport_rect(); -} - -void SimpleWebView::update_viewport_rect() -{ - auto scaled_width = int(viewport()->size().width() / m_inverse_pixel_scaling_ratio); - auto scaled_height = int(viewport()->size().height() / m_inverse_pixel_scaling_ratio); - Gfx::IntRect rect(horizontalScrollBar()->value(), verticalScrollBar()->value(), scaled_width, scaled_height); - m_page_client->set_viewport_rect(rect); -} - -static void platform_init() -{ -#ifdef AK_OS_ANDROID - extern void android_platform_init(); - android_platform_init(); -#else - 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); - auto home_lagom = String::formatted("{}/.lagom", home); - if (Core::File::is_directory(home_lagom)) - return home_lagom; - auto app_dir = akstring_from_qstring(QApplication::applicationDirPath()); - return LexicalPath(app_dir).parent().append("share"sv).string(); - }(); -#endif -} - -static ErrorOr load_content_filters() -{ - auto file_or_error = Core::Stream::File::open(String::formatted("{}/home/anon/.config/BrowserContentFilters.txt", s_serenity_resource_root), Core::Stream::OpenMode::Read); - if (file_or_error.is_error()) - file_or_error = Core::Stream::File::open(String::formatted("{}/res/ladybird/BrowserContentFilters.txt", s_serenity_resource_root), Core::Stream::OpenMode::Read); - if (file_or_error.is_error()) - return file_or_error.release_error(); - auto file = file_or_error.release_value(); - auto ad_filter_list = TRY(Core::Stream::BufferedFile::create(move(file))); - auto buffer = TRY(ByteBuffer::create_uninitialized(4096)); - size_t num_filters = 0; - while (TRY(ad_filter_list->can_read_line())) { - auto line = TRY(ad_filter_list->read_line(buffer)); - if (!line.is_empty()) { - Web::ContentFilter::the().add_pattern(line); - ++num_filters; - } - } - dbgln("Added {} content filters", num_filters); - return {}; -} - -void initialize_web_engine() -{ - platform_init(); - - Web::Platform::EventLoopPlugin::install(*new Ladybird::EventLoopPluginQt); - Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPluginLadybird); - - Web::ResourceLoader::initialize(RequestManagerQt::create()); - Web::WebSockets::WebSocketClientManager::initialize(Ladybird::WebSocketClientManagerLadybird::create()); - - Web::FrameLoader::set_default_favicon_path(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root)); - - Web::Platform::FontPlugin::install(*new Ladybird::FontPluginQt); - - Web::FrameLoader::set_error_page_url(String::formatted("file://{}/res/html/error.html", s_serenity_resource_root)); - - auto maybe_content_filter_error = load_content_filters(); - if (maybe_content_filter_error.is_error()) - dbgln("Failed to load content filters: {}", maybe_content_filter_error.error()); -} - -void SimpleWebView::debug_request(String const& request, String const& argument) -{ - auto& page = m_page_client->page(); - - if (request == "dump-dom-tree") { - if (auto* doc = page.top_level_browsing_context().active_document()) - Web::dump_tree(*doc); - } else if (request == "dump-layout-tree") { - if (auto* doc = page.top_level_browsing_context().active_document()) { - if (auto* icb = doc->layout_node()) - Web::dump_tree(*icb); - } - } else if (request == "dump-stacking-context-tree") { - if (auto* doc = page.top_level_browsing_context().active_document()) { - if (auto* icb = doc->layout_node()) { - if (auto* stacking_context = icb->paint_box()->stacking_context()) - stacking_context->dump(); - } - } - } else if (request == "dump-style-sheets") { - if (auto* doc = page.top_level_browsing_context().active_document()) { - for (auto& sheet : doc->style_sheets().sheets()) { - Web::dump_sheet(sheet); - } - } - } else if (request == "collect-garbage") { - Web::Bindings::main_thread_vm().heap().collect_garbage(JS::Heap::CollectionType::CollectGarbage, true); - } else if (request == "set-line-box-borders") { - bool state = argument == "on"; - m_page_client->set_should_show_line_box_borders(state); - page.top_level_browsing_context().set_needs_display(page.top_level_browsing_context().viewport_rect()); - } else if (request == "clear-cache") { - Web::ResourceLoader::the().clear_cache(); - } else if (request == "spoof-user-agent") { - Web::ResourceLoader::the().set_user_agent(argument); - } else if (request == "same-origin-policy") { - page.set_same_origin_policy_enabled(argument == "on"); - } else if (request == "scripting") { - page.set_is_scripting_enabled(argument == "on"); - } else if (request == "dump-local-storage") { - if (auto* doc = page.top_level_browsing_context().active_document()) - doc->window().local_storage()->dump(); - } else if (request == "dump-cookies"sv) { - m_page_client->dump_cookies(); - } else { - dbgln("Unknown debug request: {}", request); - } -} - -String SimpleWebView::source() const -{ - auto* document = m_page_client->page().top_level_browsing_context().active_document(); - if (!document) - return String::empty(); - return document->source(); -} - -void SimpleWebView::run_javascript(String const& js_source) const -{ - auto* active_document = const_cast(m_page_client->page().top_level_browsing_context().active_document()); - - if (!active_document) - return; - - // This is partially based on "execute a javascript: URL request" https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol - - // Let settings be browsingContext's active document's relevant settings object. - auto& settings = active_document->relevant_settings_object(); - - // Let baseURL be settings's API base URL. - auto base_url = settings.api_base_url(); - - // Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options. - // FIXME: This doesn't pass in "default classic script fetch options" - // FIXME: What should the filename be here? - auto script = Web::HTML::ClassicScript::create("(client connection run_javascript)", js_source, settings, base_url); - - // Let evaluationStatus be the result of running the classic script script. - auto evaluation_status = script->run(); - - if (evaluation_status.is_error()) - dbgln("Exception :("); -} - -void SimpleWebView::did_output_js_console_message(i32 message_index) -{ - m_page_client->m_console_client->send_messages(message_index); -} - -void SimpleWebView::did_get_js_console_messages(i32, Vector, Vector messages) -{ - ensure_js_console_widget(); - for (auto& message : messages) { - m_js_console_output_edit->append(qstring_from_akstring(message).trimmed()); - } -} - -void SimpleWebView::ensure_js_console_widget() -{ - if (!m_js_console_widget) { - m_js_console_widget = new QWidget; - m_js_console_widget->setWindowTitle("JS Console"); - auto* layout = new QVBoxLayout(m_js_console_widget); - m_js_console_widget->setLayout(layout); - m_js_console_output_edit = new QTextEdit(this); - m_js_console_output_edit->setReadOnly(true); - m_js_console_input_edit = new QLineEdit(this); - layout->addWidget(m_js_console_output_edit); - layout->addWidget(m_js_console_input_edit); - m_js_console_widget->resize(640, 480); - - QObject::connect(m_js_console_input_edit, &QLineEdit::returnPressed, [this] { - auto code = m_js_console_input_edit->text().trimmed(); - m_js_console_input_edit->clear(); - - m_js_console_output_edit->append(QString("> %1").arg(code)); - - m_page_client->initialize_js_console(); - m_page_client->m_console_client->handle_input(akstring_from_qstring(code)); - }); - } -} - -void SimpleWebView::show_js_console() -{ - ensure_js_console_widget(); - m_js_console_widget->show(); - m_js_console_input_edit->setFocus(); -} - -void SimpleWebView::ensure_inspector_widget() -{ - if (m_inspector_widget) - return; - m_inspector_widget = new QWidget; - m_inspector_widget->setWindowTitle("Inspector"); - auto* layout = new QVBoxLayout; - m_inspector_widget->setLayout(layout); - auto* tree_view = new QTreeView; - layout->addWidget(tree_view); - - auto dom_tree = m_page_client->page().top_level_browsing_context().active_document()->dump_dom_tree_as_json(); - auto dom_tree_model = ::WebView::DOMTreeModel::create(dom_tree); - auto* model = new Ladybird::ModelTranslator(dom_tree_model); - tree_view->setModel(model); - tree_view->setHeaderHidden(true); - tree_view->expandToDepth(3); - - m_inspector_widget->resize(640, 480); - - QObject::connect(tree_view->selectionModel(), &QItemSelectionModel::currentChanged, [this](QModelIndex const& index, QModelIndex const&) { - auto const* json = (JsonObject const*)index.internalPointer(); - m_page_client->page().top_level_browsing_context().active_document()->set_inspected_node(Web::DOM::Node::from_id(json->get("id"sv).to_i32())); - }); -} - -void SimpleWebView::show_inspector() -{ - ensure_inspector_widget(); - m_inspector_widget->show(); -} - -void SimpleWebView::set_color_scheme(ColorScheme color_scheme) -{ - switch (color_scheme) { - case ColorScheme::Auto: - m_page_client->m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Auto; - break; - case ColorScheme::Light: - m_page_client->m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Light; - break; - case ColorScheme::Dark: - m_page_client->m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Dark; - break; - } - if (auto* document = m_page_client->page().top_level_browsing_context().active_document()) - document->invalidate_style(); -} - -void SimpleWebView::showEvent(QShowEvent* event) -{ - QAbstractScrollArea::showEvent(event); - m_page_client->page().top_level_browsing_context().set_system_visibility_state(Web::HTML::VisibilityState::Visible); -} - -void SimpleWebView::hideEvent(QHideEvent* event) -{ - QAbstractScrollArea::hideEvent(event); - m_page_client->page().top_level_browsing_context().set_system_visibility_state(Web::HTML::VisibilityState::Hidden); -} diff --git a/Ladybird/SimpleWebView.h b/Ladybird/SimpleWebView.h deleted file mode 100644 index 790ea2ba4a7..00000000000 --- a/Ladybird/SimpleWebView.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2022, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#define AK_DONT_REPLACE_STD - -#include -#include -#include -#include -#include - -class QTextEdit; -class QLineEdit; - -namespace Ladybird { -class PageClientLadybird; -} - -enum class ColorScheme { - Auto, - Light, - Dark, -}; - -class SimpleWebView final : public QAbstractScrollArea { - Q_OBJECT -public: - SimpleWebView(); - virtual ~SimpleWebView() override; - - void load(String const& url); - void reload(); - - virtual void paintEvent(QPaintEvent*) override; - virtual void resizeEvent(QResizeEvent*) override; - virtual void mouseMoveEvent(QMouseEvent*) override; - virtual void mousePressEvent(QMouseEvent*) override; - virtual void mouseReleaseEvent(QMouseEvent*) override; - virtual void keyPressEvent(QKeyEvent* event) override; - virtual void keyReleaseEvent(QKeyEvent* event) override; - virtual void showEvent(QShowEvent*) override; - virtual void hideEvent(QHideEvent*) override; - - void debug_request(String const& request, String const& argument); - - String source() const; - - void run_javascript(String const& js_source) const; - - void did_output_js_console_message(i32 message_index); - void did_get_js_console_messages(i32 start_index, Vector message_types, Vector messages); - - void show_js_console(); - void show_inspector(); - - Gfx::IntPoint to_content(Gfx::IntPoint) const; - Gfx::IntPoint to_widget(Gfx::IntPoint) const; - - void set_color_scheme(ColorScheme); - -signals: - void link_hovered(QString, int timeout = 0); - void link_unhovered(); - void load_started(const URL&); - void title_changed(QString); - void favicon_changed(QIcon); - -private: - void update_viewport_rect(); - - void ensure_js_console_widget(); - void ensure_inspector_widget(); - - OwnPtr m_page_client; - - qreal m_inverse_pixel_scaling_ratio { 1.0 }; - bool m_should_show_line_box_borders { false }; - - QPointer m_js_console_widget; - QPointer m_inspector_widget; - - QTextEdit* m_js_console_output_edit { nullptr }; - QLineEdit* m_js_console_input_edit { nullptr }; -}; diff --git a/Ladybird/Tab.cpp b/Ladybird/Tab.cpp index 7b67046dead..f19ac4941b2 100644 --- a/Ladybird/Tab.cpp +++ b/Ladybird/Tab.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -27,7 +28,7 @@ Tab::Tab(BrowserWindow* window) m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); - m_view = new SimpleWebView; + m_view = new WebContentView; m_toolbar = new QToolBar(this); m_location_edit = new QLineEdit(this); @@ -63,30 +64,39 @@ Tab::Tab(BrowserWindow* window) m_toolbar->addAction(m_home_action); m_toolbar->addWidget(m_location_edit); - QObject::connect(m_view, &SimpleWebView::link_hovered, [this](QString const& title) { + QObject::connect(m_view, &WebContentView::link_hovered, [this](QString const& title) { m_hover_label->setText(title); update_hover_label(); m_hover_label->show(); }); - QObject::connect(m_view, &SimpleWebView::link_unhovered, [this] { + QObject::connect(m_view, &WebContentView::link_unhovered, [this] { m_hover_label->hide(); }); - QObject::connect(m_view, &SimpleWebView::load_started, [this](const URL& url) { + QObject::connect(m_view, &WebContentView::load_started, [this](const URL& url) { m_location_edit->setText(url.to_string().characters()); m_history.push(url, m_title.toUtf8().data()); m_back_action->setEnabled(m_history.can_go_back()); m_forward_action->setEnabled(m_history.can_go_forward()); }); QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &Tab::location_edit_return_pressed); - QObject::connect(m_view, &SimpleWebView::title_changed, this, &Tab::page_title_changed); - QObject::connect(m_view, &SimpleWebView::favicon_changed, this, &Tab::page_favicon_changed); + QObject::connect(m_view, &WebContentView::title_changed, this, &Tab::page_title_changed); + QObject::connect(m_view, &WebContentView::favicon_changed, this, &Tab::page_favicon_changed); QObject::connect(m_back_action, &QAction::triggered, this, &Tab::back); QObject::connect(m_forward_action, &QAction::triggered, this, &Tab::forward); QObject::connect(m_home_action, &QAction::triggered, this, &Tab::home); QObject::connect(m_reload_action, &QAction::triggered, this, &Tab::reload); QObject::connect(focus_location_editor_action, &QAction::triggered, this, &Tab::focus_location_editor); + + QObject::connect(m_view, &WebContentView::got_source, this, [this](AK::URL, QString const& source) { + auto* text_edit = new QPlainTextEdit(this); + text_edit->setWindowFlags(Qt::Window); + text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont)); + text_edit->resize(800, 600); + text_edit->setPlainText(source); + text_edit->show(); + }); } void Tab::focus_location_editor() @@ -99,7 +109,7 @@ void Tab::navigate(QString url) { if (!url.startsWith("http://", Qt::CaseInsensitive) && !url.startsWith("https://", Qt::CaseInsensitive) && !url.startsWith("file://", Qt::CaseInsensitive)) url = "http://" + url; - view().load(url.toUtf8().data()); + view().load(akstring_from_qstring(url)); } void Tab::back() diff --git a/Ladybird/Tab.h b/Ladybird/Tab.h index 8fd31322ae4..1b6737555dc 100644 --- a/Ladybird/Tab.h +++ b/Ladybird/Tab.h @@ -10,7 +10,7 @@ #define AK_DONT_REPLACE_STD #include "History.h" -#include "SimpleWebView.h" +#include "WebContentView.h" #include #include #include @@ -24,7 +24,7 @@ class Tab final : public QWidget { public: explicit Tab(BrowserWindow* window); - SimpleWebView& view() { return *m_view; } + WebContentView& view() { return *m_view; } void navigate(QString); @@ -52,7 +52,7 @@ private: QBoxLayout* m_layout; QToolBar* m_toolbar { nullptr }; QLineEdit* m_location_edit { nullptr }; - SimpleWebView* m_view { nullptr }; + WebContentView* m_view { nullptr }; BrowserWindow* m_window { nullptr }; Browser::History m_history; QString m_title; diff --git a/Ladybird/Utilities.cpp b/Ladybird/Utilities.cpp index 554ef9e956b..93bd83e2792 100644 --- a/Ladybird/Utilities.cpp +++ b/Ladybird/Utilities.cpp @@ -7,6 +7,12 @@ #define AK_DONT_REPLACE_STD #include "Utilities.h" +#include +#include +#include +#include + +String s_serenity_resource_root; AK::String akstring_from_qstring(QString const& qstring) { @@ -17,3 +23,25 @@ QString qstring_from_akstring(AK::String const& akstring) { return QString::fromUtf8(akstring.characters(), akstring.length()); } + +void platform_init() +{ +#ifdef AK_OS_ANDROID + extern void android_platform_init(); + android_platform_init(); +#else + 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); + auto home_lagom = String::formatted("{}/.lagom", home); + if (Core::File::is_directory(home_lagom)) + return home_lagom; + auto app_dir = akstring_from_qstring(QCoreApplication::applicationDirPath()); + return LexicalPath(app_dir).parent().append("share"sv).string(); + }(); +#endif +} diff --git a/Ladybird/Utilities.h b/Ladybird/Utilities.h index dc79b10a16c..60132304c74 100644 --- a/Ladybird/Utilities.h +++ b/Ladybird/Utilities.h @@ -11,3 +11,6 @@ AK::String akstring_from_qstring(QString const&); QString qstring_from_akstring(AK::String const&); +void platform_init(); + +extern String s_serenity_resource_root; diff --git a/Ladybird/WebContent/CMakeLists.txt b/Ladybird/WebContent/CMakeLists.txt new file mode 100644 index 00000000000..de967f37e9a --- /dev/null +++ b/Ladybird/WebContent/CMakeLists.txt @@ -0,0 +1,28 @@ +set(WEBCONTENT_SOURCE_DIR ${SERENITY_SOURCE_DIR}/Userland/Services/WebContent/) + +compile_ipc(${WEBCONTENT_SOURCE_DIR}/WebContentServer.ipc WebContentServerEndpoint.h) +compile_ipc(${WEBCONTENT_SOURCE_DIR}/WebContentClient.ipc WebContentClientEndpoint.h) + +set(WEBCONTENT_SOURCES + ${WEBCONTENT_SOURCE_DIR}/ConnectionFromClient.cpp + ${WEBCONTENT_SOURCE_DIR}/ConsoleGlobalObject.cpp + ${WEBCONTENT_SOURCE_DIR}/PageHost.cpp + ${WEBCONTENT_SOURCE_DIR}/WebContentConsoleClient.cpp + ../EventLoopPluginQt.cpp + ../FontPluginQt.cpp + ../ImageCodecPluginLadybird.cpp + ../RequestManagerQt.cpp + ../TimerQt.cpp + ../Utilities.cpp + ../WebSocketClientManagerLadybird.cpp + ../WebSocketLadybird.cpp + WebContentClientEndpoint.h + WebContentServerEndpoint.h + main.cpp +) + +qt_add_executable(WebContent ${WEBCONTENT_SOURCES}) + +target_include_directories(WebContent PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/) +target_include_directories(WebContent PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..) +target_link_libraries(WebContent PRIVATE Qt::Core Qt::Network Qt::Gui LibCore LibIPC LibGfx LibWebView LibWebSocket LibWeb LibMain) diff --git a/Ladybird/WebContent/main.cpp b/Ladybird/WebContent/main.cpp new file mode 100644 index 00000000000..911348b3b16 --- /dev/null +++ b/Ladybird/WebContent/main.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include "../EventLoopPluginQt.h" +#include "../FontPluginQt.h" +#include "../ImageCodecPluginLadybird.h" +#include "../RequestManagerQt.h" +#include "../Utilities.h" +#include "../WebSocketClientManagerLadybird.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static ErrorOr load_content_filters(); + +extern String s_serenity_resource_root; + +ErrorOr serenity_main(Main::Arguments arguments) +{ + // NOTE: This is only used for the Core::Socket inside the IPC connection. + // FIXME: Refactor things so we can get rid of this somehow. + Core::EventLoop event_loop; + + platform_init(); + + QGuiApplication app(arguments.argc, arguments.argv); + + Web::Platform::EventLoopPlugin::install(*new Ladybird::EventLoopPluginQt); + Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPluginLadybird); + + Web::ResourceLoader::initialize(RequestManagerQt::create()); + Web::WebSockets::WebSocketClientManager::initialize(Ladybird::WebSocketClientManagerLadybird::create()); + + Web::FrameLoader::set_default_favicon_path(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root)); + + Web::Platform::FontPlugin::install(*new Ladybird::FontPluginQt); + + Web::FrameLoader::set_error_page_url(String::formatted("file://{}/res/html/error.html", s_serenity_resource_root)); + + auto maybe_content_filter_error = load_content_filters(); + if (maybe_content_filter_error.is_error()) + dbgln("Failed to load content filters: {}", maybe_content_filter_error.error()); + + auto client = TRY(IPC::take_over_accepted_client_from_system_server()); + + auto* fd_passing_socket_spec = getenv("FD_PASSING_SOCKET"); + VERIFY(fd_passing_socket_spec); + auto fd_passing_socket_spec_string = String(fd_passing_socket_spec); + auto maybe_fd_passing_socket = fd_passing_socket_spec_string.to_int(); + VERIFY(maybe_fd_passing_socket.has_value()); + + client->set_fd_passing_socket(TRY(Core::Stream::LocalSocket::adopt_fd(maybe_fd_passing_socket.value()))); + + QSocketNotifier notifier(client->socket().fd().value(), QSocketNotifier::Type::Read); + QObject::connect(¬ifier, &QSocketNotifier::activated, [&] { + client->socket().notifier()->on_ready_to_read(); + }); + + struct DeferredInvokerQt final : IPC::DeferredInvoker { + virtual ~DeferredInvokerQt() = default; + virtual void schedule(Function callback) override + { + QTimer::singleShot(0, move(callback)); + } + }; + + client->set_deferred_invoker(make()); + + return app.exec(); +} + +static ErrorOr load_content_filters() +{ + auto file_or_error = Core::Stream::File::open(String::formatted("{}/home/anon/.config/BrowserContentFilters.txt", s_serenity_resource_root), Core::Stream::OpenMode::Read); + if (file_or_error.is_error()) + file_or_error = Core::Stream::File::open(String::formatted("{}/res/ladybird/BrowserContentFilters.txt", s_serenity_resource_root), Core::Stream::OpenMode::Read); + if (file_or_error.is_error()) + return file_or_error.release_error(); + auto file = file_or_error.release_value(); + auto ad_filter_list = TRY(Core::Stream::BufferedFile::create(move(file))); + auto buffer = TRY(ByteBuffer::create_uninitialized(4096)); + while (TRY(ad_filter_list->can_read_line())) { + auto line = TRY(ad_filter_list->read_line(buffer)); + if (!line.is_empty()) { + Web::ContentFilter::the().add_pattern(line); + } + } + return {}; +} diff --git a/Ladybird/WebContentView.cpp b/Ladybird/WebContentView.cpp new file mode 100644 index 00000000000..d7cb3527aed --- /dev/null +++ b/Ladybird/WebContentView.cpp @@ -0,0 +1,911 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include "WebContentView.h" +#include "CookieJar.h" +#include "ModelTranslator.h" +#include "Utilities.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 + +WebContentView::WebContentView() +{ + setMouseTracking(true); + + m_inverse_pixel_scaling_ratio = 1.0 / devicePixelRatio(); + + verticalScrollBar()->setSingleStep(24); + horizontalScrollBar()->setSingleStep(24); + + QObject::connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) { + update_viewport_rect(); + }); + QObject::connect(horizontalScrollBar(), &QScrollBar::valueChanged, [this](int) { + update_viewport_rect(); + }); + + create_client(); +} + +WebContentView::~WebContentView() +{ +} + +void WebContentView::reload() +{ + load(m_url); +} + +void WebContentView::load(AK::URL const& url) +{ + m_url = url; + client().async_load_url(url); +} + +void WebContentView::load_html(StringView html, AK::URL const& url) +{ + m_url = url; + client().async_load_html(html, 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; + if (event.button() == Qt::MouseButton::BackButton) + return 8; + if (event.buttons() == Qt::MouseButton::ForwardButton) + return 16; + 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; + if (event.buttons() & Qt::MouseButton::BackButton) + buttons |= 8; + if (event.buttons() & Qt::MouseButton::ForwardButton) + buttons |= 16; + return buttons; +} + +unsigned get_modifiers_from_qt_mouse_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; +} + +unsigned get_modifiers_from_qt_keyboard_event(QKeyEvent const& event) +{ + auto modifiers = 0; + if (event.modifiers().testFlag(Qt::AltModifier)) + modifiers |= KeyModifier::Mod_Alt; + if (event.modifiers().testFlag(Qt::ControlModifier)) + modifiers |= KeyModifier::Mod_Ctrl; + if (event.modifiers().testFlag(Qt::MetaModifier)) + modifiers |= KeyModifier::Mod_Super; + if (event.modifiers().testFlag(Qt::ShiftModifier)) + modifiers |= KeyModifier::Mod_Shift; + if (event.modifiers().testFlag(Qt::AltModifier)) + modifiers |= KeyModifier::Mod_AltGr; + return modifiers; +} + +KeyCode get_keycode_from_qt_keyboard_event(QKeyEvent const& event) +{ + struct Mapping { + constexpr Mapping(Qt::Key q, KeyCode s) + : qt_key(q) + , serenity_key(s) + { + } + + Qt::Key qt_key; + KeyCode serenity_key; + }; + + constexpr Mapping mappings[] = { + { Qt::Key_0, Key_0 }, + { Qt::Key_1, Key_1 }, + { Qt::Key_2, Key_2 }, + { Qt::Key_3, Key_3 }, + { Qt::Key_4, Key_4 }, + { Qt::Key_5, Key_5 }, + { Qt::Key_6, Key_6 }, + { Qt::Key_7, Key_7 }, + { Qt::Key_8, Key_8 }, + { Qt::Key_9, Key_9 }, + { Qt::Key_A, Key_A }, + { Qt::Key_Alt, Key_Alt }, + { Qt::Key_Ampersand, Key_Ampersand }, + { Qt::Key_Apostrophe, Key_Apostrophe }, + { Qt::Key_AsciiCircum, Key_Circumflex }, + { Qt::Key_AsciiTilde, Key_Tilde }, + { Qt::Key_Asterisk, Key_Asterisk }, + { Qt::Key_At, Key_AtSign }, + { Qt::Key_B, Key_B }, + { Qt::Key_Backslash, Key_Backslash }, + { Qt::Key_Backspace, Key_Backspace }, + { Qt::Key_Bar, Key_Pipe }, + { Qt::Key_BraceLeft, Key_LeftBrace }, + { Qt::Key_BraceRight, Key_RightBrace }, + { Qt::Key_BracketLeft, Key_LeftBracket }, + { Qt::Key_BracketRight, Key_RightBracket }, + { Qt::Key_C, Key_C }, + { Qt::Key_CapsLock, Key_CapsLock }, + { Qt::Key_Colon, Key_Colon }, + { Qt::Key_Comma, Key_Comma }, + { Qt::Key_Control, Key_Control }, + { Qt::Key_D, Key_D }, + { Qt::Key_Delete, Key_Delete }, + { Qt::Key_Dollar, Key_Dollar }, + { Qt::Key_Down, Key_Down }, + { Qt::Key_E, Key_E }, + { Qt::Key_End, Key_End }, + { Qt::Key_Equal, Key_Equal }, + { Qt::Key_Escape, Key_Escape }, + { Qt::Key_exclamdown, Key_ExclamationPoint }, + { Qt::Key_F, Key_F }, + { Qt::Key_F1, Key_F1 }, + { Qt::Key_F10, Key_F10 }, + { Qt::Key_F11, Key_F11 }, + { Qt::Key_F12, Key_F12 }, + { Qt::Key_F2, Key_F2 }, + { Qt::Key_F3, Key_F3 }, + { Qt::Key_F4, Key_F4 }, + { Qt::Key_F5, Key_F5 }, + { Qt::Key_F6, Key_F6 }, + { Qt::Key_F7, Key_F7 }, + { Qt::Key_F8, Key_F8 }, + { Qt::Key_F9, Key_F9 }, + { Qt::Key_G, Key_G }, + { Qt::Key_Greater, Key_GreaterThan }, + { Qt::Key_H, Key_H }, + { Qt::Key_Home, Key_Home }, + { Qt::Key_I, Key_I }, + { Qt::Key_Insert, Key_Insert }, + { Qt::Key_J, Key_J }, + { Qt::Key_K, Key_K }, + { Qt::Key_L, Key_L }, + { Qt::Key_Left, Key_Left }, + { Qt::Key_Less, Key_LessThan }, + { Qt::Key_M, Key_M }, + { Qt::Key_Menu, Key_Menu }, + { Qt::Key_Minus, Key_Minus }, + { Qt::Key_N, Key_N }, + { Qt::Key_NumLock, Key_NumLock }, + { Qt::Key_O, Key_O }, + { Qt::Key_P, Key_P }, + { Qt::Key_PageDown, Key_PageDown }, + { Qt::Key_PageUp, Key_PageUp }, + { Qt::Key_ParenLeft, Key_LeftParen }, + { Qt::Key_ParenRight, Key_RightParen }, + { Qt::Key_Percent, Key_Percent }, + { Qt::Key_Period, Key_Period }, + { Qt::Key_Plus, Key_Plus }, + { Qt::Key_Print, Key_PrintScreen }, + { Qt::Key_Q, Key_Q }, + { Qt::Key_Question, Key_QuestionMark }, + { Qt::Key_QuoteDbl, Key_DoubleQuote }, + { Qt::Key_R, Key_R }, + { Qt::Key_Return, Key_Return }, + { Qt::Key_Right, Key_Right }, + { Qt::Key_S, Key_S }, + { Qt::Key_ScrollLock, Key_ScrollLock }, + { Qt::Key_Semicolon, Key_Semicolon }, + { Qt::Key_Shift, Key_LeftShift }, + { Qt::Key_Slash, Key_Slash }, + { Qt::Key_Space, Key_Space }, + { Qt::Key_Super_L, Key_Super }, + { Qt::Key_SysReq, Key_SysRq }, + { Qt::Key_T, Key_T }, + { Qt::Key_Tab, Key_Tab }, + { Qt::Key_U, Key_U }, + { Qt::Key_Underscore, Key_Underscore }, + { Qt::Key_Up, Key_Up }, + { Qt::Key_V, Key_V }, + { Qt::Key_W, Key_W }, + { Qt::Key_X, Key_X }, + { Qt::Key_Y, Key_Y }, + { Qt::Key_Z, Key_Z }, + }; + + for (auto const& mapping : mappings) { + if (event.key() == mapping.qt_key) + return mapping.serenity_key; + } + return Key_Invalid; +} + +void WebContentView::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_mouse_event(*event); + client().async_mouse_move(to_content(position), 0, buttons, modifiers); +} + +void WebContentView::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); + if (button == 0) { + // We could not convert Qt buttons to something that Lagom can + // recognize - don't even bother propagating this to the web engine + // as it will not handle it anyway, and it will (currently) assert + return; + } + auto modifiers = get_modifiers_from_qt_mouse_event(*event); + auto buttons = get_buttons_from_qt_event(*event); + client().async_mouse_down(to_content(position), button, buttons, modifiers); +} + +void WebContentView::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); + if (button == 0) { + // We could not convert Qt buttons to something that Lagom can + // recognize - don't even bother propagating this to the web engine + // as it will not handle it anyway, and it will (currently) assert + return; + } + auto modifiers = get_modifiers_from_qt_mouse_event(*event); + auto buttons = get_buttons_from_qt_event(*event); + client().async_mouse_up(to_content(position), button, buttons, modifiers); +} + +void WebContentView::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) { + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + QAbstractScrollArea::keyPressEvent(event); + break; + default: + break; + } + + auto text = event->text(); + if (text.isEmpty()) { + return; + } + auto point = event->text()[0].unicode(); + auto keycode = get_keycode_from_qt_keyboard_event(*event); + auto modifiers = get_modifiers_from_qt_keyboard_event(*event); + client().async_key_down(keycode, modifiers, point); +} + +void WebContentView::keyReleaseEvent(QKeyEvent* event) +{ + auto text = event->text(); + if (text.isEmpty()) { + return; + } + auto point = event->text()[0].unicode(); + auto keycode = get_keycode_from_qt_keyboard_event(*event); + auto modifiers = get_modifiers_from_qt_keyboard_event(*event); + client().async_key_up(keycode, modifiers, point); +} + +Gfx::IntPoint WebContentView::to_content(Gfx::IntPoint viewport_position) const +{ + return viewport_position.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()); +} + +Gfx::IntPoint WebContentView::to_widget(Gfx::IntPoint content_position) const +{ + return content_position.translated(-horizontalScrollBar()->value(), -verticalScrollBar()->value()); +} + +void WebContentView::paintEvent(QPaintEvent*) +{ + QPainter painter(viewport()); + painter.scale(m_inverse_pixel_scaling_ratio, m_inverse_pixel_scaling_ratio); + + if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr()) { + QImage q_image(bitmap->scanline_u8(0), bitmap->width(), bitmap->height(), QImage::Format_RGB32); + painter.drawImage(QPoint(0, 0), q_image); + return; + } + + painter.fillRect(rect(), palette().base()); +} + +void WebContentView::resizeEvent(QResizeEvent* event) +{ + QAbstractScrollArea::resizeEvent(event); + handle_resize(); +} + +void WebContentView::handle_resize() +{ + update_viewport_rect(); + + if (m_client_state.has_usable_bitmap) { + // NOTE: We keep the outgoing front bitmap as a backup so we have something to paint until we get a new one. + m_backup_bitmap = m_client_state.front_bitmap.bitmap; + } + + if (m_client_state.front_bitmap.bitmap) + client().async_remove_backing_store(m_client_state.front_bitmap.id); + + if (m_client_state.back_bitmap.bitmap) + client().async_remove_backing_store(m_client_state.back_bitmap.id); + + m_client_state.front_bitmap = {}; + m_client_state.back_bitmap = {}; + m_client_state.has_usable_bitmap = false; + + auto available_size = m_viewport_rect.size(); + + if (available_size.is_empty()) + return; + + if (auto new_bitmap_or_error = Gfx::Bitmap::try_create_shareable(Gfx::BitmapFormat::BGRx8888, available_size); !new_bitmap_or_error.is_error()) { + m_client_state.front_bitmap.bitmap = new_bitmap_or_error.release_value(); + m_client_state.front_bitmap.id = m_client_state.next_bitmap_id++; + client().async_add_backing_store(m_client_state.front_bitmap.id, m_client_state.front_bitmap.bitmap->to_shareable_bitmap()); + } + + if (auto new_bitmap_or_error = Gfx::Bitmap::try_create_shareable(Gfx::BitmapFormat::BGRx8888, available_size); !new_bitmap_or_error.is_error()) { + m_client_state.back_bitmap.bitmap = new_bitmap_or_error.release_value(); + m_client_state.back_bitmap.id = m_client_state.next_bitmap_id++; + client().async_add_backing_store(m_client_state.back_bitmap.id, m_client_state.back_bitmap.bitmap->to_shareable_bitmap()); + } + + request_repaint(); +} + +void WebContentView::update_viewport_rect() +{ + auto scaled_width = int(viewport()->width() / m_inverse_pixel_scaling_ratio); + auto scaled_height = int(viewport()->height() / m_inverse_pixel_scaling_ratio); + Gfx::IntRect rect(horizontalScrollBar()->value(), verticalScrollBar()->value(), scaled_width, scaled_height); + + m_viewport_rect = rect; + client().async_set_viewport_rect(rect); + + request_repaint(); +} + +void WebContentView::debug_request(String const& request, String const& argument) +{ + client().async_debug_request(request, argument); +} + +void WebContentView::run_javascript(String const& js_source) +{ + client().async_run_javascript(js_source); +} + +void WebContentView::did_output_js_console_message(i32 message_index) +{ + // FIXME + (void)message_index; +} + +void WebContentView::did_get_js_console_messages(i32, Vector, Vector messages) +{ + ensure_js_console_widget(); + for (auto& message : messages) { + m_js_console_output_edit->append(qstring_from_akstring(message).trimmed()); + } +} + +void WebContentView::ensure_js_console_widget() +{ + if (!m_js_console_widget) { + m_js_console_widget = new QWidget; + m_js_console_widget->setWindowTitle("JS Console"); + auto* layout = new QVBoxLayout(m_js_console_widget); + m_js_console_widget->setLayout(layout); + m_js_console_output_edit = new QTextEdit(this); + m_js_console_output_edit->setReadOnly(true); + m_js_console_input_edit = new QLineEdit(this); + layout->addWidget(m_js_console_output_edit); + layout->addWidget(m_js_console_input_edit); + m_js_console_widget->resize(640, 480); + + QObject::connect(m_js_console_input_edit, &QLineEdit::returnPressed, [this] { + auto code = m_js_console_input_edit->text().trimmed(); + client().async_js_console_input(akstring_from_qstring(code)); + m_js_console_input_edit->clear(); + m_js_console_output_edit->append(QString("> %1").arg(code)); + }); + } +} + +void WebContentView::show_js_console() +{ + ensure_js_console_widget(); + m_js_console_widget->show(); + m_js_console_input_edit->setFocus(); +} + +void WebContentView::ensure_inspector_widget() +{ + if (m_inspector_widget) + return; +#if 0 + m_inspector_widget = new QWidget; + m_inspector_widget->setWindowTitle("Inspector"); + auto* layout = new QVBoxLayout; + m_inspector_widget->setLayout(layout); + auto* tree_view = new QTreeView; + layout->addWidget(tree_view); + + auto dom_tree = m_page_client->page().top_level_browsing_context().active_document()->dump_dom_tree_as_json(); + auto dom_tree_model = ::WebView::DOMTreeModel::create(dom_tree); + auto* model = new Ladybird::ModelTranslator(dom_tree_model); + tree_view->setModel(model); + tree_view->setHeaderHidden(true); + tree_view->expandToDepth(3); + + m_inspector_widget->resize(640, 480); + + QObject::connect(tree_view->selectionModel(), &QItemSelectionModel::currentChanged, [this](QModelIndex const& index, QModelIndex const&) { + auto const* json = (JsonObject const*)index.internalPointer(); + m_page_client->page().top_level_browsing_context().active_document()->set_inspected_node(Web::DOM::Node::from_id(json->get("id"sv).to_i32())); + }); +#endif +} + +void WebContentView::show_inspector() +{ + ensure_inspector_widget(); + m_inspector_widget->show(); +} + +void WebContentView::set_color_scheme(ColorScheme color_scheme) +{ + switch (color_scheme) { + case ColorScheme::Auto: + client().async_set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto); + break; + case ColorScheme::Light: + client().async_set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light); + break; + case ColorScheme::Dark: + client().async_set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark); + break; + } +} + +void WebContentView::showEvent(QShowEvent* event) +{ + QAbstractScrollArea::showEvent(event); + client().async_set_system_visibility_state(true); +} + +void WebContentView::hideEvent(QHideEvent* event) +{ + QAbstractScrollArea::hideEvent(event); + client().async_set_system_visibility_state(false); +} + +WebContentClient& WebContentView::client() +{ + VERIFY(m_client_state.client); + return *m_client_state.client; +} + +void WebContentView::create_client() +{ + m_client_state = {}; + + int socket_fds[2] {}; + MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds)); + + int ui_fd = socket_fds[0]; + int wc_fd = socket_fds[1]; + + int fd_passing_socket_fds[2] {}; + MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd_passing_socket_fds)); + + int ui_fd_passing_fd = fd_passing_socket_fds[0]; + int wc_fd_passing_fd = fd_passing_socket_fds[1]; + + auto child_pid = fork(); + if (!child_pid) { + auto takeover_string = String::formatted("x:{}", wc_fd); + MUST(Core::System::setenv("SOCKET_TAKEOVER"sv, takeover_string, true)); + + auto fd_passing_socket_string = String::formatted("{}", wc_fd_passing_fd); + MUST(Core::System::setenv("FD_PASSING_SOCKET"sv, fd_passing_socket_string, true)); + + auto rc = execlp("./WebContent/WebContent", "WebContent", nullptr); + if (rc < 0) + perror("execlp"); + VERIFY_NOT_REACHED(); + } + + auto socket = MUST(Core::Stream::LocalSocket::adopt_fd(ui_fd)); + MUST(socket->set_blocking(true)); + + auto new_client = MUST(adopt_nonnull_ref_or_enomem(new (nothrow) WebView::WebContentClient(std::move(socket), *this))); + new_client->set_fd_passing_socket(MUST(Core::Stream::LocalSocket::adopt_fd(ui_fd_passing_fd))); + + auto* notifier = new QSocketNotifier(new_client->socket().fd().value(), QSocketNotifier::Type::Read); + QObject::connect(notifier, &QSocketNotifier::activated, [new_client = new_client.ptr()] { + if (auto notifier = new_client->socket().notifier()) + notifier->on_ready_to_read(); + }); + + struct DeferredInvokerQt final : IPC::DeferredInvoker { + virtual ~DeferredInvokerQt() = default; + virtual void schedule(Function callback) override + { + QTimer::singleShot(0, std::move(callback)); + } + }; + + new_client->set_deferred_invoker(make()); + + m_client_state.client = new_client; + m_client_state.client->on_web_content_process_crash = [this] { + QTimer::singleShot(0, [this] { + handle_web_content_process_crash(); + }); + }; + + client().async_update_system_theme(Gfx::load_system_theme(String::formatted("{}/res/themes/Default.ini", s_serenity_resource_root))); + client().async_update_system_fonts(Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query(), Gfx::FontDatabase::window_title_font_query()); + + // FIXME: Get the screen rect. + // client().async_update_screen_rects(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index()); +} + +void WebContentView::handle_web_content_process_crash() +{ + dbgln("WebContent process crashed!"); + create_client(); + VERIFY(m_client_state.client); + + // Don't keep a stale backup bitmap around. + m_backup_bitmap = nullptr; + + handle_resize(); + StringBuilder builder; + builder.append("Crashed: "sv); + builder.append(escape_html_entities(m_url.to_string())); + builder.append(""sv); + builder.append("

Web page crashed"sv); + if (!m_url.host().is_empty()) { + builder.appendff(" on {}", escape_html_entities(m_url.host())); + } + builder.append("

"sv); + auto escaped_url = escape_html_entities(m_url.to_string()); + builder.appendff("The web page {} has crashed.

You can reload the page to try again.", escaped_url, escaped_url); + builder.append(""sv); + load_html(builder.to_string(), m_url); +} + +void WebContentView::notify_server_did_paint(Badge, i32 bitmap_id) +{ + if (m_client_state.back_bitmap.id == bitmap_id) { + m_client_state.has_usable_bitmap = true; + m_client_state.back_bitmap.pending_paints--; + swap(m_client_state.back_bitmap, m_client_state.front_bitmap); + // We don't need the backup bitmap anymore, so drop it. + m_backup_bitmap = nullptr; + viewport()->update(); + + if (m_client_state.got_repaint_requests_while_painting) { + m_client_state.got_repaint_requests_while_painting = false; + request_repaint(); + } + } +} + +void WebContentView::notify_server_did_invalidate_content_rect(Badge, [[maybe_unused]] Gfx::IntRect const& content_rect) +{ + request_repaint(); +} + +void WebContentView::notify_server_did_change_selection(Badge) +{ + request_repaint(); +} + +void WebContentView::notify_server_did_request_cursor_change(Badge, Gfx::StandardCursor cursor) +{ + switch (cursor) { + case Gfx::StandardCursor::Hand: + setCursor(Qt::PointingHandCursor); + break; + case Gfx::StandardCursor::IBeam: + setCursor(Qt::IBeamCursor); + break; + case Gfx::StandardCursor::Arrow: + default: + setCursor(Qt::ArrowCursor); + break; + } +} + +void WebContentView::notify_server_did_layout(Badge, Gfx::IntSize const& content_size) +{ + verticalScrollBar()->setMinimum(0); + verticalScrollBar()->setMaximum(content_size.height() - m_viewport_rect.height()); + verticalScrollBar()->setPageStep(m_viewport_rect.height()); + horizontalScrollBar()->setMinimum(0); + horizontalScrollBar()->setMaximum(content_size.width() - m_viewport_rect.width()); + horizontalScrollBar()->setPageStep(m_viewport_rect.width()); +} + +void WebContentView::notify_server_did_change_title(Badge, String const& title) +{ + emit title_changed(qstring_from_akstring(title)); +} + +void WebContentView::notify_server_did_request_scroll(Badge, i32 x_delta, i32 y_delta) +{ + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x_delta); + verticalScrollBar()->setValue(verticalScrollBar()->value() + y_delta); +} + +void WebContentView::notify_server_did_request_scroll_to(Badge, Gfx::IntPoint const& scroll_position) +{ + horizontalScrollBar()->setValue(scroll_position.x()); + verticalScrollBar()->setValue(scroll_position.y()); +} + +void WebContentView::notify_server_did_request_scroll_into_view(Badge, Gfx::IntRect const& rect) +{ + if (m_viewport_rect.contains(rect)) + return; + + if (rect.top() < m_viewport_rect.top()) { + verticalScrollBar()->setValue(rect.top()); + } else if (rect.top() > m_viewport_rect.top() && rect.bottom() > m_viewport_rect.bottom()) { + verticalScrollBar()->setValue(rect.bottom() - m_viewport_rect.height() + 1); + } +} + +void WebContentView::notify_server_did_enter_tooltip_area(Badge, Gfx::IntPoint const& content_position, String const& tooltip) +{ + auto widget_position = to_widget(content_position); + QToolTip::showText( + mapToGlobal(QPoint(widget_position.x(), widget_position.y())), + qstring_from_akstring(tooltip), + this); +} + +void WebContentView::notify_server_did_leave_tooltip_area(Badge) +{ + QToolTip::hideText(); +} + +void WebContentView::notify_server_did_hover_link(Badge, AK::URL const& url) +{ + emit link_hovered(qstring_from_akstring(url.to_string())); +} + +void WebContentView::notify_server_did_unhover_link(Badge) +{ + emit link_unhovered(); +} + +void WebContentView::notify_server_did_click_link(Badge, AK::URL const& url, String const& target, unsigned int modifiers) +{ + // FIXME + (void)url; + (void)target; + (void)modifiers; + // if (on_link_click) + // on_link_click(url, target, modifiers); +} + +void WebContentView::notify_server_did_middle_click_link(Badge, AK::URL const& url, String const& target, unsigned int modifiers) +{ + (void)url; + (void)target; + (void)modifiers; +} + +void WebContentView::notify_server_did_start_loading(Badge, AK::URL const& url) +{ + emit load_started(url); +} + +void WebContentView::notify_server_did_finish_loading(Badge, AK::URL const& url) +{ + // FIXME + (void)url; +} + +void WebContentView::notify_server_did_request_context_menu(Badge, Gfx::IntPoint const& content_position) +{ + // FIXME + (void)content_position; +} + +void WebContentView::notify_server_did_request_link_context_menu(Badge, Gfx::IntPoint const& content_position, AK::URL const& url, String const&, unsigned) +{ + // FIXME + (void)content_position; + (void)url; +} + +void WebContentView::notify_server_did_request_image_context_menu(Badge, Gfx::IntPoint const& content_position, AK::URL const& url, String const&, unsigned, Gfx::ShareableBitmap const& bitmap) +{ + // FIXME + (void)content_position; + (void)url; + (void)bitmap; +} + +void WebContentView::notify_server_did_request_alert(Badge, String const& message) +{ + QMessageBox::warning(this, "Ladybird", qstring_from_akstring(message)); +} + +bool WebContentView::notify_server_did_request_confirm(Badge, String const& message) +{ + auto result = QMessageBox::question(this, "Ladybird", qstring_from_akstring(message), + QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel); + + return result == QMessageBox::StandardButton::Ok; +} + +String WebContentView::notify_server_did_request_prompt(Badge, String const& message, String const& default_) +{ + // FIXME + (void)message; + (void)default_; + return String::empty(); +} + +void WebContentView::get_source() +{ + client().async_get_source(); +} + +void WebContentView::notify_server_did_get_source(AK::URL const& url, String const& source) +{ + emit got_source(url, qstring_from_akstring(source)); +} + +void WebContentView::notify_server_did_get_dom_tree(String const& dom_tree) +{ + if (on_get_dom_tree) + on_get_dom_tree(dom_tree); +} + +void WebContentView::notify_server_did_get_dom_node_properties(i32 node_id, String const& specified_style, String const& computed_style, String const& custom_properties, String const& node_box_sizing) +{ + if (on_get_dom_node_properties) + on_get_dom_node_properties(node_id, specified_style, computed_style, custom_properties, node_box_sizing); +} + +void WebContentView::notify_server_did_output_js_console_message(i32 message_index) +{ + if (on_js_console_new_message) + on_js_console_new_message(message_index); +} + +void WebContentView::notify_server_did_get_js_console_messages(i32 start_index, Vector const& message_types, Vector const& messages) +{ + if (on_get_js_console_messages) + on_get_js_console_messages(start_index, message_types, messages); +} + +void WebContentView::notify_server_did_change_favicon(Gfx::Bitmap const& bitmap) +{ + 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 favicon_changed(QIcon(qpixmap)); +} + +String WebContentView::notify_server_did_request_cookie(Badge, AK::URL const& url, Web::Cookie::Source source) +{ + if (on_get_cookie) + return on_get_cookie(url, source); + return {}; +} + +void WebContentView::notify_server_did_set_cookie(Badge, AK::URL const& url, Web::Cookie::ParsedCookie const& cookie, Web::Cookie::Source source) +{ + if (on_set_cookie) + on_set_cookie(url, cookie, source); +} + +void WebContentView::notify_server_did_update_resource_count(i32 count_waiting) +{ + // FIXME + (void)count_waiting; +} + +void WebContentView::notify_server_did_request_file(Badge, String const& path, i32 request_id) +{ + auto file = Core::File::open(path, Core::OpenMode::ReadOnly); + if (file.is_error()) + client().async_handle_file_return(file.error().code(), {}, request_id); + else + client().async_handle_file_return(0, IPC::File(file.value()->leak_fd()), request_id); +} + +void WebContentView::request_repaint() +{ + // If this widget was instantiated but not yet added to a window, + // it won't have a back bitmap yet, so we can just skip repaint requests. + if (!m_client_state.back_bitmap.bitmap) + return; + // Don't request a repaint until pending paint requests have finished. + if (m_client_state.back_bitmap.pending_paints) { + m_client_state.got_repaint_requests_while_painting = true; + return; + } + m_client_state.back_bitmap.pending_paints++; + client().async_paint(m_client_state.back_bitmap.bitmap->rect().translated(horizontalScrollBar()->value(), verticalScrollBar()->value()), m_client_state.back_bitmap.id); +} diff --git a/Ladybird/WebContentView.h b/Ladybird/WebContentView.h new file mode 100644 index 00000000000..f34cb2c5796 --- /dev/null +++ b/Ladybird/WebContentView.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#define AK_DONT_REPLACE_STD + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class QTextEdit; +class QLineEdit; + +namespace WebView { +class WebContentClient; +} + +using WebView::WebContentClient; + +enum class ColorScheme { + Auto, + Light, + Dark, +}; + +class Tab; + +class WebContentView final + : public QAbstractScrollArea + , public WebView::ViewImplementation { + Q_OBJECT +public: + WebContentView(); + virtual ~WebContentView() override; + + void load(AK::URL const&); + void load_html(StringView html, AK::URL const&); + void reload(); + + Function on_context_menu_request; + Function on_link_click; + Function on_link_context_menu_request; + Function on_image_context_menu_request; + Function on_link_middle_click; + Function on_link_hover; + Function on_title_change; + Function on_load_start; + Function on_load_finish; + Function on_favicon_change; + Function on_url_drop; + Function on_set_document; + Function on_get_source; + Function on_get_dom_tree; + Function on_get_dom_node_properties; + Function on_js_console_new_message; + Function const& message_types, Vector const& messages)> on_get_js_console_messages; + Function on_get_cookie; + Function on_set_cookie; + Function on_resource_status_change; + + virtual void paintEvent(QPaintEvent*) override; + virtual void resizeEvent(QResizeEvent*) override; + virtual void mouseMoveEvent(QMouseEvent*) override; + virtual void mousePressEvent(QMouseEvent*) override; + virtual void mouseReleaseEvent(QMouseEvent*) override; + virtual void keyPressEvent(QKeyEvent* event) override; + virtual void keyReleaseEvent(QKeyEvent* event) override; + virtual void showEvent(QShowEvent*) override; + virtual void hideEvent(QHideEvent*) override; + + void debug_request(String const& request, String const& argument); + + void get_source(); + + void run_javascript(String const& js_source); + + void did_output_js_console_message(i32 message_index); + void did_get_js_console_messages(i32 start_index, Vector message_types, Vector messages); + + void show_js_console(); + void show_inspector(); + + Gfx::IntPoint to_content(Gfx::IntPoint) const; + Gfx::IntPoint to_widget(Gfx::IntPoint) const; + + void set_color_scheme(ColorScheme); + + virtual void notify_server_did_layout(Badge, Gfx::IntSize const& content_size) override; + virtual void notify_server_did_paint(Badge, i32 bitmap_id) override; + virtual void notify_server_did_invalidate_content_rect(Badge, Gfx::IntRect const&) override; + virtual void notify_server_did_change_selection(Badge) override; + virtual void notify_server_did_request_cursor_change(Badge, Gfx::StandardCursor cursor) override; + virtual void notify_server_did_change_title(Badge, String const&) override; + virtual void notify_server_did_request_scroll(Badge, i32, i32) override; + virtual void notify_server_did_request_scroll_to(Badge, Gfx::IntPoint const&) override; + virtual void notify_server_did_request_scroll_into_view(Badge, Gfx::IntRect const&) override; + virtual void notify_server_did_enter_tooltip_area(Badge, Gfx::IntPoint const&, String const&) override; + virtual void notify_server_did_leave_tooltip_area(Badge) override; + virtual void notify_server_did_hover_link(Badge, const AK::URL&) override; + virtual void notify_server_did_unhover_link(Badge) override; + virtual void notify_server_did_click_link(Badge, const AK::URL&, String const& target, unsigned modifiers) override; + virtual void notify_server_did_middle_click_link(Badge, const AK::URL&, String const& target, unsigned modifiers) override; + virtual void notify_server_did_start_loading(Badge, const AK::URL&) override; + virtual void notify_server_did_finish_loading(Badge, const AK::URL&) override; + virtual void notify_server_did_request_context_menu(Badge, Gfx::IntPoint const&) override; + virtual void notify_server_did_request_link_context_menu(Badge, Gfx::IntPoint const&, const AK::URL&, String const& target, unsigned modifiers) override; + virtual void notify_server_did_request_image_context_menu(Badge, Gfx::IntPoint const&, const AK::URL&, String const& target, unsigned modifiers, Gfx::ShareableBitmap const&) override; + virtual void notify_server_did_request_alert(Badge, String const& message) override; + virtual bool notify_server_did_request_confirm(Badge, String const& message) override; + virtual String notify_server_did_request_prompt(Badge, String const& message, String const& default_) override; + virtual void notify_server_did_get_source(const AK::URL& url, String const& source) override; + virtual void notify_server_did_get_dom_tree(String const& dom_tree) override; + virtual void notify_server_did_get_dom_node_properties(i32 node_id, String const& specified_style, String const& computed_style, String const& custom_properties, String const& node_box_sizing) override; + virtual void notify_server_did_output_js_console_message(i32 message_index) override; + virtual void notify_server_did_get_js_console_messages(i32 start_index, Vector const& message_types, Vector const& messages) override; + virtual void notify_server_did_change_favicon(Gfx::Bitmap const& favicon) override; + virtual String notify_server_did_request_cookie(Badge, const AK::URL& url, Web::Cookie::Source source) override; + virtual void notify_server_did_set_cookie(Badge, const AK::URL& url, Web::Cookie::ParsedCookie const& cookie, Web::Cookie::Source source) override; + virtual void notify_server_did_update_resource_count(i32 count_waiting) override; + virtual void notify_server_did_request_file(Badge, String const& path, i32) override; + +signals: + void link_hovered(QString, int timeout = 0); + void link_unhovered(); + void load_started(const URL&); + void title_changed(QString); + void favicon_changed(QIcon); + void got_source(URL, QString); + +private: + void request_repaint(); + void update_viewport_rect(); + void handle_resize(); + + void ensure_js_console_widget(); + void ensure_inspector_widget(); + + qreal m_inverse_pixel_scaling_ratio { 1.0 }; + bool m_should_show_line_box_borders { false }; + + QPointer m_js_console_widget; + QPointer m_inspector_widget; + + QTextEdit* m_js_console_output_edit { nullptr }; + QLineEdit* m_js_console_input_edit { nullptr }; + + Gfx::IntRect m_viewport_rect; + + void create_client(); + WebContentClient& client(); + + void handle_web_content_process_crash(); + + AK::URL m_url; + + struct SharedBitmap { + i32 id { -1 }; + i32 pending_paints { 0 }; + RefPtr bitmap; + }; + + struct ClientState { + RefPtr client; + SharedBitmap front_bitmap; + SharedBitmap back_bitmap; + i32 next_bitmap_id { 0 }; + bool has_usable_bitmap { false }; + bool got_repaint_requests_while_painting { false }; + } m_client_state; + + RefPtr m_backup_bitmap; +}; diff --git a/Ladybird/main.cpp b/Ladybird/main.cpp index a38d9cd4664..c0f0d266ee5 100644 --- a/Ladybird/main.cpp +++ b/Ladybird/main.cpp @@ -6,21 +6,35 @@ #include "BrowserWindow.h" #include "Settings.h" -#include "SimpleWebView.h" +#include "Utilities.h" +#include "WebContentView.h" +#include #include +#include +#include #include +#include #include #include +#include #include -extern void initialize_web_engine(); Browser::Settings* s_settings; +extern String s_serenity_resource_root; ErrorOr serenity_main(Main::Arguments arguments) { - QApplication app(arguments.argc, arguments.argv); + platform_init(); - initialize_web_engine(); + // NOTE: We only instantiate this to ensure that Gfx::FontDatabase has its default queries initialized. + Gfx::FontDatabase::set_default_font_query("Katica 10 400 0"); + Gfx::FontDatabase::set_fixed_width_font_query("Csilla 10 400 0"); + + // NOTE: This is only used for the Core::Socket inside the IPC connections. + // FIXME: Refactor things so we can get rid of this somehow. + Core::EventLoop event_loop; + + QApplication app(arguments.argc, arguments.argv); String url; Core::ArgsParser args_parser;