Console.cpp 18 KB

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