Console.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /*
  2. * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
  3. * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibJS/Console.h>
  9. #include <LibJS/Runtime/GlobalObject.h>
  10. #include <LibJS/Runtime/Temporal/Duration.h>
  11. namespace JS {
  12. Console::Console(GlobalObject& global_object)
  13. : m_global_object(global_object)
  14. {
  15. }
  16. VM& Console::vm()
  17. {
  18. return m_global_object.vm();
  19. }
  20. // 1.1.3. debug(...data), https://console.spec.whatwg.org/#debug
  21. ThrowCompletionOr<Value> Console::debug()
  22. {
  23. // 1. Perform Logger("debug", data).
  24. if (m_client) {
  25. auto data = vm_arguments();
  26. return m_client->logger(LogLevel::Debug, data);
  27. }
  28. return js_undefined();
  29. }
  30. // 1.1.4. error(...data), https://console.spec.whatwg.org/#error
  31. ThrowCompletionOr<Value> Console::error()
  32. {
  33. // 1. Perform Logger("error", data).
  34. if (m_client) {
  35. auto data = vm_arguments();
  36. return m_client->logger(LogLevel::Error, data);
  37. }
  38. return js_undefined();
  39. }
  40. // 1.1.5. info(...data), https://console.spec.whatwg.org/#info
  41. ThrowCompletionOr<Value> Console::info()
  42. {
  43. // 1. Perform Logger("info", data).
  44. if (m_client) {
  45. auto data = vm_arguments();
  46. return m_client->logger(LogLevel::Info, data);
  47. }
  48. return js_undefined();
  49. }
  50. // 1.1.6. log(...data), https://console.spec.whatwg.org/#log
  51. ThrowCompletionOr<Value> Console::log()
  52. {
  53. // 1. Perform Logger("log", data).
  54. if (m_client) {
  55. auto data = vm_arguments();
  56. return m_client->logger(LogLevel::Log, data);
  57. }
  58. return js_undefined();
  59. }
  60. // 1.1.9. warn(...data), https://console.spec.whatwg.org/#warn
  61. ThrowCompletionOr<Value> Console::warn()
  62. {
  63. // 1. Perform Logger("warn", data).
  64. if (m_client) {
  65. auto data = vm_arguments();
  66. return m_client->logger(LogLevel::Warn, data);
  67. }
  68. return js_undefined();
  69. }
  70. // 1.1.2. clear(), https://console.spec.whatwg.org/#clear
  71. Value Console::clear()
  72. {
  73. // 1. Empty the appropriate group stack.
  74. m_group_stack.clear();
  75. // 2. If possible for the environment, clear the console. (Otherwise, do nothing.)
  76. if (m_client)
  77. m_client->clear();
  78. return js_undefined();
  79. }
  80. // 1.1.8. trace(...data), https://console.spec.whatwg.org/#trace
  81. ThrowCompletionOr<Value> Console::trace()
  82. {
  83. if (!m_client)
  84. return js_undefined();
  85. // 1. Let trace be some implementation-specific, potentially-interactive representation of the callstack from where this function was called.
  86. Console::Trace trace;
  87. auto& execution_context_stack = vm().execution_context_stack();
  88. // NOTE: -2 to skip the console.trace() execution context
  89. for (ssize_t i = execution_context_stack.size() - 2; i >= 0; --i) {
  90. auto& function_name = execution_context_stack[i]->function_name;
  91. trace.stack.append(function_name.is_empty() ? "<anonymous>" : function_name);
  92. }
  93. // 2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
  94. if (vm().argument_count() > 0) {
  95. StringBuilder builder;
  96. auto data = vm_arguments();
  97. auto formatted_data = TRY(m_client->formatter(data));
  98. trace.label = TRY(value_vector_to_string(formatted_data));
  99. }
  100. // 3. Perform Printer("trace", « trace »).
  101. return m_client->printer(JS::Console::LogLevel::Trace, trace);
  102. }
  103. // 1.2.1. count(label), https://console.spec.whatwg.org/#count
  104. ThrowCompletionOr<Value> Console::count()
  105. {
  106. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-count
  107. auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default";
  108. // 1. Let map be the associated count map.
  109. auto& map = m_counters;
  110. // 2. If map[label] exists, set map[label] to map[label] + 1.
  111. if (auto found = map.find(label); found != map.end()) {
  112. map.set(label, found->value + 1);
  113. }
  114. // 3. Otherwise, set map[label] to 1.
  115. else {
  116. map.set(label, 1);
  117. }
  118. // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and ToString(map[label]).
  119. String concat = String::formatted("{}: {}", label, map.get(label).value());
  120. // 5. Perform Logger("count", « concat »).
  121. Vector<Value> concat_as_vector { js_string(vm(), concat) };
  122. if (m_client)
  123. TRY(m_client->logger(LogLevel::Count, concat_as_vector));
  124. return js_undefined();
  125. }
  126. // 1.2.2. countReset(label), https://console.spec.whatwg.org/#countreset
  127. ThrowCompletionOr<Value> Console::count_reset()
  128. {
  129. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-countreset
  130. auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default";
  131. // 1. Let map be the associated count map.
  132. auto& map = m_counters;
  133. // 2. If map[label] exists, set map[label] to 0.
  134. if (auto found = map.find(label); found != map.end()) {
  135. map.set(label, 0);
  136. }
  137. // 3. Otherwise:
  138. else {
  139. // 1. Let message be a string without any formatting specifiers indicating generically
  140. // that the given label does not have an associated count.
  141. auto message = String::formatted("\"{}\" doesn't have a count", label);
  142. // 2. Perform Logger("countReset", « message »);
  143. Vector<Value> message_as_vector { js_string(vm(), message) };
  144. if (m_client)
  145. TRY(m_client->logger(LogLevel::CountReset, message_as_vector));
  146. }
  147. return js_undefined();
  148. }
  149. // 1.1.1. assert(condition, ...data), https://console.spec.whatwg.org/#assert
  150. ThrowCompletionOr<Value> Console::assert_()
  151. {
  152. // 1. If condition is true, return.
  153. auto condition = vm().argument(0).to_boolean();
  154. if (condition)
  155. return js_undefined();
  156. // 2. Let message be a string without any formatting specifiers indicating generically an assertion failure (such as "Assertion failed").
  157. auto message = js_string(vm(), "Assertion failed");
  158. // NOTE: Assemble `data` from the function arguments.
  159. Vector<JS::Value> data;
  160. if (vm().argument_count() > 1) {
  161. data.ensure_capacity(vm().argument_count() - 1);
  162. for (size_t i = 1; i < vm().argument_count(); ++i) {
  163. data.append(vm().argument(i));
  164. }
  165. }
  166. // 3. If data is empty, append message to data.
  167. if (data.is_empty()) {
  168. data.append(message);
  169. }
  170. // 4. Otherwise:
  171. else {
  172. // 1. Let first be data[0].
  173. auto& first = data[0];
  174. // 2. If Type(first) is not String, then prepend message to data.
  175. if (!first.is_string()) {
  176. data.prepend(message);
  177. }
  178. // 3. Otherwise:
  179. else {
  180. // 1. Let concat be the concatenation of message, U+003A (:), U+0020 SPACE, and first.
  181. auto concat = js_string(vm(), String::formatted("{}: {}", message->string(), first.to_string(global_object()).value()));
  182. // 2. Set data[0] to concat.
  183. data[0] = concat;
  184. }
  185. }
  186. // 5. Perform Logger("assert", data).
  187. if (m_client)
  188. TRY(m_client->logger(LogLevel::Assert, data));
  189. return js_undefined();
  190. }
  191. // 1.3.1. group(...data), https://console.spec.whatwg.org/#group
  192. ThrowCompletionOr<Value> Console::group()
  193. {
  194. // 1. Let group be a new group.
  195. Group group;
  196. // 2. If data is not empty, let groupLabel be the result of Formatter(data).
  197. String group_label;
  198. auto data = vm_arguments();
  199. if (!data.is_empty()) {
  200. auto formatted_data = TRY(m_client->formatter(data));
  201. group_label = TRY(value_vector_to_string(formatted_data));
  202. }
  203. // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
  204. else {
  205. group_label = "Group";
  206. }
  207. // 3. Incorporate groupLabel as a label for group.
  208. group.label = group_label;
  209. // 4. Optionally, if the environment supports interactive groups, group should be expanded by default.
  210. // NOTE: This is handled in Printer.
  211. // 5. Perform Printer("group", « group »).
  212. if (m_client)
  213. TRY(m_client->printer(LogLevel::Group, group));
  214. // 6. Push group onto the appropriate group stack.
  215. m_group_stack.append(group);
  216. return js_undefined();
  217. }
  218. // 1.3.2. groupCollapsed(...data), https://console.spec.whatwg.org/#groupcollapsed
  219. ThrowCompletionOr<Value> Console::group_collapsed()
  220. {
  221. // 1. Let group be a new group.
  222. Group group;
  223. // 2. If data is not empty, let groupLabel be the result of Formatter(data).
  224. String group_label;
  225. auto data = vm_arguments();
  226. if (!data.is_empty()) {
  227. auto formatted_data = TRY(m_client->formatter(data));
  228. group_label = TRY(value_vector_to_string(formatted_data));
  229. }
  230. // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
  231. else {
  232. group_label = "Group";
  233. }
  234. // 3. Incorporate groupLabel as a label for group.
  235. group.label = group_label;
  236. // 4. Optionally, if the environment supports interactive groups, group should be collapsed by default.
  237. // NOTE: This is handled in Printer.
  238. // 5. Perform Printer("groupCollapsed", « group »).
  239. if (m_client)
  240. TRY(m_client->printer(LogLevel::GroupCollapsed, group));
  241. // 6. Push group onto the appropriate group stack.
  242. m_group_stack.append(group);
  243. return js_undefined();
  244. }
  245. // 1.3.3. groupEnd(), https://console.spec.whatwg.org/#groupend
  246. ThrowCompletionOr<Value> Console::group_end()
  247. {
  248. if (m_group_stack.is_empty())
  249. return js_undefined();
  250. // 1. Pop the last group from the group stack.
  251. m_group_stack.take_last();
  252. if (m_client)
  253. m_client->end_group();
  254. return js_undefined();
  255. }
  256. // 1.4.1. time(label), https://console.spec.whatwg.org/#time
  257. ThrowCompletionOr<Value> Console::time()
  258. {
  259. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-time
  260. auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default";
  261. // 1. If the associated timer table contains an entry with key label, return, optionally reporting
  262. // a warning to the console indicating that a timer with label `label` has already been started.
  263. if (m_timer_table.contains(label)) {
  264. if (m_client)
  265. TRY(m_client->printer(LogLevel::Warn, { Vector<Value> { js_string(vm(), String::formatted("Timer '{}' already exists.", label)) } }));
  266. return js_undefined();
  267. }
  268. // 2. Otherwise, set the value of the entry with key label in the associated timer table to the current time.
  269. m_timer_table.set(label, Core::ElapsedTimer::start_new());
  270. return js_undefined();
  271. }
  272. // 1.4.2. timeLog(label, ...data), https://console.spec.whatwg.org/#timelog
  273. ThrowCompletionOr<Value> Console::time_log()
  274. {
  275. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timelog
  276. auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default";
  277. // 1. Let timerTable be the associated timer table.
  278. // 2. Let startTime be timerTable[label].
  279. auto maybe_start_time = m_timer_table.find(label);
  280. // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134
  281. if (maybe_start_time == m_timer_table.end()) {
  282. if (m_client)
  283. TRY(m_client->printer(LogLevel::Warn, { Vector<Value> { js_string(vm(), String::formatted("Timer '{}' does not exist.", label)) } }));
  284. return js_undefined();
  285. }
  286. auto start_time = maybe_start_time->value;
  287. // 3. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format.
  288. auto duration = TRY(format_time_since(start_time));
  289. // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
  290. auto concat = String::formatted("{}: {}", label, duration);
  291. // 5. Prepend concat to data.
  292. Vector<Value> data;
  293. data.ensure_capacity(vm().argument_count());
  294. data.append(js_string(vm(), concat));
  295. for (size_t i = 1; i < vm().argument_count(); ++i)
  296. data.append(vm().argument(i));
  297. // 6. Perform Printer("timeLog", data).
  298. if (m_client)
  299. TRY(m_client->printer(LogLevel::TimeLog, data));
  300. return js_undefined();
  301. }
  302. // 1.4.3. timeEnd(label), https://console.spec.whatwg.org/#timeend
  303. ThrowCompletionOr<Value> Console::time_end()
  304. {
  305. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timeend
  306. auto label = vm().argument_count() ? TRY(vm().argument(0).to_string(global_object())) : "default";
  307. // 1. Let timerTable be the associated timer table.
  308. // 2. Let startTime be timerTable[label].
  309. auto maybe_start_time = m_timer_table.find(label);
  310. // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134
  311. if (maybe_start_time == m_timer_table.end()) {
  312. if (m_client)
  313. TRY(m_client->printer(LogLevel::Warn, { Vector<Value> { js_string(vm(), String::formatted("Timer '{}' does not exist.", label)) } }));
  314. return js_undefined();
  315. }
  316. auto start_time = maybe_start_time->value;
  317. // 3. Remove timerTable[label].
  318. m_timer_table.remove(label);
  319. // 4. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format.
  320. auto duration = TRY(format_time_since(start_time));
  321. // 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
  322. auto concat = String::formatted("{}: {}", label, duration);
  323. // 6. Perform Printer("timeEnd", « concat »).
  324. if (m_client) {
  325. Vector<Value> concat_as_vector { js_string(vm(), concat) };
  326. TRY(m_client->printer(LogLevel::TimeEnd, concat_as_vector));
  327. }
  328. return js_undefined();
  329. }
  330. Vector<Value> Console::vm_arguments()
  331. {
  332. Vector<Value> arguments;
  333. arguments.ensure_capacity(vm().argument_count());
  334. for (size_t i = 0; i < vm().argument_count(); ++i) {
  335. arguments.append(vm().argument(i));
  336. }
  337. return arguments;
  338. }
  339. void Console::output_debug_message([[maybe_unused]] LogLevel log_level, [[maybe_unused]] String output) const
  340. {
  341. #ifdef __serenity__
  342. switch (log_level) {
  343. case JS::Console::LogLevel::Debug:
  344. dbgln("\033[32;1m(js debug)\033[0m {}", output);
  345. break;
  346. case JS::Console::LogLevel::Error:
  347. dbgln("\033[32;1m(js error)\033[0m {}", output);
  348. break;
  349. case JS::Console::LogLevel::Info:
  350. dbgln("\033[32;1m(js info)\033[0m {}", output);
  351. break;
  352. case JS::Console::LogLevel::Log:
  353. dbgln("\033[32;1m(js log)\033[0m {}", output);
  354. break;
  355. case JS::Console::LogLevel::Warn:
  356. dbgln("\033[32;1m(js warn)\033[0m {}", output);
  357. break;
  358. default:
  359. dbgln("\033[32;1m(js)\033[0m {}", output);
  360. break;
  361. }
  362. #endif
  363. }
  364. ThrowCompletionOr<String> Console::value_vector_to_string(Vector<Value>& values)
  365. {
  366. StringBuilder builder;
  367. for (auto const& item : values) {
  368. if (!builder.is_empty())
  369. builder.append(' ');
  370. builder.append(TRY(item.to_string(global_object())));
  371. }
  372. return builder.to_string();
  373. }
  374. ThrowCompletionOr<String> Console::format_time_since(Core::ElapsedTimer timer)
  375. {
  376. auto elapsed_ms = timer.elapsed_time().to_milliseconds();
  377. auto duration = TRY(Temporal::balance_duration(global_object(), 0, 0, 0, 0, elapsed_ms, 0, "0"_sbigint, "year"));
  378. auto append = [&](StringBuilder& builder, auto format, auto... number) {
  379. if (!builder.is_empty())
  380. builder.append(' ');
  381. builder.appendff(format, number...);
  382. };
  383. StringBuilder builder;
  384. if (duration.days > 0)
  385. append(builder, "{:.0} day(s)", duration.days);
  386. if (duration.hours > 0)
  387. append(builder, "{:.0} hour(s)", duration.hours);
  388. if (duration.minutes > 0)
  389. append(builder, "{:.0} minute(s)", duration.minutes);
  390. if (duration.seconds > 0 || duration.milliseconds > 0) {
  391. double combined_seconds = duration.seconds + (0.001 * duration.milliseconds);
  392. append(builder, "{:.3} seconds", combined_seconds);
  393. }
  394. return builder.to_string();
  395. }
  396. VM& ConsoleClient::vm()
  397. {
  398. return global_object().vm();
  399. }
  400. // 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger
  401. ThrowCompletionOr<Value> ConsoleClient::logger(Console::LogLevel log_level, Vector<Value>& args)
  402. {
  403. auto& global_object = this->global_object();
  404. // 1. If args is empty, return.
  405. if (args.is_empty())
  406. return js_undefined();
  407. // 2. Let first be args[0].
  408. auto first = args[0];
  409. // 3. Let rest be all elements following first in args.
  410. size_t rest_size = args.size() - 1;
  411. // 4. If rest is empty, perform Printer(logLevel, « first ») and return.
  412. if (rest_size == 0) {
  413. auto first_as_vector = Vector { first };
  414. return printer(log_level, first_as_vector);
  415. }
  416. // 5. If first does not contain any format specifiers, perform Printer(logLevel, args).
  417. if (!TRY(first.to_string(global_object)).contains('%')) {
  418. TRY(printer(log_level, args));
  419. } else {
  420. // 6. Otherwise, perform Printer(logLevel, Formatter(args)).
  421. auto formatted = TRY(formatter(args));
  422. TRY(printer(log_level, formatted));
  423. }
  424. // 7. Return undefined.
  425. return js_undefined();
  426. }
  427. // 2.2. Formatter(args), https://console.spec.whatwg.org/#formatter
  428. ThrowCompletionOr<Vector<Value>> ConsoleClient::formatter(Vector<Value>& args)
  429. {
  430. // TODO: Actually implement formatting
  431. return args;
  432. }
  433. }