diff --git a/Ladybird/Qt/ConsoleWidget.cpp b/Ladybird/Qt/ConsoleWidget.cpp index 7972b69b9b..6b44549a33 100644 --- a/Ladybird/Qt/ConsoleWidget.cpp +++ b/Ladybird/Qt/ConsoleWidget.cpp @@ -10,35 +10,26 @@ #include "ConsoleWidget.h" #include "StringUtils.h" #include "WebContentView.h" -#include -#include +#include #include #include #include -#include #include -#include #include namespace Ladybird { bool is_using_dark_system_theme(QWidget&); -ConsoleWidget::ConsoleWidget() +ConsoleWidget::ConsoleWidget(WebContentView& content_view) { setLayout(new QVBoxLayout); m_output_view = new WebContentView({}, WebView::EnableCallgrindProfiling::No, UseLagomNetworking::No); - m_output_view->use_native_user_style_sheet(); if (is_using_dark_system_theme(*this)) m_output_view->update_palette(WebContentView::PaletteMode::Dark); - m_output_view->load("data:text/html,"sv); - // Wait until our output WebView is loaded, and then request any messages that occurred before we existed - m_output_view->on_load_finish = [this](auto&) { - if (on_request_messages) - on_request_messages(0); - }; + m_console_client = make(content_view, *m_output_view); layout()->addWidget(m_output_view); @@ -59,117 +50,17 @@ ConsoleWidget::ConsoleWidget() clear_button->setText("X"); clear_button->setToolTip("Clear the console output"); QObject::connect(clear_button, &QPushButton::pressed, [this] { - clear_output(); + client().clear(); }); 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).release_value_but_fixme_should_propagate_errors()); - - print_html(html.string_view()); -} - -void ConsoleWidget::print_html(StringView line) -{ - StringBuilder builder; - - builder.append(R"~~~( - var p = document.createElement("p"); - p.innerHTML = ")~~~"sv); - builder.append_escaped_for_json(line); - builder.append(R"~~~(" - document.body.appendChild(p); -)~~~"sv); - - // FIXME: It should be sufficient to scrollTo a y value of document.documentElement.offsetHeight, - // but due to an unknown bug offsetHeight seems to not be properly updated after spamming - // a lot of document changes. - // - // The setTimeout makes the scrollTo async and allows the DOM to be updated. - builder.append("setTimeout(function() { window.scrollTo(0, 1_000_000_000); }, 0);"sv); - - m_output_view->run_javascript(builder.string_view()); -} - -void ConsoleWidget::clear_output() -{ - m_output_view->run_javascript(R"~~~( - document.body.innerHTML = ""; - )~~~"sv); -} +ConsoleWidget::~ConsoleWidget() = default; void ConsoleWidget::reset() { - clear_output(); - m_highest_notified_message_index = -1; - m_highest_received_message_index = -1; - m_waiting_for_messages = false; + m_console_client->reset(); } void ConsoleInputEdit::keyPressEvent(QKeyEvent* event) @@ -188,12 +79,14 @@ void ConsoleInputEdit::keyPressEvent(QKeyEvent* event) } break; } + case Qt::Key_Up: if (m_history_index > 0) { m_history_index--; setText(qstring_from_ak_deprecated_string(m_history.at(m_history_index))); } break; + case Qt::Key_Return: { auto js_source = ak_deprecated_string_from_qstring(text()); if (js_source.is_whitespace()) @@ -204,15 +97,12 @@ void ConsoleInputEdit::keyPressEvent(QKeyEvent* event) m_history_index = m_history.size(); } + m_console_widget.client().execute(js_source); clear(); - m_console_widget.print_source_line(js_source); - - if (m_console_widget.on_js_input) - m_console_widget.on_js_input(js_source); - break; } + default: QLineEdit::keyPressEvent(event); } diff --git a/Ladybird/Qt/ConsoleWidget.h b/Ladybird/Qt/ConsoleWidget.h index 2b08729984..df736c99a7 100644 --- a/Ladybird/Qt/ConsoleWidget.h +++ b/Ladybird/Qt/ConsoleWidget.h @@ -10,12 +10,12 @@ #pragma once #include -#include +#include #include +#include #include #include -class QLineEdit; namespace Ladybird { class WebContentView; @@ -23,30 +23,19 @@ class WebContentView; 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(); + explicit ConsoleWidget(WebContentView& content_view); + virtual ~ConsoleWidget(); + WebView::ConsoleClient& client() { return *m_console_client; } WebContentView& view() { return *m_output_view; } - Function on_js_input; - Function on_request_messages; + void reset(); private: - void request_console_messages(); - void clear_output(); + OwnPtr m_console_client; WebContentView* 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 }; }; class ConsoleInputEdit final : public QLineEdit { diff --git a/Ladybird/Qt/Tab.cpp b/Ladybird/Qt/Tab.cpp index 055cacfc5e..58f3d6f0b1 100644 --- a/Ladybird/Qt/Tab.cpp +++ b/Ladybird/Qt/Tab.cpp @@ -272,16 +272,6 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: m_inspector_widget->set_accessibility_json(accessibility_tree); }; - view().on_received_console_message = [this](auto message_index) { - if (m_console_widget) - m_console_widget->notify_about_new_console_message(message_index); - }; - - view().on_received_console_messages = [this](auto start_index, auto& message_types, auto& messages) { - if (m_console_widget) - m_console_widget->handle_console_messages(start_index, message_types, messages); - }; - auto* take_visible_screenshot_action = new QAction("Take &Visible Screenshot", this); take_visible_screenshot_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters()))); QObject::connect(take_visible_screenshot_action, &QAction::triggered, this, [this]() { @@ -702,7 +692,7 @@ void Tab::show_inspector_window(InspectorTarget inspector_target) void Tab::show_console_window() { if (!m_console_widget) { - m_console_widget = new Ladybird::ConsoleWidget; + m_console_widget = new Ladybird::ConsoleWidget(view()); m_console_widget->setWindowTitle("JS Console"); m_console_widget->resize(640, 480); @@ -719,12 +709,6 @@ void Tab::show_console_window() auto screen_position = QCursor::pos(); m_console_context_menu->exec(screen_position); }; - m_console_widget->on_js_input = [this](auto js_source) { - view().js_console_input(js_source); - }; - m_console_widget->on_request_messages = [this](i32 start_index) { - view().js_console_request_messages(start_index); - }; } m_console_widget->show(); diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 15c76102b5..e76f66901a 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -430,6 +430,7 @@ if (BUILD_LAGOM) # WebView list(APPEND LIBWEBVIEW_SOURCES "../../Userland/Libraries/LibWebView/AccessibilityTreeModel.cpp") + list(APPEND LIBWEBVIEW_SOURCES "../../Userland/Libraries/LibWebView/ConsoleClient.cpp") list(APPEND LIBWEBVIEW_SOURCES "../../Userland/Libraries/LibWebView/DOMTreeModel.cpp") list(APPEND LIBWEBVIEW_SOURCES "../../Userland/Libraries/LibWebView/RequestServerAdapter.cpp") list(APPEND LIBWEBVIEW_SOURCES "../../Userland/Libraries/LibWebView/SourceHighlighter.cpp") @@ -463,7 +464,7 @@ if (BUILD_LAGOM) lagom_lib(LibWebView webview SOURCES ${LIBWEBVIEW_SOURCES} ${LIBWEBVIEW_GENERATED_SOURCES} - LIBS LibGfx LibGUI LibIPC LibWeb LibProtocol) + LIBS LibGfx LibGUI LibIPC LibJS LibWeb LibProtocol) foreach(header ${LIBWEBVIEW_GENERATED_SOURCES}) get_filename_component(subdirectory ${header} DIRECTORY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${header}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${subdirectory}") diff --git a/Userland/Applications/Browser/ConsoleWidget.cpp b/Userland/Applications/Browser/ConsoleWidget.cpp index 6e19ec328c..6e30e14875 100644 --- a/Userland/Applications/Browser/ConsoleWidget.cpp +++ b/Userland/Applications/Browser/ConsoleWidget.cpp @@ -16,22 +16,18 @@ #include #include #include +#include +#include namespace Browser { -ConsoleWidget::ConsoleWidget() +ConsoleWidget::ConsoleWidget(WebView::OutOfProcessWebView& content_view) { set_layout(); set_fill_with_background_color(true); m_output_view = add(); - m_output_view->use_native_user_style_sheet(); - m_output_view->load("data:text/html,"sv); - // Wait until our output WebView is loaded, and then request any messages that occurred before we existed - m_output_view->on_load_finish = [this](auto&) { - if (on_request_messages) - on_request_messages(0); - }; + m_console_client = make(content_view, *m_output_view); auto& bottom_container = add(); bottom_container.set_layout(); @@ -52,10 +48,7 @@ ConsoleWidget::ConsoleWidget() m_input->add_current_text_to_history(); m_input->clear(); - print_source_line(js_source); - - if (on_js_input) - on_js_input(js_source); + m_console_client->execute(js_source); }; set_focus_proxy(m_input); @@ -65,168 +58,15 @@ ConsoleWidget::ConsoleWidget() clear_button.set_icon(g_icon_bag.delete_icon); clear_button.set_tooltip_deprecated("Clear the console output"); clear_button.on_click = [this](auto) { - clear_output(); + m_console_client->clear(); }; } -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") { - begin_group(message, true); - } else if (type == "groupCollapsed") { - begin_group(message, false); - } else if (type == "groupEnd") { - end_group(); - } 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).release_value_but_fixme_should_propagate_errors()); - - print_html(html.string_view()); -} - -void ConsoleWidget::print_html(StringView line) -{ - StringBuilder builder; - - int parent_id = m_group_stack.is_empty() ? 0 : m_group_stack.last().id; - if (parent_id == 0) { - builder.append(R"~~~( - var parentGroup = document.body; -)~~~"sv); - } else { - builder.appendff(R"~~~( - var parentGroup = document.getElementById("group_{}"); -)~~~", - parent_id); - } - - builder.append(R"~~~( - var p = document.createElement("p"); - p.innerHTML = ")~~~"sv); - builder.append_escaped_for_json(line); - builder.append(R"~~~(" - parentGroup.appendChild(p); -)~~~"sv); - m_output_view->run_javascript(builder.string_view()); - // FIXME: Make it scroll to the bottom, using `window.scrollTo()` in the JS above. - // We used to call `m_output_view->scroll_to_bottom();` here, but that does not work because - // it runs synchronously, meaning it happens before the HTML is output via IPC above. - // (See also: begin_group()) -} - -void ConsoleWidget::clear_output() -{ - m_group_stack.clear(); - m_output_view->run_javascript(R"~~~( - document.body.innerHTML = ""; - )~~~"sv); -} - -void ConsoleWidget::begin_group(StringView label, bool start_expanded) -{ - StringBuilder builder; - int parent_id = m_group_stack.is_empty() ? 0 : m_group_stack.last().id; - if (parent_id == 0) { - builder.append(R"~~~( - var parentGroup = document.body; -)~~~"sv); - } else { - builder.appendff(R"~~~( - var parentGroup = document.getElementById("group_{}"); -)~~~", - parent_id); - } - - Group group; - group.id = m_next_group_id++; - group.label = label; - - builder.appendff(R"~~~( - var group = document.createElement("details"); - group.id = "group_{}"; - var label = document.createElement("summary"); - label.innerHTML = ")~~~", - group.id); - builder.append_escaped_for_json(label); - builder.append(R"~~~("; - group.appendChild(label); - parentGroup.appendChild(group); -)~~~"sv); - - if (start_expanded) - builder.append("group.open = true;"sv); - - m_output_view->run_javascript(builder.string_view()); - // FIXME: Scroll console to bottom - see note in print_html() - m_group_stack.append(group); -} - -void ConsoleWidget::end_group() -{ - m_group_stack.take_last(); -} +ConsoleWidget::~ConsoleWidget() = default; void ConsoleWidget::reset() { - clear_output(); - m_highest_notified_message_index = -1; - m_highest_received_message_index = -1; - m_waiting_for_messages = false; + m_console_client->reset(); } } diff --git a/Userland/Applications/Browser/ConsoleWidget.h b/Userland/Applications/Browser/ConsoleWidget.h index 1b9f15aa83..08a5646206 100644 --- a/Userland/Applications/Browser/ConsoleWidget.h +++ b/Userland/Applications/Browser/ConsoleWidget.h @@ -9,47 +9,29 @@ #pragma once -#include "History.h" +#include #include -#include +#include namespace Browser { class ConsoleWidget final : public GUI::Widget { C_OBJECT(ConsoleWidget) public: - virtual ~ConsoleWidget() = default; + virtual ~ConsoleWidget(); - 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: - ConsoleWidget(); + explicit ConsoleWidget(WebView::OutOfProcessWebView& content_view); void request_console_messages(); void clear_output(); - void begin_group(StringView label, bool start_expanded); - void end_group(); + + OwnPtr m_console_client; RefPtr m_input; RefPtr m_output_view; - - i32 m_highest_notified_message_index { -1 }; - i32 m_highest_received_message_index { -1 }; - bool m_waiting_for_messages { false }; - - struct Group { - int id { 0 }; - DeprecatedString label; - }; - Vector m_group_stack; - int m_next_group_id { 1 }; }; } diff --git a/Userland/Applications/Browser/Tab.cpp b/Userland/Applications/Browser/Tab.cpp index f08231be66..c5d048ad14 100644 --- a/Userland/Applications/Browser/Tab.cpp +++ b/Userland/Applications/Browser/Tab.cpp @@ -584,16 +584,6 @@ Tab::Tab(BrowserWindow& window) m_dom_inspector_widget->set_accessibility_json(accessibility_tree); }; - view().on_received_console_message = [this](auto message_index) { - if (m_console_widget) - m_console_widget->notify_about_new_console_message(message_index); - }; - - view().on_received_console_messages = [this](auto start_index, auto& message_types, auto& messages) { - if (m_console_widget) - m_console_widget->handle_console_messages(start_index, message_types, messages); - }; - auto focus_location_box_action = GUI::Action::create( "Focus location box", { Mod_Ctrl, Key_L }, Key_F6, [this](auto&) { m_location_box->set_focus(true); @@ -927,13 +917,8 @@ void Tab::show_console_window() console_window->resize(500, 300); console_window->set_title("JS Console"); console_window->set_icon(g_icon_bag.filetype_javascript); - m_console_widget = console_window->set_main_widget().release_value_but_fixme_should_propagate_errors(); - m_console_widget->on_js_input = [this](DeprecatedString const& js_source) { - m_web_content_view->js_console_input(js_source); - }; - m_console_widget->on_request_messages = [this](i32 start_index) { - m_web_content_view->js_console_request_messages(start_index); - }; + + m_console_widget = MUST(console_window->set_main_widget(view())); } auto* window = m_console_widget->window(); diff --git a/Userland/Libraries/LibWebView/CMakeLists.txt b/Userland/Libraries/LibWebView/CMakeLists.txt index 835751ac43..a06b92b748 100644 --- a/Userland/Libraries/LibWebView/CMakeLists.txt +++ b/Userland/Libraries/LibWebView/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES AccessibilityTreeModel.cpp AriaPropertiesStateModel.cpp + ConsoleClient.cpp DOMTreeModel.cpp OutOfProcessWebView.cpp RequestServerAdapter.cpp @@ -28,4 +29,4 @@ set(GENERATED_SOURCES ) serenity_lib(LibWebView webview) -target_link_libraries(LibWebView PRIVATE LibCore LibFileSystemAccessClient LibGfx LibGUI LibIPC LibProtocol LibWeb) +target_link_libraries(LibWebView PRIVATE LibCore LibFileSystemAccessClient LibGfx LibGUI LibIPC LibProtocol LibJS LibWeb) diff --git a/Userland/Libraries/LibWebView/ConsoleClient.cpp b/Userland/Libraries/LibWebView/ConsoleClient.cpp new file mode 100644 index 0000000000..da177d9560 --- /dev/null +++ b/Userland/Libraries/LibWebView/ConsoleClient.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace WebView { + +static constexpr auto CONSOLE_HTML = "data:text/html,"sv; + +// FIXME: It should be sufficient to scrollTo a y value of document.documentElement.offsetHeight, +// but due to an unknown bug offsetHeight seems to not be properly updated after spamming +// a lot of document changes. +// +// The setTimeout makes the scrollTo async and allows the DOM to be updated. +static constexpr auto SCROLL_TO_BOTTOM = "setTimeout(function() { window.scrollTo(0, 1_000_000_000); }, 0);"sv; + +ConsoleClient::ConsoleClient(ViewImplementation& content_web_view, ViewImplementation& console_web_view) + : m_content_web_view(content_web_view) + , m_console_web_view(console_web_view) +{ + m_content_web_view.on_received_console_message = [this](auto message_index) { + handle_console_message(message_index); + }; + + m_content_web_view.on_received_console_messages = [this](auto start_index, auto const& message_types, auto const& messages) { + handle_console_messages(start_index, message_types, messages); + }; + + // Wait until our output WebView is loaded, and then request any messages that occurred before we existed. + m_console_web_view.on_load_finish = [this](auto const&) { + request_console_messages(0); + }; + + m_console_web_view.use_native_user_style_sheet(); + m_console_web_view.load(CONSOLE_HTML); +} + +ConsoleClient::~ConsoleClient() +{ + m_content_web_view.on_received_console_message = nullptr; + m_content_web_view.on_received_console_messages = nullptr; +} + +void ConsoleClient::execute(StringView script) +{ + print_source(script); + m_content_web_view.js_console_input(script); +} + +void ConsoleClient::clear() +{ + m_console_web_view.js_console_input("document.body.innerHTML = \"\";"sv); + m_group_stack.clear(); +} + +void ConsoleClient::reset() +{ + clear(); + + m_highest_notified_message_index = -1; + m_highest_received_message_index = -1; + m_waiting_for_messages = false; +} + +void ConsoleClient::handle_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 ConsoleClient::handle_console_messages(i32 start_index, ReadonlySpan message_types, ReadonlySpan messages) +{ + auto end_index = start_index + static_cast(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 const& type = message_types[i]; + auto const& message = messages[i]; + + if (type == "html"sv) + print_html(message); + else if (type == "clear"sv) + clear(); + else if (type == "group"sv) + begin_group(message, true); + else if (type == "groupCollapsed"sv) + begin_group(message, false); + else if (type == "groupEnd"sv) + end_group(); + 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 ConsoleClient::print_source(StringView source) +{ + StringBuilder builder; + + builder.append("> "sv); + builder.append(MUST(JS::MarkupGenerator::html_from_source(source))); + + print_html(builder.string_view()); +} + +void ConsoleClient::print_html(StringView html) +{ + StringBuilder builder; + + if (m_group_stack.is_empty()) + builder.append("var parentGroup = document.body;"sv); + else + builder.appendff("var parentGroup = document.getElementById(\"group_{}\");", m_group_stack.last().id); + + builder.append(R"~~~( + var p = document.createElement("p"); + p.innerHTML = ")~~~"sv); + builder.append_escaped_for_json(html); + builder.append(R"~~~(" + parentGroup.appendChild(p); +)~~~"sv); + + builder.append(SCROLL_TO_BOTTOM); + + m_console_web_view.run_javascript(builder.string_view()); +} + +void ConsoleClient::request_console_messages() +{ + request_console_messages(m_highest_received_message_index + 1); +} + +void ConsoleClient::request_console_messages(i32 start_index) +{ + VERIFY(!m_waiting_for_messages); + + m_content_web_view.js_console_request_messages(start_index); + m_waiting_for_messages = true; +} + +void ConsoleClient::begin_group(StringView label, bool start_expanded) +{ + StringBuilder builder; + + if (m_group_stack.is_empty()) + builder.append("var parentGroup = document.body;"sv); + else + builder.appendff("var parentGroup = document.getElementById(\"group_{}\");", m_group_stack.last().id); + + Group group; + group.id = m_next_group_id++; + group.label = label; + + builder.appendff(R"~~~( + var group = document.createElement("details"); + group.id = "group_{}"; + var label = document.createElement("summary"); + label.innerHTML = ")~~~", + group.id); + builder.append_escaped_for_json(label); + builder.append(R"~~~("; + group.appendChild(label); + parentGroup.appendChild(group); +)~~~"sv); + + if (start_expanded) + builder.append("group.open = true;"sv); + + builder.append(SCROLL_TO_BOTTOM); + + m_console_web_view.run_javascript(builder.string_view()); + m_group_stack.append(group); +} + +void ConsoleClient::end_group() +{ + m_group_stack.take_last(); +} + +} diff --git a/Userland/Libraries/LibWebView/ConsoleClient.h b/Userland/Libraries/LibWebView/ConsoleClient.h new file mode 100644 index 0000000000..6f81c76d68 --- /dev/null +++ b/Userland/Libraries/LibWebView/ConsoleClient.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WebView { + +class ConsoleClient { +public: + explicit ConsoleClient(ViewImplementation& content_web_view, ViewImplementation& console_web_view); + ~ConsoleClient(); + + void execute(StringView); + + void clear(); + void reset(); + +private: + void handle_console_message(i32 message_index); + void handle_console_messages(i32 start_index, ReadonlySpan message_types, ReadonlySpan messages); + + void print_source(StringView); + void print_html(StringView); + + void request_console_messages(); + void request_console_messages(i32 start_index); + + void begin_group(StringView label, bool start_expanded); + void end_group(); + + ViewImplementation& m_content_web_view; + ViewImplementation& m_console_web_view; + + i32 m_highest_notified_message_index { -1 }; + i32 m_highest_received_message_index { -1 }; + bool m_waiting_for_messages { false }; + + struct Group { + int id { 0 }; + DeprecatedString label; + }; + Vector m_group_stack; + int m_next_group_id { 1 }; +}; + +} diff --git a/Userland/Libraries/LibWebView/Forward.h b/Userland/Libraries/LibWebView/Forward.h index a89e644404..3d71fcc1c4 100644 --- a/Userland/Libraries/LibWebView/Forward.h +++ b/Userland/Libraries/LibWebView/Forward.h @@ -8,6 +8,7 @@ namespace WebView { +class ConsoleClient; class OutOfProcessWebView; class ViewImplementation; class WebContentClient;