Console.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*
  2. * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
  3. * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibJS/Console.h>
  9. #include <LibJS/Runtime/AbstractOperations.h>
  10. #include <LibJS/Runtime/StringConstructor.h>
  11. #include <LibJS/Runtime/Temporal/Duration.h>
  12. namespace JS {
  13. Console::Console(Realm& realm)
  14. : m_realm(realm)
  15. {
  16. }
  17. // 1.1.3. debug(...data), https://console.spec.whatwg.org/#debug
  18. ThrowCompletionOr<Value> Console::debug()
  19. {
  20. // 1. Perform Logger("debug", data).
  21. if (m_client) {
  22. auto data = vm_arguments();
  23. return m_client->logger(LogLevel::Debug, data);
  24. }
  25. return js_undefined();
  26. }
  27. // 1.1.4. error(...data), https://console.spec.whatwg.org/#error
  28. ThrowCompletionOr<Value> Console::error()
  29. {
  30. // 1. Perform Logger("error", data).
  31. if (m_client) {
  32. auto data = vm_arguments();
  33. return m_client->logger(LogLevel::Error, data);
  34. }
  35. return js_undefined();
  36. }
  37. // 1.1.5. info(...data), https://console.spec.whatwg.org/#info
  38. ThrowCompletionOr<Value> Console::info()
  39. {
  40. // 1. Perform Logger("info", data).
  41. if (m_client) {
  42. auto data = vm_arguments();
  43. return m_client->logger(LogLevel::Info, data);
  44. }
  45. return js_undefined();
  46. }
  47. // 1.1.6. log(...data), https://console.spec.whatwg.org/#log
  48. ThrowCompletionOr<Value> Console::log()
  49. {
  50. // 1. Perform Logger("log", data).
  51. if (m_client) {
  52. auto data = vm_arguments();
  53. return m_client->logger(LogLevel::Log, data);
  54. }
  55. return js_undefined();
  56. }
  57. // 1.1.9. warn(...data), https://console.spec.whatwg.org/#warn
  58. ThrowCompletionOr<Value> Console::warn()
  59. {
  60. // 1. Perform Logger("warn", data).
  61. if (m_client) {
  62. auto data = vm_arguments();
  63. return m_client->logger(LogLevel::Warn, data);
  64. }
  65. return js_undefined();
  66. }
  67. // 1.1.2. clear(), https://console.spec.whatwg.org/#clear
  68. Value Console::clear()
  69. {
  70. // 1. Empty the appropriate group stack.
  71. m_group_stack.clear();
  72. // 2. If possible for the environment, clear the console. (Otherwise, do nothing.)
  73. if (m_client)
  74. m_client->clear();
  75. return js_undefined();
  76. }
  77. // 1.1.8. trace(...data), https://console.spec.whatwg.org/#trace
  78. ThrowCompletionOr<Value> Console::trace()
  79. {
  80. if (!m_client)
  81. return js_undefined();
  82. auto& vm = realm().vm();
  83. // 1. Let trace be some implementation-specific, potentially-interactive representation of the callstack from where this function was called.
  84. Console::Trace trace;
  85. auto& execution_context_stack = vm.execution_context_stack();
  86. // NOTE: -2 to skip the console.trace() execution context
  87. for (ssize_t i = execution_context_stack.size() - 2; i >= 0; --i) {
  88. auto& function_name = execution_context_stack[i]->function_name;
  89. trace.stack.append(function_name.is_empty() ? "<anonymous>" : function_name);
  90. }
  91. // 2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
  92. if (vm.argument_count() > 0) {
  93. StringBuilder builder;
  94. auto data = vm_arguments();
  95. auto formatted_data = TRY(m_client->formatter(data));
  96. trace.label = TRY(value_vector_to_deprecated_string(formatted_data));
  97. }
  98. // 3. Perform Printer("trace", « trace »).
  99. return m_client->printer(Console::LogLevel::Trace, trace);
  100. }
  101. // 1.2.1. count(label), https://console.spec.whatwg.org/#count
  102. ThrowCompletionOr<Value> Console::count()
  103. {
  104. auto& vm = realm().vm();
  105. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-count
  106. auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default";
  107. // 1. Let map be the associated count map.
  108. auto& map = m_counters;
  109. // 2. If map[label] exists, set map[label] to map[label] + 1.
  110. if (auto found = map.find(label); found != map.end()) {
  111. map.set(label, found->value + 1);
  112. }
  113. // 3. Otherwise, set map[label] to 1.
  114. else {
  115. map.set(label, 1);
  116. }
  117. // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and ToString(map[label]).
  118. DeprecatedString concat = DeprecatedString::formatted("{}: {}", label, map.get(label).value());
  119. // 5. Perform Logger("count", « concat »).
  120. MarkedVector<Value> concat_as_vector { vm.heap() };
  121. concat_as_vector.append(PrimitiveString::create(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. auto& vm = realm().vm();
  130. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-countreset
  131. auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default";
  132. // 1. Let map be the associated count map.
  133. auto& map = m_counters;
  134. // 2. If map[label] exists, set map[label] to 0.
  135. if (auto found = map.find(label); found != map.end()) {
  136. map.set(label, 0);
  137. }
  138. // 3. Otherwise:
  139. else {
  140. // 1. Let message be a string without any formatting specifiers indicating generically
  141. // that the given label does not have an associated count.
  142. auto message = DeprecatedString::formatted("\"{}\" doesn't have a count", label);
  143. // 2. Perform Logger("countReset", « message »);
  144. MarkedVector<Value> message_as_vector { vm.heap() };
  145. message_as_vector.append(PrimitiveString::create(vm, message));
  146. if (m_client)
  147. TRY(m_client->logger(LogLevel::CountReset, message_as_vector));
  148. }
  149. return js_undefined();
  150. }
  151. // 1.1.1. assert(condition, ...data), https://console.spec.whatwg.org/#assert
  152. ThrowCompletionOr<Value> Console::assert_()
  153. {
  154. auto& vm = realm().vm();
  155. // 1. If condition is true, return.
  156. auto condition = vm.argument(0).to_boolean();
  157. if (condition)
  158. return js_undefined();
  159. // 2. Let message be a string without any formatting specifiers indicating generically an assertion failure (such as "Assertion failed").
  160. auto message = PrimitiveString::create(vm, "Assertion failed");
  161. // NOTE: Assemble `data` from the function arguments.
  162. MarkedVector<Value> data { vm.heap() };
  163. if (vm.argument_count() > 1) {
  164. data.ensure_capacity(vm.argument_count() - 1);
  165. for (size_t i = 1; i < vm.argument_count(); ++i) {
  166. data.append(vm.argument(i));
  167. }
  168. }
  169. // 3. If data is empty, append message to data.
  170. if (data.is_empty()) {
  171. data.append(message);
  172. }
  173. // 4. Otherwise:
  174. else {
  175. // 1. Let first be data[0].
  176. auto& first = data[0];
  177. // 2. If Type(first) is not String, then prepend message to data.
  178. if (!first.is_string()) {
  179. data.prepend(message);
  180. }
  181. // 3. Otherwise:
  182. else {
  183. // 1. Let concat be the concatenation of message, U+003A (:), U+0020 SPACE, and first.
  184. auto concat = PrimitiveString::create(vm, DeprecatedString::formatted("{}: {}", TRY(message->deprecated_string()), first.to_deprecated_string(vm).value()));
  185. // 2. Set data[0] to concat.
  186. data[0] = concat;
  187. }
  188. }
  189. // 5. Perform Logger("assert", data).
  190. if (m_client)
  191. TRY(m_client->logger(LogLevel::Assert, data));
  192. return js_undefined();
  193. }
  194. // 1.3.1. group(...data), https://console.spec.whatwg.org/#group
  195. ThrowCompletionOr<Value> Console::group()
  196. {
  197. // 1. Let group be a new group.
  198. Group group;
  199. // 2. If data is not empty, let groupLabel be the result of Formatter(data).
  200. DeprecatedString group_label;
  201. auto data = vm_arguments();
  202. if (!data.is_empty()) {
  203. auto formatted_data = TRY(m_client->formatter(data));
  204. group_label = TRY(value_vector_to_deprecated_string(formatted_data));
  205. }
  206. // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
  207. else {
  208. group_label = "Group";
  209. }
  210. // 3. Incorporate groupLabel as a label for group.
  211. group.label = group_label;
  212. // 4. Optionally, if the environment supports interactive groups, group should be expanded by default.
  213. // NOTE: This is handled in Printer.
  214. // 5. Perform Printer("group", « group »).
  215. if (m_client)
  216. TRY(m_client->printer(LogLevel::Group, group));
  217. // 6. Push group onto the appropriate group stack.
  218. m_group_stack.append(group);
  219. return js_undefined();
  220. }
  221. // 1.3.2. groupCollapsed(...data), https://console.spec.whatwg.org/#groupcollapsed
  222. ThrowCompletionOr<Value> Console::group_collapsed()
  223. {
  224. // 1. Let group be a new group.
  225. Group group;
  226. // 2. If data is not empty, let groupLabel be the result of Formatter(data).
  227. DeprecatedString group_label;
  228. auto data = vm_arguments();
  229. if (!data.is_empty()) {
  230. auto formatted_data = TRY(m_client->formatter(data));
  231. group_label = TRY(value_vector_to_deprecated_string(formatted_data));
  232. }
  233. // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
  234. else {
  235. group_label = "Group";
  236. }
  237. // 3. Incorporate groupLabel as a label for group.
  238. group.label = group_label;
  239. // 4. Optionally, if the environment supports interactive groups, group should be collapsed by default.
  240. // NOTE: This is handled in Printer.
  241. // 5. Perform Printer("groupCollapsed", « group »).
  242. if (m_client)
  243. TRY(m_client->printer(LogLevel::GroupCollapsed, group));
  244. // 6. Push group onto the appropriate group stack.
  245. m_group_stack.append(group);
  246. return js_undefined();
  247. }
  248. // 1.3.3. groupEnd(), https://console.spec.whatwg.org/#groupend
  249. ThrowCompletionOr<Value> Console::group_end()
  250. {
  251. if (m_group_stack.is_empty())
  252. return js_undefined();
  253. // 1. Pop the last group from the group stack.
  254. m_group_stack.take_last();
  255. if (m_client)
  256. m_client->end_group();
  257. return js_undefined();
  258. }
  259. // 1.4.1. time(label), https://console.spec.whatwg.org/#time
  260. ThrowCompletionOr<Value> Console::time()
  261. {
  262. auto& vm = realm().vm();
  263. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-time
  264. auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default";
  265. // 1. If the associated timer table contains an entry with key label, return, optionally reporting
  266. // a warning to the console indicating that a timer with label `label` has already been started.
  267. if (m_timer_table.contains(label)) {
  268. if (m_client) {
  269. MarkedVector<Value> timer_already_exists_warning_message_as_vector { vm.heap() };
  270. timer_already_exists_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' already exists.", label)));
  271. TRY(m_client->printer(LogLevel::Warn, move(timer_already_exists_warning_message_as_vector)));
  272. }
  273. return js_undefined();
  274. }
  275. // 2. Otherwise, set the value of the entry with key label in the associated timer table to the current time.
  276. m_timer_table.set(label, Core::ElapsedTimer::start_new());
  277. return js_undefined();
  278. }
  279. // 1.4.2. timeLog(label, ...data), https://console.spec.whatwg.org/#timelog
  280. ThrowCompletionOr<Value> Console::time_log()
  281. {
  282. auto& vm = realm().vm();
  283. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timelog
  284. auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default";
  285. // 1. Let timerTable be the associated timer table.
  286. // 2. Let startTime be timerTable[label].
  287. auto maybe_start_time = m_timer_table.find(label);
  288. // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134
  289. if (maybe_start_time == m_timer_table.end()) {
  290. if (m_client) {
  291. MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() };
  292. timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' does not exist.", label)));
  293. TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector)));
  294. }
  295. return js_undefined();
  296. }
  297. auto start_time = maybe_start_time->value;
  298. // 3. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format.
  299. auto duration = TRY(format_time_since(start_time));
  300. // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
  301. auto concat = DeprecatedString::formatted("{}: {}", label, duration);
  302. // 5. Prepend concat to data.
  303. MarkedVector<Value> data { vm.heap() };
  304. data.ensure_capacity(vm.argument_count());
  305. data.append(PrimitiveString::create(vm, concat));
  306. for (size_t i = 1; i < vm.argument_count(); ++i)
  307. data.append(vm.argument(i));
  308. // 6. Perform Printer("timeLog", data).
  309. if (m_client)
  310. TRY(m_client->printer(LogLevel::TimeLog, move(data)));
  311. return js_undefined();
  312. }
  313. // 1.4.3. timeEnd(label), https://console.spec.whatwg.org/#timeend
  314. ThrowCompletionOr<Value> Console::time_end()
  315. {
  316. auto& vm = realm().vm();
  317. // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timeend
  318. auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default";
  319. // 1. Let timerTable be the associated timer table.
  320. // 2. Let startTime be timerTable[label].
  321. auto maybe_start_time = m_timer_table.find(label);
  322. // NOTE: Warn if the timer doesn't exist. Not part of the spec yet, but discussed here: https://github.com/whatwg/console/issues/134
  323. if (maybe_start_time == m_timer_table.end()) {
  324. if (m_client) {
  325. MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() };
  326. timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' does not exist.", label)));
  327. TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector)));
  328. }
  329. return js_undefined();
  330. }
  331. auto start_time = maybe_start_time->value;
  332. // 3. Remove timerTable[label].
  333. m_timer_table.remove(label);
  334. // 4. Let duration be a string representing the difference between the current time and startTime, in an implementation-defined format.
  335. auto duration = TRY(format_time_since(start_time));
  336. // 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
  337. auto concat = DeprecatedString::formatted("{}: {}", label, duration);
  338. // 6. Perform Printer("timeEnd", « concat »).
  339. if (m_client) {
  340. MarkedVector<Value> concat_as_vector { vm.heap() };
  341. concat_as_vector.append(PrimitiveString::create(vm, concat));
  342. TRY(m_client->printer(LogLevel::TimeEnd, move(concat_as_vector)));
  343. }
  344. return js_undefined();
  345. }
  346. MarkedVector<Value> Console::vm_arguments()
  347. {
  348. auto& vm = realm().vm();
  349. MarkedVector<Value> arguments { vm.heap() };
  350. arguments.ensure_capacity(vm.argument_count());
  351. for (size_t i = 0; i < vm.argument_count(); ++i) {
  352. arguments.append(vm.argument(i));
  353. }
  354. return arguments;
  355. }
  356. void Console::output_debug_message(LogLevel log_level, DeprecatedString const& output) const
  357. {
  358. switch (log_level) {
  359. case Console::LogLevel::Debug:
  360. dbgln("\033[32;1m(js debug)\033[0m {}", output);
  361. break;
  362. case Console::LogLevel::Error:
  363. dbgln("\033[32;1m(js error)\033[0m {}", output);
  364. break;
  365. case Console::LogLevel::Info:
  366. dbgln("\033[32;1m(js info)\033[0m {}", output);
  367. break;
  368. case Console::LogLevel::Log:
  369. dbgln("\033[32;1m(js log)\033[0m {}", output);
  370. break;
  371. case Console::LogLevel::Warn:
  372. dbgln("\033[32;1m(js warn)\033[0m {}", output);
  373. break;
  374. default:
  375. dbgln("\033[32;1m(js)\033[0m {}", output);
  376. break;
  377. }
  378. }
  379. void Console::report_exception(JS::Error const& exception, bool in_promise) const
  380. {
  381. if (m_client)
  382. m_client->report_exception(exception, in_promise);
  383. }
  384. ThrowCompletionOr<DeprecatedString> Console::value_vector_to_deprecated_string(MarkedVector<Value> const& values)
  385. {
  386. auto& vm = realm().vm();
  387. StringBuilder builder;
  388. for (auto const& item : values) {
  389. if (!builder.is_empty())
  390. builder.append(' ');
  391. builder.append(TRY(item.to_deprecated_string(vm)));
  392. }
  393. return builder.to_deprecated_string();
  394. }
  395. ThrowCompletionOr<DeprecatedString> Console::format_time_since(Core::ElapsedTimer timer)
  396. {
  397. auto& vm = realm().vm();
  398. auto elapsed_ms = timer.elapsed_time().to_milliseconds();
  399. auto duration = TRY(Temporal::balance_duration(vm, 0, 0, 0, 0, elapsed_ms, 0, "0"_sbigint, "year"sv));
  400. auto append = [&](StringBuilder& builder, auto format, auto... number) {
  401. if (!builder.is_empty())
  402. builder.append(' ');
  403. builder.appendff(format, number...);
  404. };
  405. StringBuilder builder;
  406. if (duration.days > 0)
  407. append(builder, "{:.0} day(s)"sv, duration.days);
  408. if (duration.hours > 0)
  409. append(builder, "{:.0} hour(s)"sv, duration.hours);
  410. if (duration.minutes > 0)
  411. append(builder, "{:.0} minute(s)"sv, duration.minutes);
  412. if (duration.seconds > 0 || duration.milliseconds > 0) {
  413. double combined_seconds = duration.seconds + (0.001 * duration.milliseconds);
  414. append(builder, "{:.3} seconds"sv, combined_seconds);
  415. }
  416. return builder.to_deprecated_string();
  417. }
  418. // 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger
  419. ThrowCompletionOr<Value> ConsoleClient::logger(Console::LogLevel log_level, MarkedVector<Value> const& args)
  420. {
  421. auto& vm = m_console.realm().vm();
  422. // 1. If args is empty, return.
  423. if (args.is_empty())
  424. return js_undefined();
  425. // 2. Let first be args[0].
  426. auto first = args[0];
  427. // 3. Let rest be all elements following first in args.
  428. size_t rest_size = args.size() - 1;
  429. // 4. If rest is empty, perform Printer(logLevel, « first ») and return.
  430. if (rest_size == 0) {
  431. MarkedVector<Value> first_as_vector { vm.heap() };
  432. first_as_vector.append(first);
  433. return printer(log_level, move(first_as_vector));
  434. }
  435. // 5. Otherwise, perform Printer(logLevel, Formatter(args)).
  436. else {
  437. auto formatted = TRY(formatter(args));
  438. TRY(printer(log_level, formatted));
  439. }
  440. // 6. Return undefined.
  441. return js_undefined();
  442. }
  443. // 2.2. Formatter(args), https://console.spec.whatwg.org/#formatter
  444. ThrowCompletionOr<MarkedVector<Value>> ConsoleClient::formatter(MarkedVector<Value> const& args)
  445. {
  446. auto& realm = m_console.realm();
  447. auto& vm = realm.vm();
  448. // 1. If args’s size is 1, return args.
  449. if (args.size() == 1)
  450. return args;
  451. // 2. Let target be the first element of args.
  452. auto target = (!args.is_empty()) ? TRY(args.first().to_deprecated_string(vm)) : "";
  453. // 3. Let current be the second element of args.
  454. auto current = (args.size() > 1) ? args[1] : js_undefined();
  455. // 4. Find the first possible format specifier specifier, from the left to the right in target.
  456. auto find_specifier = [](StringView target) -> Optional<StringView> {
  457. size_t start_index = 0;
  458. while (start_index < target.length()) {
  459. auto maybe_index = target.find('%');
  460. if (!maybe_index.has_value())
  461. return {};
  462. auto index = maybe_index.value();
  463. if (index + 1 >= target.length())
  464. return {};
  465. switch (target[index + 1]) {
  466. case 'c':
  467. case 'd':
  468. case 'f':
  469. case 'i':
  470. case 'o':
  471. case 'O':
  472. case 's':
  473. return target.substring_view(index, 2);
  474. }
  475. start_index = index + 1;
  476. }
  477. return {};
  478. };
  479. auto maybe_specifier = find_specifier(target);
  480. // 5. If no format specifier was found, return args.
  481. if (!maybe_specifier.has_value()) {
  482. return args;
  483. }
  484. // 6. Otherwise:
  485. else {
  486. auto specifier = maybe_specifier.release_value();
  487. Optional<Value> converted;
  488. // 1. If specifier is %s, let converted be the result of Call(%String%, undefined, « current »).
  489. if (specifier == "%s"sv) {
  490. converted = TRY(call(vm, realm.intrinsics().string_constructor(), js_undefined(), current));
  491. }
  492. // 2. If specifier is %d or %i:
  493. else if (specifier.is_one_of("%d"sv, "%i"sv)) {
  494. // 1. If Type(current) is Symbol, let converted be NaN
  495. if (current.is_symbol()) {
  496. converted = js_nan();
  497. }
  498. // 2. Otherwise, let converted be the result of Call(%parseInt%, undefined, « current, 10 »).
  499. else {
  500. converted = TRY(call(vm, realm.intrinsics().parse_int_function(), js_undefined(), current, Value { 10 }));
  501. }
  502. }
  503. // 3. If specifier is %f:
  504. else if (specifier == "%f"sv) {
  505. // 1. If Type(current) is Symbol, let converted be NaN
  506. if (current.is_symbol()) {
  507. converted = js_nan();
  508. }
  509. // 2. Otherwise, let converted be the result of Call(% parseFloat %, undefined, « current »).
  510. else {
  511. converted = TRY(call(vm, realm.intrinsics().parse_float_function(), js_undefined(), current));
  512. }
  513. }
  514. // 4. If specifier is %o, optionally let converted be current with optimally useful formatting applied.
  515. else if (specifier == "%o"sv) {
  516. // TODO: "Optimally-useful formatting"
  517. converted = current;
  518. }
  519. // 5. If specifier is %O, optionally let converted be current with generic JavaScript object formatting applied.
  520. else if (specifier == "%O"sv) {
  521. // TODO: "generic JavaScript object formatting"
  522. converted = current;
  523. }
  524. // 6. TODO: process %c
  525. else if (specifier == "%c"sv) {
  526. // NOTE: This has no spec yet. `%c` specifiers treat the argument as CSS styling for the log message.
  527. add_css_style_to_current_message(TRY(current.to_deprecated_string(vm)));
  528. converted = PrimitiveString::create(vm, String {});
  529. }
  530. // 7. If any of the previous steps set converted, replace specifier in target with converted.
  531. if (converted.has_value())
  532. target = target.replace(specifier, TRY(converted->to_deprecated_string(vm)), ReplaceMode::FirstOnly);
  533. }
  534. // 7. Let result be a list containing target together with the elements of args starting from the third onward.
  535. MarkedVector<Value> result { vm.heap() };
  536. result.ensure_capacity(args.size() - 1);
  537. result.empend(PrimitiveString::create(vm, target));
  538. for (size_t i = 2; i < args.size(); ++i)
  539. result.unchecked_append(args[i]);
  540. // 8. Return Formatter(result).
  541. return formatter(result);
  542. }
  543. }