mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:32:44 +00:00 
			
		
		
		
	LibJS+Userland: Port the JS Console object and direct callers to String
This commit is contained in:
		
							parent
							
								
									8f9659a549
								
							
						
					
					
						commit
						3b4879d29b
					
				
					 5 changed files with 125 additions and 84 deletions
				
			
		|  | @ -10,6 +10,7 @@ | ||||||
| #include <LibJS/Runtime/AbstractOperations.h> | #include <LibJS/Runtime/AbstractOperations.h> | ||||||
| #include <LibJS/Runtime/StringConstructor.h> | #include <LibJS/Runtime/StringConstructor.h> | ||||||
| #include <LibJS/Runtime/Temporal/Duration.h> | #include <LibJS/Runtime/Temporal/Duration.h> | ||||||
|  | #include <LibJS/Runtime/ThrowableStringBuilder.h> | ||||||
| 
 | 
 | ||||||
| namespace JS { | namespace JS { | ||||||
| 
 | 
 | ||||||
|  | @ -98,29 +99,37 @@ ThrowCompletionOr<Value> Console::trace() | ||||||
|     auto& execution_context_stack = vm.execution_context_stack(); |     auto& execution_context_stack = vm.execution_context_stack(); | ||||||
|     // NOTE: -2 to skip the console.trace() execution context
 |     // NOTE: -2 to skip the console.trace() execution context
 | ||||||
|     for (ssize_t i = execution_context_stack.size() - 2; i >= 0; --i) { |     for (ssize_t i = execution_context_stack.size() - 2; i >= 0; --i) { | ||||||
|         auto& function_name = execution_context_stack[i]->function_name; |         auto const& function_name = execution_context_stack[i]->function_name; | ||||||
|         trace.stack.append(function_name.is_empty() ? "<anonymous>" : function_name); |         trace.stack.append(function_name.is_empty() | ||||||
|  |                 ? TRY_OR_THROW_OOM(vm, String::from_utf8("<anonymous>"sv)) | ||||||
|  |                 : TRY_OR_THROW_OOM(vm, String::from_deprecated_string(function_name))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
 |     // 2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
 | ||||||
|     if (vm.argument_count() > 0) { |     if (vm.argument_count() > 0) { | ||||||
|         StringBuilder builder; |  | ||||||
|         auto data = vm_arguments(); |         auto data = vm_arguments(); | ||||||
|         auto formatted_data = TRY(m_client->formatter(data)); |         auto formatted_data = TRY(m_client->formatter(data)); | ||||||
|         trace.label = TRY(value_vector_to_deprecated_string(formatted_data)); |         trace.label = TRY(value_vector_to_string(formatted_data)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 3. Perform Printer("trace", « trace »).
 |     // 3. Perform Printer("trace", « trace »).
 | ||||||
|     return m_client->printer(Console::LogLevel::Trace, trace); |     return m_client->printer(Console::LogLevel::Trace, trace); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static ThrowCompletionOr<String> label_or_fallback(VM& vm, StringView fallback) | ||||||
|  | { | ||||||
|  |     return vm.argument_count() > 0 | ||||||
|  |         ? vm.argument(0).to_string(vm) | ||||||
|  |         : TRY_OR_THROW_OOM(vm, String::from_utf8(fallback)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // 1.2.1. count(label), https://console.spec.whatwg.org/#count
 | // 1.2.1. count(label), https://console.spec.whatwg.org/#count
 | ||||||
| ThrowCompletionOr<Value> Console::count() | ThrowCompletionOr<Value> Console::count() | ||||||
| { | { | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-count
 |     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-count
 | ||||||
|     auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default"; |     auto label = TRY(label_or_fallback(vm, "default"sv)); | ||||||
| 
 | 
 | ||||||
|     // 1. Let map be the associated count map.
 |     // 1. Let map be the associated count map.
 | ||||||
|     auto& map = m_counters; |     auto& map = m_counters; | ||||||
|  | @ -135,11 +144,11 @@ ThrowCompletionOr<Value> Console::count() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and ToString(map[label]).
 |     // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and ToString(map[label]).
 | ||||||
|     DeprecatedString concat = DeprecatedString::formatted("{}: {}", label, map.get(label).value()); |     auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, map.get(label).value())); | ||||||
| 
 | 
 | ||||||
|     // 5. Perform Logger("count", « concat »).
 |     // 5. Perform Logger("count", « concat »).
 | ||||||
|     MarkedVector<Value> concat_as_vector { vm.heap() }; |     MarkedVector<Value> concat_as_vector { vm.heap() }; | ||||||
|     concat_as_vector.append(PrimitiveString::create(vm, concat)); |     concat_as_vector.append(PrimitiveString::create(vm, move(concat))); | ||||||
|     if (m_client) |     if (m_client) | ||||||
|         TRY(m_client->logger(LogLevel::Count, concat_as_vector)); |         TRY(m_client->logger(LogLevel::Count, concat_as_vector)); | ||||||
|     return js_undefined(); |     return js_undefined(); | ||||||
|  | @ -151,7 +160,7 @@ ThrowCompletionOr<Value> Console::count_reset() | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-countreset
 |     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-countreset
 | ||||||
|     auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default"; |     auto label = TRY(label_or_fallback(vm, "default"sv)); | ||||||
| 
 | 
 | ||||||
|     // 1. Let map be the associated count map.
 |     // 1. Let map be the associated count map.
 | ||||||
|     auto& map = m_counters; |     auto& map = m_counters; | ||||||
|  | @ -164,10 +173,10 @@ ThrowCompletionOr<Value> Console::count_reset() | ||||||
|     else { |     else { | ||||||
|         // 1. Let message be a string without any formatting specifiers indicating generically
 |         // 1. Let message be a string without any formatting specifiers indicating generically
 | ||||||
|         //    that the given label does not have an associated count.
 |         //    that the given label does not have an associated count.
 | ||||||
|         auto message = DeprecatedString::formatted("\"{}\" doesn't have a count", label); |         auto message = TRY_OR_THROW_OOM(vm, String::formatted("\"{}\" doesn't have a count", label)); | ||||||
|         // 2. Perform Logger("countReset", « message »);
 |         // 2. Perform Logger("countReset", « message »);
 | ||||||
|         MarkedVector<Value> message_as_vector { vm.heap() }; |         MarkedVector<Value> message_as_vector { vm.heap() }; | ||||||
|         message_as_vector.append(PrimitiveString::create(vm, message)); |         message_as_vector.append(PrimitiveString::create(vm, move(message))); | ||||||
|         if (m_client) |         if (m_client) | ||||||
|             TRY(m_client->logger(LogLevel::CountReset, message_as_vector)); |             TRY(m_client->logger(LogLevel::CountReset, message_as_vector)); | ||||||
|     } |     } | ||||||
|  | @ -212,9 +221,9 @@ ThrowCompletionOr<Value> Console::assert_() | ||||||
|         // 3. Otherwise:
 |         // 3. Otherwise:
 | ||||||
|         else { |         else { | ||||||
|             // 1. Let concat be the concatenation of message, U+003A (:), U+0020 SPACE, and first.
 |             // 1. Let concat be the concatenation of message, U+003A (:), U+0020 SPACE, and first.
 | ||||||
|             auto concat = PrimitiveString::create(vm, DeprecatedString::formatted("{}: {}", TRY(message->deprecated_string()), first.to_deprecated_string(vm).value())); |             auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", TRY(message->utf8_string()), MUST(first.to_string(vm)))); | ||||||
|             // 2. Set data[0] to concat.
 |             // 2. Set data[0] to concat.
 | ||||||
|             data[0] = concat; |             data[0] = PrimitiveString::create(vm, move(concat)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -227,19 +236,21 @@ ThrowCompletionOr<Value> Console::assert_() | ||||||
| // 1.3.1. group(...data), https://console.spec.whatwg.org/#group
 | // 1.3.1. group(...data), https://console.spec.whatwg.org/#group
 | ||||||
| ThrowCompletionOr<Value> Console::group() | ThrowCompletionOr<Value> Console::group() | ||||||
| { | { | ||||||
|  |     auto& vm = realm().vm(); | ||||||
|  | 
 | ||||||
|     // 1. Let group be a new group.
 |     // 1. Let group be a new group.
 | ||||||
|     Group group; |     Group group; | ||||||
| 
 | 
 | ||||||
|     // 2. If data is not empty, let groupLabel be the result of Formatter(data).
 |     // 2. If data is not empty, let groupLabel be the result of Formatter(data).
 | ||||||
|     DeprecatedString group_label; |     String group_label {}; | ||||||
|     auto data = vm_arguments(); |     auto data = vm_arguments(); | ||||||
|     if (!data.is_empty()) { |     if (!data.is_empty()) { | ||||||
|         auto formatted_data = TRY(m_client->formatter(data)); |         auto formatted_data = TRY(m_client->formatter(data)); | ||||||
|         group_label = TRY(value_vector_to_deprecated_string(formatted_data)); |         group_label = TRY(value_vector_to_string(formatted_data)); | ||||||
|     } |     } | ||||||
|     // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
 |     // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
 | ||||||
|     else { |     else { | ||||||
|         group_label = "Group"; |         group_label = TRY_OR_THROW_OOM(vm, String::from_utf8("Group"sv)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 3. Incorporate groupLabel as a label for group.
 |     // 3. Incorporate groupLabel as a label for group.
 | ||||||
|  | @ -261,19 +272,21 @@ ThrowCompletionOr<Value> Console::group() | ||||||
| // 1.3.2. groupCollapsed(...data), https://console.spec.whatwg.org/#groupcollapsed
 | // 1.3.2. groupCollapsed(...data), https://console.spec.whatwg.org/#groupcollapsed
 | ||||||
| ThrowCompletionOr<Value> Console::group_collapsed() | ThrowCompletionOr<Value> Console::group_collapsed() | ||||||
| { | { | ||||||
|  |     auto& vm = realm().vm(); | ||||||
|  | 
 | ||||||
|     // 1. Let group be a new group.
 |     // 1. Let group be a new group.
 | ||||||
|     Group group; |     Group group; | ||||||
| 
 | 
 | ||||||
|     // 2. If data is not empty, let groupLabel be the result of Formatter(data).
 |     // 2. If data is not empty, let groupLabel be the result of Formatter(data).
 | ||||||
|     DeprecatedString group_label; |     String group_label {}; | ||||||
|     auto data = vm_arguments(); |     auto data = vm_arguments(); | ||||||
|     if (!data.is_empty()) { |     if (!data.is_empty()) { | ||||||
|         auto formatted_data = TRY(m_client->formatter(data)); |         auto formatted_data = TRY(m_client->formatter(data)); | ||||||
|         group_label = TRY(value_vector_to_deprecated_string(formatted_data)); |         group_label = TRY(value_vector_to_string(formatted_data)); | ||||||
|     } |     } | ||||||
|     // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
 |     // ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
 | ||||||
|     else { |     else { | ||||||
|         group_label = "Group"; |         group_label = TRY_OR_THROW_OOM(vm, String::from_utf8("Group"sv)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 3. Incorporate groupLabel as a label for group.
 |     // 3. Incorporate groupLabel as a label for group.
 | ||||||
|  | @ -312,14 +325,17 @@ ThrowCompletionOr<Value> Console::time() | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-time
 |     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-time
 | ||||||
|     auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default"; |     auto label = TRY(label_or_fallback(vm, "default"sv)); | ||||||
| 
 | 
 | ||||||
|     // 1. If the associated timer table contains an entry with key label, return, optionally reporting
 |     // 1. If the associated timer table contains an entry with key label, return, optionally reporting
 | ||||||
|     // a warning to the console indicating that a timer with label `label` has already been started.
 |     //    a warning to the console indicating that a timer with label `label` has already been started.
 | ||||||
|     if (m_timer_table.contains(label)) { |     if (m_timer_table.contains(label)) { | ||||||
|         if (m_client) { |         if (m_client) { | ||||||
|             MarkedVector<Value> timer_already_exists_warning_message_as_vector { vm.heap() }; |             MarkedVector<Value> timer_already_exists_warning_message_as_vector { vm.heap() }; | ||||||
|             timer_already_exists_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' already exists.", label))); | 
 | ||||||
|  |             auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' already exists.", label)); | ||||||
|  |             timer_already_exists_warning_message_as_vector.append(PrimitiveString::create(vm, move(message))); | ||||||
|  | 
 | ||||||
|             TRY(m_client->printer(LogLevel::Warn, move(timer_already_exists_warning_message_as_vector))); |             TRY(m_client->printer(LogLevel::Warn, move(timer_already_exists_warning_message_as_vector))); | ||||||
|         } |         } | ||||||
|         return js_undefined(); |         return js_undefined(); | ||||||
|  | @ -336,7 +352,7 @@ ThrowCompletionOr<Value> Console::time_log() | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timelog
 |     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timelog
 | ||||||
|     auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default"; |     auto label = TRY(label_or_fallback(vm, "default"sv)); | ||||||
| 
 | 
 | ||||||
|     // 1. Let timerTable be the associated timer table.
 |     // 1. Let timerTable be the associated timer table.
 | ||||||
| 
 | 
 | ||||||
|  | @ -347,7 +363,10 @@ ThrowCompletionOr<Value> Console::time_log() | ||||||
|     if (maybe_start_time == m_timer_table.end()) { |     if (maybe_start_time == m_timer_table.end()) { | ||||||
|         if (m_client) { |         if (m_client) { | ||||||
|             MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() }; |             MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() }; | ||||||
|             timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' does not exist.", label))); | 
 | ||||||
|  |             auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' does not exist.", label)); | ||||||
|  |             timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, move(message))); | ||||||
|  | 
 | ||||||
|             TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector))); |             TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector))); | ||||||
|         } |         } | ||||||
|         return js_undefined(); |         return js_undefined(); | ||||||
|  | @ -358,12 +377,12 @@ ThrowCompletionOr<Value> Console::time_log() | ||||||
|     auto duration = TRY(format_time_since(start_time)); |     auto duration = TRY(format_time_since(start_time)); | ||||||
| 
 | 
 | ||||||
|     // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
 |     // 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
 | ||||||
|     auto concat = DeprecatedString::formatted("{}: {}", label, duration); |     auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, duration)); | ||||||
| 
 | 
 | ||||||
|     // 5. Prepend concat to data.
 |     // 5. Prepend concat to data.
 | ||||||
|     MarkedVector<Value> data { vm.heap() }; |     MarkedVector<Value> data { vm.heap() }; | ||||||
|     data.ensure_capacity(vm.argument_count()); |     data.ensure_capacity(vm.argument_count()); | ||||||
|     data.append(PrimitiveString::create(vm, concat)); |     data.append(PrimitiveString::create(vm, move(concat))); | ||||||
|     for (size_t i = 1; i < vm.argument_count(); ++i) |     for (size_t i = 1; i < vm.argument_count(); ++i) | ||||||
|         data.append(vm.argument(i)); |         data.append(vm.argument(i)); | ||||||
| 
 | 
 | ||||||
|  | @ -379,7 +398,7 @@ ThrowCompletionOr<Value> Console::time_end() | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timeend
 |     // NOTE: "default" is the default value in the IDL. https://console.spec.whatwg.org/#ref-for-timeend
 | ||||||
|     auto label = vm.argument_count() ? TRY(vm.argument(0).to_deprecated_string(vm)) : "default"; |     auto label = TRY(label_or_fallback(vm, "default"sv)); | ||||||
| 
 | 
 | ||||||
|     // 1. Let timerTable be the associated timer table.
 |     // 1. Let timerTable be the associated timer table.
 | ||||||
| 
 | 
 | ||||||
|  | @ -390,7 +409,10 @@ ThrowCompletionOr<Value> Console::time_end() | ||||||
|     if (maybe_start_time == m_timer_table.end()) { |     if (maybe_start_time == m_timer_table.end()) { | ||||||
|         if (m_client) { |         if (m_client) { | ||||||
|             MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() }; |             MarkedVector<Value> timer_does_not_exist_warning_message_as_vector { vm.heap() }; | ||||||
|             timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, DeprecatedString::formatted("Timer '{}' does not exist.", label))); | 
 | ||||||
|  |             auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' does not exist.", label)); | ||||||
|  |             timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, move(message))); | ||||||
|  | 
 | ||||||
|             TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector))); |             TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector))); | ||||||
|         } |         } | ||||||
|         return js_undefined(); |         return js_undefined(); | ||||||
|  | @ -404,12 +426,12 @@ ThrowCompletionOr<Value> Console::time_end() | ||||||
|     auto duration = TRY(format_time_since(start_time)); |     auto duration = TRY(format_time_since(start_time)); | ||||||
| 
 | 
 | ||||||
|     // 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
 |     // 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
 | ||||||
|     auto concat = DeprecatedString::formatted("{}: {}", label, duration); |     auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, duration)); | ||||||
| 
 | 
 | ||||||
|     // 6. Perform Printer("timeEnd", « concat »).
 |     // 6. Perform Printer("timeEnd", « concat »).
 | ||||||
|     if (m_client) { |     if (m_client) { | ||||||
|         MarkedVector<Value> concat_as_vector { vm.heap() }; |         MarkedVector<Value> concat_as_vector { vm.heap() }; | ||||||
|         concat_as_vector.append(PrimitiveString::create(vm, concat)); |         concat_as_vector.append(PrimitiveString::create(vm, move(concat))); | ||||||
|         TRY(m_client->printer(LogLevel::TimeEnd, move(concat_as_vector))); |         TRY(m_client->printer(LogLevel::TimeEnd, move(concat_as_vector))); | ||||||
|     } |     } | ||||||
|     return js_undefined(); |     return js_undefined(); | ||||||
|  | @ -427,7 +449,7 @@ MarkedVector<Value> Console::vm_arguments() | ||||||
|     return arguments; |     return arguments; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Console::output_debug_message(LogLevel log_level, DeprecatedString const& output) const | void Console::output_debug_message(LogLevel log_level, String const& output) const | ||||||
| { | { | ||||||
|     switch (log_level) { |     switch (log_level) { | ||||||
|     case Console::LogLevel::Debug: |     case Console::LogLevel::Debug: | ||||||
|  | @ -457,43 +479,49 @@ void Console::report_exception(JS::Error const& exception, bool in_promise) cons | ||||||
|         m_client->report_exception(exception, in_promise); |         m_client->report_exception(exception, in_promise); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ThrowCompletionOr<DeprecatedString> Console::value_vector_to_deprecated_string(MarkedVector<Value> const& values) | ThrowCompletionOr<String> Console::value_vector_to_string(MarkedVector<Value> const& values) | ||||||
| { | { | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
|     StringBuilder builder; |     ThrowableStringBuilder builder(vm); | ||||||
|  | 
 | ||||||
|     for (auto const& item : values) { |     for (auto const& item : values) { | ||||||
|         if (!builder.is_empty()) |         if (!builder.is_empty()) | ||||||
|             builder.append(' '); |             MUST_OR_THROW_OOM(builder.append(' ')); | ||||||
|         builder.append(TRY(item.to_deprecated_string(vm))); | 
 | ||||||
|  |         MUST_OR_THROW_OOM(builder.append(TRY(item.to_string(vm)))); | ||||||
|     } |     } | ||||||
|     return builder.to_deprecated_string(); | 
 | ||||||
|  |     return builder.to_string(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ThrowCompletionOr<DeprecatedString> Console::format_time_since(Core::ElapsedTimer timer) | ThrowCompletionOr<String> Console::format_time_since(Core::ElapsedTimer timer) | ||||||
| { | { | ||||||
|     auto& vm = realm().vm(); |     auto& vm = realm().vm(); | ||||||
| 
 | 
 | ||||||
|     auto elapsed_ms = timer.elapsed_time().to_milliseconds(); |     auto elapsed_ms = timer.elapsed_time().to_milliseconds(); | ||||||
|     auto duration = TRY(Temporal::balance_duration(vm, 0, 0, 0, 0, elapsed_ms, 0, "0"_sbigint, "year"sv)); |     auto duration = TRY(Temporal::balance_duration(vm, 0, 0, 0, 0, elapsed_ms, 0, "0"_sbigint, "year"sv)); | ||||||
| 
 | 
 | ||||||
|     auto append = [&](StringBuilder& builder, auto format, auto... number) { |     auto append = [&](ThrowableStringBuilder& builder, auto format, auto number) -> ThrowCompletionOr<void> { | ||||||
|         if (!builder.is_empty()) |         if (!builder.is_empty()) | ||||||
|             builder.append(' '); |             MUST_OR_THROW_OOM(builder.append(' ')); | ||||||
|         builder.appendff(format, number...); |         MUST_OR_THROW_OOM(builder.appendff(format, number)); | ||||||
|  |         return {}; | ||||||
|     }; |     }; | ||||||
|     StringBuilder builder; | 
 | ||||||
|  |     ThrowableStringBuilder builder(vm); | ||||||
|  | 
 | ||||||
|     if (duration.days > 0) |     if (duration.days > 0) | ||||||
|         append(builder, "{:.0} day(s)"sv, duration.days); |         MUST_OR_THROW_OOM(append(builder, "{:.0} day(s)"sv, duration.days)); | ||||||
|     if (duration.hours > 0) |     if (duration.hours > 0) | ||||||
|         append(builder, "{:.0} hour(s)"sv, duration.hours); |         MUST_OR_THROW_OOM(append(builder, "{:.0} hour(s)"sv, duration.hours)); | ||||||
|     if (duration.minutes > 0) |     if (duration.minutes > 0) | ||||||
|         append(builder, "{:.0} minute(s)"sv, duration.minutes); |         MUST_OR_THROW_OOM(append(builder, "{:.0} minute(s)"sv, duration.minutes)); | ||||||
|     if (duration.seconds > 0 || duration.milliseconds > 0) { |     if (duration.seconds > 0 || duration.milliseconds > 0) { | ||||||
|         double combined_seconds = duration.seconds + (0.001 * duration.milliseconds); |         double combined_seconds = duration.seconds + (0.001 * duration.milliseconds); | ||||||
|         append(builder, "{:.3} seconds"sv, combined_seconds); |         MUST_OR_THROW_OOM(append(builder, "{:.3} seconds"sv, combined_seconds)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return builder.to_deprecated_string(); |     return builder.to_string(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger
 | // 2.1. Logger(logLevel, args), https://console.spec.whatwg.org/#logger
 | ||||||
|  | @ -539,7 +567,7 @@ ThrowCompletionOr<MarkedVector<Value>> ConsoleClient::formatter(MarkedVector<Val | ||||||
|         return args; |         return args; | ||||||
| 
 | 
 | ||||||
|     // 2. Let target be the first element of args.
 |     // 2. Let target be the first element of args.
 | ||||||
|     auto target = (!args.is_empty()) ? TRY(args.first().to_deprecated_string(vm)) : ""; |     auto target = (!args.is_empty()) ? TRY(args.first().to_string(vm)) : String {}; | ||||||
| 
 | 
 | ||||||
|     // 3. Let current be the second element of args.
 |     // 3. Let current be the second element of args.
 | ||||||
|     auto current = (args.size() > 1) ? args[1] : js_undefined(); |     auto current = (args.size() > 1) ? args[1] : js_undefined(); | ||||||
|  | @ -621,19 +649,19 @@ ThrowCompletionOr<MarkedVector<Value>> ConsoleClient::formatter(MarkedVector<Val | ||||||
|         // 6. TODO: process %c
 |         // 6. TODO: process %c
 | ||||||
|         else if (specifier == "%c"sv) { |         else if (specifier == "%c"sv) { | ||||||
|             // NOTE: This has no spec yet. `%c` specifiers treat the argument as CSS styling for the log message.
 |             // NOTE: This has no spec yet. `%c` specifiers treat the argument as CSS styling for the log message.
 | ||||||
|             add_css_style_to_current_message(TRY(current.to_deprecated_string(vm))); |             add_css_style_to_current_message(TRY(current.to_string(vm))); | ||||||
|             converted = PrimitiveString::create(vm, String {}); |             converted = PrimitiveString::create(vm, String {}); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // 7. If any of the previous steps set converted, replace specifier in target with converted.
 |         // 7. If any of the previous steps set converted, replace specifier in target with converted.
 | ||||||
|         if (converted.has_value()) |         if (converted.has_value()) | ||||||
|             target = target.replace(specifier, TRY(converted->to_deprecated_string(vm)), ReplaceMode::FirstOnly); |             target = TRY_OR_THROW_OOM(vm, target.replace(specifier, TRY(converted->to_string(vm)), ReplaceMode::FirstOnly)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 7. Let result be a list containing target together with the elements of args starting from the third onward.
 |     // 7. Let result be a list containing target together with the elements of args starting from the third onward.
 | ||||||
|     MarkedVector<Value> result { vm.heap() }; |     MarkedVector<Value> result { vm.heap() }; | ||||||
|     result.ensure_capacity(args.size() - 1); |     result.ensure_capacity(args.size() - 1); | ||||||
|     result.empend(PrimitiveString::create(vm, target)); |     result.empend(PrimitiveString::create(vm, move(target))); | ||||||
|     for (size_t i = 2; i < args.size(); ++i) |     for (size_t i = 2; i < args.size(); ++i) | ||||||
|         result.unchecked_append(args[i]); |         result.unchecked_append(args[i]); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #include <AK/Function.h> | #include <AK/Function.h> | ||||||
| #include <AK/HashMap.h> | #include <AK/HashMap.h> | ||||||
| #include <AK/Noncopyable.h> | #include <AK/Noncopyable.h> | ||||||
|  | #include <AK/String.h> | ||||||
| #include <AK/Vector.h> | #include <AK/Vector.h> | ||||||
| #include <LibCore/ElapsedTimer.h> | #include <LibCore/ElapsedTimer.h> | ||||||
| #include <LibJS/Forward.h> | #include <LibJS/Forward.h> | ||||||
|  | @ -45,12 +46,12 @@ public: | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct Group { |     struct Group { | ||||||
|         DeprecatedString label; |         String label; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct Trace { |     struct Trace { | ||||||
|         DeprecatedString label; |         String label; | ||||||
|         Vector<DeprecatedString> stack; |         Vector<String> stack; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     explicit Console(Realm&); |     explicit Console(Realm&); | ||||||
|  | @ -61,8 +62,8 @@ public: | ||||||
| 
 | 
 | ||||||
|     MarkedVector<Value> vm_arguments(); |     MarkedVector<Value> vm_arguments(); | ||||||
| 
 | 
 | ||||||
|     HashMap<DeprecatedString, unsigned>& counters() { return m_counters; } |     HashMap<String, unsigned>& counters() { return m_counters; } | ||||||
|     HashMap<DeprecatedString, unsigned> const& counters() const { return m_counters; } |     HashMap<String, unsigned> const& counters() const { return m_counters; } | ||||||
| 
 | 
 | ||||||
|     ThrowCompletionOr<Value> debug(); |     ThrowCompletionOr<Value> debug(); | ||||||
|     ThrowCompletionOr<Value> error(); |     ThrowCompletionOr<Value> error(); | ||||||
|  | @ -81,18 +82,18 @@ public: | ||||||
|     ThrowCompletionOr<Value> time_log(); |     ThrowCompletionOr<Value> time_log(); | ||||||
|     ThrowCompletionOr<Value> time_end(); |     ThrowCompletionOr<Value> time_end(); | ||||||
| 
 | 
 | ||||||
|     void output_debug_message(LogLevel log_level, DeprecatedString const& output) const; |     void output_debug_message(LogLevel log_level, String const& output) const; | ||||||
|     void report_exception(JS::Error const&, bool) const; |     void report_exception(JS::Error const&, bool) const; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     ThrowCompletionOr<DeprecatedString> value_vector_to_deprecated_string(MarkedVector<Value> const&); |     ThrowCompletionOr<String> value_vector_to_string(MarkedVector<Value> const&); | ||||||
|     ThrowCompletionOr<DeprecatedString> format_time_since(Core::ElapsedTimer timer); |     ThrowCompletionOr<String> format_time_since(Core::ElapsedTimer timer); | ||||||
| 
 | 
 | ||||||
|     Realm& m_realm; |     Realm& m_realm; | ||||||
|     ConsoleClient* m_client { nullptr }; |     ConsoleClient* m_client { nullptr }; | ||||||
| 
 | 
 | ||||||
|     HashMap<DeprecatedString, unsigned> m_counters; |     HashMap<String, unsigned> m_counters; | ||||||
|     HashMap<DeprecatedString, Core::ElapsedTimer> m_timer_table; |     HashMap<String, Core::ElapsedTimer> m_timer_table; | ||||||
|     Vector<Group> m_group_stack; |     Vector<Group> m_group_stack; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ | ||||||
| 
 | 
 | ||||||
| #include <LibJS/Heap/MarkedVector.h> | #include <LibJS/Heap/MarkedVector.h> | ||||||
| #include <LibJS/Runtime/Completion.h> | #include <LibJS/Runtime/Completion.h> | ||||||
|  | #include <LibJS/Runtime/Realm.h> | ||||||
|  | #include <LibJS/Runtime/ThrowableStringBuilder.h> | ||||||
|  | #include <LibJS/Runtime/VM.h> | ||||||
| #include <LibWeb/HTML/WorkerDebugConsoleClient.h> | #include <LibWeb/HTML/WorkerDebugConsoleClient.h> | ||||||
| 
 | 
 | ||||||
| namespace Web::HTML { | namespace Web::HTML { | ||||||
|  | @ -31,16 +34,18 @@ void WorkerDebugConsoleClient::end_group() | ||||||
| // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 | // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 | ||||||
| JS::ThrowCompletionOr<JS::Value> WorkerDebugConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments) | JS::ThrowCompletionOr<JS::Value> WorkerDebugConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments) | ||||||
| { | { | ||||||
|     DeprecatedString indent = DeprecatedString::repeated("  "sv, m_group_stack_depth); |     auto& vm = m_console.realm().vm(); | ||||||
|  | 
 | ||||||
|  |     auto indent = TRY_OR_THROW_OOM(vm, String::repeated(' ', m_group_stack_depth * 2)); | ||||||
| 
 | 
 | ||||||
|     if (log_level == JS::Console::LogLevel::Trace) { |     if (log_level == JS::Console::LogLevel::Trace) { | ||||||
|         auto trace = arguments.get<JS::Console::Trace>(); |         auto trace = arguments.get<JS::Console::Trace>(); | ||||||
|         StringBuilder builder; |         JS::ThrowableStringBuilder builder(vm); | ||||||
|         if (!trace.label.is_empty()) |         if (!trace.label.is_empty()) | ||||||
|             builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label); |             MUST_OR_THROW_OOM(builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label)); | ||||||
| 
 | 
 | ||||||
|         for (auto& function_name : trace.stack) |         for (auto& function_name : trace.stack) | ||||||
|             builder.appendff("{}-> {}\n", indent, function_name); |             MUST_OR_THROW_OOM(builder.appendff("{}-> {}\n", indent, function_name)); | ||||||
| 
 | 
 | ||||||
|         dbgln("{}", builder.string_view()); |         dbgln("{}", builder.string_view()); | ||||||
|         return JS::js_undefined(); |         return JS::js_undefined(); | ||||||
|  | @ -53,7 +58,7 @@ JS::ThrowCompletionOr<JS::Value> WorkerDebugConsoleClient::printer(JS::Console:: | ||||||
|         return JS::js_undefined(); |         return JS::js_undefined(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto output = DeprecatedString::join(' ', arguments.get<JS::MarkedVector<JS::Value>>()); |     auto output = TRY_OR_THROW_OOM(vm, String::join(' ', arguments.get<JS::MarkedVector<JS::Value>>())); | ||||||
|     m_console.output_debug_message(log_level, output); |     m_console.output_debug_message(log_level, output); | ||||||
| 
 | 
 | ||||||
|     switch (log_level) { |     switch (log_level) { | ||||||
|  |  | ||||||
|  | @ -12,6 +12,9 @@ | ||||||
| #include <LibJS/MarkupGenerator.h> | #include <LibJS/MarkupGenerator.h> | ||||||
| #include <LibJS/Runtime/AbstractOperations.h> | #include <LibJS/Runtime/AbstractOperations.h> | ||||||
| #include <LibJS/Runtime/ObjectEnvironment.h> | #include <LibJS/Runtime/ObjectEnvironment.h> | ||||||
|  | #include <LibJS/Runtime/Realm.h> | ||||||
|  | #include <LibJS/Runtime/ThrowableStringBuilder.h> | ||||||
|  | #include <LibJS/Runtime/VM.h> | ||||||
| #include <LibWeb/HTML/PolicyContainers.h> | #include <LibWeb/HTML/PolicyContainers.h> | ||||||
| #include <LibWeb/HTML/Scripting/ClassicScript.h> | #include <LibWeb/HTML/Scripting/ClassicScript.h> | ||||||
| #include <LibWeb/HTML/Scripting/Environments.h> | #include <LibWeb/HTML/Scripting/Environments.h> | ||||||
|  | @ -129,19 +132,21 @@ void WebContentConsoleClient::clear() | ||||||
| // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 | // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 | ||||||
| JS::ThrowCompletionOr<JS::Value> WebContentConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments) | JS::ThrowCompletionOr<JS::Value> WebContentConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments) | ||||||
| { | { | ||||||
|  |     auto& vm = m_console.realm().vm(); | ||||||
|  | 
 | ||||||
|     auto styling = escape_html_entities(m_current_message_style.string_view()); |     auto styling = escape_html_entities(m_current_message_style.string_view()); | ||||||
|     m_current_message_style.clear(); |     m_current_message_style.clear(); | ||||||
| 
 | 
 | ||||||
|     if (log_level == JS::Console::LogLevel::Trace) { |     if (log_level == JS::Console::LogLevel::Trace) { | ||||||
|         auto trace = arguments.get<JS::Console::Trace>(); |         auto trace = arguments.get<JS::Console::Trace>(); | ||||||
|         StringBuilder html; |         JS::ThrowableStringBuilder html(vm); | ||||||
|         if (!trace.label.is_empty()) |         if (!trace.label.is_empty()) | ||||||
|             html.appendff("<span class='title' style='{}'>{}</span><br>", styling, escape_html_entities(trace.label)); |             MUST_OR_THROW_OOM(html.appendff("<span class='title' style='{}'>{}</span><br>", styling, escape_html_entities(trace.label))); | ||||||
| 
 | 
 | ||||||
|         html.append("<span class='trace'>"sv); |         MUST_OR_THROW_OOM(html.append("<span class='trace'>"sv)); | ||||||
|         for (auto& function_name : trace.stack) |         for (auto& function_name : trace.stack) | ||||||
|             html.appendff("-> {}<br>", escape_html_entities(function_name)); |             MUST_OR_THROW_OOM(html.appendff("-> {}<br>", escape_html_entities(function_name))); | ||||||
|         html.append("</span>"sv); |         MUST_OR_THROW_OOM(html.append("</span>"sv)); | ||||||
| 
 | 
 | ||||||
|         print_html(html.string_view()); |         print_html(html.string_view()); | ||||||
|         return JS::js_undefined(); |         return JS::js_undefined(); | ||||||
|  | @ -153,35 +158,36 @@ JS::ThrowCompletionOr<JS::Value> WebContentConsoleClient::printer(JS::Console::L | ||||||
|         return JS::js_undefined(); |         return JS::js_undefined(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto output = DeprecatedString::join(' ', arguments.get<JS::MarkedVector<JS::Value>>()); |     auto output = TRY_OR_THROW_OOM(vm, String::join(' ', arguments.get<JS::MarkedVector<JS::Value>>())); | ||||||
|     m_console.output_debug_message(log_level, output); |     m_console.output_debug_message(log_level, output); | ||||||
| 
 | 
 | ||||||
|     StringBuilder html; |     JS::ThrowableStringBuilder html(vm); | ||||||
|     switch (log_level) { |     switch (log_level) { | ||||||
|     case JS::Console::LogLevel::Debug: |     case JS::Console::LogLevel::Debug: | ||||||
|         html.appendff("<span class=\"debug\" style=\"{}\">(d) "sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span class=\"debug\" style=\"{}\">(d) "sv, styling)); | ||||||
|         break; |         break; | ||||||
|     case JS::Console::LogLevel::Error: |     case JS::Console::LogLevel::Error: | ||||||
|         html.appendff("<span class=\"error\" style=\"{}\">(e) "sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span class=\"error\" style=\"{}\">(e) "sv, styling)); | ||||||
|         break; |         break; | ||||||
|     case JS::Console::LogLevel::Info: |     case JS::Console::LogLevel::Info: | ||||||
|         html.appendff("<span class=\"info\" style=\"{}\">(i) "sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span class=\"info\" style=\"{}\">(i) "sv, styling)); | ||||||
|         break; |         break; | ||||||
|     case JS::Console::LogLevel::Log: |     case JS::Console::LogLevel::Log: | ||||||
|         html.appendff("<span class=\"log\" style=\"{}\"> "sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span class=\"log\" style=\"{}\"> "sv, styling)); | ||||||
|         break; |         break; | ||||||
|     case JS::Console::LogLevel::Warn: |     case JS::Console::LogLevel::Warn: | ||||||
|     case JS::Console::LogLevel::CountReset: |     case JS::Console::LogLevel::CountReset: | ||||||
|         html.appendff("<span class=\"warn\" style=\"{}\">(w) "sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span class=\"warn\" style=\"{}\">(w) "sv, styling)); | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|         html.appendff("<span style=\"{}\">"sv, styling); |         MUST_OR_THROW_OOM(html.appendff("<span style=\"{}\">"sv, styling)); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     html.append(escape_html_entities(output)); |     MUST_OR_THROW_OOM(html.append(escape_html_entities(output))); | ||||||
|     html.append("</span>"sv); |     MUST_OR_THROW_OOM(html.append("</span>"sv)); | ||||||
|     print_html(html.string_view()); |     print_html(html.string_view()); | ||||||
|  | 
 | ||||||
|     return JS::js_undefined(); |     return JS::js_undefined(); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ | ||||||
| #include <LibJS/Runtime/ConsoleObject.h> | #include <LibJS/Runtime/ConsoleObject.h> | ||||||
| #include <LibJS/Runtime/JSONObject.h> | #include <LibJS/Runtime/JSONObject.h> | ||||||
| #include <LibJS/Runtime/StringPrototype.h> | #include <LibJS/Runtime/StringPrototype.h> | ||||||
|  | #include <LibJS/Runtime/ThrowableStringBuilder.h> | ||||||
| #include <LibJS/SourceTextModule.h> | #include <LibJS/SourceTextModule.h> | ||||||
| #include <LibLine/Editor.h> | #include <LibLine/Editor.h> | ||||||
| #include <LibMain/Main.h> | #include <LibMain/Main.h> | ||||||
|  | @ -531,16 +532,16 @@ public: | ||||||
|     // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 |     // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
 | ||||||
|     virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments arguments) override |     virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments arguments) override | ||||||
|     { |     { | ||||||
|         DeprecatedString indent = DeprecatedString::repeated("  "sv, m_group_stack_depth); |         auto indent = TRY_OR_THROW_OOM(*g_vm, String::repeated(' ', m_group_stack_depth * 2)); | ||||||
| 
 | 
 | ||||||
|         if (log_level == JS::Console::LogLevel::Trace) { |         if (log_level == JS::Console::LogLevel::Trace) { | ||||||
|             auto trace = arguments.get<JS::Console::Trace>(); |             auto trace = arguments.get<JS::Console::Trace>(); | ||||||
|             StringBuilder builder; |             JS::ThrowableStringBuilder builder(*g_vm); | ||||||
|             if (!trace.label.is_empty()) |             if (!trace.label.is_empty()) | ||||||
|                 builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label); |                 MUST_OR_THROW_OOM(builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label)); | ||||||
| 
 | 
 | ||||||
|             for (auto& function_name : trace.stack) |             for (auto& function_name : trace.stack) | ||||||
|                 builder.appendff("{}-> {}\n", indent, function_name); |                 MUST_OR_THROW_OOM(builder.appendff("{}-> {}\n", indent, function_name)); | ||||||
| 
 | 
 | ||||||
|             outln("{}", builder.string_view()); |             outln("{}", builder.string_view()); | ||||||
|             return JS::js_undefined(); |             return JS::js_undefined(); | ||||||
|  | @ -553,7 +554,7 @@ public: | ||||||
|             return JS::js_undefined(); |             return JS::js_undefined(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         auto output = DeprecatedString::join(' ', arguments.get<JS::MarkedVector<JS::Value>>()); |         auto output = TRY_OR_THROW_OOM(*g_vm, String::join(' ', arguments.get<JS::MarkedVector<JS::Value>>())); | ||||||
| #ifdef AK_OS_SERENITY | #ifdef AK_OS_SERENITY | ||||||
|         m_console.output_debug_message(log_level, output); |         m_console.output_debug_message(log_level, output); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn