Console.cpp 24 KB

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