[console] Proper type conversions in console builtins.
This updates the following set of console builtins in V8 to match the Console Standard (https://console.spec.whatwg.org) with respect to (potentially side effecting) type conversions: - console.debug - console.error - console.info - console.log - console.trace - console.warn - console.group - console.groupCollapsed - console.assert The V8 implementation only performs the type conversions and updates the arguments in-place with the results from the %String% constructor, %parseInt%, or %parseFloat% invocations. The actual formatting is still left completely to the debugger front-end. To give a concrete example, the following code ```js const msgFmt = { toString() { return 'Message %i' } }; console.log('LOG: %s`, msgFmt, 42); ``` sends the following parameters to the debugger front-end ```js ["LOG: %s", "Message %i", 42] ``` and it's then the job of the front-end to perform the actual string substitutions. It's also worth calling out that the console builtins are only concerned with %s, %f, %d, and %i formatting specifiers, since these are the only ones that trigger type conversions, and %o, %O, and %c can only be implemented in a meaningful way at a higher level. Fixed: chromium:1277944 Bug: chromium:1282076 Doc: https://bit.ly/v8-proper-console-type-conversions Spec: https://console.spec.whatwg.org Change-Id: I0996680811aa96236bd0d879e4a11101629ef1a7 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3352118 Reviewed-by: Kim-Anh Tran <kimanh@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Commit-Queue: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/main@{#78432}
This commit is contained in:
parent
d19a707d14
commit
099cb420b9
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/builtins/builtins-utils-inl.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
@ -16,28 +18,128 @@ namespace internal {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Console
|
||||
|
||||
#define CONSOLE_METHOD_LIST(V) \
|
||||
V(Debug, debug) \
|
||||
V(Error, error) \
|
||||
V(Info, info) \
|
||||
V(Log, log) \
|
||||
V(Warn, warn) \
|
||||
V(Dir, dir) \
|
||||
V(DirXml, dirXml) \
|
||||
V(Table, table) \
|
||||
V(Trace, trace) \
|
||||
V(Group, group) \
|
||||
V(GroupCollapsed, groupCollapsed) \
|
||||
V(GroupEnd, groupEnd) \
|
||||
V(Clear, clear) \
|
||||
V(Count, count) \
|
||||
V(CountReset, countReset) \
|
||||
V(Assert, assert) \
|
||||
V(Profile, profile) \
|
||||
V(ProfileEnd, profileEnd) \
|
||||
#define CONSOLE_METHOD_LIST(V) \
|
||||
V(Dir, dir) \
|
||||
V(DirXml, dirXml) \
|
||||
V(Table, table) \
|
||||
V(GroupEnd, groupEnd) \
|
||||
V(Clear, clear) \
|
||||
V(Count, count) \
|
||||
V(CountReset, countReset) \
|
||||
V(Profile, profile) \
|
||||
V(ProfileEnd, profileEnd) \
|
||||
V(TimeLog, timeLog)
|
||||
|
||||
#define CONSOLE_METHOD_WITH_FORMATTER_LIST(V) \
|
||||
V(Debug, debug, 1) \
|
||||
V(Error, error, 1) \
|
||||
V(Info, info, 1) \
|
||||
V(Log, log, 1) \
|
||||
V(Warn, warn, 1) \
|
||||
V(Trace, trace, 1) \
|
||||
V(Group, group, 1) \
|
||||
V(GroupCollapsed, groupCollapsed, 1) \
|
||||
V(Assert, assert, 2)
|
||||
|
||||
namespace {
|
||||
|
||||
// 2.2 Formatter(args) [https://console.spec.whatwg.org/#formatter]
|
||||
//
|
||||
// This implements the formatter operation defined in the Console
|
||||
// specification to the degree that it makes sense for V8. That
|
||||
// means we primarily deal with %s, %i, %f, and %d, and any side
|
||||
// effects caused by the type conversions, and we preserve the %o,
|
||||
// %c, and %O specifiers and their parameters unchanged, and instead
|
||||
// leave it to the debugger front-end to make sense of those.
|
||||
//
|
||||
// Chrome also supports the non-standard bypass format specifier %_
|
||||
// which just skips over the parameter.
|
||||
//
|
||||
// This implementation updates the |args| in-place with the results
|
||||
// from the conversion.
|
||||
//
|
||||
// The |index| describes the position of the format string within,
|
||||
// |args| (starting with 1, since |args| also includes the receiver),
|
||||
// which is different for example in case of `console.log` where it
|
||||
// is 1 compared to `console.assert` where it is 2.
|
||||
bool Formatter(Isolate* isolate, BuiltinArguments& args, int index) {
|
||||
if (args.length() < index + 2 || !args[index].IsString()) {
|
||||
return true;
|
||||
}
|
||||
struct State {
|
||||
Handle<String> str;
|
||||
int off;
|
||||
};
|
||||
std::stack<State> states;
|
||||
HandleScope scope(isolate);
|
||||
auto percent = isolate->factory()->LookupSingleCharacterStringFromCode('%');
|
||||
states.push({args.at<String>(index++), 0});
|
||||
while (!states.empty() && index < args.length()) {
|
||||
State& state = states.top();
|
||||
state.off = String::IndexOf(isolate, state.str, percent, state.off);
|
||||
if (state.off < 0 || state.off == state.str->length() - 1) {
|
||||
states.pop();
|
||||
continue;
|
||||
}
|
||||
Handle<Object> current = args.at(index);
|
||||
uint16_t specifier = state.str->Get(state.off + 1, isolate);
|
||||
if (specifier == 'd' || specifier == 'f' || specifier == 'i') {
|
||||
if (current->IsSymbol()) {
|
||||
current = isolate->factory()->nan_value();
|
||||
} else {
|
||||
Handle<Object> params[] = {current,
|
||||
isolate->factory()->NewNumberFromInt(10)};
|
||||
auto builtin = specifier == 'f' ? isolate->global_parse_float_fun()
|
||||
: isolate->global_parse_int_fun();
|
||||
if (!Execution::CallBuiltin(isolate, builtin,
|
||||
isolate->factory()->undefined_value(),
|
||||
arraysize(params), params)
|
||||
.ToHandle(¤t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (specifier == 's') {
|
||||
Handle<Object> params[] = {current};
|
||||
if (!Execution::CallBuiltin(isolate, isolate->string_function(),
|
||||
isolate->factory()->undefined_value(),
|
||||
arraysize(params), params)
|
||||
.ToHandle(¤t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recurse into string results from type conversions, as they
|
||||
// can themselves contain formatting specifiers.
|
||||
states.push({Handle<String>::cast(current), 0});
|
||||
} else if (specifier == 'c' || specifier == 'o' || specifier == 'O' ||
|
||||
specifier == '_') {
|
||||
// We leave the interpretation of %c (CSS), %o (optimally useful
|
||||
// formatting), and %O (generic JavaScript object formatting) as
|
||||
// well as the non-standard %_ (bypass formatter in Chrome) to
|
||||
// the debugger front-end, and preserve these specifiers as well
|
||||
// as their arguments verbatim.
|
||||
index++;
|
||||
state.off += 2;
|
||||
continue;
|
||||
} else if (specifier == '%') {
|
||||
// Chrome also supports %% as a way to generate a single % in the
|
||||
// output.
|
||||
state.off += 2;
|
||||
continue;
|
||||
} else {
|
||||
state.off++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace the |specifier| (including the '%' character) in |target|
|
||||
// with the |current| value. We perform the replacement only morally
|
||||
// by updating the argument to the conversion result, but leave it to
|
||||
// the debugger front-end to perform the actual substitution.
|
||||
args.set_at(index++, *current);
|
||||
state.off += 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsoleCall(
|
||||
Isolate* isolate, const internal::BuiltinArguments& args,
|
||||
void (debug::ConsoleDelegate::*func)(const v8::debug::ConsoleCallArguments&,
|
||||
@ -74,6 +176,7 @@ void LogTimerEvent(Isolate* isolate, BuiltinArguments args,
|
||||
}
|
||||
LOG(isolate, TimerEvent(se, raw_name));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#define CONSOLE_BUILTIN_IMPLEMENTATION(call, name) \
|
||||
@ -85,6 +188,18 @@ void LogTimerEvent(Isolate* isolate, BuiltinArguments args,
|
||||
CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_IMPLEMENTATION)
|
||||
#undef CONSOLE_BUILTIN_IMPLEMENTATION
|
||||
|
||||
#define CONSOLE_BUILTIN_IMPLEMENTATION(call, name, index) \
|
||||
BUILTIN(Console##call) { \
|
||||
if (!Formatter(isolate, args, index)) { \
|
||||
return ReadOnlyRoots(isolate).exception(); \
|
||||
} \
|
||||
ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \
|
||||
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \
|
||||
return ReadOnlyRoots(isolate).undefined_value(); \
|
||||
}
|
||||
CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_IMPLEMENTATION)
|
||||
#undef CONSOLE_BUILTIN_IMPLEMENTATION
|
||||
|
||||
BUILTIN(ConsoleTime) {
|
||||
LogTimerEvent(isolate, args, v8::LogEventStatus::kStart);
|
||||
ConsoleCall(isolate, args, &debug::ConsoleDelegate::Time);
|
||||
@ -162,10 +277,11 @@ BUILTIN(ConsoleContext) {
|
||||
int id = isolate->last_console_context_id() + 1;
|
||||
isolate->set_last_console_context_id(id);
|
||||
|
||||
#define CONSOLE_BUILTIN_SETUP(call, name) \
|
||||
#define CONSOLE_BUILTIN_SETUP(call, name, ...) \
|
||||
InstallContextFunction(isolate, context, #name, Builtin::kConsole##call, id, \
|
||||
args.at(1));
|
||||
CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_SETUP)
|
||||
CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_SETUP)
|
||||
#undef CONSOLE_BUILTIN_SETUP
|
||||
InstallContextFunction(isolate, context, "time", Builtin::kConsoleTime, id,
|
||||
args.at(1));
|
||||
|
@ -1901,12 +1901,14 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
Builtin::kNumberParseFloat, 1, true);
|
||||
JSObject::AddProperty(isolate_, global_object, "parseFloat",
|
||||
parse_float_fun, DONT_ENUM);
|
||||
native_context()->set_global_parse_float_fun(*parse_float_fun);
|
||||
|
||||
// Install Number.parseInt and Global.parseInt.
|
||||
Handle<JSFunction> parse_int_fun = SimpleInstallFunction(
|
||||
isolate_, number_fun, "parseInt", Builtin::kNumberParseInt, 2, true);
|
||||
JSObject::AddProperty(isolate_, global_object, "parseInt", parse_int_fun,
|
||||
DONT_ENUM);
|
||||
native_context()->set_global_parse_int_fun(*parse_int_fun);
|
||||
|
||||
// Install Number constants
|
||||
const double kMaxValue = 1.7976931348623157e+308;
|
||||
|
@ -325,6 +325,8 @@ enum ContextLookupFlags {
|
||||
V(EVAL_ERROR_FUNCTION_INDEX, JSFunction, eval_error_function) \
|
||||
V(AGGREGATE_ERROR_FUNCTION_INDEX, JSFunction, aggregate_error_function) \
|
||||
V(GLOBAL_EVAL_FUN_INDEX, JSFunction, global_eval_fun) \
|
||||
V(GLOBAL_PARSE_FLOAT_FUN_INDEX, JSFunction, global_parse_float_fun) \
|
||||
V(GLOBAL_PARSE_INT_FUN_INDEX, JSFunction, global_parse_int_fun) \
|
||||
V(GLOBAL_PROXY_FUNCTION_INDEX, JSFunction, global_proxy_function) \
|
||||
V(MAP_DELETE_INDEX, JSFunction, map_delete) \
|
||||
V(MAP_GET_INDEX, JSFunction, map_get) \
|
||||
|
@ -10,28 +10,28 @@ console.context description:
|
||||
}
|
||||
console.context() methods:
|
||||
[
|
||||
[0] : debug
|
||||
[1] : error
|
||||
[2] : info
|
||||
[3] : log
|
||||
[4] : warn
|
||||
[0] : assert
|
||||
[1] : clear
|
||||
[2] : count
|
||||
[3] : countReset
|
||||
[4] : debug
|
||||
[5] : dir
|
||||
[6] : dirXml
|
||||
[7] : table
|
||||
[8] : trace
|
||||
[9] : group
|
||||
[10] : groupCollapsed
|
||||
[11] : groupEnd
|
||||
[12] : clear
|
||||
[13] : count
|
||||
[14] : countReset
|
||||
[15] : assert
|
||||
[16] : profile
|
||||
[17] : profileEnd
|
||||
[7] : error
|
||||
[8] : group
|
||||
[9] : groupCollapsed
|
||||
[10] : groupEnd
|
||||
[11] : info
|
||||
[12] : log
|
||||
[13] : profile
|
||||
[14] : profileEnd
|
||||
[15] : table
|
||||
[16] : time
|
||||
[17] : timeEnd
|
||||
[18] : timeLog
|
||||
[19] : time
|
||||
[20] : timeEnd
|
||||
[21] : timeStamp
|
||||
[19] : timeStamp
|
||||
[20] : trace
|
||||
[21] : warn
|
||||
]
|
||||
|
||||
Running test: testDefaultConsoleContext
|
||||
|
@ -11,9 +11,14 @@ InspectorTest.runAsyncTestSuite([
|
||||
expression: 'console.context'});
|
||||
InspectorTest.logMessage(result);
|
||||
|
||||
// Enumerate the methods alpha-sorted to make the test
|
||||
// independent of the (unspecified) enumeration order
|
||||
// of console.context() methods.
|
||||
InspectorTest.log('console.context() methods:');
|
||||
var {result:{result:{value}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Object.keys(console.context())', returnByValue: true});
|
||||
var {result: {result: {value}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Object.keys(console.context()).sort()',
|
||||
returnByValue: true
|
||||
});
|
||||
InspectorTest.logMessage(value);
|
||||
},
|
||||
|
||||
|
700
test/inspector/runtime/console-formatter-expected.txt
Normal file
700
test/inspector/runtime/console-formatter-expected.txt
Normal file
@ -0,0 +1,700 @@
|
||||
Test for console formatting
|
||||
|
||||
Running test: testFloatFormatter
|
||||
Testing console.debug('%f', 3.1415)...
|
||||
debug[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %f
|
||||
}
|
||||
[1] : {
|
||||
description : 3.1415
|
||||
type : number
|
||||
value : 3.1415
|
||||
}
|
||||
]
|
||||
Testing console.error('%f', '3e2')...
|
||||
error[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %f
|
||||
}
|
||||
[1] : {
|
||||
description : 300
|
||||
type : number
|
||||
value : 300
|
||||
}
|
||||
]
|
||||
Testing console.info('%f', Symbol('1.1'))...
|
||||
info[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %f
|
||||
}
|
||||
[1] : {
|
||||
description : NaN
|
||||
type : number
|
||||
unserializableValue : NaN
|
||||
}
|
||||
]
|
||||
Testing console.log('%f', {toString() { return '42'; }})...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %f
|
||||
}
|
||||
[1] : {
|
||||
description : 42
|
||||
type : number
|
||||
value : 42
|
||||
}
|
||||
]
|
||||
Testing console.trace('%f', {[Symbol.toPrimitive]() { return 2.78; }})...
|
||||
trace[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %f
|
||||
}
|
||||
[1] : {
|
||||
description : 2.78
|
||||
type : number
|
||||
value : 2.78
|
||||
}
|
||||
]
|
||||
Testing console.warn('%f', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 33
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:40) at parseFloat (<anonymous>) at console.warn (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 39
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
|
||||
Running test: testIntegerFormatter
|
||||
Testing console.debug('%d', 42)...
|
||||
debug[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %d
|
||||
}
|
||||
[1] : {
|
||||
description : 42
|
||||
type : number
|
||||
value : 42
|
||||
}
|
||||
]
|
||||
Testing console.error('%i', '987654321')...
|
||||
error[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %i
|
||||
}
|
||||
[1] : {
|
||||
description : 987654321
|
||||
type : number
|
||||
value : 987654321
|
||||
}
|
||||
]
|
||||
Testing console.info('%d', Symbol('12345'))...
|
||||
info[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %d
|
||||
}
|
||||
[1] : {
|
||||
description : NaN
|
||||
type : number
|
||||
unserializableValue : NaN
|
||||
}
|
||||
]
|
||||
Testing console.log('%i', {toString() { return '42'; }})...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %i
|
||||
}
|
||||
[1] : {
|
||||
description : 42
|
||||
type : number
|
||||
value : 42
|
||||
}
|
||||
]
|
||||
Testing console.trace('%d', {[Symbol.toPrimitive]() { return 256; }})...
|
||||
trace[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %d
|
||||
}
|
||||
[1] : {
|
||||
description : 256
|
||||
type : number
|
||||
value : 256
|
||||
}
|
||||
]
|
||||
Testing console.warn('%i', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 33
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:40) at parseInt (<anonymous>) at console.warn (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 39
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
|
||||
Running test: testStringFormatter
|
||||
Testing console.debug('%s', 42)...
|
||||
debug[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : 42
|
||||
}
|
||||
]
|
||||
Testing console.error('%s', 'Test string')...
|
||||
error[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Test string
|
||||
}
|
||||
]
|
||||
Testing console.info('%s', Symbol('Test symbol'))...
|
||||
info[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Symbol(Test symbol)
|
||||
}
|
||||
]
|
||||
Testing console.log('%s', {toString() { return 'Test object'; }})...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Test object
|
||||
}
|
||||
]
|
||||
Testing console.trace('%s', {[Symbol.toPrimitive]() { return true; }})...
|
||||
trace[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : true
|
||||
}
|
||||
]
|
||||
Testing console.warn('%s', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 33
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:40) at String (<anonymous>) at console.warn (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 39
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
|
||||
Running test: testOtherFormatters
|
||||
Testing console.debug('%c', 'color:red')...
|
||||
debug[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %c
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : color:red
|
||||
}
|
||||
]
|
||||
Testing console.error('%o', {toString() { throw new Error(); }})...
|
||||
error[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %o
|
||||
}
|
||||
[1] : {
|
||||
className : Object
|
||||
description : Object
|
||||
objectId : 1.1.7
|
||||
preview : {
|
||||
description : Object
|
||||
overflow : false
|
||||
properties : [
|
||||
[0] : {
|
||||
name : toString
|
||||
type : function
|
||||
value :
|
||||
}
|
||||
]
|
||||
type : object
|
||||
}
|
||||
type : object
|
||||
}
|
||||
]
|
||||
Testing console.info('%O', {toString() { throw new Error(); }})...
|
||||
info[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %O
|
||||
}
|
||||
[1] : {
|
||||
className : Object
|
||||
description : Object
|
||||
objectId : 1.1.8
|
||||
preview : {
|
||||
description : Object
|
||||
overflow : false
|
||||
properties : [
|
||||
[0] : {
|
||||
name : toString
|
||||
type : function
|
||||
value :
|
||||
}
|
||||
]
|
||||
type : object
|
||||
}
|
||||
type : object
|
||||
}
|
||||
]
|
||||
Testing console.log('We have reached 100% of our users', 'with this!')...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : We have reached 100% of our users
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : with this!
|
||||
}
|
||||
]
|
||||
|
||||
Running test: testMultipleFormatters
|
||||
Testing console.debug('%s%some Text%i', '', 'S', 1)...
|
||||
debug[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s%some Text%i
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value :
|
||||
}
|
||||
[2] : {
|
||||
type : string
|
||||
value : S
|
||||
}
|
||||
[3] : {
|
||||
description : 1
|
||||
type : number
|
||||
value : 1
|
||||
}
|
||||
]
|
||||
Testing console.error('%c%i%c%s', 'color:red', 42, 'color:green', 'Message!')...
|
||||
error[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %c%i%c%s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : color:red
|
||||
}
|
||||
[2] : {
|
||||
description : 42
|
||||
type : number
|
||||
value : 42
|
||||
}
|
||||
[3] : {
|
||||
type : string
|
||||
value : color:green
|
||||
}
|
||||
[4] : {
|
||||
type : string
|
||||
value : Message!
|
||||
}
|
||||
]
|
||||
Testing console.info('%s', {toString() { return '%i% %s %s'; }}, {toString() { return '100'; }}, 'more', 'arguments')...
|
||||
info[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : %i% %s %s
|
||||
}
|
||||
[2] : {
|
||||
description : 100
|
||||
type : number
|
||||
value : 100
|
||||
}
|
||||
[3] : {
|
||||
type : string
|
||||
value : more
|
||||
}
|
||||
[4] : {
|
||||
type : string
|
||||
value : arguments
|
||||
}
|
||||
]
|
||||
Testing console.log('%s %s', {toString() { return 'Too %s %s'; }}, 'many', 'specifiers')...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s %s
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Too %s %s
|
||||
}
|
||||
[2] : {
|
||||
type : string
|
||||
value : many
|
||||
}
|
||||
[3] : {
|
||||
type : string
|
||||
value : specifiers
|
||||
}
|
||||
]
|
||||
Testing console.trace('%s %f', {toString() { return '%s'; }}, {[Symbol.toPrimitive]() { return 'foo'; }}, 1, 'Test')...
|
||||
trace[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s %f
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[2] : {
|
||||
type : string
|
||||
value : foo
|
||||
}
|
||||
[3] : {
|
||||
description : 1
|
||||
type : number
|
||||
value : 1
|
||||
}
|
||||
[4] : {
|
||||
type : string
|
||||
value : Test
|
||||
}
|
||||
]
|
||||
|
||||
Running test: testAssert
|
||||
Testing console.assert(true, '%s', {toString() { throw new Error(); }})...
|
||||
Testing console.assert(false, '%s %i', {toString() { return '%s'; }}, {[Symbol.toPrimitive]() { return 1; }}, 1, 'Test')...
|
||||
assert[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s %i
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : %s
|
||||
}
|
||||
[2] : {
|
||||
type : string
|
||||
value : 1
|
||||
}
|
||||
[3] : {
|
||||
description : 1
|
||||
type : number
|
||||
value : 1
|
||||
}
|
||||
[4] : {
|
||||
type : string
|
||||
value : Test
|
||||
}
|
||||
]
|
||||
Testing console.assert(false, '%s', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 42
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:49) at String (<anonymous>) at console.assert (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 48
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
|
||||
Running test: testGroup
|
||||
Testing console.group('%s', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 34
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:41) at String (<anonymous>) at console.group (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 40
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
Testing console.group('%s%i', 'Gruppe', {[Symbol.toPrimitive]() { return 1; }})...
|
||||
startGroup[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s%i
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Gruppe
|
||||
}
|
||||
[2] : {
|
||||
description : 1
|
||||
type : number
|
||||
value : 1
|
||||
}
|
||||
]
|
||||
Testing console.groupEnd()...
|
||||
endGroup[
|
||||
[0] : {
|
||||
type : string
|
||||
value : console.groupEnd
|
||||
}
|
||||
]
|
||||
|
||||
Running test: testGroupCollapsed
|
||||
Testing console.groupCollapsed('%d', {toString() { throw new Error(); }})...
|
||||
{
|
||||
columnNumber : 43
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error at Object.toString (<anonymous>:1:50) at parseInt (<anonymous>) at console.groupCollapsed (<anonymous>) at <anonymous>:1:9
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 49
|
||||
functionName : toString
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 8
|
||||
functionName :
|
||||
lineNumber : 0
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Uncaught
|
||||
}
|
||||
Testing console.groupCollapsed('%s%f', {[Symbol.toPrimitive]() { return 'Gruppe'; }}, 3.1415)...
|
||||
startGroupCollapsed[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %s%f
|
||||
}
|
||||
[1] : {
|
||||
type : string
|
||||
value : Gruppe
|
||||
}
|
||||
[2] : {
|
||||
description : 3.1415
|
||||
type : number
|
||||
value : 3.1415
|
||||
}
|
||||
]
|
||||
Testing console.groupEnd()...
|
||||
endGroup[
|
||||
[0] : {
|
||||
type : string
|
||||
value : console.groupEnd
|
||||
}
|
||||
]
|
||||
|
||||
Running test: testNonStandardFormatSpecifiers
|
||||
Testing console.log('%_ %s', {toString() { throw new Error(); }}, {toString() { return 'foo'; }})...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %_ %s
|
||||
}
|
||||
[1] : {
|
||||
className : Object
|
||||
description : Object
|
||||
objectId : 1.1.15
|
||||
preview : {
|
||||
description : Object
|
||||
overflow : false
|
||||
properties : [
|
||||
[0] : {
|
||||
name : toString
|
||||
type : function
|
||||
value :
|
||||
}
|
||||
]
|
||||
type : object
|
||||
}
|
||||
type : object
|
||||
}
|
||||
[2] : {
|
||||
type : string
|
||||
value : foo
|
||||
}
|
||||
]
|
||||
Testing console.log('%%s', {toString() { throw new Error(); }})...
|
||||
log[
|
||||
[0] : {
|
||||
type : string
|
||||
value : %%s
|
||||
}
|
||||
[1] : {
|
||||
className : Object
|
||||
description : Object
|
||||
objectId : 1.1.16
|
||||
preview : {
|
||||
description : Object
|
||||
overflow : false
|
||||
properties : [
|
||||
[0] : {
|
||||
name : toString
|
||||
type : function
|
||||
value :
|
||||
}
|
||||
]
|
||||
type : object
|
||||
}
|
||||
type : object
|
||||
}
|
||||
]
|
144
test/inspector/runtime/console-formatter.js
Normal file
144
test/inspector/runtime/console-formatter.js
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
const {session, contextGroup, Protocol} =
|
||||
InspectorTest.start('Test for console formatting');
|
||||
|
||||
Protocol.Runtime.onConsoleAPICalled(({params: {args, type}}) => {
|
||||
InspectorTest.logObject(args, type);
|
||||
});
|
||||
|
||||
async function test(expression) {
|
||||
InspectorTest.logMessage(`Testing ${expression}...`);
|
||||
const {result} = await Protocol.Runtime.evaluate({expression});
|
||||
if ('exceptionDetails' in result) {
|
||||
InspectorTest.logMessage(result.exceptionDetails);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorTest.runAsyncTestSuite([
|
||||
async function testFloatFormatter() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.debug('%f', 3.1415)`);
|
||||
await test(`console.error('%f', '3e2')`);
|
||||
await test(`console.info('%f', Symbol('1.1'))`);
|
||||
await test(`console.log('%f', {toString() { return '42'; }})`);
|
||||
await test(
|
||||
`console.trace('%f', {[Symbol.toPrimitive]() { return 2.78; }})`);
|
||||
await test(`console.warn('%f', {toString() { throw new Error(); }})`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testIntegerFormatter() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.debug('%d', 42)`);
|
||||
await test(`console.error('%i', '987654321')`);
|
||||
await test(`console.info('%d', Symbol('12345'))`);
|
||||
await test(`console.log('%i', {toString() { return '42'; }})`);
|
||||
await test(`console.trace('%d', {[Symbol.toPrimitive]() { return 256; }})`);
|
||||
await test(`console.warn('%i', {toString() { throw new Error(); }})`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testStringFormatter() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.debug('%s', 42)`);
|
||||
await test(`console.error('%s', 'Test string')`);
|
||||
await test(`console.info('%s', Symbol('Test symbol'))`);
|
||||
await test(`console.log('%s', {toString() { return 'Test object'; }})`);
|
||||
await test(
|
||||
`console.trace('%s', {[Symbol.toPrimitive]() { return true; }})`);
|
||||
await test(`console.warn('%s', {toString() { throw new Error(); }})`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testOtherFormatters() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.debug('%c', 'color:red')`);
|
||||
await test(`console.error('%o', {toString() { throw new Error(); }})`);
|
||||
await test(`console.info('%O', {toString() { throw new Error(); }})`);
|
||||
await test(
|
||||
`console.log('We have reached 100% of our users', 'with this!')`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testMultipleFormatters() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.debug('%s%some Text%i', '', 'S', 1)`);
|
||||
await test(
|
||||
`console.error('%c%i%c%s', 'color:red', 42, 'color:green', 'Message!')`);
|
||||
await test(
|
||||
`console.info('%s', {toString() { return '%i% %s %s'; }}, {toString() { return '100'; }}, 'more', 'arguments')`);
|
||||
await test(
|
||||
`console.log('%s %s', {toString() { return 'Too %s %s'; }}, 'many', 'specifiers')`);
|
||||
await test(
|
||||
`console.trace('%s %f', {toString() { return '%s'; }}, {[Symbol.toPrimitive]() { return 'foo'; }}, 1, 'Test')`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testAssert() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(
|
||||
`console.assert(true, '%s', {toString() { throw new Error(); }})`);
|
||||
await test(
|
||||
`console.assert(false, '%s %i', {toString() { return '%s'; }}, {[Symbol.toPrimitive]() { return 1; }}, 1, 'Test')`);
|
||||
await test(
|
||||
`console.assert(false, '%s', {toString() { throw new Error(); }})`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testGroup() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(`console.group('%s', {toString() { throw new Error(); }})`);
|
||||
await test(
|
||||
`console.group('%s%i', 'Gruppe', {[Symbol.toPrimitive]() { return 1; }})`);
|
||||
await test(`console.groupEnd()`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testGroupCollapsed() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(
|
||||
`console.groupCollapsed('%d', {toString() { throw new Error(); }})`);
|
||||
await test(
|
||||
`console.groupCollapsed('%s%f', {[Symbol.toPrimitive]() { return 'Gruppe'; }}, 3.1415)`);
|
||||
await test(`console.groupEnd()`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
},
|
||||
|
||||
async function testNonStandardFormatSpecifiers() {
|
||||
await Protocol.Runtime.enable();
|
||||
await test(
|
||||
`console.log('%_ %s', {toString() { throw new Error(); }}, {toString() { return 'foo'; }})`);
|
||||
await test(`console.log('%%s', {toString() { throw new Error(); }})`);
|
||||
await Promise.all([
|
||||
Protocol.Runtime.discardConsoleEntries(),
|
||||
Protocol.Runtime.disable(),
|
||||
]);
|
||||
}
|
||||
]);
|
Loading…
Reference in New Issue
Block a user