From 260836135a17596e5b481edcf81a79824b44ca51 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 10 Dec 2021 12:26:25 +0000 Subject: [PATCH] LibJS+WebContent+js: Reimplement console.log() and friends to spec This implements the Logger and Printer abstract operations defined in the console spec, and stubs out the Formatter AO. These are then used for the "output a categorized log message" functions. --- Userland/Libraries/LibJS/Console.cpp | 143 ++++++++++++++---- Userland/Libraries/LibJS/Console.h | 45 ++++-- .../WebContent/WebContentConsoleClient.cpp | 84 +++++----- .../WebContent/WebContentConsoleClient.h | 6 +- Userland/Utilities/js.cpp | 58 ++++--- 5 files changed, 211 insertions(+), 125 deletions(-) diff --git a/Userland/Libraries/LibJS/Console.cpp b/Userland/Libraries/LibJS/Console.cpp index ae5d2bfb7d..3866897c27 100644 --- a/Userland/Libraries/LibJS/Console.cpp +++ b/Userland/Libraries/LibJS/Console.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Emanuele Torre * Copyright (c) 2020-2021, Linus Groh + * Copyright (c) 2021, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -20,53 +21,58 @@ VM& Console::vm() return m_global_object.vm(); } -Value Console::debug() +// 1.1.3. debug(...data), https://console.spec.whatwg.org/#debug +ThrowCompletionOr Console::debug() { -#ifdef __serenity__ - dbgln("\033[32;1m(js debug)\033[0m {}", vm().join_arguments()); -#endif - if (m_client) - return m_client->debug(); + // 1. Perform Logger("debug", data). + if (m_client) { + auto data = vm_arguments(); + return m_client->logger(LogLevel::Debug, data); + } return js_undefined(); } -Value Console::error() +// 1.1.4. error(...data), https://console.spec.whatwg.org/#error +ThrowCompletionOr Console::error() { -#ifdef __serenity__ - dbgln("\033[32;1m(js error)\033[0m {}", vm().join_arguments()); -#endif - if (m_client) - return m_client->error(); + // 1. Perform Logger("error", data). + if (m_client) { + auto data = vm_arguments(); + return m_client->logger(LogLevel::Error, data); + } return js_undefined(); } -Value Console::info() +// 1.1.5. info(...data), https://console.spec.whatwg.org/#info +ThrowCompletionOr Console::info() { -#ifdef __serenity__ - dbgln("\033[32;1m(js info)\033[0m {}", vm().join_arguments()); -#endif - if (m_client) - return m_client->info(); + // 1. Perform Logger("info", data). + if (m_client) { + auto data = vm_arguments(); + return m_client->logger(LogLevel::Info, data); + } return js_undefined(); } -Value Console::log() +// 1.1.6. log(...data), https://console.spec.whatwg.org/#log +ThrowCompletionOr Console::log() { -#ifdef __serenity__ - dbgln("\033[32;1m(js log)\033[0m {}", vm().join_arguments()); -#endif - if (m_client) - return m_client->log(); + // 1. Perform Logger("log", data). + if (m_client) { + auto data = vm_arguments(); + return m_client->logger(LogLevel::Log, data); + } return js_undefined(); } -Value Console::warn() +// 1.1.9. warn(...data), https://console.spec.whatwg.org/#warn +ThrowCompletionOr Console::warn() { -#ifdef __serenity__ - dbgln("\033[32;1m(js warn)\033[0m {}", vm().join_arguments()); -#endif - if (m_client) - return m_client->warn(); + // 1. Perform Logger("warn", data). + if (m_client) { + auto data = vm_arguments(); + return m_client->logger(LogLevel::Warn, data); + } return js_undefined(); } @@ -127,6 +133,42 @@ bool Console::counter_reset(String label) return true; } +Vector Console::vm_arguments() +{ + Vector arguments; + arguments.ensure_capacity(vm().argument_count()); + for (size_t i = 0; i < vm().argument_count(); ++i) { + arguments.append(vm().argument(i)); + } + return arguments; +} + +void Console::output_debug_message([[maybe_unused]] LogLevel log_level, [[maybe_unused]] String output) const +{ +#ifdef __serenity__ + switch (log_level) { + case JS::Console::LogLevel::Debug: + dbgln("\033[32;1m(js debug)\033[0m {}", output); + break; + case JS::Console::LogLevel::Error: + dbgln("\033[32;1m(js error)\033[0m {}", output); + break; + case JS::Console::LogLevel::Info: + dbgln("\033[32;1m(js info)\033[0m {}", output); + break; + case JS::Console::LogLevel::Log: + dbgln("\033[32;1m(js log)\033[0m {}", output); + break; + case JS::Console::LogLevel::Warn: + dbgln("\033[32;1m(js warn)\033[0m {}", output); + break; + default: + dbgln("\033[32;1m(js)\033[0m {}", output); + break; + } +#endif +} + VM& ConsoleClient::vm() { return global_object().vm(); @@ -142,4 +184,45 @@ Vector ConsoleClient::get_trace() const return trace; } +// 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger +ThrowCompletionOr ConsoleClient::logger(Console::LogLevel log_level, Vector& args) +{ + auto& global_object = this->global_object(); + + // 1. If args is empty, return. + if (args.is_empty()) + return js_undefined(); + + // 2. Let first be args[0]. + auto first = args[0]; + + // 3. Let rest be all elements following first in args. + size_t rest_size = args.size() - 1; + + // 4. If rest is empty, perform Printer(logLevel, « first ») and return. + if (rest_size == 0) { + auto first_as_vector = Vector { first }; + return printer(log_level, first_as_vector); + } + + // 5. If first does not contain any format specifiers, perform Printer(logLevel, args). + if (!TRY(first.to_string(global_object)).contains('%')) { + TRY(printer(log_level, args)); + } else { + // 6. Otherwise, perform Printer(logLevel, Formatter(args)). + auto formatted = TRY(formatter(args)); + TRY(printer(log_level, formatted)); + } + + // 7. Return undefined. + return js_undefined(); +} + +// 2.2. Formatter(args), https://console.spec.whatwg.org/#formatter +ThrowCompletionOr> ConsoleClient::formatter(Vector& args) +{ + // TODO: Actually implement formatting + return args; +} + } diff --git a/Userland/Libraries/LibJS/Console.h b/Userland/Libraries/LibJS/Console.h index 0fb757f7f7..9461301a98 100644 --- a/Userland/Libraries/LibJS/Console.h +++ b/Userland/Libraries/LibJS/Console.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Emanuele Torre + * Copyright (c) 2021, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -9,17 +10,39 @@ #include #include #include +#include #include +#include namespace JS { class ConsoleClient; +// https://console.spec.whatwg.org class Console { AK_MAKE_NONCOPYABLE(Console); AK_MAKE_NONMOVABLE(Console); public: + // These are not really levels, but that's the term used in the spec. + enum class LogLevel { + Assert, + Count, + CountReset, + Debug, + Dir, + DirXML, + Error, + Group, + GroupCollapsed, + Info, + Log, + TimeEnd, + TimeLog, + Trace, + Warn, + }; + explicit Console(GlobalObject&); void set_client(ConsoleClient& client) { m_client = &client; } @@ -28,15 +51,16 @@ public: const GlobalObject& global_object() const { return m_global_object; } VM& vm(); + Vector vm_arguments(); HashMap& counters() { return m_counters; } const HashMap& counters() const { return m_counters; } - Value debug(); - Value error(); - Value info(); - Value log(); - Value warn(); + ThrowCompletionOr debug(); + ThrowCompletionOr error(); + ThrowCompletionOr info(); + ThrowCompletionOr log(); + ThrowCompletionOr warn(); Value clear(); Value trace(); Value count(); @@ -46,6 +70,8 @@ public: unsigned counter_increment(String label); bool counter_reset(String label); + void output_debug_message(LogLevel log_level, String output) const; + private: GlobalObject& m_global_object; ConsoleClient* m_client { nullptr }; @@ -60,11 +86,10 @@ public: { } - virtual Value debug() = 0; - virtual Value error() = 0; - virtual Value info() = 0; - virtual Value log() = 0; - virtual Value warn() = 0; + ThrowCompletionOr logger(Console::LogLevel log_level, Vector& args); + ThrowCompletionOr> formatter(Vector& args); + virtual ThrowCompletionOr printer(Console::LogLevel log_level, Vector&) = 0; + virtual Value clear() = 0; virtual Value trace() = 0; virtual Value count() = 0; diff --git a/Userland/Services/WebContent/WebContentConsoleClient.cpp b/Userland/Services/WebContent/WebContentConsoleClient.cpp index 54a0cc6ab9..4966248508 100644 --- a/Userland/Services/WebContent/WebContentConsoleClient.cpp +++ b/Userland/Services/WebContent/WebContentConsoleClient.cpp @@ -115,56 +115,6 @@ void WebContentConsoleClient::send_messages(i32 start_index) m_client.async_did_get_js_console_messages(start_index, message_types, messages); } -JS::Value WebContentConsoleClient::log() -{ - print_html(escape_html_entities(vm().join_arguments())); - return JS::js_undefined(); -} - -JS::Value WebContentConsoleClient::info() -{ - StringBuilder html; - html.append(""); - html.append("(i) "); - html.append(escape_html_entities(vm().join_arguments())); - html.append(""); - print_html(html.string_view()); - return JS::js_undefined(); -} - -JS::Value WebContentConsoleClient::debug() -{ - StringBuilder html; - html.append(""); - html.append("(d) "); - html.append(escape_html_entities(vm().join_arguments())); - html.append(""); - print_html(html.string_view()); - return JS::js_undefined(); -} - -JS::Value WebContentConsoleClient::warn() -{ - StringBuilder html; - html.append(""); - html.append("(w) "); - html.append(escape_html_entities(vm().join_arguments())); - html.append(""); - print_html(html.string_view()); - return JS::js_undefined(); -} - -JS::Value WebContentConsoleClient::error() -{ - StringBuilder html; - html.append(""); - html.append("(e) "); - html.append(escape_html_entities(vm().join_arguments())); - html.append(""); - print_html(html.string_view()); - return JS::js_undefined(); -} - JS::Value WebContentConsoleClient::clear() { clear_output(); @@ -225,4 +175,38 @@ JS::Value WebContentConsoleClient::assert_() return JS::js_undefined(); } +// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer +JS::ThrowCompletionOr WebContentConsoleClient::printer(JS::Console::LogLevel log_level, Vector& arguments) +{ + auto output = String::join(" ", arguments); + m_console.output_debug_message(log_level, output); + + StringBuilder html; + switch (log_level) { + case JS::Console::LogLevel::Debug: + html.append("(d) "); + break; + case JS::Console::LogLevel::Error: + html.append("(e) "); + break; + case JS::Console::LogLevel::Info: + html.append("(i) "); + break; + case JS::Console::LogLevel::Log: + html.append(" "); + break; + case JS::Console::LogLevel::Warn: + html.append("(w) "); + break; + default: + html.append(""); + break; + } + + html.append(escape_html_entities(output)); + html.append(""); + print_html(html.string_view()); + return JS::js_undefined(); +} + } diff --git a/Userland/Services/WebContent/WebContentConsoleClient.h b/Userland/Services/WebContent/WebContentConsoleClient.h index 16df4bcf9e..0373344a64 100644 --- a/Userland/Services/WebContent/WebContentConsoleClient.h +++ b/Userland/Services/WebContent/WebContentConsoleClient.h @@ -24,16 +24,12 @@ public: void send_messages(i32 start_index); private: - virtual JS::Value log() override; - virtual JS::Value info() override; - virtual JS::Value debug() override; - virtual JS::Value warn() override; - virtual JS::Value error() override; virtual JS::Value clear() override; virtual JS::Value trace() override; virtual JS::Value count() override; virtual JS::Value count_reset() override; virtual JS::Value assert_() override; + virtual JS::ThrowCompletionOr printer(JS::Console::LogLevel log_level, Vector&) override; ClientConnection& m_client; WeakPtr m_interpreter; diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 85a24ae7e4..335ea81fe6 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -1122,36 +1122,6 @@ public: { } - virtual JS::Value log() override - { - js_outln("{}", vm().join_arguments()); - return JS::js_undefined(); - } - - virtual JS::Value info() override - { - js_outln("(i) {}", vm().join_arguments()); - return JS::js_undefined(); - } - - virtual JS::Value debug() override - { - js_outln("\033[36;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - - virtual JS::Value warn() override - { - js_outln("\033[33;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - - virtual JS::Value error() override - { - js_outln("\033[31;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value clear() override { js_out("\033[3J\033[H\033[2J"); @@ -1202,6 +1172,34 @@ public: } return JS::js_undefined(); } + + virtual JS::ThrowCompletionOr printer(JS::Console::LogLevel log_level, Vector& arguments) override + { + auto output = String::join(" ", arguments); + m_console.output_debug_message(log_level, output); + + switch (log_level) { + case JS::Console::LogLevel::Debug: + js_outln("\033[36;1m{}\033[0m", output); + break; + case JS::Console::LogLevel::Error: + js_outln("\033[31;1m{}\033[0m", output); + break; + case JS::Console::LogLevel::Info: + js_outln("(i) {}", output); + break; + case JS::Console::LogLevel::Log: + js_outln("{}", output); + break; + case JS::Console::LogLevel::Warn: + js_outln("\033[33;1m{}\033[0m", output); + break; + default: + js_outln("{}", output); + break; + } + return JS::js_undefined(); + } }; ErrorOr serenity_main(Main::Arguments arguments)