From 1298baa9ad9825104b7bd331e73d5be42536bdd4 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 6 Oct 2022 10:39:02 +0200 Subject: [PATCH] Ladybird: Port over ConsoleWidget from the SerenityOS Browser While this adds a fair bit of widget code, we're also increasing code sharing by using the same bits in WebContentClient for interacting with the JS console. That said, we should look for more ways to share code here. --- Ladybird/CMakeLists.txt | 3 +- Ladybird/ConsoleWidget.cpp | 158 ++++++++++++++++++++++++++++++++++++ Ladybird/ConsoleWidget.h | 49 +++++++++++ Ladybird/WebContentView.cpp | 52 +++++------- Ladybird/WebContentView.h | 8 +- 5 files changed, 235 insertions(+), 35 deletions(-) create mode 100644 Ladybird/ConsoleWidget.cpp create mode 100644 Ladybird/ConsoleWidget.h diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 576e1aa6bc5..54a221250a3 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -50,14 +50,15 @@ find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network) set(SOURCES BrowserWindow.cpp + ConsoleWidget.cpp CookieJar.cpp - WebContentView.cpp History.cpp ModelTranslator.cpp Settings.cpp SettingsDialog.cpp Tab.cpp Utilities.cpp + WebContentView.cpp main.cpp ) diff --git a/Ladybird/ConsoleWidget.cpp b/Ladybird/ConsoleWidget.cpp new file mode 100644 index 00000000000..aab3585bb86 --- /dev/null +++ b/Ladybird/ConsoleWidget.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020, Hunter Salyer + * Copyright (c) 2021-2022, Andreas Kling + * Copyright (c) 2021, Sam Atkins + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include "ConsoleWidget.h" +#include "Utilities.h" +#include +#include +#include +#include +#include +#include + +namespace Ladybird { + +ConsoleWidget::ConsoleWidget() +{ + setLayout(new QVBoxLayout); + + m_output_view = new QTextEdit(this); + m_output_view->setReadOnly(true); + layout()->addWidget(m_output_view); + + if (on_request_messages) + on_request_messages(0); + + auto* bottom_container = new QWidget(this); + bottom_container->setLayout(new QHBoxLayout); + + layout()->addWidget(bottom_container); + + m_input = new QLineEdit(bottom_container); + bottom_container->layout()->addWidget(m_input); + + QObject::connect(m_input, &QLineEdit::returnPressed, [this] { + auto js_source = akstring_from_qstring(m_input->text()); + + if (js_source.is_whitespace()) + return; + + m_input->clear(); + + print_source_line(js_source); + + if (on_js_input) + on_js_input(js_source); + }); + + setFocusProxy(m_input); + + auto* clear_button = new QPushButton(bottom_container); + bottom_container->layout()->addWidget(clear_button); + clear_button->setFixedSize(22, 22); + clear_button->setText("X"); + clear_button->setToolTip("Clear the console output"); + QObject::connect(clear_button, &QPushButton::pressed, [this] { + clear_output(); + }); + + m_input->setFocus(); +} + +void ConsoleWidget::request_console_messages() +{ + VERIFY(!m_waiting_for_messages); + VERIFY(on_request_messages); + on_request_messages(m_highest_received_message_index + 1); + m_waiting_for_messages = true; +} + +void ConsoleWidget::notify_about_new_console_message(i32 message_index) +{ + if (message_index <= m_highest_received_message_index) { + dbgln("Notified about console message we already have"); + return; + } + if (message_index <= m_highest_notified_message_index) { + dbgln("Notified about console message we're already aware of"); + return; + } + + m_highest_notified_message_index = message_index; + if (!m_waiting_for_messages) + request_console_messages(); +} + +void ConsoleWidget::handle_console_messages(i32 start_index, Vector const& message_types, Vector const& messages) +{ + i32 end_index = start_index + message_types.size() - 1; + if (end_index <= m_highest_received_message_index) { + dbgln("Received old console messages"); + return; + } + + for (size_t i = 0; i < message_types.size(); i++) { + auto& type = message_types[i]; + auto& message = messages[i]; + + if (type == "html") { + print_html(message); + } else if (type == "clear") { + clear_output(); + } else if (type == "group") { + // FIXME: Implement. + } else if (type == "groupCollapsed") { + // FIXME: Implement. + } else if (type == "groupEnd") { + // FIXME: Implement. + } else { + VERIFY_NOT_REACHED(); + } + } + + m_highest_received_message_index = end_index; + m_waiting_for_messages = false; + + if (m_highest_received_message_index < m_highest_notified_message_index) + request_console_messages(); +} + +void ConsoleWidget::print_source_line(StringView source) +{ + StringBuilder html; + html.append(""sv); + html.append("> "sv); + html.append(""sv); + + html.append(JS::MarkupGenerator::html_from_source(source)); + + print_html(html.string_view()); +} + +void ConsoleWidget::print_html(StringView line) +{ + m_output_view->append(QString::fromUtf8(line.characters_without_null_termination(), line.length())); +} + +void ConsoleWidget::clear_output() +{ + m_output_view->clear(); +} + +void ConsoleWidget::reset() +{ + clear_output(); + m_highest_notified_message_index = -1; + m_highest_received_message_index = -1; + m_waiting_for_messages = false; +} + +} diff --git a/Ladybird/ConsoleWidget.h b/Ladybird/ConsoleWidget.h new file mode 100644 index 00000000000..7b2d0fb591c --- /dev/null +++ b/Ladybird/ConsoleWidget.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Hunter Salyer + * Copyright (c) 2021-2022, Andreas Kling + * Copyright (c) 2021, Sam Atkins + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +class QLineEdit; +class QTextEdit; + +namespace Ladybird { + +class ConsoleWidget final : public QWidget { + Q_OBJECT +public: + ConsoleWidget(); + virtual ~ConsoleWidget() = default; + + void notify_about_new_console_message(i32 message_index); + void handle_console_messages(i32 start_index, Vector const& message_types, Vector const& messages); + void print_source_line(StringView); + void print_html(StringView); + void reset(); + + Function on_js_input; + Function on_request_messages; + +private: + void request_console_messages(); + void clear_output(); + + QTextEdit* m_output_view { nullptr }; + QLineEdit* m_input { nullptr }; + + i32 m_highest_notified_message_index { -1 }; + i32 m_highest_received_message_index { -1 }; + bool m_waiting_for_messages { false }; +}; + +} diff --git a/Ladybird/WebContentView.cpp b/Ladybird/WebContentView.cpp index d7cb3527aed..c3b82d33a48 100644 --- a/Ladybird/WebContentView.cpp +++ b/Ladybird/WebContentView.cpp @@ -7,6 +7,7 @@ #define AK_DONT_REPLACE_STD #include "WebContentView.h" +#include "ConsoleWidget.h" #include "CookieJar.h" #include "ModelTranslator.h" #include "Utilities.h" @@ -446,46 +447,35 @@ void WebContentView::run_javascript(String const& js_source) void WebContentView::did_output_js_console_message(i32 message_index) { - // FIXME - (void)message_index; + if (m_console_widget) + m_console_widget->notify_about_new_console_message(message_index); } -void WebContentView::did_get_js_console_messages(i32, Vector, Vector messages) +void WebContentView::did_get_js_console_messages(i32 start_index, Vector message_types, Vector messages) { - ensure_js_console_widget(); - for (auto& message : messages) { - m_js_console_output_edit->append(qstring_from_akstring(message).trimmed()); - } + if (m_console_widget) + m_console_widget->handle_console_messages(start_index, message_types, messages); } 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)); - }); + if (!m_console_widget) { + m_console_widget = new Ladybird::ConsoleWidget; + m_console_widget->setWindowTitle("JS Console"); + m_console_widget->resize(640, 480); + m_console_widget->on_js_input = [this](auto js_source) { + client().async_js_console_input(js_source); + }; + m_console_widget->on_request_messages = [this](i32 start_index) { + client().async_js_console_request_messages(start_index); + }; } } void WebContentView::show_js_console() { ensure_js_console_widget(); - m_js_console_widget->show(); - m_js_console_input_edit->setFocus(); + m_console_widget->show(); } void WebContentView::ensure_inspector_widget() @@ -846,14 +836,14 @@ void WebContentView::notify_server_did_get_dom_node_properties(i32 node_id, Stri 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); + if (m_console_widget) + m_console_widget->notify_about_new_console_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); + if (m_console_widget) + m_console_widget->handle_console_messages(start_index, message_types, messages); } void WebContentView::notify_server_did_change_favicon(Gfx::Bitmap const& bitmap) diff --git a/Ladybird/WebContentView.h b/Ladybird/WebContentView.h index f34cb2c5796..80f2976ad75 100644 --- a/Ladybird/WebContentView.h +++ b/Ladybird/WebContentView.h @@ -25,6 +25,10 @@ class QTextEdit; class QLineEdit; +namespace Ladybird { +class ConsoleWidget; +} + namespace WebView { class WebContentClient; } @@ -152,11 +156,9 @@ private: 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 }; + Ladybird::ConsoleWidget* m_console_widget { nullptr }; Gfx::IntRect m_viewport_rect;