diff options
Diffstat (limited to 'modules/console.cpp')
-rw-r--r-- | modules/console.cpp | 334 |
1 files changed, 133 insertions, 201 deletions
diff --git a/modules/console.cpp b/modules/console.cpp index 35df0d2a..f2f6c140 100644 --- a/modules/console.cpp +++ b/modules/console.cpp @@ -3,29 +3,24 @@ // SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later // SPDX-FileCopyrightText: 1998 Netscape Communications Corporation -#include <config.h> // for HAVE_READLINE_READLINE_H - -#ifdef HAVE_SIGNAL_H -# include <setjmp.h> -# include <signal.h> -# ifdef _WIN32 -# define sigjmp_buf jmp_buf -# define siglongjmp(e, v) longjmp (e, v) -# define sigsetjmp(v, m) setjmp (v) -# endif -#endif - -#ifdef HAVE_READLINE_READLINE_H -# include <stdio.h> // include before readline/readline.h - -# include <readline/history.h> -# include <readline/readline.h> -#endif +#include <config.h> #include <string> #include <glib.h> + +#include <glib.h> #include <glib/gprintf.h> // for g_fprintf +#include <stdio.h> + +#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_UNISTD_H) +# include <fcntl.h> +# include <sys/ioctl.h> +# include <unistd.h> +# if defined(TIOCGWINSZ) +# define GET_SIZE_USE_IOCTL +# endif +#endif #include <js/CallAndConstruct.h> #include <js/CallArgs.h> @@ -35,6 +30,8 @@ #include <js/ErrorReport.h> #include <js/Exception.h> #include <js/PropertyAndElement.h> +#include <js/PropertyDescriptor.h> +#include <js/PropertySpec.h> #include <js/RootingAPI.h> #include <js/SourceText.h> #include <js/TypeDecls.h> @@ -47,18 +44,16 @@ #include "gjs/atoms.h" #include "gjs/context-private.h" #include "gjs/global.h" +#include "gjs/jsapi-util-args.h" #include "gjs/jsapi-util.h" #include "gjs/macros.h" #include "modules/console.h" +#include "util/console.h" namespace mozilla { union Utf8Unit; } -static void gjs_console_warning_reporter(JSContext*, JSErrorReport* report) { - JS::PrintError(stderr, report, /* reportWarnings = */ true); -} - /* Based on js::shell::AutoReportException from SpiderMonkey. */ class AutoReportException { JSContext *m_cx; @@ -98,84 +93,20 @@ public: } }; - -// Adapted from https://stackoverflow.com/a/17035073/172999 -class AutoCatchCtrlC { -#ifdef HAVE_SIGNAL_H - void (*m_prev_handler)(int); - - static void handler(int signal) { - if (signal == SIGINT) - siglongjmp(jump_buffer, 1); - } - - public: - static sigjmp_buf jump_buffer; - - AutoCatchCtrlC() { - m_prev_handler = signal(SIGINT, &AutoCatchCtrlC::handler); - } - - ~AutoCatchCtrlC() { - if (m_prev_handler != SIG_ERR) - signal(SIGINT, m_prev_handler); - } - - void raise_default() { - if (m_prev_handler != SIG_ERR) - signal(SIGINT, m_prev_handler); - raise(SIGINT); - } -#endif // HAVE_SIGNAL_H -}; - -#ifdef HAVE_SIGNAL_H -sigjmp_buf AutoCatchCtrlC::jump_buffer; -#endif // HAVE_SIGNAL_H - [[nodiscard]] static bool gjs_console_readline(char** bufp, const char* prompt) { -#ifdef HAVE_READLINE_READLINE_H - char *line; - line = readline(prompt); - if (!line) - return false; - if (line[0] != '\0') - add_history(line); - *bufp = line; -#else // !HAVE_READLINE_READLINE_H char line[256]; fprintf(stdout, "%s", prompt); fflush(stdout); if (!fgets(line, sizeof line, stdin)) return false; *bufp = g_strdup(line); -#endif // !HAVE_READLINE_READLINE_H return true; } -std::string print_string_value(JSContext* cx, JS::HandleValue v_string) { - if (!v_string.isString()) - return "[unexpected result from printing value]"; - - JS::RootedString printed_string(cx, v_string.toString()); - JS::AutoSaveExceptionState exc_state(cx); - JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, printed_string)); - exc_state.restore(); - if (!chars) - return "[error printing value]"; - - return chars.get(); -} - -/* Return value of false indicates an uncatchable exception, rather than any - * exception. (This is because the exception should be auto-printed around the - * invocation of this function.) - */ -[[nodiscard]] static bool gjs_console_eval_and_print(JSContext* cx, - JS::HandleObject global, - const std::string& bytes, - int lineno) { +[[nodiscard]] static bool gjs_console_eval(JSContext* cx, + const std::string& bytes, int lineno, + JS::MutableHandleValue result) { JS::SourceText<mozilla::Utf8Unit> source; if (!source.init(cx, bytes.c_str(), bytes.size(), JS::SourceOwnership::Borrowed)) @@ -184,132 +115,86 @@ std::string print_string_value(JSContext* cx, JS::HandleValue v_string) { JS::CompileOptions options(cx); options.setFileAndLine("typein", lineno); - JS::RootedValue result(cx); - if (!JS::Evaluate(cx, options, source, &result)) { - if (!JS_IsExceptionPending(cx)) - return false; - } + JS::RootedValue eval_result(cx); + if (!JS::Evaluate(cx, options, source, &eval_result)) + return false; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); gjs->schedule_gc_if_needed(); - if (result.isUndefined()) - return true; + result.set(eval_result); + return true; +} + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_console_interact(JSContext* context, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject global(context, gjs_get_import_global(context)); - JS::AutoSaveExceptionState exc_state(cx); - JS::RootedValue v_printed_string(cx); - JS::RootedValue v_pretty_print( - cx, gjs_get_global_slot(global, GjsGlobalSlot::PRETTY_PRINT_FUNC)); - bool ok = JS::Call(cx, global, v_pretty_print, JS::HandleValueArray(result), - &v_printed_string); - if (!ok) - gjs_log_exception(cx); - exc_state.restore(); - - if (ok) { - g_fprintf(stdout, "%s\n", - print_string_value(cx, v_printed_string).c_str()); - } else { - g_fprintf(stdout, "[error printing value]\n"); + JS::UniqueChars prompt; + if (!gjs_parse_call_args(context, "interact", args, "s", "prompt", &prompt)) + return false; + + GjsAutoChar buffer; + if (!gjs_console_readline(buffer.out(), prompt.get())) { + return true; } + return gjs_string_from_utf8(context, buffer, args.rval()); +} + +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_console_enable_raw_mode(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!gjs_parse_call_args(cx, "enableRawMode", args, "")) + return false; + + args.rval().setBoolean(Gjs::Console::enable_raw_mode()); return true; } GJS_JSAPI_RETURN_CONVENTION -static bool -gjs_console_interact(JSContext *context, - unsigned argc, - JS::Value *vp) -{ - JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); - volatile bool eof, exit_warning; // accessed after setjmp() - JS::RootedObject global(context, gjs_get_import_global(context)); - char* temp_buf; - volatile int lineno; // accessed after setjmp() - volatile int startline; // accessed after setjmp() +static bool gjs_console_disable_raw_mode(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!gjs_parse_call_args(cx, "disableRawMode", args, "")) + return false; -#ifndef HAVE_READLINE_READLINE_H - int rl_end = 0; // nonzero if using readline and any text is typed in -#endif + args.rval().setBoolean(Gjs::Console::disable_raw_mode()); + return true; +} - JS::SetWarningReporter(context, gjs_console_warning_reporter); - - AutoCatchCtrlC ctrl_c; - - // Separate initialization from declaration because of possible overwriting - // when siglongjmp() jumps into this function - eof = exit_warning = false; - temp_buf = nullptr; - lineno = 1; - do { - /* - * Accumulate lines until we get a 'compilable unit' - one that either - * generates an error (before running out of source) or that compiles - * cleanly. This should be whenever we get a complete statement that - * coincides with the end of a line. - */ - startline = lineno; - std::string buffer; - do { -#ifdef HAVE_SIGNAL_H - // sigsetjmp() returns 0 if control flow encounters it normally, and - // nonzero if it's been jumped to. In the latter case, use a while - // loop so that we call sigsetjmp() a second time to reinit the jump - // buffer. - while (sigsetjmp(AutoCatchCtrlC::jump_buffer, 1) != 0) { - g_fprintf(stdout, "\n"); - if (buffer.empty() && rl_end == 0) { - if (!exit_warning) { - g_fprintf(stdout, - "(To exit, press Ctrl+C again or Ctrl+D)\n"); - exit_warning = true; - } else { - ctrl_c.raise_default(); - } - } else { - exit_warning = false; - } - buffer.clear(); - startline = lineno = 1; - } -#endif // HAVE_SIGNAL_H - - if (!gjs_console_readline( - &temp_buf, startline == lineno ? "gjs> " : ".... ")) { - eof = true; - break; - } - buffer += temp_buf; - buffer += "\n"; - g_free(temp_buf); - lineno++; - } while (!JS_Utf8BufferIsCompilableUnit(context, global, buffer.c_str(), - buffer.size())); - - bool ok; - { - AutoReportException are(context); - ok = gjs_console_eval_and_print(context, global, buffer, startline); - } - exit_warning = false; - - GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); - ok = gjs->run_jobs_fallible() && ok; - - if (!ok) { - /* If this was an uncatchable exception, throw another uncatchable - * exception on up to the surrounding JS::Evaluate() in main(). This - * happens when you run gjs-console and type imports.system.exit(0); - * at the prompt. If we don't throw another uncatchable exception - * here, then it's swallowed and main() won't exit. */ - return false; - } - } while (!eof); +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_console_eval_js(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::UniqueChars expr; + int lineno; + if (!gjs_parse_call_args(cx, "eval", args, "si", "expression", &expr, + "lineNumber", &lineno)) + return false; + + return gjs_console_eval(cx, std::string(expr.get()), lineno, args.rval()); +} - g_fprintf(stdout, "\n"); +GJS_JSAPI_RETURN_CONVENTION +static bool gjs_console_is_valid_js(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedString str(cx); + if (!gjs_parse_call_args(cx, "isValid", args, "S", "code", &str)) + return false; + + JS::UniqueChars code; + size_t code_len; + if (!gjs_string_to_utf8_n(cx, str, &code, &code_len)) + return false; - argv.rval().setUndefined(); + JS::RootedObject global(cx, gjs_get_import_global(cx)); + + args.rval().setBoolean( + JS_Utf8BufferIsCompilableUnit(cx, global, code.get(), code_len)); return true; } @@ -329,10 +214,57 @@ static bool gjs_console_clear_terminal(JSContext* cx, unsigned argc, return true; } +bool gjs_console_get_terminal_size(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) + return false; + + // Use 'int' because Windows uses int values, whereas most Unix systems + // use 'short' + unsigned int width, height; + + JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); +#ifdef GET_SIZE_USE_IOCTL + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) { + gjs_throw(cx, "No terminal output is present.\n"); + return false; + } + + width = ws.ws_col; + height = ws.ws_row; +#else + // TODO(ewlsh): Implement Windows equivalent. + // See + // https://docs.microsoft.com/en-us/windows/console/window-and-screen-buffer-size. + gjs_throw(cx, "Unable to retrieve terminal size on this platform.\n"); + return false; +#endif + + const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); + if (!JS_DefinePropertyById(cx, obj, atoms.height(), height, + JSPROP_READONLY) || + !JS_DefinePropertyById(cx, obj, atoms.width(), width, JSPROP_READONLY)) + return false; + + argv.rval().setObject(*obj); + return true; +} + static JSFunctionSpec console_module_funcs[] = { + JS_FN("interact", gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS), + JS_FN("enableRawMode", gjs_console_enable_raw_mode, 0, + GJS_MODULE_PROP_FLAGS), + JS_FN("getDimensions", gjs_console_get_terminal_size, 0, + GJS_MODULE_PROP_FLAGS), + JS_FN("disableRawMode", gjs_console_disable_raw_mode, 0, + GJS_MODULE_PROP_FLAGS), + JS_FN("eval", gjs_console_eval_js, 2, GJS_MODULE_PROP_FLAGS), + JS_FN("isValid", gjs_console_is_valid_js, 1, GJS_MODULE_PROP_FLAGS), JS_FN("clearTerminal", gjs_console_clear_terminal, 1, GJS_MODULE_PROP_FLAGS), - JS_FN("interact", gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS), JS_FS_END, }; |