185 lines
6 KiB
C
185 lines
6 KiB
C
/*
|
|
* Minimal 'console' binding.
|
|
*
|
|
* https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
|
|
* https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
|
|
* https://developer.mozilla.org/en/docs/Web/API/console
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include "duktape.h"
|
|
#include "duk_console.h"
|
|
|
|
/* XXX: Add some form of log level filtering. */
|
|
|
|
/* XXX: Should all output be written via e.g. console.write(formattedMsg)?
|
|
* This would make it easier for user code to redirect all console output
|
|
* to a custom backend.
|
|
*/
|
|
|
|
/* XXX: Init console object using duk_def_prop() when that call is available. */
|
|
|
|
static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) {
|
|
duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx);
|
|
FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr;
|
|
duk_idx_t n = duk_get_top(ctx);
|
|
duk_idx_t i;
|
|
|
|
duk_get_global_string(ctx, "console");
|
|
duk_get_prop_string(ctx, -1, "format");
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
|
|
/* Slow path formatting. */
|
|
duk_dup(ctx, -1); /* console.format */
|
|
duk_dup(ctx, i);
|
|
duk_call(ctx, 1);
|
|
duk_replace(ctx, i); /* arg[i] = console.format(arg[i]); */
|
|
}
|
|
}
|
|
|
|
duk_pop_2(ctx);
|
|
|
|
duk_push_string(ctx, " ");
|
|
duk_insert(ctx, 0);
|
|
duk_join(ctx, n);
|
|
|
|
if (error_name) {
|
|
duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
|
|
duk_push_string(ctx, "name");
|
|
duk_push_string(ctx, error_name);
|
|
duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE); /* to get e.g. 'Trace: 1 2 3' */
|
|
duk_get_prop_string(ctx, -1, "stack");
|
|
}
|
|
|
|
fprintf(output, "%s\n", duk_to_string(ctx, -1));
|
|
if (flags & DUK_CONSOLE_FLUSH) {
|
|
fflush(output);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t duk__console_assert(duk_context *ctx) {
|
|
if (duk_to_boolean(ctx, 0)) {
|
|
return 0;
|
|
}
|
|
duk_remove(ctx, 0);
|
|
|
|
return duk__console_log_helper(ctx, "AssertionError");
|
|
}
|
|
|
|
static duk_ret_t duk__console_log(duk_context *ctx) {
|
|
return duk__console_log_helper(ctx, NULL);
|
|
}
|
|
|
|
static duk_ret_t duk__console_trace(duk_context *ctx) {
|
|
return duk__console_log_helper(ctx, "Trace");
|
|
}
|
|
|
|
static duk_ret_t duk__console_info(duk_context *ctx) {
|
|
return duk__console_log_helper(ctx, NULL);
|
|
}
|
|
|
|
static duk_ret_t duk__console_warn(duk_context *ctx) {
|
|
return duk__console_log_helper(ctx, NULL);
|
|
}
|
|
|
|
static duk_ret_t duk__console_error(duk_context *ctx) {
|
|
return duk__console_log_helper(ctx, "Error");
|
|
}
|
|
|
|
static duk_ret_t duk__console_dir(duk_context *ctx) {
|
|
/* For now, just share the formatting of .log() */
|
|
return duk__console_log_helper(ctx, 0);
|
|
}
|
|
|
|
static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
|
|
duk_push_c_function(ctx, func, DUK_VARARGS);
|
|
duk_push_string(ctx, "name");
|
|
duk_push_string(ctx, name);
|
|
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
|
|
duk_set_magic(ctx, -1, (duk_int_t) flags);
|
|
duk_put_prop_string(ctx, -2, name);
|
|
}
|
|
|
|
void duk_console_init(duk_context *ctx, duk_uint_t flags) {
|
|
duk_uint_t flags_orig;
|
|
|
|
/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
|
|
* just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
|
|
*/
|
|
if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
|
|
flags &= ~DUK_CONSOLE_STDOUT_ONLY;
|
|
}
|
|
/* Remember the (possibly corrected) flags we received. */
|
|
flags_orig = flags;
|
|
|
|
duk_push_object(ctx);
|
|
|
|
/* Custom function to format objects; user can replace.
|
|
* For now, try JX-formatting and if that fails, fall back
|
|
* to ToString(v).
|
|
*/
|
|
duk_eval_string(ctx,
|
|
"(function (E) {"
|
|
"return function format(v){"
|
|
"try{"
|
|
"return E('jx',v);"
|
|
"}catch(e){"
|
|
"return String(v);" /* String() allows symbols, ToString() internal algorithm doesn't. */
|
|
"}"
|
|
"};"
|
|
"})(Duktape.enc)");
|
|
duk_put_prop_string(ctx, -2, "format");
|
|
|
|
flags = flags_orig;
|
|
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
|
|
/* No output indicators were specified; these levels go to stdout. */
|
|
flags |= DUK_CONSOLE_STDOUT_ONLY;
|
|
}
|
|
duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
|
|
duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
|
|
duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags); /* alias to console.log */
|
|
duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
|
|
duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
|
|
|
|
flags = flags_orig;
|
|
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
|
|
/* No output indicators were specified; these levels go to stderr. */
|
|
flags |= DUK_CONSOLE_STDERR_ONLY;
|
|
}
|
|
duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
|
|
duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
|
|
duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags); /* alias to console.error */
|
|
duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
|
|
|
|
duk_put_global_string(ctx, "console");
|
|
|
|
/* Proxy wrapping: ensures any undefined console method calls are
|
|
* ignored silently. This was required specifically by the
|
|
* DeveloperToolsWG proposal (and was implemented also by Firefox:
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=629607). This is
|
|
* apparently no longer the preferred way of implementing console.
|
|
* When Proxy is enabled, whitelist at least .toJSON() to avoid
|
|
* confusing JX serialization of the console object.
|
|
*/
|
|
|
|
if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
|
|
/* Tolerate failure to initialize Proxy wrapper in case
|
|
* Proxy support is disabled.
|
|
*/
|
|
(void) duk_peval_string_noresult(ctx,
|
|
"(function(){"
|
|
"var D=function(){};"
|
|
"var W={toJSON:true};" /* whitelisted */
|
|
"console=new Proxy(console,{"
|
|
"get:function(t,k){"
|
|
"var v=t[k];"
|
|
"return typeof v==='function'||W[k]?v:D;"
|
|
"}"
|
|
"});"
|
|
"})();"
|
|
);
|
|
}
|
|
}
|