From 4aca24481e19e76d29e9a0a10adc43ba62f4ab56 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 22 Apr 2023 21:57:08 -0400 Subject: [PATCH] Ladybird: Implement the JavaScript console using a WebContentView This aligns the Ladybird console implementation with the Browser console a bit more, which uses OutOfProcessWebView for rendering console output. This allows us to style the console output to try and match the system theme. Using a WebContentView is simpler than trying to style the old QTextEdit widget, as the console output is HTML with built-in "-libweb-palette-*" colors. These will override any color we set on the QTextEdit widget. --- Ladybird/BrowserWindow.cpp | 27 ++++++++++++++------ Ladybird/ConsoleWidget.cpp | 50 +++++++++++++++++++++++++++++++------ Ladybird/ConsoleWidget.h | 6 +++-- Ladybird/WebContentView.cpp | 9 ++++--- Ladybird/WebContentView.h | 10 ++++++-- 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/Ladybird/BrowserWindow.cpp b/Ladybird/BrowserWindow.cpp index dc45a367c3..1d34d7b196 100644 --- a/Ladybird/BrowserWindow.cpp +++ b/Ladybird/BrowserWindow.cpp @@ -8,6 +8,7 @@ */ #include "BrowserWindow.h" +#include "ConsoleWidget.h" #include "Settings.h" #include "SettingsDialog.h" #include "Utilities.h" @@ -546,8 +547,13 @@ void BrowserWindow::reset_zoom() void BrowserWindow::select_all() { - if (auto* tab = m_current_tab) - tab->view().select_all(); + if (!m_current_tab) + return; + + if (auto* console = m_current_tab->view().console(); console && console->isActiveWindow()) + console->view().select_all(); + else + m_current_tab->view().select_all(); } void BrowserWindow::update_displayed_zoom_level() @@ -560,11 +566,18 @@ void BrowserWindow::update_displayed_zoom_level() void BrowserWindow::copy_selected_text() { - if (auto* tab = m_current_tab) { - auto text = tab->view().selected_text(); - auto* clipboard = QGuiApplication::clipboard(); - clipboard->setText(qstring_from_ak_deprecated_string(text)); - } + if (!m_current_tab) + return; + + DeprecatedString text; + + if (auto* console = m_current_tab->view().console(); console && console->isActiveWindow()) + text = console->view().selected_text(); + else + text = m_current_tab->view().selected_text(); + + auto* clipboard = QGuiApplication::clipboard(); + clipboard->setText(qstring_from_ak_deprecated_string(text)); } void BrowserWindow::resizeEvent(QResizeEvent* event) diff --git a/Ladybird/ConsoleWidget.cpp b/Ladybird/ConsoleWidget.cpp index b252eac2a1..761768ff26 100644 --- a/Ladybird/ConsoleWidget.cpp +++ b/Ladybird/ConsoleWidget.cpp @@ -11,25 +11,46 @@ #include "ConsoleWidget.h" #include "Utilities.h" +#include "WebContentView.h" #include #include #include +#include #include #include #include namespace Ladybird { +static bool is_using_dark_system_theme(QWidget& widget) +{ + // FIXME: Qt does not provide any method to query if the system is using a dark theme. We will have to implement + // platform-specific methods if we wish to have better detection. For now, this inspects if Qt is using a + // dark color for widget backgrounds using Rec. 709 luma coefficients. + // https://en.wikipedia.org/wiki/Rec._709#Luma_coefficients + + auto color = widget.palette().color(widget.backgroundRole()); + auto luma = 0.2126f * color.redF() + 0.7152f * color.greenF() + 0.0722f * color.blueF(); + + return luma <= 0.5f; +} + ConsoleWidget::ConsoleWidget() { setLayout(new QVBoxLayout); - m_output_view = new QTextEdit(this); - m_output_view->setReadOnly(true); - layout()->addWidget(m_output_view); + m_output_view = new WebContentView({}, WebView::EnableCallgrindProfiling::No); + if (is_using_dark_system_theme(*this)) + m_output_view->update_palette(WebContentView::PaletteMode::Dark); - if (on_request_messages) - on_request_messages(0); + 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); + }; + + layout()->addWidget(m_output_view); auto* bottom_container = new QWidget(this); bottom_container->setLayout(new QHBoxLayout); @@ -139,12 +160,27 @@ void ConsoleWidget::print_source_line(StringView source) void ConsoleWidget::print_html(StringView line) { - m_output_view->append(QString::fromUtf8(line.characters_without_null_termination(), line.length())); + 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: 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. + m_output_view->run_javascript(builder.string_view()); } void ConsoleWidget::clear_output() { - m_output_view->clear(); + m_output_view->run_javascript(R"~~~( + document.body.innerHTML = ""; + )~~~"sv); } void ConsoleWidget::reset() diff --git a/Ladybird/ConsoleWidget.h b/Ladybird/ConsoleWidget.h index 6928db9d6a..4a943b8b2a 100644 --- a/Ladybird/ConsoleWidget.h +++ b/Ladybird/ConsoleWidget.h @@ -15,7 +15,7 @@ #include class QLineEdit; -class QTextEdit; +class WebContentView; namespace Ladybird { @@ -31,6 +31,8 @@ public: void print_html(StringView); void reset(); + WebContentView& view() { return *m_output_view; } + Function on_js_input; Function on_request_messages; @@ -38,7 +40,7 @@ private: void request_console_messages(); void clear_output(); - QTextEdit* m_output_view { nullptr }; + WebContentView* m_output_view { nullptr }; QLineEdit* m_input { nullptr }; i32 m_highest_notified_message_index { -1 }; diff --git a/Ladybird/WebContentView.cpp b/Ladybird/WebContentView.cpp index 3a02f536f7..f2938e0111 100644 --- a/Ladybird/WebContentView.cpp +++ b/Ladybird/WebContentView.cpp @@ -567,11 +567,12 @@ void WebContentView::hideEvent(QHideEvent* event) client().async_set_system_visibility_state(false); } -static Core::AnonymousBuffer make_system_theme_from_qt_palette(QWidget& widget) +static Core::AnonymousBuffer make_system_theme_from_qt_palette(QWidget& widget, WebContentView::PaletteMode mode) { auto qt_palette = widget.palette(); - auto theme = Gfx::load_system_theme(DeprecatedString::formatted("{}/res/themes/Default.ini", s_serenity_resource_root)).release_value_but_fixme_should_propagate_errors(); + auto theme_file = mode == WebContentView::PaletteMode::Default ? "Default"sv : "Dark"sv; + auto theme = Gfx::load_system_theme(DeprecatedString::formatted("{}/res/themes/{}.ini", s_serenity_resource_root, theme_file)).release_value_but_fixme_should_propagate_errors(); auto palette_impl = Gfx::PaletteImpl::create_with_anonymous_buffer(theme); auto palette = Gfx::Palette(move(palette_impl)); @@ -594,9 +595,9 @@ static Core::AnonymousBuffer make_system_theme_from_qt_palette(QWidget& widget) return theme; } -void WebContentView::update_palette() +void WebContentView::update_palette(PaletteMode mode) { - client().async_update_system_theme(make_system_theme_from_qt_palette(*this)); + client().async_update_system_theme(make_system_theme_from_qt_palette(*this, mode)); } void WebContentView::create_client(WebView::EnableCallgrindProfiling enable_callgrind_profiling) diff --git a/Ladybird/WebContentView.h b/Ladybird/WebContentView.h index 3ec8c44878..3c4be3a4aa 100644 --- a/Ladybird/WebContentView.h +++ b/Ladybird/WebContentView.h @@ -98,6 +98,8 @@ public: void show_js_console(); void show_inspector(); + Ladybird::ConsoleWidget* console() { return m_console_widget; }; + ErrorOr dump_layout_tree(); void set_viewport_rect(Gfx::IntRect); @@ -107,6 +109,12 @@ public: Gfx::IntPoint to_content(Gfx::IntPoint) const; Gfx::IntPoint to_widget(Gfx::IntPoint) const; + enum class PaletteMode { + Default, + Dark, + }; + void update_palette(PaletteMode = PaletteMode::Default); + virtual void notify_server_did_layout(Badge, Gfx::IntSize 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; @@ -198,8 +206,6 @@ private: bool is_inspector_open() const; void close_sub_widgets(); - void update_palette(); - qreal m_inverse_pixel_scaling_ratio { 1.0 }; bool m_should_show_line_box_borders { false };