mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-05 15:49:11 +00:00
LibWebView+WebContent: Create a different console client for DevTools
Our existing WebContentConsoleClient is very specific to our home-grown Inspector. It renders console output to an HTML string. For DevTools, we will not want this behavior; we will want to send representations of raw JS values. This patch makes WebContentConsoleClient a base class to handle console input from the user, either from the Inspector or from DevTools. It then moves the HTML rendering needed for the Inspector to a new class, InspectorConsoleClient. And we add a DevToolsConsoleClient (currently just stubbed) to handle needs specific to DevTools. We choose at runtime which console client to install, based on the --devtools command line flag.
This commit is contained in:
parent
a8d3252f93
commit
37f07c176a
Notes:
github-actions[bot]
2025-02-28 12:09:47 +00:00
Author: https://github.com/trflynn89
Commit: 37f07c176a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3686
12 changed files with 419 additions and 229 deletions
|
@ -84,6 +84,7 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
|
|||
IPC::File image_decoder_socket,
|
||||
Optional<IPC::File> request_server_socket)
|
||||
{
|
||||
auto const& chrome_options = WebView::Application::chrome_options();
|
||||
auto const& web_content_options = WebView::Application::web_content_options();
|
||||
|
||||
Vector<ByteString> arguments {
|
||||
|
@ -93,6 +94,8 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
|
|||
web_content_options.executable_path.to_byte_string(),
|
||||
};
|
||||
|
||||
if (chrome_options.devtools_port.has_value())
|
||||
arguments.append("--devtools"sv);
|
||||
if (web_content_options.config_path.has_value()) {
|
||||
arguments.append("--config-path"sv);
|
||||
arguments.append(web_content_options.config_path.value());
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
include(audio)
|
||||
|
||||
set(SOURCES
|
||||
BackingStoreManager.cpp
|
||||
ConnectionFromClient.cpp
|
||||
ConsoleGlobalEnvironmentExtensions.cpp
|
||||
BackingStoreManager.cpp
|
||||
DevToolsConsoleClient.cpp
|
||||
InspectorConsoleClient.cpp
|
||||
PageClient.cpp
|
||||
PageHost.cpp
|
||||
WebContentConsoleClient.cpp
|
||||
|
|
56
Services/WebContent/DevToolsConsoleClient.cpp
Normal file
56
Services/WebContent/DevToolsConsoleClient.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
|
||||
#include <WebContent/DevToolsConsoleClient.h>
|
||||
#include <WebContent/PageClient.h>
|
||||
|
||||
namespace WebContent {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(DevToolsConsoleClient);
|
||||
|
||||
GC::Ref<DevToolsConsoleClient> DevToolsConsoleClient::create(JS::Realm& realm, JS::Console& console, PageClient& client)
|
||||
{
|
||||
auto& window = as<Web::HTML::Window>(realm.global_object());
|
||||
auto console_global_environment_extensions = realm.create<ConsoleGlobalEnvironmentExtensions>(realm, window);
|
||||
|
||||
return realm.heap().allocate<DevToolsConsoleClient>(realm, console, client, console_global_environment_extensions);
|
||||
}
|
||||
|
||||
DevToolsConsoleClient::DevToolsConsoleClient(JS::Realm& realm, JS::Console& console, PageClient& client, ConsoleGlobalEnvironmentExtensions& console_global_environment_extensions)
|
||||
: WebContentConsoleClient(realm, console, client, console_global_environment_extensions)
|
||||
{
|
||||
}
|
||||
|
||||
DevToolsConsoleClient::~DevToolsConsoleClient() = default;
|
||||
|
||||
void DevToolsConsoleClient::handle_result(JS::Value result)
|
||||
{
|
||||
(void)result;
|
||||
}
|
||||
|
||||
void DevToolsConsoleClient::report_exception(JS::Error const& exception, bool in_promise)
|
||||
{
|
||||
(void)exception;
|
||||
(void)in_promise;
|
||||
}
|
||||
|
||||
void DevToolsConsoleClient::send_messages(i32 start_index)
|
||||
{
|
||||
(void)start_index;
|
||||
}
|
||||
|
||||
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
|
||||
JS::ThrowCompletionOr<JS::Value> DevToolsConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
|
||||
{
|
||||
(void)log_level;
|
||||
(void)arguments;
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
}
|
35
Services/WebContent/DevToolsConsoleClient.h
Normal file
35
Services/WebContent/DevToolsConsoleClient.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Console.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <WebContent/Forward.h>
|
||||
#include <WebContent/WebContentConsoleClient.h>
|
||||
|
||||
namespace WebContent {
|
||||
|
||||
class DevToolsConsoleClient final : public WebContentConsoleClient {
|
||||
GC_CELL(DevToolsConsoleClient, WebContentConsoleClient);
|
||||
GC_DECLARE_ALLOCATOR(DevToolsConsoleClient);
|
||||
|
||||
public:
|
||||
static GC::Ref<DevToolsConsoleClient> create(JS::Realm&, JS::Console&, PageClient&);
|
||||
virtual ~DevToolsConsoleClient() override;
|
||||
|
||||
private:
|
||||
DevToolsConsoleClient(JS::Realm&, JS::Console&, PageClient&, ConsoleGlobalEnvironmentExtensions&);
|
||||
|
||||
virtual void handle_result(JS::Value) override;
|
||||
virtual void report_exception(JS::Error const&, bool) override;
|
||||
virtual void send_messages(i32 start_index) override;
|
||||
|
||||
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -10,6 +10,8 @@ namespace WebContent {
|
|||
|
||||
class ConnectionFromClient;
|
||||
class ConsoleGlobalEnvironmentExtensions;
|
||||
class DevToolsConsoleClient;
|
||||
class InspectorConsoleClient;
|
||||
class PageHost;
|
||||
class PageClient;
|
||||
class WebContentConsoleClient;
|
||||
|
|
231
Services/WebContent/InspectorConsoleClient.cpp
Normal file
231
Services/WebContent/InspectorConsoleClient.cpp
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Brandon Scott <xeon.productions@gmail.com>
|
||||
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
|
||||
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2024, Gasim Gasimzada <gasim@gasimzada.net>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibJS/MarkupGenerator.h>
|
||||
#include <LibJS/Print.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
|
||||
#include <WebContent/InspectorConsoleClient.h>
|
||||
#include <WebContent/PageClient.h>
|
||||
|
||||
namespace WebContent {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(InspectorConsoleClient);
|
||||
|
||||
GC::Ref<InspectorConsoleClient> InspectorConsoleClient::create(JS::Realm& realm, JS::Console& console, PageClient& client)
|
||||
{
|
||||
auto& window = as<Web::HTML::Window>(realm.global_object());
|
||||
auto console_global_environment_extensions = realm.create<ConsoleGlobalEnvironmentExtensions>(realm, window);
|
||||
|
||||
return realm.heap().allocate<InspectorConsoleClient>(realm, console, client, console_global_environment_extensions);
|
||||
}
|
||||
|
||||
InspectorConsoleClient::InspectorConsoleClient(JS::Realm& realm, JS::Console& console, PageClient& client, ConsoleGlobalEnvironmentExtensions& console_global_environment_extensions)
|
||||
: WebContentConsoleClient(realm, console, client, console_global_environment_extensions)
|
||||
{
|
||||
}
|
||||
|
||||
InspectorConsoleClient::~InspectorConsoleClient() = default;
|
||||
|
||||
void InspectorConsoleClient::handle_result(JS::Value result)
|
||||
{
|
||||
print_html(JS::MarkupGenerator::html_from_value(result).release_value_but_fixme_should_propagate_errors());
|
||||
}
|
||||
|
||||
void InspectorConsoleClient::report_exception(JS::Error const& exception, bool in_promise)
|
||||
{
|
||||
print_html(JS::MarkupGenerator::html_from_error(exception, in_promise).release_value_but_fixme_should_propagate_errors());
|
||||
}
|
||||
|
||||
void InspectorConsoleClient::send_messages(i32 start_index)
|
||||
{
|
||||
// FIXME: Cap the number of messages we send at once?
|
||||
auto messages_to_send = m_message_log.size() - start_index;
|
||||
if (messages_to_send < 1) {
|
||||
// When the console is first created, it requests any messages that happened before
|
||||
// then, by requesting with start_index=0. If we don't have any messages at all, that
|
||||
// is still a valid request, and we can just ignore it.
|
||||
if (start_index != 0)
|
||||
m_client->console_peer_did_misbehave("Requested non-existent console message index.");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Replace with a single Vector of message structs
|
||||
Vector<String> message_types;
|
||||
Vector<String> messages;
|
||||
message_types.ensure_capacity(messages_to_send);
|
||||
messages.ensure_capacity(messages_to_send);
|
||||
|
||||
for (size_t i = start_index; i < m_message_log.size(); i++) {
|
||||
auto& message = m_message_log[i];
|
||||
switch (message.type) {
|
||||
case ConsoleOutput::Type::HTML:
|
||||
message_types.append("html"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::Clear:
|
||||
message_types.append("clear"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroup:
|
||||
message_types.append("group"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroupCollapsed:
|
||||
message_types.append("groupCollapsed"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::EndGroup:
|
||||
message_types.append("groupEnd"_string);
|
||||
break;
|
||||
}
|
||||
|
||||
messages.append(message.data);
|
||||
}
|
||||
|
||||
m_client->did_get_js_console_messages(start_index, move(message_types), move(messages));
|
||||
}
|
||||
|
||||
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
|
||||
JS::ThrowCompletionOr<JS::Value> InspectorConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
|
||||
{
|
||||
auto styling = escape_html_entities(m_current_message_style.string_view());
|
||||
m_current_message_style.clear();
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Table) {
|
||||
auto& vm = m_console->realm().vm();
|
||||
|
||||
auto table_args = arguments.get<GC::RootVector<JS::Value>>();
|
||||
auto& table = table_args.at(0).as_object();
|
||||
auto& columns = TRY(table.get(vm.names.columns)).as_array().indexed_properties();
|
||||
auto& rows = TRY(table.get(vm.names.rows)).as_array().indexed_properties();
|
||||
|
||||
StringBuilder html;
|
||||
|
||||
html.appendff("<div class=\"console-log-table\">");
|
||||
html.appendff("<table>");
|
||||
html.appendff("<thead>");
|
||||
html.appendff("<tr>");
|
||||
for (auto const& col : columns) {
|
||||
auto index = col.index();
|
||||
auto value = columns.storage()->get(index).value().value;
|
||||
html.appendff("<td>{}</td>", value);
|
||||
}
|
||||
|
||||
html.appendff("</tr>");
|
||||
html.appendff("</thead>");
|
||||
html.appendff("<tbody>");
|
||||
|
||||
for (auto const& row : rows) {
|
||||
auto row_index = row.index();
|
||||
auto& row_obj = rows.storage()->get(row_index).value().value.as_object();
|
||||
html.appendff("<tr>");
|
||||
|
||||
for (auto const& col : columns) {
|
||||
auto col_index = col.index();
|
||||
auto col_name = columns.storage()->get(col_index).value().value;
|
||||
|
||||
auto property_key = TRY(JS::PropertyKey::from_value(vm, col_name));
|
||||
auto cell = TRY(row_obj.get(property_key));
|
||||
html.appendff("<td>");
|
||||
if (TRY(cell.is_array(vm))) {
|
||||
AllocatingMemoryStream stream;
|
||||
JS::PrintContext ctx { vm, stream, true };
|
||||
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
|
||||
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
|
||||
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
|
||||
|
||||
auto size = cell.as_array().indexed_properties().array_like_size();
|
||||
html.appendff("<details><summary>Array({})</summary>{}</details>", size, output);
|
||||
|
||||
} else if (cell.is_object()) {
|
||||
AllocatingMemoryStream stream;
|
||||
JS::PrintContext ctx { vm, stream, true };
|
||||
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
|
||||
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
|
||||
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
|
||||
|
||||
html.appendff("<details><summary>Object({{...}})</summary>{}</details>", output);
|
||||
} else if (cell.is_function() || cell.is_constructor()) {
|
||||
html.appendff("ƒ");
|
||||
} else if (!cell.is_undefined()) {
|
||||
html.appendff("{}", cell);
|
||||
}
|
||||
html.appendff("</td>");
|
||||
}
|
||||
|
||||
html.appendff("</tr>");
|
||||
}
|
||||
|
||||
html.appendff("</tbody>");
|
||||
html.appendff("</table>");
|
||||
html.appendff("</div>");
|
||||
print_html(MUST(html.to_string()));
|
||||
|
||||
auto output = TRY(generically_format_values(table_args));
|
||||
m_console->output_debug_message(log_level, output);
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Trace) {
|
||||
auto trace = arguments.get<JS::Console::Trace>();
|
||||
StringBuilder html;
|
||||
if (!trace.label.is_empty())
|
||||
html.appendff("<span class='title' style='{}'>{}</span><br>", styling, escape_html_entities(trace.label));
|
||||
|
||||
html.append("<span class='trace'>"sv);
|
||||
for (auto& function_name : trace.stack)
|
||||
html.appendff("-> {}<br>", escape_html_entities(function_name));
|
||||
html.append("</span>"sv);
|
||||
|
||||
print_html(MUST(html.to_string()));
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) {
|
||||
auto group = arguments.get<JS::Console::Group>();
|
||||
begin_group(MUST(String::formatted("<span style='{}'>{}</span>", styling, escape_html_entities(group.label))), log_level == JS::Console::LogLevel::Group);
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
auto output = TRY(generically_format_values(arguments.get<GC::RootVector<JS::Value>>()));
|
||||
m_console->output_debug_message(log_level, output);
|
||||
|
||||
StringBuilder html;
|
||||
switch (log_level) {
|
||||
case JS::Console::LogLevel::Debug:
|
||||
html.appendff("<span class=\"debug\" style=\"{}\">(d) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Error:
|
||||
html.appendff("<span class=\"error\" style=\"{}\">(e) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Info:
|
||||
html.appendff("<span class=\"info\" style=\"{}\">(i) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Log:
|
||||
html.appendff("<span class=\"log\" style=\"{}\"> "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Warn:
|
||||
case JS::Console::LogLevel::CountReset:
|
||||
html.appendff("<span class=\"warn\" style=\"{}\">(w) "sv, styling);
|
||||
break;
|
||||
default:
|
||||
html.appendff("<span style=\"{}\">"sv, styling);
|
||||
break;
|
||||
}
|
||||
|
||||
html.append(escape_html_entities(output));
|
||||
html.append("</span>"sv);
|
||||
print_html(MUST(html.to_string()));
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
}
|
44
Services/WebContent/InspectorConsoleClient.h
Normal file
44
Services/WebContent/InspectorConsoleClient.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Brandon Scott <xeon.productions@gmail.com>
|
||||
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
|
||||
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <WebContent/Forward.h>
|
||||
#include <WebContent/WebContentConsoleClient.h>
|
||||
|
||||
namespace WebContent {
|
||||
|
||||
class InspectorConsoleClient final : public WebContentConsoleClient {
|
||||
GC_CELL(InspectorConsoleClient, WebContentConsoleClient);
|
||||
GC_DECLARE_ALLOCATOR(InspectorConsoleClient);
|
||||
|
||||
public:
|
||||
static GC::Ref<InspectorConsoleClient> create(JS::Realm&, JS::Console&, PageClient&);
|
||||
virtual ~InspectorConsoleClient() override;
|
||||
|
||||
private:
|
||||
InspectorConsoleClient(JS::Realm&, JS::Console&, PageClient&, ConsoleGlobalEnvironmentExtensions&);
|
||||
|
||||
virtual void handle_result(JS::Value) override;
|
||||
virtual void report_exception(JS::Error const&, bool) override;
|
||||
virtual void send_messages(i32 start_index) override;
|
||||
|
||||
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments) override;
|
||||
|
||||
virtual void add_css_style_to_current_message(StringView style) override
|
||||
{
|
||||
m_current_message_style.append(style);
|
||||
m_current_message_style.append(';');
|
||||
}
|
||||
|
||||
StringBuilder m_current_message_style;
|
||||
};
|
||||
|
||||
}
|
|
@ -24,6 +24,8 @@
|
|||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
#include <WebContent/ConnectionFromClient.h>
|
||||
#include <WebContent/DevToolsConsoleClient.h>
|
||||
#include <WebContent/InspectorConsoleClient.h>
|
||||
#include <WebContent/PageClient.h>
|
||||
#include <WebContent/PageHost.h>
|
||||
#include <WebContent/WebContentClientEndpoint.h>
|
||||
|
@ -33,6 +35,7 @@ namespace WebContent {
|
|||
|
||||
static PageClient::UseSkiaPainter s_use_skia_painter = PageClient::UseSkiaPainter::GPUBackendIfAvailable;
|
||||
static bool s_is_headless { false };
|
||||
static bool s_devtools_enabled { false };
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PageClient);
|
||||
|
||||
|
@ -51,6 +54,11 @@ void PageClient::set_is_headless(bool is_headless)
|
|||
s_is_headless = is_headless;
|
||||
}
|
||||
|
||||
void PageClient::set_devtools_enabled(bool devtools_enabled)
|
||||
{
|
||||
s_devtools_enabled = devtools_enabled;
|
||||
}
|
||||
|
||||
GC::Ref<PageClient> PageClient::create(JS::VM& vm, PageHost& page_host, u64 id)
|
||||
{
|
||||
return vm.heap().allocate<PageClient>(page_host, id);
|
||||
|
@ -731,9 +739,13 @@ void PageClient::initialize_js_console(Web::DOM::Document& document)
|
|||
return;
|
||||
|
||||
auto& realm = document.realm();
|
||||
|
||||
auto console_object = realm.intrinsics().console_object();
|
||||
auto console_client = heap().allocate<WebContentConsoleClient>(console_object->console(), document.realm(), *this);
|
||||
|
||||
GC::Ptr<JS::ConsoleClient> console_client;
|
||||
if (s_devtools_enabled)
|
||||
console_client = DevToolsConsoleClient::create(document.realm(), console_object->console(), *this);
|
||||
else
|
||||
console_client = InspectorConsoleClient::create(document.realm(), console_object->console(), *this);
|
||||
|
||||
document.set_console_client(console_client);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
virtual bool is_headless() const override;
|
||||
static void set_is_headless(bool);
|
||||
|
||||
static void set_devtools_enabled(bool);
|
||||
|
||||
virtual bool is_ready_to_paint() const override;
|
||||
|
||||
virtual Web::Page& page() override { return *m_page; }
|
||||
|
|
|
@ -7,17 +7,11 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibJS/MarkupGenerator.h>
|
||||
#include <LibJS/Print.h>
|
||||
#include <LibJS/Runtime/GlobalEnvironment.h>
|
||||
#include <LibJS/Runtime/ObjectEnvironment.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
|
||||
#include <WebContent/PageClient.h>
|
||||
#include <WebContent/WebContentConsoleClient.h>
|
||||
|
@ -26,12 +20,12 @@ namespace WebContent {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(WebContentConsoleClient);
|
||||
|
||||
WebContentConsoleClient::WebContentConsoleClient(JS::Console& console, JS::Realm& realm, PageClient& client)
|
||||
WebContentConsoleClient::WebContentConsoleClient(JS::Realm& realm, JS::Console& console, PageClient& client, ConsoleGlobalEnvironmentExtensions& console_global_environment_extensions)
|
||||
: ConsoleClient(console)
|
||||
, m_realm(realm)
|
||||
, m_client(client)
|
||||
, m_console_global_environment_extensions(console_global_environment_extensions)
|
||||
{
|
||||
auto& window = as<Web::HTML::Window>(realm.global_object());
|
||||
m_console_global_environment_extensions = realm.create<ConsoleGlobalEnvironmentExtensions>(realm, window);
|
||||
}
|
||||
|
||||
WebContentConsoleClient::~WebContentConsoleClient() = default;
|
||||
|
@ -39,15 +33,13 @@ WebContentConsoleClient::~WebContentConsoleClient() = default;
|
|||
void WebContentConsoleClient::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_realm);
|
||||
visitor.visit(m_client);
|
||||
visitor.visit(m_console_global_environment_extensions);
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::handle_input(StringView js_source)
|
||||
{
|
||||
if (!m_console_global_environment_extensions)
|
||||
return;
|
||||
|
||||
auto& settings = Web::HTML::relevant_settings_object(*m_console_global_environment_extensions);
|
||||
auto script = Web::HTML::ClassicScript::create("(console)", js_source, settings.realm(), settings.api_base_url());
|
||||
|
||||
|
@ -57,22 +49,22 @@ void WebContentConsoleClient::handle_input(StringView js_source)
|
|||
auto result = script->run(Web::HTML::ClassicScript::RethrowErrors::No, with_scope);
|
||||
|
||||
if (result.value().has_value()) {
|
||||
m_console_global_environment_extensions->set_most_recent_result(result.value().value());
|
||||
print_html(JS::MarkupGenerator::html_from_value(*result.value()).release_value_but_fixme_should_propagate_errors());
|
||||
m_console_global_environment_extensions->set_most_recent_result(*result.value());
|
||||
handle_result(*result.value());
|
||||
}
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::report_exception(JS::Error const& exception, bool in_promise)
|
||||
{
|
||||
print_html(JS::MarkupGenerator::html_from_error(exception, in_promise).release_value_but_fixme_should_propagate_errors());
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::print_html(String const& line)
|
||||
{
|
||||
m_message_log.append({ .type = ConsoleOutput::Type::HTML, .data = line });
|
||||
m_client->did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::clear()
|
||||
{
|
||||
clear_output();
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::clear_output()
|
||||
{
|
||||
m_message_log.append({ .type = ConsoleOutput::Type::Clear, .data = String {} });
|
||||
|
@ -91,189 +83,4 @@ void WebContentConsoleClient::end_group()
|
|||
m_client->did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::send_messages(i32 start_index)
|
||||
{
|
||||
// FIXME: Cap the number of messages we send at once?
|
||||
auto messages_to_send = m_message_log.size() - start_index;
|
||||
if (messages_to_send < 1) {
|
||||
// When the console is first created, it requests any messages that happened before
|
||||
// then, by requesting with start_index=0. If we don't have any messages at all, that
|
||||
// is still a valid request, and we can just ignore it.
|
||||
if (start_index != 0)
|
||||
m_client->console_peer_did_misbehave("Requested non-existent console message index.");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Replace with a single Vector of message structs
|
||||
Vector<String> message_types;
|
||||
Vector<String> messages;
|
||||
message_types.ensure_capacity(messages_to_send);
|
||||
messages.ensure_capacity(messages_to_send);
|
||||
|
||||
for (size_t i = start_index; i < m_message_log.size(); i++) {
|
||||
auto& message = m_message_log[i];
|
||||
switch (message.type) {
|
||||
case ConsoleOutput::Type::HTML:
|
||||
message_types.append("html"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::Clear:
|
||||
message_types.append("clear"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroup:
|
||||
message_types.append("group"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroupCollapsed:
|
||||
message_types.append("groupCollapsed"_string);
|
||||
break;
|
||||
case ConsoleOutput::Type::EndGroup:
|
||||
message_types.append("groupEnd"_string);
|
||||
break;
|
||||
}
|
||||
|
||||
messages.append(message.data);
|
||||
}
|
||||
|
||||
m_client->did_get_js_console_messages(start_index, move(message_types), move(messages));
|
||||
}
|
||||
|
||||
void WebContentConsoleClient::clear()
|
||||
{
|
||||
clear_output();
|
||||
}
|
||||
|
||||
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
|
||||
JS::ThrowCompletionOr<JS::Value> WebContentConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
|
||||
{
|
||||
auto styling = escape_html_entities(m_current_message_style.string_view());
|
||||
m_current_message_style.clear();
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Table) {
|
||||
auto& vm = m_console->realm().vm();
|
||||
|
||||
auto table_args = arguments.get<GC::RootVector<JS::Value>>();
|
||||
auto& table = table_args.at(0).as_object();
|
||||
auto& columns = TRY(table.get(vm.names.columns)).as_array().indexed_properties();
|
||||
auto& rows = TRY(table.get(vm.names.rows)).as_array().indexed_properties();
|
||||
|
||||
StringBuilder html;
|
||||
|
||||
html.appendff("<div class=\"console-log-table\">");
|
||||
html.appendff("<table>");
|
||||
html.appendff("<thead>");
|
||||
html.appendff("<tr>");
|
||||
for (auto const& col : columns) {
|
||||
auto index = col.index();
|
||||
auto value = columns.storage()->get(index).value().value;
|
||||
html.appendff("<td>{}</td>", value);
|
||||
}
|
||||
|
||||
html.appendff("</tr>");
|
||||
html.appendff("</thead>");
|
||||
html.appendff("<tbody>");
|
||||
|
||||
for (auto const& row : rows) {
|
||||
auto row_index = row.index();
|
||||
auto& row_obj = rows.storage()->get(row_index).value().value.as_object();
|
||||
html.appendff("<tr>");
|
||||
|
||||
for (auto const& col : columns) {
|
||||
auto col_index = col.index();
|
||||
auto col_name = columns.storage()->get(col_index).value().value;
|
||||
|
||||
auto property_key = TRY(JS::PropertyKey::from_value(vm, col_name));
|
||||
auto cell = TRY(row_obj.get(property_key));
|
||||
html.appendff("<td>");
|
||||
if (TRY(cell.is_array(vm))) {
|
||||
AllocatingMemoryStream stream;
|
||||
JS::PrintContext ctx { vm, stream, true };
|
||||
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
|
||||
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
|
||||
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
|
||||
|
||||
auto size = cell.as_array().indexed_properties().array_like_size();
|
||||
html.appendff("<details><summary>Array({})</summary>{}</details>", size, output);
|
||||
|
||||
} else if (cell.is_object()) {
|
||||
AllocatingMemoryStream stream;
|
||||
JS::PrintContext ctx { vm, stream, true };
|
||||
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
|
||||
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
|
||||
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
|
||||
|
||||
html.appendff("<details><summary>Object({{...}})</summary>{}</details>", output);
|
||||
} else if (cell.is_function() || cell.is_constructor()) {
|
||||
html.appendff("ƒ");
|
||||
} else if (!cell.is_undefined()) {
|
||||
html.appendff("{}", cell);
|
||||
}
|
||||
html.appendff("</td>");
|
||||
}
|
||||
|
||||
html.appendff("</tr>");
|
||||
}
|
||||
|
||||
html.appendff("</tbody>");
|
||||
html.appendff("</table>");
|
||||
html.appendff("</div>");
|
||||
print_html(MUST(html.to_string()));
|
||||
|
||||
auto output = TRY(generically_format_values(table_args));
|
||||
m_console->output_debug_message(log_level, output);
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Trace) {
|
||||
auto trace = arguments.get<JS::Console::Trace>();
|
||||
StringBuilder html;
|
||||
if (!trace.label.is_empty())
|
||||
html.appendff("<span class='title' style='{}'>{}</span><br>", styling, escape_html_entities(trace.label));
|
||||
|
||||
html.append("<span class='trace'>"sv);
|
||||
for (auto& function_name : trace.stack)
|
||||
html.appendff("-> {}<br>", escape_html_entities(function_name));
|
||||
html.append("</span>"sv);
|
||||
|
||||
print_html(MUST(html.to_string()));
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) {
|
||||
auto group = arguments.get<JS::Console::Group>();
|
||||
begin_group(MUST(String::formatted("<span style='{}'>{}</span>", styling, escape_html_entities(group.label))), log_level == JS::Console::LogLevel::Group);
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
auto output = TRY(generically_format_values(arguments.get<GC::RootVector<JS::Value>>()));
|
||||
m_console->output_debug_message(log_level, output);
|
||||
|
||||
StringBuilder html;
|
||||
switch (log_level) {
|
||||
case JS::Console::LogLevel::Debug:
|
||||
html.appendff("<span class=\"debug\" style=\"{}\">(d) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Error:
|
||||
html.appendff("<span class=\"error\" style=\"{}\">(e) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Info:
|
||||
html.appendff("<span class=\"info\" style=\"{}\">(i) "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Log:
|
||||
html.appendff("<span class=\"log\" style=\"{}\"> "sv, styling);
|
||||
break;
|
||||
case JS::Console::LogLevel::Warn:
|
||||
case JS::Console::LogLevel::CountReset:
|
||||
html.appendff("<span class=\"warn\" style=\"{}\">(w) "sv, styling);
|
||||
break;
|
||||
default:
|
||||
html.appendff("<span style=\"{}\">"sv, styling);
|
||||
break;
|
||||
}
|
||||
|
||||
html.append(escape_html_entities(output));
|
||||
html.append("</span>"sv);
|
||||
print_html(MUST(html.to_string()));
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,16 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibJS/Console.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
|
||||
#include <WebContent/Forward.h>
|
||||
|
||||
namespace WebContent {
|
||||
|
||||
class WebContentConsoleClient final : public JS::ConsoleClient {
|
||||
class WebContentConsoleClient : public JS::ConsoleClient {
|
||||
GC_CELL(WebContentConsoleClient, JS::ConsoleClient);
|
||||
GC_DECLARE_ALLOCATOR(WebContentConsoleClient);
|
||||
|
||||
|
@ -27,30 +25,27 @@ public:
|
|||
virtual ~WebContentConsoleClient() override;
|
||||
|
||||
void handle_input(StringView js_source);
|
||||
void send_messages(i32 start_index);
|
||||
void report_exception(JS::Error const&, bool) override;
|
||||
|
||||
private:
|
||||
WebContentConsoleClient(JS::Console&, JS::Realm&, PageClient&);
|
||||
virtual void handle_result(JS::Value) = 0;
|
||||
virtual void send_messages(i32 start_index) = 0;
|
||||
|
||||
protected:
|
||||
WebContentConsoleClient(JS::Realm&, JS::Console&, PageClient&, ConsoleGlobalEnvironmentExtensions&);
|
||||
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
virtual void clear() override;
|
||||
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments) override;
|
||||
|
||||
virtual void add_css_style_to_current_message(StringView style) override
|
||||
{
|
||||
m_current_message_style.append(style);
|
||||
m_current_message_style.append(';');
|
||||
}
|
||||
|
||||
GC::Ref<PageClient> m_client;
|
||||
GC::Ptr<ConsoleGlobalEnvironmentExtensions> m_console_global_environment_extensions;
|
||||
|
||||
void clear_output();
|
||||
void print_html(String const& line);
|
||||
|
||||
virtual void clear() override;
|
||||
void clear_output();
|
||||
|
||||
void begin_group(String const& label, bool start_expanded);
|
||||
virtual void end_group() override;
|
||||
|
||||
GC::Ref<JS::Realm> m_realm;
|
||||
GC::Ref<PageClient> m_client;
|
||||
GC::Ref<ConsoleGlobalEnvironmentExtensions> m_console_global_environment_extensions;
|
||||
|
||||
struct ConsoleOutput {
|
||||
enum class Type {
|
||||
HTML,
|
||||
|
@ -63,8 +58,6 @@ private:
|
|||
String data;
|
||||
};
|
||||
Vector<ConsoleOutput> m_message_log;
|
||||
|
||||
StringBuilder m_current_message_style;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
bool collect_garbage_on_every_allocation = false;
|
||||
bool is_headless = false;
|
||||
bool disable_scrollbar_painting = false;
|
||||
bool devtools = false;
|
||||
StringView echo_server_port_string_view {};
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
|
@ -129,6 +130,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
args_parser.add_option(disable_scrollbar_painting, "Don't paint horizontal or vertical viewport scrollbars", "disable-scrollbar-painting");
|
||||
args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port");
|
||||
args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless");
|
||||
args_parser.add_option(devtools, "Report that the browser is running with Firefox DevTools support", "devtools");
|
||||
|
||||
args_parser.parse(arguments);
|
||||
|
||||
|
@ -155,6 +157,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
WebContent::PageClient::set_use_skia_painter(force_cpu_painting ? WebContent::PageClient::UseSkiaPainter::CPUBackend : WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
|
||||
|
||||
WebContent::PageClient::set_is_headless(is_headless);
|
||||
WebContent::PageClient::set_devtools_enabled(devtools);
|
||||
|
||||
if (enable_http_cache) {
|
||||
Web::Fetch::Fetching::g_http_cache_enabled = true;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue