diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 576e1aa6bc..54a221250a 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 0000000000..aab3585bb8 --- /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 0000000000..7b2d0fb591 --- /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 d7cb3527ae..c3b82d33a4 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 f34cb2c579..80f2976ad7 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;