mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 01:12:44 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									5af715e394
								
							
						
					
					
						commit
						4aca24481e
					
				
					 5 changed files with 80 additions and 22 deletions
				
			
		|  | @ -8,6 +8,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include "BrowserWindow.h" | #include "BrowserWindow.h" | ||||||
|  | #include "ConsoleWidget.h" | ||||||
| #include "Settings.h" | #include "Settings.h" | ||||||
| #include "SettingsDialog.h" | #include "SettingsDialog.h" | ||||||
| #include "Utilities.h" | #include "Utilities.h" | ||||||
|  | @ -546,8 +547,13 @@ void BrowserWindow::reset_zoom() | ||||||
| 
 | 
 | ||||||
| void BrowserWindow::select_all() | void BrowserWindow::select_all() | ||||||
| { | { | ||||||
|     if (auto* tab = m_current_tab) |     if (!m_current_tab) | ||||||
|         tab->view().select_all(); |         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() | void BrowserWindow::update_displayed_zoom_level() | ||||||
|  | @ -560,11 +566,18 @@ void BrowserWindow::update_displayed_zoom_level() | ||||||
| 
 | 
 | ||||||
| void BrowserWindow::copy_selected_text() | void BrowserWindow::copy_selected_text() | ||||||
| { | { | ||||||
|     if (auto* tab = m_current_tab) { |     if (!m_current_tab) | ||||||
|         auto text = tab->view().selected_text(); |         return; | ||||||
|         auto* clipboard = QGuiApplication::clipboard(); | 
 | ||||||
|         clipboard->setText(qstring_from_ak_deprecated_string(text)); |     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) | void BrowserWindow::resizeEvent(QResizeEvent* event) | ||||||
|  |  | ||||||
|  | @ -11,25 +11,46 @@ | ||||||
| 
 | 
 | ||||||
| #include "ConsoleWidget.h" | #include "ConsoleWidget.h" | ||||||
| #include "Utilities.h" | #include "Utilities.h" | ||||||
|  | #include "WebContentView.h" | ||||||
| #include <AK/StringBuilder.h> | #include <AK/StringBuilder.h> | ||||||
| #include <LibJS/MarkupGenerator.h> | #include <LibJS/MarkupGenerator.h> | ||||||
| #include <QLineEdit> | #include <QLineEdit> | ||||||
|  | #include <QPalette> | ||||||
| #include <QPushButton> | #include <QPushButton> | ||||||
| #include <QTextEdit> | #include <QTextEdit> | ||||||
| #include <QVBoxLayout> | #include <QVBoxLayout> | ||||||
| 
 | 
 | ||||||
| namespace Ladybird { | 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() | ConsoleWidget::ConsoleWidget() | ||||||
| { | { | ||||||
|     setLayout(new QVBoxLayout); |     setLayout(new QVBoxLayout); | ||||||
| 
 | 
 | ||||||
|     m_output_view = new QTextEdit(this); |     m_output_view = new WebContentView({}, WebView::EnableCallgrindProfiling::No); | ||||||
|     m_output_view->setReadOnly(true); |     if (is_using_dark_system_theme(*this)) | ||||||
|     layout()->addWidget(m_output_view); |         m_output_view->update_palette(WebContentView::PaletteMode::Dark); | ||||||
| 
 | 
 | ||||||
|     if (on_request_messages) |     m_output_view->load("data:text/html,<html></html>"sv); | ||||||
|         on_request_messages(0); |     // 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); |     auto* bottom_container = new QWidget(this); | ||||||
|     bottom_container->setLayout(new QHBoxLayout); |     bottom_container->setLayout(new QHBoxLayout); | ||||||
|  | @ -139,12 +160,27 @@ void ConsoleWidget::print_source_line(StringView source) | ||||||
| 
 | 
 | ||||||
| void ConsoleWidget::print_html(StringView line) | 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() | void ConsoleWidget::clear_output() | ||||||
| { | { | ||||||
|     m_output_view->clear(); |     m_output_view->run_javascript(R"~~~( | ||||||
|  |         document.body.innerHTML = ""; | ||||||
|  |     )~~~"sv); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConsoleWidget::reset() | void ConsoleWidget::reset() | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
| 
 | 
 | ||||||
| class QLineEdit; | class QLineEdit; | ||||||
| class QTextEdit; | class WebContentView; | ||||||
| 
 | 
 | ||||||
| namespace Ladybird { | namespace Ladybird { | ||||||
| 
 | 
 | ||||||
|  | @ -31,6 +31,8 @@ public: | ||||||
|     void print_html(StringView); |     void print_html(StringView); | ||||||
|     void reset(); |     void reset(); | ||||||
| 
 | 
 | ||||||
|  |     WebContentView& view() { return *m_output_view; } | ||||||
|  | 
 | ||||||
|     Function<void(DeprecatedString const&)> on_js_input; |     Function<void(DeprecatedString const&)> on_js_input; | ||||||
|     Function<void(i32)> on_request_messages; |     Function<void(i32)> on_request_messages; | ||||||
| 
 | 
 | ||||||
|  | @ -38,7 +40,7 @@ private: | ||||||
|     void request_console_messages(); |     void request_console_messages(); | ||||||
|     void clear_output(); |     void clear_output(); | ||||||
| 
 | 
 | ||||||
|     QTextEdit* m_output_view { nullptr }; |     WebContentView* m_output_view { nullptr }; | ||||||
|     QLineEdit* m_input { nullptr }; |     QLineEdit* m_input { nullptr }; | ||||||
| 
 | 
 | ||||||
|     i32 m_highest_notified_message_index { -1 }; |     i32 m_highest_notified_message_index { -1 }; | ||||||
|  |  | ||||||
|  | @ -567,11 +567,12 @@ void WebContentView::hideEvent(QHideEvent* event) | ||||||
|     client().async_set_system_visibility_state(false); |     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 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_impl = Gfx::PaletteImpl::create_with_anonymous_buffer(theme); | ||||||
|     auto palette = Gfx::Palette(move(palette_impl)); |     auto palette = Gfx::Palette(move(palette_impl)); | ||||||
| 
 | 
 | ||||||
|  | @ -594,9 +595,9 @@ static Core::AnonymousBuffer make_system_theme_from_qt_palette(QWidget& widget) | ||||||
|     return theme; |     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) | void WebContentView::create_client(WebView::EnableCallgrindProfiling enable_callgrind_profiling) | ||||||
|  |  | ||||||
|  | @ -98,6 +98,8 @@ public: | ||||||
|     void show_js_console(); |     void show_js_console(); | ||||||
|     void show_inspector(); |     void show_inspector(); | ||||||
| 
 | 
 | ||||||
|  |     Ladybird::ConsoleWidget* console() { return m_console_widget; }; | ||||||
|  | 
 | ||||||
|     ErrorOr<String> dump_layout_tree(); |     ErrorOr<String> dump_layout_tree(); | ||||||
| 
 | 
 | ||||||
|     void set_viewport_rect(Gfx::IntRect); |     void set_viewport_rect(Gfx::IntRect); | ||||||
|  | @ -107,6 +109,12 @@ public: | ||||||
|     Gfx::IntPoint to_content(Gfx::IntPoint) const; |     Gfx::IntPoint to_content(Gfx::IntPoint) const; | ||||||
|     Gfx::IntPoint to_widget(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<WebContentClient>, Gfx::IntSize content_size) override; |     virtual void notify_server_did_layout(Badge<WebContentClient>, Gfx::IntSize content_size) override; | ||||||
|     virtual void notify_server_did_paint(Badge<WebContentClient>, i32 bitmap_id) override; |     virtual void notify_server_did_paint(Badge<WebContentClient>, i32 bitmap_id) override; | ||||||
|     virtual void notify_server_did_invalidate_content_rect(Badge<WebContentClient>, Gfx::IntRect const&) override; |     virtual void notify_server_did_invalidate_content_rect(Badge<WebContentClient>, Gfx::IntRect const&) override; | ||||||
|  | @ -198,8 +206,6 @@ private: | ||||||
|     bool is_inspector_open() const; |     bool is_inspector_open() const; | ||||||
|     void close_sub_widgets(); |     void close_sub_widgets(); | ||||||
| 
 | 
 | ||||||
|     void update_palette(); |  | ||||||
| 
 |  | ||||||
|     qreal m_inverse_pixel_scaling_ratio { 1.0 }; |     qreal m_inverse_pixel_scaling_ratio { 1.0 }; | ||||||
|     bool m_should_show_line_box_borders { false }; |     bool m_should_show_line_box_borders { false }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn