/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h>#include <string.h>#include <strings.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <dirent.h>#include <math.h>#include <stdint.h> #include <mujs.h> #include "osdep/io.h"#include "mpv_talloc.h"#include "common/common.h"#include "options/m_property.h"#include "common/msg.h"#include "common/msg_control.h"#include "options/m_option.h"#include "input/input.h"#include "options/path.h"#include "misc/bstr.h"#include "osdep/timer.h"#include "osdep/threads.h"#include "osdep/getpid.h"#include "stream/stream.h"#include "sub/osd.h"#include "core.h"#include "command.h"#include "client.h"#include "libmpv/client.h" // List of builtin modules and their contents as strings.// All these are generated from player/javascript/*.jsstatic const char *const builtin_files[][3] = { {"@/defaults.js",# include "generated/player/javascript/defaults.js.inc" }, {0}}; // Represents a loaded script. Each has its own js state.struct script_ctx { const char *filename; const char *path; // NULL if single file struct mpv_handle *client; struct MPContext *mpctx; struct mp_log *log; char *last_error_str;}; static struct script_ctx *jctx(js_State *J){ return (struct script_ctx *)js_getcontext(J);} static mpv_handle *jclient(js_State *J){ return jctx(J)->client;} static void pushnode(js_State *J, mpv_node *node);static void makenode(void *ta_ctx, mpv_node *dst, js_State *J, int idx);static int jsL_checkint(js_State *J, int idx);static uint64_t jsL_checkuint64(js_State *J, int idx); /********************************************************************** * conventions, MuJS notes and vm errors *********************************************************************/// - push_foo functions are called from C and push a value to the vm stack.//// - JavaScript C functions are code which the vm can call as a js function.// By convention, script_bar and script__baz are js C functions. The former// is exposed to end users as bar, and _baz is for internal use.//// - js C functions get a fresh vm stack with their arguments, and may// manipulate their stack as they see fit. On exit, the vm considers the// top value of their stack as their return value, and GC the rest.//// - js C function's stack[0] is "this", and the rest (1, 2, ...) are the args.// On entry the stack has at least the number of args defined for the func,// padded with undefined if called with less, or bigger if called with more.//// - Almost all vm APIs (js_*) may throw an error - a longjmp to the last// recovery/catch point, which could skip releasing resources. Use protected// code (e.g. js_pcall) between aquisition and release. Alternatively, use// the autofree mechanism to manage it more easily. See more details below.//// - Unless named s_foo, all the functions at this file (inc. init) which// touch the vm may throw, but either cleanup resources regardless (mostly// autofree) or leave allocated resources on caller-provided talloc context// which the caller should release, typically with autofree (e.g. makenode).//// - Functions named s_foo (safe foo) never throw, return 0 on success, else 1. /********************************************************************** * mpv scripting API error handling *********************************************************************/// - Errors may be thrown on some cases - the reason is at the exception.//// - Some APIs also set last error which can be fetched with mp.last_error(),// where empty string (false-y) is success, or an error string otherwise.//// - The rest of the APIs are guaranteed to return undefined on error or a// true-thy value on success and may or may not set last error.//// - push_success, push_failure, push_status and pushed_error set last error. // iserr as true indicates an error, and if so, str may indicate a reason.// Internally ctx->last_error_str is never NULL, and empty indicates success.static void set_last_error(struct script_ctx *ctx, bool iserr, const char *str){ ctx->last_error_str[0] = 0; if (!iserr) return; if (!str || !str[0]) str = "Error"; ctx->last_error_str = talloc_strdup_append(ctx->last_error_str, str);} // For use only by wrappers at defaults.js.// arg: error string. Use empty string to indicate success.static void script__set_last_error(js_State *J){ const char *e = js_tostring(J, 1); set_last_error(jctx(J), e[0], e);} // mp.last_error() . args: none. return the last error without modifying it.static void script_last_error(js_State *J){ js_pushstring(J, jctx(J)->last_error_str);} // Generic success for APIs which don't return an actual value.static void push_success(js_State *J){ set_last_error(jctx(J), 0, NULL); js_pushboolean(J, true);} // Doesn't (intentionally) throw. Just sets last_error and pushes undefinedstatic void push_failure(js_State *J, const char *str){ set_last_error(jctx(J), 1, str); js_pushundefined(J);} // Most of the scripting APIs are either sending some values and getting status// code in return, or requesting some value while providing a default in case an// error happened. These simplify the C code for that and always set last_error. static void push_status(js_State *J, int err){ if (err >= 0) { push_success(J); } else { push_failure(J, mpv_error_string(err)); }} // If err is success then return 0, else push the item at def and return 1static bool pushed_error(js_State *J, int err, int def){ bool iserr = err < 0; set_last_error(jctx(J), iserr, iserr ? mpv_error_string(err) : NULL); if (!iserr) return false; js_copy(J, def); return true;} /********************************************************************** * Autofree - care-free resource deallocation on vm errors, and otherwise *********************************************************************/// - Autofree (af) functions are called with a talloc context argument which is// freed after the function exits - either normally or because it threw an// error, on the latter case it then re-throws the error after the cleanup.//// Autofree js C functions should have an additional void* talloc arg and// inserted into the vm using af_newcfunction, but otherwise used normally.//// To wrap an autofree function af_TARGET in C:// 1. Create a wrapper s_TARGET which runs af_TARGET safely inside js_try.// 2. Use s_TARGET like so (always autofree, and throws if af_TARGET threw):// void *af = talloc_new(NULL);// int r = s_TARGET(J, ..., af); // use J, af where the callee expects.// talloc_free(af);// if (r)// js_throw(J); // add_af_file, add_af_dir, add_af_mpv_alloc take a valid FILE*/DIR*/char* value// respectively, and fclose/closedir/mpv_free it when the parent is freed. static void destruct_af_file(void *p){ fclose(*(FILE**)p);} static void add_af_file(void *parent, FILE *f){ FILE **pf = talloc(parent, FILE*); *pf = f; talloc_set_destructor(pf, destruct_af_file);} static void destruct_af_dir(void *p){ closedir(*(DIR**)p);} static void add_af_dir(void *parent, DIR *d){ DIR **pd = talloc(parent, DIR*); *pd = d; talloc_set_destructor(pd, destruct_af_dir);} static void destruct_af_mpv_alloc(void *p){ mpv_free(*(char**)p);} static void add_af_mpv_alloc(void *parent, char *ma){ char **p = talloc(parent, char*); *p = ma; talloc_set_destructor(p, destruct_af_mpv_alloc);} static void destruct_af_mpv_node(void *p){ mpv_free_node_contents((mpv_node*)p); // does nothing for MPV_FORMAT_NONE} // returns a new zeroed allocated struct mpv_node, and free it and its content// when the parent is freed.static mpv_node *new_af_mpv_node(void *parent){ mpv_node *p = talloc_zero(parent, mpv_node); // .format == MPV_FORMAT_NONE talloc_set_destructor(p, destruct_af_mpv_node); return p;} // Prototype for autofree functions which can be called from inside the vm.typedef void (*af_CFunction)(js_State*, void*); // safely run autofree js c function directlystatic int s_run_af_jsc(js_State *J, af_CFunction fn, void *af){ if (js_try(J)) return 1; fn(J, af); js_endtry(J); return 0;} // The trampoline function through which all autofree functions are called from// inside the vm. Obtains the target function address and autofree-call it.static void script__autofree(js_State *J){ // The target function is at the "af_" property of this function instance. js_currentfunction(J); js_getproperty(J, -1, "af_"); af_CFunction fn = (af_CFunction)js_touserdata(J, -1, "af_fn"); js_pop(J, 2); void *af = talloc_new(NULL); int r = s_run_af_jsc(J, fn, af); talloc_free(af); if (r) js_throw(J);} // Identical to js_newcfunction, but the function is inserted with an autofree// wrapper, and its prototype should have the additional af argument.static void af_newcfunction(js_State *J, af_CFunction fn, const char *name, int length){ js_newcfunction(J, script__autofree, name, length); js_pushnull(J); // a prototype for the userdata object js_newuserdata(J, "af_fn", fn, NULL); // uses a "af_fn" verification tag js_defproperty(J, -2, "af_", JS_READONLY | JS_DONTENUM | JS_DONTCONF);} /********************************************************************** * Initialization and file loading *********************************************************************/ static const char *get_builtin_file(const char *name){ for (int n = 0; builtin_files[n][0]; n++) { if (strcmp(builtin_files[n][0], name) == 0) return builtin_files[n][1]; } return NULL;} // Push up to limit bytes of file fname: from builtin_files, else from the OS.static void af_push_file(js_State *J, const char *fname, int limit, void *af){ char *filename = mp_get_user_path(af, jctx(J)->mpctx->global, fname); MP_VERBOSE(jctx(J), "Reading file '%s'\n", filename); if (limit < 0) limit = INT_MAX - 1; const char *builtin = get_builtin_file(filename); if (builtin) { js_pushlstring(J, builtin, MPMIN(limit, strlen(builtin))); return; } FILE *f = fopen(filename, "rb"); if (!f) js_error(J, "cannot open file: '%s'", filename); add_af_file(af, f); int len = MPMIN(limit, 32 * 1024); // initial allocation, size*2 strategy int got = 0; char *s = NULL; while ((s = talloc_realloc(af, s, char, len))) { int want = len - got; int r = fread(s + got, 1, want, f); if (feof(f) || (len == limit && r == want)) { js_pushlstring(J, s, got + r); return; } if (r != want) js_error(J, "cannot read data from file: '%s'", filename); got = got + r; len = MPMIN(limit, len * 2); } js_error(J, "cannot allocate %d bytes for file: '%s'", len, filename);} // Safely run af_push_file.static int s_push_file(js_State *J, const char *fname, int limit, void *af){ if (js_try(J)) return 1; af_push_file(J, fname, limit, af); js_endtry(J); return 0;} // Called directly, push up to limit bytes of file fname (from builtin/os).static void push_file_content(js_State *J, const char *fname, int limit){ void *af = talloc_new(NULL); int r = s_push_file(J, fname, limit, af); talloc_free(af); if (r) js_throw(J);} // utils.read_file(..). args: fname [,max]. returns [up to max] bytes as string.static void script_read_file(js_State *J){ int limit = js_isundefined(J, 2) ? -1 : jsL_checkint(J, 2); push_file_content(J, js_tostring(J, 1), limit);} // Runs a file with the caller's this, leaves the stack as is.static void run_file(js_State *J, const char *fname){ MP_VERBOSE(jctx(J), "Loading file %s\n", fname); push_file_content(J, fname, -1); js_loadstring(J, fname, js_tostring(J, -1)); js_copy(J, 0); // use the caller's this js_call(J, 0); js_pop(J, 2); // result, file content} // The spec defines .name and .message for Error objects. Most engines also set// a very convenient .stack = name + message + trace, but MuJS instead sets// .stackTrace = trace only. Normalize by adding such .stack if required.// Run this before anything such that we can get traces on any following errors.static const char *norm_err_proto_js = "\ if (Error().stackTrace && !Error().stack) {\ Object.defineProperty(Error.prototype, 'stack', {\ get: function() {\ return this.name + ': ' + this.message + this.stackTrace;\ }\ });\ }\"; static void add_functions(js_State*, struct script_ctx*); // args: none. called as script, setup and run the main scriptstatic void script__run_script(js_State *J){ js_loadstring(J, "@/norm_err.js", norm_err_proto_js); js_copy(J, 0); js_pcall(J, 0); struct script_ctx *ctx = jctx(J); add_functions(J, ctx); run_file(J, "@/defaults.js"); run_file(J, ctx->filename); // the main file to run if (!js_hasproperty(J, 0, "mp_event_loop") || !js_iscallable(J, -1)) js_error(J, "no event loop function"); js_copy(J, 0); js_call(J, 0); // mp_event_loop} // Safely set last error from stack top: stack trace or toString or generic.// May leave items on stack - the caller should detect and pop if it cares.static void s_top_to_last_error(struct script_ctx *ctx, js_State *J){ set_last_error(ctx, 1, "unknown error"); if (js_try(J)) return; if (js_isobject(J, -1)) js_hasproperty(J, -1, "stack"); // fetches it if exists set_last_error(ctx, 1, js_tostring(J, -1)); js_endtry(J);} // MuJS can report warnings through this.static void report_handler(js_State *J, const char *msg){ MP_WARN(jctx(J), "[JS] %s\n", msg);} // Safely setup the js vm for calling run_script.static int s_init_js(js_State *J, struct script_ctx *ctx){ if (js_try(J)) return 1; js_setcontext(J, ctx); js_setreport(J, report_handler); js_newcfunction(J, script__run_script, "run_script", 0); js_pushglobal(J); // 'this' for script__run_script js_endtry(J); return 0;} /********************************************************************** * Initialization - booting the script *********************************************************************/// s_load_javascript: (entry point) creates the js vm, runs the script, returns// on script exit or uncaught js errors. Never throws.// script__run_script: - loads the built in functions and vars into the vm// - runs the default file[s] and the main script file// - calls mp_event_loop, returns on script-exit or throws.//// Note: init functions don't need autofree. They can use ctx as a talloc// context and free normally. If they throw - ctx is freed right afterwards.static int s_load_javascript(struct mp_script_args *args){ struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { .client = args->client, .mpctx = args->mpctx, .log = args->log, .last_error_str = talloc_strdup(ctx, "Cannot initialize JavaScript"), .filename = args->filename, .path = args->path, }; int r = -1; js_State *J = js_newstate(NULL, NULL, 0); if (!J || s_init_js(J, ctx)) goto error_out; set_last_error(ctx, 0, NULL); if (js_pcall(J, 0)) { // script__run_script s_top_to_last_error(ctx, J); goto error_out; } r = 0; error_out: if (r) MP_FATAL(ctx, "%s\n", ctx->last_error_str); if (J)Lines 1–500 of 1215. Load more
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h> #include <string.h> #include <strings.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> #include <math.h> #include <stdint.h> #include <mujs.h> #include "osdep/io.h" #include "mpv_talloc.h" #include "common/common.h" #include "options/m_property.h" #include "common/msg.h" #include "common/msg_control.h" #include "options/m_option.h" #include "input/input.h" #include "options/path.h" #include "misc/bstr.h" #include "osdep/timer.h" #include "osdep/threads.h" #include "osdep/getpid.h" #include "stream/stream.h" #include "sub/osd.h" #include "core.h" #include "command.h" #include "client.h" #include "libmpv/client.h" // List of builtin modules and their contents as strings. // All these are generated from player/javascript/*.js static const char *const builtin_files[][3] = { {"@/defaults.js", # include "generated/player/javascript/defaults.js.inc" }, {0} }; // Represents a loaded script. Each has its own js state. struct script_ctx { const char *filename; const char *path; // NULL if single file struct mpv_handle *client; struct MPContext *mpctx; struct mp_log *log; char *last_error_str; }; static struct script_ctx *jctx(js_State *J) { return (struct script_ctx *)js_getcontext(J); } static mpv_handle *jclient(js_State *J) { return jctx(J)->client; } static void pushnode(js_State *J, mpv_node *node); static void makenode(void *ta_ctx, mpv_node *dst, js_State *J, int idx); static int jsL_checkint(js_State *J, int idx); static uint64_t jsL_checkuint64(js_State *J, int idx); /********************************************************************** * conventions, MuJS notes and vm errors *********************************************************************/ // - push_foo functions are called from C and push a value to the vm stack. // // - JavaScript C functions are code which the vm can call as a js function. // By convention, script_bar and script__baz are js C functions. The former // is exposed to end users as bar, and _baz is for internal use. // // - js C functions get a fresh vm stack with their arguments, and may // manipulate their stack as they see fit. On exit, the vm considers the // top value of their stack as their return value, and GC the rest. // // - js C function's stack[0] is "this", and the rest (1, 2, ...) are the args. // On entry the stack has at least the number of args defined for the func, // padded with undefined if called with less, or bigger if called with more. // // - Almost all vm APIs (js_*) may throw an error - a longjmp to the last // recovery/catch point, which could skip releasing resources. Use protected // code (e.g. js_pcall) between aquisition and release. Alternatively, use // the autofree mechanism to manage it more easily. See more details below. // // - Unless named s_foo, all the functions at this file (inc. init) which // touch the vm may throw, but either cleanup resources regardless (mostly // autofree) or leave allocated resources on caller-provided talloc context // which the caller should release, typically with autofree (e.g. makenode). // // - Functions named s_foo (safe foo) never throw, return 0 on success, else 1. /********************************************************************** * mpv scripting API error handling *********************************************************************/ // - Errors may be thrown on some cases - the reason is at the exception. // // - Some APIs also set last error which can be fetched with mp.last_error(), // where empty string (false-y) is success, or an error string otherwise. // // - The rest of the APIs are guaranteed to return undefined on error or a // true-thy value on success and may or may not set last error. // // - push_success, push_failure, push_status and pushed_error set last error. // iserr as true indicates an error, and if so, str may indicate a reason. // Internally ctx->last_error_str is never NULL, and empty indicates success. static void set_last_error(struct script_ctx *ctx, bool iserr, const char *str) { ctx->last_error_str[0] = 0; if (!iserr) return; if (!str || !str[0]) str = "Error"; ctx->last_error_str = talloc_strdup_append(ctx->last_error_str, str); } // For use only by wrappers at defaults.js. // arg: error string. Use empty string to indicate success. static void script__set_last_error(js_State *J) { const char *e = js_tostring(J, 1); set_last_error(jctx(J), e[0], e); } // mp.last_error() . args: none. return the last error without modifying it. static void script_last_error(js_State *J) { js_pushstring(J, jctx(J)->last_error_str); } // Generic success for APIs which don't return an actual value. static void push_success(js_State *J) { set_last_error(jctx(J), 0, NULL); js_pushboolean(J, true); } // Doesn't (intentionally) throw. Just sets last_error and pushes undefined static void push_failure(js_State *J, const char *str) { set_last_error(jctx(J), 1, str); js_pushundefined(J); } // Most of the scripting APIs are either sending some values and getting status // code in return, or requesting some value while providing a default in case an // error happened. These simplify the C code for that and always set last_error. static void push_status(js_State *J, int err) { if (err >= 0) { push_success(J); } else { push_failure(J, mpv_error_string(err)); } } // If err is success then return 0, else push the item at def and return 1 static bool pushed_error(js_State *J, int err, int def) { bool iserr = err < 0; set_last_error(jctx(J), iserr, iserr ? mpv_error_string(err) : NULL); if (!iserr) return false; js_copy(J, def); return true; } /********************************************************************** * Autofree - care-free resource deallocation on vm errors, and otherwise *********************************************************************/ // - Autofree (af) functions are called with a talloc context argument which is // freed after the function exits - either normally or because it threw an // error, on the latter case it then re-throws the error after the cleanup. // // Autofree js C functions should have an additional void* talloc arg and // inserted into the vm using af_newcfunction, but otherwise used normally. // // To wrap an autofree function af_TARGET in C: // 1. Create a wrapper s_TARGET which runs af_TARGET safely inside js_try. // 2. Use s_TARGET like so (always autofree, and throws if af_TARGET threw): // void *af = talloc_new(NULL); // int r = s_TARGET(J, ..., af); // use J, af where the callee expects. // talloc_free(af); // if (r) // js_throw(J); // add_af_file, add_af_dir, add_af_mpv_alloc take a valid FILE*/DIR*/char* value // respectively, and fclose/closedir/mpv_free it when the parent is freed. static void destruct_af_file(void *p) { fclose(*(FILE**)p); } static void add_af_file(void *parent, FILE *f) { FILE **pf = talloc(parent, FILE*); *pf = f; talloc_set_destructor(pf, destruct_af_file); } static void destruct_af_dir(void *p) { closedir(*(DIR**)p); } static void add_af_dir(void *parent, DIR *d) { DIR **pd = talloc(parent, DIR*); *pd = d; talloc_set_destructor(pd, destruct_af_dir); } static void destruct_af_mpv_alloc(void *p) { mpv_free(*(char**)p); } static void add_af_mpv_alloc(void *parent, char *ma) { char **p = talloc(parent, char*); *p = ma; talloc_set_destructor(p, destruct_af_mpv_alloc); } static void destruct_af_mpv_node(void *p) { mpv_free_node_contents((mpv_node*)p); // does nothing for MPV_FORMAT_NONE } // returns a new zeroed allocated struct mpv_node, and free it and its content // when the parent is freed. static mpv_node *new_af_mpv_node(void *parent) { mpv_node *p = talloc_zero(parent, mpv_node); // .format == MPV_FORMAT_NONE talloc_set_destructor(p, destruct_af_mpv_node); return p; } // Prototype for autofree functions which can be called from inside the vm. typedef void (*af_CFunction)(js_State*, void*); // safely run autofree js c function directly static int s_run_af_jsc(js_State *J, af_CFunction fn, void *af) { if (js_try(J)) return 1; fn(J, af); js_endtry(J); return 0; } // The trampoline function through which all autofree functions are called from // inside the vm. Obtains the target function address and autofree-call it. static void script__autofree(js_State *J) { // The target function is at the "af_" property of this function instance. js_currentfunction(J); js_getproperty(J, -1, "af_"); af_CFunction fn = (af_CFunction)js_touserdata(J, -1, "af_fn"); js_pop(J, 2); void *af = talloc_new(NULL); int r = s_run_af_jsc(J, fn, af); talloc_free(af); if (r) js_throw(J); } // Identical to js_newcfunction, but the function is inserted with an autofree // wrapper, and its prototype should have the additional af argument. static void af_newcfunction(js_State *J, af_CFunction fn, const char *name, int length) { js_newcfunction(J, script__autofree, name, length); js_pushnull(J); // a prototype for the userdata object js_newuserdata(J, "af_fn", fn, NULL); // uses a "af_fn" verification tag js_defproperty(J, -2, "af_", JS_READONLY | JS_DONTENUM | JS_DONTCONF); } /********************************************************************** * Initialization and file loading *********************************************************************/ static const char *get_builtin_file(const char *name) { for (int n = 0; builtin_files[n][0]; n++) { if (strcmp(builtin_files[n][0], name) == 0) return builtin_files[n][1]; } return NULL; } // Push up to limit bytes of file fname: from builtin_files, else from the OS. static void af_push_file(js_State *J, const char *fname, int limit, void *af) { char *filename = mp_get_user_path(af, jctx(J)->mpctx->global, fname); MP_VERBOSE(jctx(J), "Reading file '%s'\n", filename); if (limit < 0) limit = INT_MAX - 1; const char *builtin = get_builtin_file(filename); if (builtin) { js_pushlstring(J, builtin, MPMIN(limit, strlen(builtin))); return; } FILE *f = fopen(filename, "rb"); if (!f) js_error(J, "cannot open file: '%s'", filename); add_af_file(af, f); int len = MPMIN(limit, 32 * 1024); // initial allocation, size*2 strategy int got = 0; char *s = NULL; while ((s = talloc_realloc(af, s, char, len))) { int want = len - got; int r = fread(s + got, 1, want, f); if (feof(f) || (len == limit && r == want)) { js_pushlstring(J, s, got + r); return; } if (r != want) js_error(J, "cannot read data from file: '%s'", filename); got = got + r; len = MPMIN(limit, len * 2); } js_error(J, "cannot allocate %d bytes for file: '%s'", len, filename); } // Safely run af_push_file. static int s_push_file(js_State *J, const char *fname, int limit, void *af) { if (js_try(J)) return 1; af_push_file(J, fname, limit, af); js_endtry(J); return 0; } // Called directly, push up to limit bytes of file fname (from builtin/os). static void push_file_content(js_State *J, const char *fname, int limit) { void *af = talloc_new(NULL); int r = s_push_file(J, fname, limit, af); talloc_free(af); if (r) js_throw(J); } // utils.read_file(..). args: fname [,max]. returns [up to max] bytes as string. static void script_read_file(js_State *J) { int limit = js_isundefined(J, 2) ? -1 : jsL_checkint(J, 2); push_file_content(J, js_tostring(J, 1), limit); } // Runs a file with the caller's this, leaves the stack as is. static void run_file(js_State *J, const char *fname) { MP_VERBOSE(jctx(J), "Loading file %s\n", fname); push_file_content(J, fname, -1); js_loadstring(J, fname, js_tostring(J, -1)); js_copy(J, 0); // use the caller's this js_call(J, 0); js_pop(J, 2); // result, file content } // The spec defines .name and .message for Error objects. Most engines also set // a very convenient .stack = name + message + trace, but MuJS instead sets // .stackTrace = trace only. Normalize by adding such .stack if required. // Run this before anything such that we can get traces on any following errors. static const char *norm_err_proto_js = "\ if (Error().stackTrace && !Error().stack) {\ Object.defineProperty(Error.prototype, 'stack', {\ get: function() {\ return this.name + ': ' + this.message + this.stackTrace;\ }\ });\ }\ "; static void add_functions(js_State*, struct script_ctx*); // args: none. called as script, setup and run the main script static void script__run_script(js_State *J) { js_loadstring(J, "@/norm_err.js", norm_err_proto_js); js_copy(J, 0); js_pcall(J, 0); struct script_ctx *ctx = jctx(J); add_functions(J, ctx); run_file(J, "@/defaults.js"); run_file(J, ctx->filename); // the main file to run if (!js_hasproperty(J, 0, "mp_event_loop") || !js_iscallable(J, -1)) js_error(J, "no event loop function"); js_copy(J, 0); js_call(J, 0); // mp_event_loop } // Safely set last error from stack top: stack trace or toString or generic. // May leave items on stack - the caller should detect and pop if it cares. static void s_top_to_last_error(struct script_ctx *ctx, js_State *J) { set_last_error(ctx, 1, "unknown error"); if (js_try(J)) return; if (js_isobject(J, -1)) js_hasproperty(J, -1, "stack"); // fetches it if exists set_last_error(ctx, 1, js_tostring(J, -1)); js_endtry(J); } // MuJS can report warnings through this. static void report_handler(js_State *J, const char *msg) { MP_WARN(jctx(J), "[JS] %s\n", msg); } // Safely setup the js vm for calling run_script. static int s_init_js(js_State *J, struct script_ctx *ctx) { if (js_try(J)) return 1; js_setcontext(J, ctx); js_setreport(J, report_handler); js_newcfunction(J, script__run_script, "run_script", 0); js_pushglobal(J); // 'this' for script__run_script js_endtry(J); return 0; } /********************************************************************** * Initialization - booting the script *********************************************************************/ // s_load_javascript: (entry point) creates the js vm, runs the script, returns // on script exit or uncaught js errors. Never throws. // script__run_script: - loads the built in functions and vars into the vm // - runs the default file[s] and the main script file // - calls mp_event_loop, returns on script-exit or throws. // // Note: init functions don't need autofree. They can use ctx as a talloc // context and free normally. If they throw - ctx is freed right afterwards. static int s_load_javascript(struct mp_script_args *args) { struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { .client = args->client, .mpctx = args->mpctx, .log = args->log, .last_error_str = talloc_strdup(ctx, "Cannot initialize JavaScript"), .filename = args->filename, .path = args->path, }; int r = -1; js_State *J = js_newstate(NULL, NULL, 0); if (!J || s_init_js(J, ctx)) goto error_out; set_last_error(ctx, 0, NULL); if (js_pcall(J, 0)) { // script__run_script s_top_to_last_error(ctx, J); goto error_out; } r = 0; error_out: if (r) MP_FATAL(ctx, "%s\n", ctx->last_error_str); if (J)