Console.cpp 25 KB

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