Ver código fonte

LibJS: Implement console.time/timeLog/timeEnd() methods

Sam Atkins 3 anos atrás
pai
commit
1fba221b46

+ 119 - 0
Userland/Libraries/LibJS/Console.cpp

@@ -8,6 +8,7 @@
 
 #include <LibJS/Console.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/Duration.h>
 
 namespace JS {
 
@@ -299,6 +300,99 @@ ThrowCompletionOr<Value> Console::group_end()
     return js_undefined();
 }
 
+// 1.4.1. time(label), https://console.spec.whatwg.org/#time
+ThrowCompletionOr<Value> 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<Value> { 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<Value> 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<Value> { 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<Value> 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<Value> 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<Value> { 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<Value> concat_as_vector { js_string(vm(), concat) };
+        TRY(m_client->printer(LogLevel::TimeEnd, concat_as_vector));
+    }
+    return js_undefined();
+}
+
 Vector<Value> Console::vm_arguments()
 {
     Vector<Value> arguments;
@@ -346,6 +440,31 @@ ThrowCompletionOr<String> Console::value_vector_to_string(Vector<Value>& values)
     return builder.to_string();
 }
 
+ThrowCompletionOr<String> 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();

+ 6 - 1
Userland/Libraries/LibJS/Console.h

@@ -11,6 +11,7 @@
 #include <AK/HashMap.h>
 #include <AK/Noncopyable.h>
 #include <AK/Vector.h>
+#include <LibCore/ElapsedTimer.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Runtime/Value.h>
 
@@ -78,17 +79,21 @@ public:
     ThrowCompletionOr<Value> group();
     ThrowCompletionOr<Value> group_collapsed();
     ThrowCompletionOr<Value> group_end();
+    ThrowCompletionOr<Value> time();
+    ThrowCompletionOr<Value> time_log();
+    ThrowCompletionOr<Value> time_end();
 
     void output_debug_message(LogLevel log_level, String output) const;
 
 private:
     ThrowCompletionOr<String> value_vector_to_string(Vector<Value>&);
+    ThrowCompletionOr<String> format_time_since(Core::ElapsedTimer timer);
 
     GlobalObject& m_global_object;
     ConsoleClient* m_client { nullptr };
 
     HashMap<String, unsigned> m_counters;
-
+    HashMap<String, Core::ElapsedTimer> m_timer_table;
     Vector<Group> m_group_stack;
 };
 

+ 3 - 0
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)                          \

+ 21 - 0
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();
+}
+
 }

+ 3 - 0
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);
 };
 
 }