diff --git a/Userland/Libraries/LibJS/Console.cpp b/Userland/Libraries/LibJS/Console.cpp index 5bf75bce2e..197f4ed69e 100644 --- a/Userland/Libraries/LibJS/Console.cpp +++ b/Userland/Libraries/LibJS/Console.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace JS { @@ -299,6 +300,99 @@ ThrowCompletionOr Console::group_end() return js_undefined(); } +// 1.4.1. time(label), https://console.spec.whatwg.org/#time +ThrowCompletionOr Console::time() +{ + // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-time + auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default"; + + // 1. If the associated timer table contains an entry with key label, return, optionally reporting + // a warning to the console indicating that a timer with label `label` has already been started. + if (m_timer_table.contains(label)) { + if (m_client) + TRY(m_client->printer(LogLevel::Warn, { Vector { js_string(vm(), String::formatted("Timer '{}' already exists.", label)) } })); + return js_undefined(); + } + + // 2. Otherwise, set the value of the entry with key label in the associated timer table to the current time. + m_timer_table.set(label, Core::ElapsedTimer::start_new()); + return js_undefined(); +} + +// 1.4.2. timeLog(label, ...data), https://console.spec.whatwg.org/#timelog +ThrowCompletionOr Console::time_log() +{ + // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timelog + auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default"; + + // 1. Let timerTable be the associated timer table. + + // 2. Let startTime be timerTable[label]. + auto maybe_start_time = m_timer_table.find(label); + + // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134 + if (maybe_start_time == m_timer_table.end()) { + if (m_client) + TRY(m_client->printer(LogLevel::Warn, { Vector { js_string(vm(), String::formatted("Timer '{}' does not exist.", label)) } })); + return js_undefined(); + } + auto start_time = maybe_start_time->value; + + // 3. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format. + auto duration = TRY(format_time_since(start_time)); + + // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration. + auto concat = String::formatted("{}: {}", label, duration); + + // 5. Prepend concat to data. + Vector data; + data.ensure_capacity(vm().argument_count()); + data.append(js_string(vm(), concat)); + for (size_t i = 1; i < vm().argument_count(); ++i) + data.append(vm().argument(i)); + + // 6. Perform Printer("timeLog", data). + if (m_client) + TRY(m_client->printer(LogLevel::TimeLog, data)); + return js_undefined(); +} + +// 1.4.3. timeEnd(label), https://console.spec.whatwg.org/#timeend +ThrowCompletionOr Console::time_end() +{ + // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timeend + auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default"; + + // 1. Let timerTable be the associated timer table. + + // 2. Let startTime be timerTable[label]. + auto maybe_start_time = m_timer_table.find(label); + + // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134 + if (maybe_start_time == m_timer_table.end()) { + if (m_client) + TRY(m_client->printer(LogLevel::Warn, { Vector { js_string(vm(), String::formatted("Timer '{}' does not exist.", label)) } })); + return js_undefined(); + } + auto start_time = maybe_start_time->value; + + // 3. Remove timerTable[label]. + m_timer_table.remove(label); + + // 4. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format. + auto duration = TRY(format_time_since(start_time)); + + // 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration. + auto concat = String::formatted("{}: {}", label, duration); + + // 6. Perform Printer("timeEnd", « concat »). + if (m_client) { + Vector concat_as_vector { js_string(vm(), concat) }; + TRY(m_client->printer(LogLevel::TimeEnd, concat_as_vector)); + } + return js_undefined(); +} + Vector Console::vm_arguments() { Vector arguments; @@ -346,6 +440,31 @@ ThrowCompletionOr Console::value_vector_to_string(Vector& values) return builder.to_string(); } +ThrowCompletionOr Console::format_time_since(Core::ElapsedTimer timer) +{ + auto elapsed_ms = timer.elapsed_time().to_milliseconds(); + auto duration = TRY(Temporal::balance_duration(global_object(), 0, 0, 0, 0, elapsed_ms, 0, *js_bigint(vm(), "0"_sbigint), "year")); + + auto append = [&](StringBuilder& builder, auto format, auto... number) { + if (!builder.is_empty()) + builder.append(' '); + builder.appendff(format, number...); + }; + StringBuilder builder; + if (duration.days > 0) + append(builder, "{:.0} day(s)", duration.days); + if (duration.hours > 0) + append(builder, "{:.0} hour(s)", duration.hours); + if (duration.minutes > 0) + append(builder, "{:.0} minute(s)", duration.minutes); + if (duration.seconds > 0 || duration.milliseconds > 0) { + double combined_seconds = duration.seconds + (0.001 * duration.milliseconds); + append(builder, "{:.3} seconds", combined_seconds); + } + + return builder.to_string(); +} + VM& ConsoleClient::vm() { return global_object().vm(); diff --git a/Userland/Libraries/LibJS/Console.h b/Userland/Libraries/LibJS/Console.h index 15e70d76ca..30a67364c2 100644 --- a/Userland/Libraries/LibJS/Console.h +++ b/Userland/Libraries/LibJS/Console.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -78,17 +79,21 @@ public: ThrowCompletionOr group(); ThrowCompletionOr group_collapsed(); ThrowCompletionOr group_end(); + ThrowCompletionOr time(); + ThrowCompletionOr time_log(); + ThrowCompletionOr time_end(); void output_debug_message(LogLevel log_level, String output) const; private: ThrowCompletionOr value_vector_to_string(Vector&); + ThrowCompletionOr format_time_since(Core::ElapsedTimer timer); GlobalObject& m_global_object; ConsoleClient* m_client { nullptr }; HashMap m_counters; - + HashMap m_timer_table; Vector m_group_stack; }; diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 00b3e37752..45c0736943 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -445,6 +445,9 @@ namespace JS { P(tanh) \ P(test) \ P(then) \ + P(time) \ + P(timeEnd) \ + P(timeLog) \ P(timeStyle) \ P(timeZone) \ P(timeZoneName) \ diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp index 1010cc42bf..fb83e58cce 100644 --- a/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp @@ -35,6 +35,9 @@ void ConsoleObject::initialize(GlobalObject& global_object) define_native_function(vm.names.group, group, 0, attr); define_native_function(vm.names.groupCollapsed, group_collapsed, 0, attr); define_native_function(vm.names.groupEnd, group_end, 0, attr); + define_native_function(vm.names.time, time, 0, attr); + define_native_function(vm.names.timeLog, time_log, 0, attr); + define_native_function(vm.names.timeEnd, time_end, 0, attr); } ConsoleObject::~ConsoleObject() @@ -119,4 +122,22 @@ JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::group_end) return global_object.console().group_end(); } +// 1.4.1. time(label), https://console.spec.whatwg.org/#time +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::time) +{ + return global_object.console().time(); +} + +// 1.4.2. timeLog(label, ...data), https://console.spec.whatwg.org/#timelog +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::time_log) +{ + return global_object.console().time_log(); +} + +// 1.4.3. timeEnd(label), https://console.spec.whatwg.org/#timeend +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::time_end) +{ + return global_object.console().time_end(); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.h b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h index 2578641a02..aafd60d500 100644 --- a/Userland/Libraries/LibJS/Runtime/ConsoleObject.h +++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h @@ -32,6 +32,9 @@ private: JS_DECLARE_NATIVE_FUNCTION(group); JS_DECLARE_NATIVE_FUNCTION(group_collapsed); JS_DECLARE_NATIVE_FUNCTION(group_end); + JS_DECLARE_NATIVE_FUNCTION(time); + JS_DECLARE_NATIVE_FUNCTION(time_log); + JS_DECLARE_NATIVE_FUNCTION(time_end); }; }