1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 00:47:34 +00:00

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.
This commit is contained in:
Sam Atkins 2021-12-10 12:26:25 +00:00 committed by Andreas Kling
parent fd7163b125
commit 260836135a
5 changed files with 211 additions and 125 deletions

View file

@ -1,6 +1,7 @@
/* /*
* Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com> * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -20,53 +21,58 @@ VM& Console::vm()
return m_global_object.vm(); return m_global_object.vm();
} }
Value Console::debug() // 1.1.3. debug(...data), https://console.spec.whatwg.org/#debug
ThrowCompletionOr<Value> Console::debug()
{ {
#ifdef __serenity__ // 1. Perform Logger("debug", data).
dbgln("\033[32;1m(js debug)\033[0m {}", vm().join_arguments()); if (m_client) {
#endif auto data = vm_arguments();
if (m_client) return m_client->logger(LogLevel::Debug, data);
return m_client->debug(); }
return js_undefined(); return js_undefined();
} }
Value Console::error() // 1.1.4. error(...data), https://console.spec.whatwg.org/#error
ThrowCompletionOr<Value> Console::error()
{ {
#ifdef __serenity__ // 1. Perform Logger("error", data).
dbgln("\033[32;1m(js error)\033[0m {}", vm().join_arguments()); if (m_client) {
#endif auto data = vm_arguments();
if (m_client) return m_client->logger(LogLevel::Error, data);
return m_client->error(); }
return js_undefined(); return js_undefined();
} }
Value Console::info() // 1.1.5. info(...data), https://console.spec.whatwg.org/#info
ThrowCompletionOr<Value> Console::info()
{ {
#ifdef __serenity__ // 1. Perform Logger("info", data).
dbgln("\033[32;1m(js info)\033[0m {}", vm().join_arguments()); if (m_client) {
#endif auto data = vm_arguments();
if (m_client) return m_client->logger(LogLevel::Info, data);
return m_client->info(); }
return js_undefined(); return js_undefined();
} }
Value Console::log() // 1.1.6. log(...data), https://console.spec.whatwg.org/#log
ThrowCompletionOr<Value> Console::log()
{ {
#ifdef __serenity__ // 1. Perform Logger("log", data).
dbgln("\033[32;1m(js log)\033[0m {}", vm().join_arguments()); if (m_client) {
#endif auto data = vm_arguments();
if (m_client) return m_client->logger(LogLevel::Log, data);
return m_client->log(); }
return js_undefined(); return js_undefined();
} }
Value Console::warn() // 1.1.9. warn(...data), https://console.spec.whatwg.org/#warn
ThrowCompletionOr<Value> Console::warn()
{ {
#ifdef __serenity__ // 1. Perform Logger("warn", data).
dbgln("\033[32;1m(js warn)\033[0m {}", vm().join_arguments()); if (m_client) {
#endif auto data = vm_arguments();
if (m_client) return m_client->logger(LogLevel::Warn, data);
return m_client->warn(); }
return js_undefined(); return js_undefined();
} }
@ -127,6 +133,42 @@ bool Console::counter_reset(String label)
return true; return true;
} }
Vector<Value> Console::vm_arguments()
{
Vector<Value> 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() VM& ConsoleClient::vm()
{ {
return global_object().vm(); return global_object().vm();
@ -142,4 +184,45 @@ Vector<String> ConsoleClient::get_trace() const
return trace; return trace;
} }
// 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger
ThrowCompletionOr<Value> ConsoleClient::logger(Console::LogLevel log_level, Vector<Value>& 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<Vector<Value>> ConsoleClient::formatter(Vector<Value>& args)
{
// TODO: Actually implement formatting
return args;
}
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com> * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -9,17 +10,39 @@
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/Noncopyable.h> #include <AK/Noncopyable.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Runtime/Value.h>
namespace JS { namespace JS {
class ConsoleClient; class ConsoleClient;
// https://console.spec.whatwg.org
class Console { class Console {
AK_MAKE_NONCOPYABLE(Console); AK_MAKE_NONCOPYABLE(Console);
AK_MAKE_NONMOVABLE(Console); AK_MAKE_NONMOVABLE(Console);
public: 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&); explicit Console(GlobalObject&);
void set_client(ConsoleClient& client) { m_client = &client; } void set_client(ConsoleClient& client) { m_client = &client; }
@ -28,15 +51,16 @@ public:
const GlobalObject& global_object() const { return m_global_object; } const GlobalObject& global_object() const { return m_global_object; }
VM& vm(); VM& vm();
Vector<Value> vm_arguments();
HashMap<String, unsigned>& counters() { return m_counters; } HashMap<String, unsigned>& counters() { return m_counters; }
const HashMap<String, unsigned>& counters() const { return m_counters; } const HashMap<String, unsigned>& counters() const { return m_counters; }
Value debug(); ThrowCompletionOr<Value> debug();
Value error(); ThrowCompletionOr<Value> error();
Value info(); ThrowCompletionOr<Value> info();
Value log(); ThrowCompletionOr<Value> log();
Value warn(); ThrowCompletionOr<Value> warn();
Value clear(); Value clear();
Value trace(); Value trace();
Value count(); Value count();
@ -46,6 +70,8 @@ public:
unsigned counter_increment(String label); unsigned counter_increment(String label);
bool counter_reset(String label); bool counter_reset(String label);
void output_debug_message(LogLevel log_level, String output) const;
private: private:
GlobalObject& m_global_object; GlobalObject& m_global_object;
ConsoleClient* m_client { nullptr }; ConsoleClient* m_client { nullptr };
@ -60,11 +86,10 @@ public:
{ {
} }
virtual Value debug() = 0; ThrowCompletionOr<Value> logger(Console::LogLevel log_level, Vector<Value>& args);
virtual Value error() = 0; ThrowCompletionOr<Vector<Value>> formatter(Vector<Value>& args);
virtual Value info() = 0; virtual ThrowCompletionOr<Value> printer(Console::LogLevel log_level, Vector<Value>&) = 0;
virtual Value log() = 0;
virtual Value warn() = 0;
virtual Value clear() = 0; virtual Value clear() = 0;
virtual Value trace() = 0; virtual Value trace() = 0;
virtual Value count() = 0; virtual Value count() = 0;

View file

@ -115,56 +115,6 @@ void WebContentConsoleClient::send_messages(i32 start_index)
m_client.async_did_get_js_console_messages(start_index, message_types, messages); 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("<span class=\"info\">");
html.append("(i) ");
html.append(escape_html_entities(vm().join_arguments()));
html.append("</span>");
print_html(html.string_view());
return JS::js_undefined();
}
JS::Value WebContentConsoleClient::debug()
{
StringBuilder html;
html.append("<span class=\"debug\">");
html.append("(d) ");
html.append(escape_html_entities(vm().join_arguments()));
html.append("</span>");
print_html(html.string_view());
return JS::js_undefined();
}
JS::Value WebContentConsoleClient::warn()
{
StringBuilder html;
html.append("<span class=\"warn\">");
html.append("(w) ");
html.append(escape_html_entities(vm().join_arguments()));
html.append("</span>");
print_html(html.string_view());
return JS::js_undefined();
}
JS::Value WebContentConsoleClient::error()
{
StringBuilder html;
html.append("<span class=\"error\">");
html.append("(e) ");
html.append(escape_html_entities(vm().join_arguments()));
html.append("</span>");
print_html(html.string_view());
return JS::js_undefined();
}
JS::Value WebContentConsoleClient::clear() JS::Value WebContentConsoleClient::clear()
{ {
clear_output(); clear_output();
@ -225,4 +175,38 @@ JS::Value WebContentConsoleClient::assert_()
return JS::js_undefined(); return JS::js_undefined();
} }
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
JS::ThrowCompletionOr<JS::Value> WebContentConsoleClient::printer(JS::Console::LogLevel log_level, Vector<JS::Value>& 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("<span class=\"debug\">(d) ");
break;
case JS::Console::LogLevel::Error:
html.append("<span class=\"error\">(e) ");
break;
case JS::Console::LogLevel::Info:
html.append("<span class=\"info\">(i) ");
break;
case JS::Console::LogLevel::Log:
html.append("<span class=\"log\"> ");
break;
case JS::Console::LogLevel::Warn:
html.append("<span class=\"warn\">(w) ");
break;
default:
html.append("<span>");
break;
}
html.append(escape_html_entities(output));
html.append("</span>");
print_html(html.string_view());
return JS::js_undefined();
}
} }

View file

@ -24,16 +24,12 @@ public:
void send_messages(i32 start_index); void send_messages(i32 start_index);
private: 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 clear() override;
virtual JS::Value trace() override; virtual JS::Value trace() override;
virtual JS::Value count() override; virtual JS::Value count() override;
virtual JS::Value count_reset() override; virtual JS::Value count_reset() override;
virtual JS::Value assert_() override; virtual JS::Value assert_() override;
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, Vector<JS::Value>&) override;
ClientConnection& m_client; ClientConnection& m_client;
WeakPtr<JS::Interpreter> m_interpreter; WeakPtr<JS::Interpreter> m_interpreter;

View file

@ -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 virtual JS::Value clear() override
{ {
js_out("\033[3J\033[H\033[2J"); js_out("\033[3J\033[H\033[2J");
@ -1202,6 +1172,34 @@ public:
} }
return JS::js_undefined(); return JS::js_undefined();
} }
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, Vector<JS::Value>& 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<int> serenity_main(Main::Arguments arguments) ErrorOr<int> serenity_main(Main::Arguments arguments)