static JSBool gjs_main_loop_quit(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *retval) { char *cancel_id; GMainLoop *main_loop; if (!gjs_parse_args(context, "quit", "s", argc, argv, "cancelId", &cancel_id)) return JS_FALSE; main_loop = g_hash_table_lookup(pending_main_loops, cancel_id); if (!main_loop) { g_free(cancel_id); gjs_throw(context, "No main loop with this id"); return JS_FALSE; } g_hash_table_remove(pending_main_loops, cancel_id); if (!g_main_loop_is_running(main_loop)) { g_free(cancel_id); gjs_throw(context, "Main loop was stopped already"); return JS_FALSE; } gjs_debug(GJS_DEBUG_MAINLOOP, "main loop %s quitting in context %p", cancel_id, context); g_free(cancel_id); g_main_loop_quit(main_loop); return JS_TRUE; }
static JSBool import_directory(JSContext *context, JSObject *obj, const std::string &name, std::vector<std::string> full_paths) { JSObject *importer; gjs_debug(GJS_DEBUG_IMPORTER, "Importing directory '%s'", name); /* We define a sub-importer that has only the given directories on * its search path. gjs_define_importer() exits if it fails, so * this always succeeds. */ importer = gjs_define_importer(context, obj, name, full_paths, false); if (importer == NULL) return JS_FALSE; return JS_TRUE; }
void gjs_define_param_class(JSContext *context, JS::HandleObject in_object) { const char *constructor_name; JS::RootedObject prototype(context), constructor(context); GIObjectInfo *info; constructor_name = "ParamSpec"; if (!gjs_init_class_dynamic(context, in_object, nullptr, "GObject", constructor_name, &gjs_param_class, gjs_param_constructor, 0, /* props of prototype */ &gjs_param_proto_props[0], /* funcs of prototype */ &gjs_param_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ gjs_param_constructor_funcs, &prototype, &constructor)) { g_error("Can't init class %s", constructor_name); } JS::RootedObject gtype_obj(context, gjs_gtype_create_gtype_wrapper(context, G_TYPE_PARAM)); JS_DefineProperty(context, constructor, "$gtype", gtype_obj, JSPROP_PERMANENT); info = (GIObjectInfo*)g_irepository_find_by_gtype(g_irepository_get_default(), G_TYPE_PARAM); gjs_object_define_static_methods(context, constructor, G_TYPE_PARAM, info); g_base_info_unref( (GIBaseInfo*) info); gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype.get(), JS_GetClass(prototype), in_object.get()); }
/* The "load context" is the one we use for loading * modules and initializing classes. */ JSContext* gjs_runtime_get_load_context(JSRuntime *runtime) { GjsContext *context; context = gjs_runtime_get_data(runtime, "gjs-load-context"); if (context == NULL) { gjs_debug(GJS_DEBUG_CONTEXT, "Creating load context for runtime %p", runtime); context = g_object_new(GJS_TYPE_CONTEXT, "runtime", runtime, "is-load-context", TRUE, NULL); gjs_runtime_set_data(runtime, "gjs-load-context", context, g_object_unref); } return (JSContext*)gjs_context_get_native_context(context); }
JSObject* gjs_define_importer(JSContext *context, JSObject *in_object, const std::string &importer_name, std::vector<std::string> initial_search_path, bool add_standard_search_path) { JSObject *importer; importer = gjs_create_importer(context, importer_name, initial_search_path, add_standard_search_path, false, in_object); if (!JS_DefineProperty(context, in_object, importer_name.c_str(), OBJECT_TO_JSVAL(importer), NULL, NULL, GJS_MODULE_PROP_FLAGS)) g_error("no memory to define importer property"); gjs_debug(GJS_DEBUG_IMPORTER, "Defined importer '%s' %p in %p", importer_name, importer, in_object); return importer; }
JSObject* gjs_define_importer(JSContext *context, JSObject *in_object, const char *importer_name, const char **initial_search_path, gboolean add_standard_search_path) { JSObject *importer; importer = gjs_create_importer(context, importer_name, initial_search_path, add_standard_search_path, FALSE, in_object); if (!JS_DefineProperty(context, in_object, importer_name, OBJECT_TO_JSVAL(importer), NULL, NULL, GJS_MODULE_PROP_FLAGS)) g_error("no memory to define importer property"); gjs_debug(GJS_DEBUG_IMPORTER, "Defined importer '%s' %p in %p", importer_name, importer, in_object); return importer; }
void gjs_define_param_class(JSContext *context, JSObject *in_object) { const char *constructor_name; JSObject *prototype; jsval value; JSObject *constructor; constructor_name = "ParamSpec"; if (!gjs_init_class_dynamic(context, in_object, NULL, "GObject", constructor_name, &gjs_param_class, gjs_param_constructor, 0, /* props of prototype */ &gjs_param_proto_props[0], /* funcs of prototype */ &gjs_param_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ gjs_param_constructor_funcs, &prototype, &constructor)) { g_error("Can't init class %s", constructor_name); } value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, G_TYPE_PARAM)); JS_DefineProperty(context, constructor, "$gtype", value, NULL, NULL, JSPROP_PERMANENT); gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype, JS_GetClass(prototype), in_object); }
/** * gjs_import_native_module: * @context: * @module_obj: * * Return a native module that's been preloaded. */ JSBool gjs_import_native_module(JSContext *context, JSObject *module_obj, const char *name) { GjsNativeModule *native_module; JSObject *parent; gjs_debug(GJS_DEBUG_NATIVE, "Defining native module '%s'", name); if (modules != NULL) native_module = g_hash_table_lookup(modules, name); else native_module = NULL; if (!native_module) { gjs_throw(context, "No native module '%s' has registered itself", name); return JS_FALSE; } if (native_module->flags & GJS_NATIVE_SUPPLIES_MODULE_OBJ) { /* In this case we just throw away "module_obj" eventually, * since the native module defines itself in the parent of * module_obj directly. */ parent = module_get_parent(context, module_obj); return (* native_module->func) (context, parent); } else { return (* native_module->func) (context, module_obj); } }
static JSObject* importer_new(JSContext *context, gboolean is_root) { JSObject *importer; Importer *priv; JSObject *global; JSBool found; global = gjs_get_import_global(context); if (!JS_HasProperty(context, global, gjs_importer_class.name, &found)) g_error("HasProperty call failed creating importer class"); if (!found) { JSObject *prototype; prototype = JS_InitClass(context, global, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ NULL, &gjs_importer_class, /* constructor for instances (NULL for * none - just name the prototype like * Math - rarely correct) */ gjs_importer_constructor, /* number of constructor args */ 0, /* props of prototype */ &gjs_importer_proto_props[0], /* funcs of prototype */ &gjs_importer_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL); if (prototype == NULL) g_error("Can't init class %s", gjs_importer_class.name); gjs_debug(GJS_DEBUG_IMPORTER, "Initialized class %s prototype %p", gjs_importer_class.name, prototype); } importer = JS_NewObject(context, &gjs_importer_class, NULL, global); if (importer == NULL) g_error("No memory to create importer importer"); priv = g_slice_new0(Importer); priv->is_root = is_root; GJS_INC_COUNTER(importer); g_assert(priv_from_js(context, importer) == NULL); JS_SetPrivate(importer, priv); gjs_debug_lifecycle(GJS_DEBUG_IMPORTER, "importer constructor, obj %p priv %p", importer, priv); return importer; }
JSBool gjs_define_error_class(JSContext *context, JSObject *in_object, GIEnumInfo *info, JSObject **constructor_p, JSObject **prototype_p) { const char *constructor_name; GIBoxedInfo *glib_error_info; JSObject *prototype, *parent_proto; JSObject *constructor; jsval value; Error *priv; /* See the comment in gjs_define_boxed_class() for an * explanation of how this all works; Error is pretty much the * same as Boxed (except that we inherit from GLib.Error). */ constructor_name = g_base_info_get_name( (GIBaseInfo*) info); if (gjs_object_get_property(context, in_object, constructor_name, &value)) { JSObject *constructor; if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "Existing property '%s' does not look like a constructor", constructor_name); return JS_FALSE; } constructor = JSVAL_TO_OBJECT(value); gjs_object_get_property(context, constructor, "prototype", &value); if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "error %s prototype property does not appear to exist or has wrong type", constructor_name); return JS_FALSE; } else { if (prototype_p) *prototype_p = JSVAL_TO_OBJECT(value); if (constructor_p) *constructor_p = constructor; return JS_TRUE; } } g_irepository_require(NULL, "GLib", "2.0", 0, NULL); glib_error_info = (GIBoxedInfo*) g_irepository_find_by_name(NULL, "GLib", "Error"); parent_proto = gjs_lookup_boxed_prototype(context, glib_error_info); g_base_info_unref((GIBaseInfo*)glib_error_info); prototype = gjs_init_class_dynamic(context, in_object, parent_proto, g_base_info_get_namespace( (GIBaseInfo*) info), constructor_name, &gjs_error_class, gjs_error_constructor, /* number of constructor args (less can be passed) */ 1, /* props of prototype */ &gjs_error_proto_props[0], /* funcs of prototype */ &gjs_error_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ &gjs_error_constructor_funcs[0]); if (prototype == NULL) { gjs_log_exception(context, NULL); gjs_fatal("Can't init class %s", constructor_name); } g_assert(gjs_object_has_property(context, in_object, constructor_name)); GJS_INC_COUNTER(gerror); priv = g_slice_new0(Error); priv->info = info; g_base_info_ref( (GIBaseInfo*) priv->info); priv->domain = g_quark_from_string (g_enum_info_get_error_domain(priv->info)); JS_SetPrivate(context, prototype, priv); gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype, JS_GET_CLASS(context, prototype), in_object); constructor = NULL; gjs_object_get_property(context, in_object, constructor_name, &value); if (!JSVAL_IS_VOID(value)) { if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "Property '%s' does not look like a constructor", constructor_name); return JS_FALSE; } } constructor = JSVAL_TO_OBJECT(value); gjs_define_enum_values(context, constructor, priv->info); if (constructor_p) *constructor_p = constructor; if (prototype_p) *prototype_p = prototype; return JS_TRUE; }
JSBool gjs_define_param_class(JSContext *context, JSObject *in_object, JSObject **prototype_p) { const char *constructor_name; JSObject *prototype; jsval value; JSObject *constructor; constructor_name = "ParamSpec"; gjs_object_get_property(context, in_object, constructor_name, &value); if (!JSVAL_IS_VOID(value)) { if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "Existing property '%s' does not look like a constructor", constructor_name); return JS_FALSE; } constructor = JSVAL_TO_OBJECT(value); gjs_object_get_property(context, constructor, "prototype", &value); if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "prototype property does not appear to exist or has wrong type"); return JS_FALSE; } else { if (prototype_p) *prototype_p = JSVAL_TO_OBJECT(value); return JS_TRUE; } return JS_TRUE; } /* we could really just use JS_InitClass for this since we have one class instead of * N classes on-demand. But, this deals with namespacing and such for us. */ prototype = gjs_init_class_dynamic(context, in_object, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ NULL, "GObject", constructor_name, &gjs_param_class, /* constructor for instances (NULL for * none - just name the prototype like * Math - rarely correct) */ gjs_param_constructor, /* number of constructor args */ 0, /* props of prototype */ &gjs_param_proto_props[0], /* funcs of prototype */ &gjs_param_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ gjs_param_constructor_funcs); if (prototype == NULL) gjs_fatal("Can't init class %s", constructor_name); constructor = NULL; gjs_object_get_property(context, in_object, constructor_name, &value); if (value != JSVAL_VOID) { if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "Property '%s' does not look like a constructor", constructor_name); return JS_FALSE; } } constructor = JSVAL_TO_OBJECT(value); value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, G_TYPE_PARAM)); JS_DefineProperty(context, constructor, "$gtype", value, NULL, NULL, JSPROP_PERMANENT); if (prototype_p) *prototype_p = prototype; gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype, JS_GET_CLASS(context, prototype), in_object); return JS_TRUE; }
static JSBool do_import(JSContext *context, JSObject *obj, Importer *priv, std::string &name) { std::string filename; std::string full_path; std::string dirname; jsval search_path_val; JSObject *search_path; JSObject *module_obj = NULL; uint32_t search_path_len; uint32_t i; JSBool result; std::vector<std::string> directories; jsid search_path_name; bool exists; search_path_name = gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH); if (!gjs_object_require_property(context, obj, "importer", search_path_name, &search_path_val)) { return JS_FALSE; } if (!search_path_val.isObject()) { gjs_throw(context, "searchPath property on importer is not an object"); return JS_FALSE; } search_path = JSVAL_TO_OBJECT(search_path_val); if (!JS_IsArrayObject(context, search_path)) { gjs_throw(context, "searchPath property on importer is not an array"); return JS_FALSE; } if (!JS_GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return JS_FALSE; } result = JS_FALSE; filename = std::string(name) + ".js"; /* First try importing an internal module like byteArray */ if (priv->is_root && gjs_is_registered_native_module(context, obj, name) && import_native_file(context, obj, name)) { gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name); result = JS_TRUE; goto out; } for (i = 0; i < search_path_len; ++i) { jsval elem; elem = JSVAL_VOID; if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem == JSVAL_VOID * means no element found */ goto out; } if (JSVAL_IS_VOID(elem)) continue; if (!JSVAL_IS_STRING(elem)) { gjs_throw(context, "importer searchPath contains non-string"); goto out; } if (!gjs_string_to_utf8(context, elem, dirname)) goto out; /* Error message already set */ /* Ignore empty path elements */ if (dirname[0] == '\0') continue; /* Try importing __init__.js and loading the symbol from it */ full_path = pathCombine(dirname, MODULE_INIT_FILENAME); module_obj = load_module_init(context, obj, full_path); if (module_obj != NULL) { jsval obj_val; if (JS_GetProperty(context, module_obj, name.c_str(), &obj_val)) { if (!JSVAL_IS_VOID(obj_val) && JS_DefineProperty(context, obj, name.c_str(), obj_val, NULL, NULL, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) { result = JS_TRUE; goto out; } } } /* Second try importing a directory (a sub-importer) */ full_path = pathCombine(dirname, name); if (is_directory(full_path)) { std::cout << "Adding directory '" << full_path << "' to child importer '" << name << "'\n", directories.push_back(full_path); } /* If we just added to directories, we know we don't need to * check for a file. If we added to directories on an earlier * iteration, we want to ignore any files later in the * path. So, always skip the rest of the loop block if we have * directories. */ if (directories.size() > 0) { continue; } /* Third, if it's not a directory, try importing a file */ full_path = pathCombine(dirname, filename); std::cout << "full path: " << full_path << "\n"; exists = is_regular(full_path); if (!exists) { std::cout << "JS import '" << name << "' not found in " << dirname << "\n"; continue; } if (import_file_on_module (context, obj, name, full_path)) { std::cout << "successfully imported module '" << name << "'\n"; result = JS_TRUE; } /* Don't keep searching path if we fail to load the file for * reasons other than it doesn't exist... i.e. broken files * block searching for nonbroken ones */ goto out; } if (directories.size() > 0) { /* NULL-terminate the char** */ if (import_directory(context, obj, name, directories)) { std::cout << "successfully imported directory '" << name << "'\n"; result = JS_TRUE; } } out: if (!result && !JS_IsExceptionPending(context)) { /* If no exception occurred, the problem is just that we got to the * end of the path. Be sure an exception is set. */ gjs_throw(context, "No JS module '%s' found in search path", name.c_str()); } return result; }
static GObject* gjs_context_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GObject *object; GjsContext *js_context; guint32 options_flags; JSVersion js_version; object = (* G_OBJECT_CLASS (gjs_context_parent_class)->constructor) (type, n_construct_properties, construct_params); js_context = GJS_CONTEXT(object); if (js_context->runtime == NULL) { js_context->runtime = JS_NewRuntime(32*1024*1024 /* max bytes */); if (js_context->runtime == NULL) gjs_fatal("Failed to create javascript runtime"); JS_SetGCParameter(js_context->runtime, JSGC_MAX_BYTES, 0xffffffff); js_context->we_own_runtime = TRUE; gjs_runtime_init(js_context->runtime); } js_context->context = JS_NewContext(js_context->runtime, 8192 /* stack chunk size */); if (js_context->context == NULL) gjs_fatal("Failed to create javascript context"); JS_BeginRequest(js_context->context); /* same as firefox, see discussion at * https://bugzilla.mozilla.org/show_bug.cgi?id=420869 */ JS_SetScriptStackQuota(js_context->context, 100*1024*1024); /* JSOPTION_DONT_REPORT_UNCAUGHT: Don't send exceptions to our * error report handler; instead leave them set. This allows us * to get at the exception object. * * JSOPTION_STRICT: Report warnings to error reporter function. */ options_flags = JSOPTION_DONT_REPORT_UNCAUGHT | JSOPTION_STRICT; if (!g_getenv("GJS_DISABLE_JIT")) { gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT"); options_flags |= JSOPTION_METHODJIT; } JS_SetOptions(js_context->context, JS_GetOptions(js_context->context) | options_flags); JS_SetLocaleCallbacks(js_context->context, &gjs_locale_callbacks); JS_SetErrorReporter(js_context->context, gjs_error_reporter); /* set ourselves as the private data */ JS_SetContextPrivate(js_context->context, js_context); js_version = JS_StringToVersion(js_context->jsversion_string); /* It doesn't make sense to throw here; just use the default if we * don't know. */ if (js_version == JSVERSION_UNKNOWN) js_version = JSVERSION_DEFAULT; /* Set the version if we need to. */ if (js_version != JSVERSION_DEFAULT && JS_GetVersion(js_context->context) != js_version) { gjs_debug(GJS_DEBUG_CONTEXT, "Changing JavaScript version to %s from %s", JS_VersionToString(js_version), JS_VersionToString(JS_GetVersion(js_context->context))); JS_SetVersion(js_context->context, js_version); } if (!gjs_init_context_standard(js_context->context)) gjs_fatal("Failed to initialize context"); js_context->global = JS_GetGlobalObject(js_context->context); if (!JS_DefineProperty(js_context->context, js_context->global, "window", OBJECT_TO_JSVAL(js_context->global), NULL, NULL, JSPROP_READONLY | JSPROP_PERMANENT)) gjs_fatal("No memory to export global object as 'window'"); /* Define a global function called log() */ if (!JS_DefineFunction(js_context->context, js_context->global, "log", (JSNative)gjs_log, 1, GJS_MODULE_PROP_FLAGS)) gjs_fatal("Failed to define log function"); if (!JS_DefineFunction(js_context->context, js_context->global, "logError", (JSNative)gjs_log_error, 2, GJS_MODULE_PROP_FLAGS)) gjs_fatal("Failed to define logError function"); /* Define global functions called print() and printerr() */ if (!JS_DefineFunction(js_context->context, js_context->global, "print", (JSNative)gjs_print, 3, GJS_MODULE_PROP_FLAGS)) gjs_fatal("Failed to define print function"); if (!JS_DefineFunction(js_context->context, js_context->global, "printerr", (JSNative)gjs_printerr, 4, GJS_MODULE_PROP_FLAGS)) gjs_fatal("Failed to define printerr function"); /* We need to know what the default context is, since it's the context whose * global object is used to load imported JS modules. We currently say that * it's the context of the runtime's owner, but if we needed to support * externally created runtimes, we could define it in some other fashion. */ if (js_context->we_own_runtime) { gjs_runtime_set_default_context(js_context->runtime, js_context->context); } else { if (gjs_runtime_get_default_context(js_context->runtime) == NULL) gjs_fatal("GjsContext created for a runtime not owned by GJS"); } /* We create the global-to-runtime root importer with the * passed-in search path. If someone else already created * the root importer, this is a no-op. */ if (!gjs_create_root_importer(js_context->context, js_context->search_path ? (const char**) js_context->search_path : NULL, TRUE)) gjs_fatal("Failed to create root importer"); /* Now copy the global root importer (which we just created, * if it didn't exist) to our global object */ if (!gjs_define_root_importer(js_context->context, js_context->global, "imports")) gjs_fatal("Failed to point 'imports' property at root importer"); if (js_context->we_own_runtime) { js_context->profiler = gjs_profiler_new(js_context->runtime); } if (!gjs_is_registered_native_module(js_context->context, NULL, "gi")) gjs_register_native_module("gi", gjs_define_gi_stuff, GJS_NATIVE_SUPPLIES_MODULE_OBJ); /* For GjsDBus */ { char *priv_typelib_dir = g_build_filename (PKGLIBDIR, "girepository-1.0", NULL); g_irepository_prepend_search_path(priv_typelib_dir); g_free (priv_typelib_dir); } if (js_context->gc_notifications_enabled) JS_SetGCCallback(js_context->context, gjs_on_context_gc); JS_EndRequest(js_context->context); g_static_mutex_lock (&contexts_lock); all_contexts = g_list_prepend(all_contexts, object); g_static_mutex_unlock (&contexts_lock); return object; }
static JSObject * gjs_keep_alive_new(JSContext *context) { KeepAlive *priv; bool found; /* This function creates an unattached KeepAlive object; following our * general strategy, we have a single KeepAlive class with a constructor * stored on our single "load global" pseudo-global object, and we create * instances with the load global as parent. */ g_assert(context != NULL); JSAutoRequest ar(context); JS::RootedObject global(context, gjs_get_import_global(context)); g_assert(global != NULL); if (!JS_HasProperty(context, global, gjs_keep_alive_class.name, &found)) return NULL; if (!found) { JSObject *prototype; gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initializing keep-alive class in context %p global %p", context, global.get()); prototype = JS_InitClass(context, global, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ JS::NullPtr(), &gjs_keep_alive_class, /* constructor for instances (NULL for * none - just name the prototype like * Math - rarely correct) */ gjs_keep_alive_constructor, /* number of constructor args */ 0, /* props of prototype */ &gjs_keep_alive_proto_props[0], /* funcs of prototype */ &gjs_keep_alive_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL); if (prototype == NULL) g_error("Can't init class %s", gjs_keep_alive_class.name); gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initialized class %s prototype %p", gjs_keep_alive_class.name, prototype); } gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Creating new keep-alive object for context %p global %p", context, global.get()); JS::RootedObject keep_alive(context, JS_NewObject(context, &gjs_keep_alive_class, JS::NullPtr(), global)); if (keep_alive == NULL) { gjs_log_exception(context); g_error("Failed to create keep_alive object"); } priv = g_slice_new0(KeepAlive); priv->children = g_hash_table_new_full(child_hash, child_equal, NULL, child_free); g_assert(priv_from_js(context, keep_alive) == NULL); JS_SetPrivate(keep_alive, priv); gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "keep_alive constructor, obj %p priv %p", keep_alive.get(), priv); return keep_alive; }
static JSBool import_file(JSContext *context, JSObject *obj, const char *name, const char *full_path) { char *script; gsize script_len; JSObject *module_obj; GError *error; jsval script_retval; JSBool retval = JS_FALSE; gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", full_path); module_obj = JS_NewObject(context, NULL, NULL, NULL); if (module_obj == NULL) { return JS_FALSE; } if (!define_import(context, obj, module_obj, name)) return JS_FALSE; if (!define_meta_properties(context, module_obj, full_path, name, obj)) goto out; script_len = 0; error = NULL; if (!(g_file_get_contents(full_path, &script, &script_len, &error))) { if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_ISDIR) && !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR) && !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) gjs_throw_g_error(context, error); else g_error_free(error); goto out; } g_assert(script != NULL); if (!JS_EvaluateScript(context, module_obj, script, script_len, full_path, 1, /* line number */ &script_retval)) { g_free(script); /* If JSOPTION_DONT_REPORT_UNCAUGHT is set then the exception * would be left set after the evaluate and not go to the error * reporter function. */ if (JS_IsExceptionPending(context)) { gjs_debug(GJS_DEBUG_IMPORTER, "Module '%s' left an exception set", name); gjs_log_and_keep_exception(context); } else { gjs_throw(context, "JS_EvaluateScript() returned FALSE but did not set exception"); } goto out; } g_free(script); if (!finish_import(context, name)) goto out; if (!seal_import(context, obj, name)) goto out; retval = JS_TRUE; out: if (!retval) cancel_import(context, obj, name); return retval; }
JSBool gjs_define_fundamental_class(JSContext *context, JSObject *in_object, GIObjectInfo *info, JSObject **constructor_p, JSObject **prototype_p) { const char *constructor_name; JSObject *prototype; jsval value; jsid js_constructor_name = JSID_VOID; JSObject *parent_proto; Fundamental *priv; JSObject *constructor; GType parent_gtype; GType gtype; GIFunctionInfo *constructor_info; /* See the comment in gjs_define_object_class() for an explanation * of how this all works; Fundamental is pretty much the same as * Object. */ constructor_name = g_base_info_get_name((GIBaseInfo *) info); constructor_info = find_fundamental_constructor(context, info, &js_constructor_name); gtype = g_registered_type_info_get_g_type (info); parent_gtype = g_type_parent(gtype); parent_proto = NULL; if (parent_gtype != G_TYPE_INVALID) parent_proto = gjs_lookup_fundamental_prototype_from_gtype(context, parent_gtype); if (!gjs_init_class_dynamic(context, in_object, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ parent_proto, g_base_info_get_namespace((GIBaseInfo *) info), constructor_name, &gjs_fundamental_instance_class, gjs_fundamental_instance_constructor, /* number of constructor args (less can be passed) */ constructor_info != NULL ? g_callable_info_get_n_args((GICallableInfo *) constructor_info) : 0, /* props of prototype */ parent_proto ? NULL : &gjs_fundamental_instance_proto_props[0], /* funcs of prototype */ parent_proto ? NULL : &gjs_fundamental_instance_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL, &prototype, &constructor)) { gjs_log_exception(context); g_error("Can't init class %s", constructor_name); } /* Put the info in the prototype */ priv = g_slice_new0(Fundamental); g_assert(priv != NULL); g_assert(priv->info == NULL); priv->info = g_base_info_ref((GIBaseInfo *) info); priv->gtype = gtype; priv->constructor_name = js_constructor_name; priv->constructor_info = constructor_info; priv->ref_function = g_object_info_get_ref_function_pointer(info); g_assert(priv->ref_function != NULL); priv->unref_function = g_object_info_get_unref_function_pointer(info); g_assert(priv->unref_function != NULL); priv->set_value_function = g_object_info_get_set_value_function_pointer(info); g_assert(priv->set_value_function != NULL); priv->get_value_function = g_object_info_get_get_value_function_pointer(info); g_assert(priv->get_value_function != NULL); JS_SetPrivate(prototype, priv); gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Defined class %s prototype is %p class %p in object %p constructor %s.%s.%s", constructor_name, prototype, JS_GetClass(prototype), in_object, constructor_info != NULL ? g_base_info_get_namespace(constructor_info) : "unknown", constructor_info != NULL ? g_base_info_get_name(g_base_info_get_container(constructor_info)) : "unknown", constructor_info != NULL ? g_base_info_get_name(constructor_info) : "unknown"); if (g_object_info_get_n_fields(priv->info) > 0) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Fundamental type '%s.%s' apparently has accessible fields. " "Gjs has no support for this yet, ignoring these.", g_base_info_get_namespace((GIBaseInfo *)priv->info), g_base_info_get_name ((GIBaseInfo *)priv->info)); } gjs_object_define_static_methods(context, constructor, gtype, info); value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, gtype)); JS_DefineProperty(context, constructor, "$gtype", value, NULL, NULL, JSPROP_PERMANENT); if (constructor_p) *constructor_p = constructor; if (prototype_p) *prototype_p = prototype; return JS_TRUE; }
JSObject* gjs_init_class_dynamic(JSContext *context, JSObject *in_object, JSObject *parent_proto, const char *ns_name, const char *class_name, JSClass *clasp, JSNative constructor, uintN nargs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { jsval value; char *private_name; JSObject *prototype; JSContext *load_context; if (clasp->name != NULL) { g_warning("Dynamic class should not have a name in the JSClass struct"); return NULL; } JS_BeginRequest(context); /* We replace the passed-in context and global object with our * runtime-global permanent load context. Otherwise, in a * process with multiple contexts, we'd arbitrarily define * the class in whatever global object initialized the * class first, which is not desirable. */ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context)); JS_BeginRequest(load_context); /* JS_InitClass() wants to define the constructor in the global object, so * we give it a private and namespaced name... passing in the namespace * object instead of global object seems to break JS_ConstructObject() * which then can't find the constructor for the class. I am probably * missing something. */ private_name = g_strdup_printf("_private_%s_%s", ns_name, class_name); prototype = NULL; if (gjs_object_get_property(load_context, JS_GetGlobalObject(load_context), private_name, &value) && JSVAL_IS_OBJECT(value)) { jsval proto_val; g_free(private_name); /* don't need it anymore */ if (!gjs_object_require_property(load_context, JSVAL_TO_OBJECT(value), NULL, "prototype", &proto_val) || !JSVAL_IS_OBJECT(proto_val)) { gjs_throw(load_context, "prototype was not defined or not an object?"); goto error; } prototype = JSVAL_TO_OBJECT(proto_val); } else { DynamicJSClass *class_copy; RuntimeData *rd; rd = get_data_from_context(load_context); class_copy = g_slice_new0(DynamicJSClass); class_copy->base = *clasp; class_copy->base.name = private_name; /* Pass ownership of memory */ class_copy->static_class = clasp; /* record the allocated class to be destroyed with the runtime and so * we can do an IS_DYNAMIC_CLASS check */ g_hash_table_replace(rd->dynamic_classes, class_copy, class_copy); gjs_debug(GJS_DEBUG_GREPO, "Initializing dynamic class %s %p", class_name, class_copy); prototype = JS_InitClass(load_context, JS_GetGlobalObject(load_context), parent_proto, &class_copy->base, constructor, nargs, ps, fs, static_ps, static_fs); /* Retrieve the property again so we can define it in * in_object */ if (!gjs_object_require_property(load_context, JS_GetGlobalObject(load_context), NULL, class_copy->base.name, &value)) goto error; } g_assert(value != JSVAL_VOID); g_assert(prototype != NULL); /* Now manually define our constructor with a sane name, in the * namespace object. */ if (!JS_DefineProperty(load_context, in_object, class_name, value, NULL, NULL, GJS_MODULE_PROP_FLAGS)) goto error; JS_EndRequest(load_context); JS_EndRequest(context); return prototype; error: /* Move the exception to the calling context from load context. */ if (!gjs_move_exception(load_context, context)) { /* set an exception since none was set */ gjs_throw(context, "No exception was set, but class initialize failed somehow"); } JS_EndRequest(load_context); JS_EndRequest(context); return NULL; }
/* * See: * https://bugzilla.mozilla.org/show_bug.cgi?id=166436 * https://bugzilla.mozilla.org/show_bug.cgi?id=215173 * * Very surprisingly, jsapi.h lacks any way to "throw new Error()" * * So here is an awful hack inspired by * http://egachine.berlios.de/embedding-sm-best-practice/embedding-sm-best-practice.html#error-handling */ static void gjs_throw_valist(JSContext *context, const char *error_class, const char *format, va_list args) { char *s; JSBool result; jsval v_constructor, v_message; JSObject *err_obj; s = g_strdup_vprintf(format, args); JS_BeginRequest(context); if (JS_IsExceptionPending(context)) { /* Often it's unclear whether a given jsapi.h function * will throw an exception, so we will throw ourselves * "just in case"; in those cases, we don't want to * overwrite an exception that already exists. * (Do log in case our second exception adds more info, * but don't log as topic ERROR because if the exception is * caught we don't want an ERROR in the logs.) */ gjs_debug(GJS_DEBUG_CONTEXT, "Ignoring second exception: '%s'", s); g_free(s); JS_EndRequest(context); return; } result = JS_FALSE; (void)JS_EnterLocalRootScope(context); if (!gjs_string_from_utf8(context, s, -1, &v_message)) { JS_ReportError(context, "Failed to copy exception string"); goto out; } if (!gjs_object_get_property(context, JS_GetGlobalObject(context), error_class, &v_constructor)) { JS_ReportError(context, "??? Missing Error constructor in global object?"); goto out; } /* throw new Error(message) */ err_obj = JS_New(context, JSVAL_TO_OBJECT(v_constructor), 1, &v_message); JS_SetPendingException(context, OBJECT_TO_JSVAL(err_obj)); result = JS_TRUE; out: JS_LeaveLocalRootScope(context); if (!result) { /* try just reporting it to error handler? should not * happen though pretty much */ JS_ReportError(context, "Failed to throw exception '%s'", s); } g_free(s); JS_EndRequest(context); }
JSBool gjs_define_boxed_class(JSContext *context, JSObject *in_object, GIBoxedInfo *info, JSObject **constructor_p, JSObject **prototype_p) { const char *constructor_name; JSObject *prototype; JSObject *constructor; jsval value; Boxed *priv; /* See the comment in gjs_define_object_class() for an * explanation of how this all works; Boxed is pretty much the * same as Object. */ constructor_name = g_base_info_get_name( (GIBaseInfo*) info); if (gjs_object_get_property(context, in_object, constructor_name, &value)) { JSObject *constructor; if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "Existing property '%s' does not look like a constructor", constructor_name); return JS_FALSE; } constructor = JSVAL_TO_OBJECT(value); gjs_object_get_property(context, constructor, "prototype", &value); if (!JSVAL_IS_OBJECT(value)) { gjs_throw(context, "boxed %s prototype property does not appear to exist or has wrong type", constructor_name); return JS_FALSE; } else { if (prototype_p) *prototype_p = JSVAL_TO_OBJECT(value); if (constructor_p) *constructor_p = constructor; return JS_TRUE; } } if (!gjs_init_class_dynamic(context, in_object, NULL, /* parent prototype */ g_base_info_get_namespace( (GIBaseInfo*) info), constructor_name, &gjs_boxed_class, gjs_boxed_constructor, 1, /* props of prototype */ &gjs_boxed_proto_props[0], /* funcs of prototype */ &gjs_boxed_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL, &prototype, &constructor)) { gjs_log_exception(context, NULL); gjs_fatal("Can't init class %s", constructor_name); } GJS_INC_COUNTER(boxed); priv = g_slice_new0(Boxed); priv->info = info; boxed_fill_prototype_info(priv); g_base_info_ref( (GIBaseInfo*) priv->info); priv->gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo*) priv->info); JS_SetPrivate(context, prototype, priv); gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype, JS_GET_CLASS(context, prototype), in_object); priv->can_allocate_directly = struct_is_simple (priv->info); define_boxed_class_fields (context, priv, prototype); gjs_define_static_methods (context, constructor, priv->gtype, priv->info); value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, priv->gtype)); JS_DefineProperty(context, constructor, "$gtype", value, NULL, NULL, JSPROP_PERMANENT); if (constructor_p) *constructor_p = constructor; if (prototype_p) *prototype_p = prototype; return JS_TRUE; }
/* * Like JSResolveOp, but flags provide contextual information as follows: * * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence * JSRESOLVE_DECLARING var, const, or boxed prolog declaration opcode * JSRESOLVE_CLASSNAME class name used when constructing * * The *objp out parameter, on success, should be null to indicate that id * was not resolved; and non-null, referring to obj or one of its prototypes, * if id was resolved. */ static JSBool boxed_new_resolve(JSContext *context, JSObject *obj, jsid id, unsigned flags, JSObject **objp) { Boxed *priv; char *name; JSBool ret = JS_FALSE; *objp = NULL; if (!gjs_get_string_id(context, id, &name)) return JS_TRUE; /* not resolved, but no error */ priv = priv_from_js(context, obj); gjs_debug_jsprop(GJS_DEBUG_GBOXED, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); if (priv == NULL) goto out; /* wrong class */ if (priv->gboxed == NULL) { /* We are the prototype, so look for methods and other class properties */ GIFunctionInfo *method_info; method_info = g_struct_info_find_method((GIStructInfo*) priv->info, name); if (method_info != NULL) { JSObject *boxed_proto; const char *method_name; #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage((GIBaseInfo*) method_info); #endif method_name = g_base_info_get_name( (GIBaseInfo*) method_info); gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", method_name, g_base_info_get_namespace( (GIBaseInfo*) priv->info), g_base_info_get_name( (GIBaseInfo*) priv->info)); boxed_proto = obj; if (gjs_define_function(context, boxed_proto, priv->gtype, (GICallableInfo *)method_info) == NULL) { g_base_info_unref( (GIBaseInfo*) method_info); goto out; } *objp = boxed_proto; /* we defined the prop in object_proto */ g_base_info_unref( (GIBaseInfo*) method_info); } } else { /* We are an instance, not a prototype, so look for * per-instance props that we want to define on the * JSObject. Generally we do not want to cache these in JS, we * want to always pull them from the C object, or JS would not * see any changes made from C. So we use the get/set prop * hooks, not this resolve hook. */ } ret = JS_TRUE; out: g_free(name); return ret; }
/* * See: * https://bugzilla.mozilla.org/show_bug.cgi?id=166436 * https://bugzilla.mozilla.org/show_bug.cgi?id=215173 * * Very surprisingly, jsapi.h lacks any way to "throw new Error()" * * So here is an awful hack inspired by * http://egachine.berlios.de/embedding-sm-best-practice/embedding-sm-best-practice.html#error-handling */ static void gjs_throw_valist(JSContext *context, const char *format, va_list args) { char *s; jsval retval; jsval argv[1]; JSFunction *func; const char *body; JSBool result; const char *names[] = { "message" }; guint options; s = g_strdup_vprintf(format, args); JS_BeginRequest(context); if (JS_IsExceptionPending(context)) { /* Often it's unclear whether a given jsapi.h function * will throw an exception, so we will throw ourselves * "just in case"; in those cases, we don't want to * overwrite an exception that already exists. * (Do log in case our second exception adds more info, * but don't log as topic ERROR because if the exception is * caught we don't want an ERROR in the logs.) */ gjs_debug(GJS_DEBUG_CONTEXT, "Ignoring second exception: '%s'", s); g_free(s); return; } result = JS_FALSE; JS_EnterLocalRootScope(context); if (!gjs_string_from_utf8(context, s, -1, &argv[0])) { JS_ReportError(context, "Failed to copy exception string"); goto out; } body = "throw new Error(message);"; func = JS_CompileFunction(context, JS_GetGlobalObject(context), /* parent object (scope chain) */ NULL, /* name of function if we wanted to define it in parent */ 1, /* nargs */ &names[0], /* array of arg names if we had args */ body, strlen(body), "gjs_throw", /* file */ 0); /* line */ if (func == NULL) { JS_ReportError(context, "Failed to compile function"); goto out; } /* we need JS_CallFunctionValue() to leave the exception set */ options = JS_GetOptions(context); if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) { JS_SetOptions(context, options | JSOPTION_DONT_REPORT_UNCAUGHT); } retval = JSVAL_VOID; /* note the return value is whether function succeeded, which it shouldn't, since it * throws... */ JS_CallFunctionValue(context, JS_GetGlobalObject(context), OBJECT_TO_JSVAL(JS_GetFunctionObject(func)), 1, &argv[0], &retval); if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) { JS_SetOptions(context, options); } if (!JS_IsExceptionPending(context)) { JS_ReportError(context, "Failed to set exception by calling our exception-setting function"); goto out; } result = JS_TRUE; out: JS_LeaveLocalRootScope(context); if (!result) { /* try just reporting it to error handler? should not * happen though pretty much */ JS_ReportError(context, "Failed to throw exception '%s'", s); } g_free(s); JS_EndRequest(context); }
JSObject* gjs_init_class_dynamic(JSContext *context, JSObject *in_object, JSObject *parent_proto, const char *ns_name, const char *class_name, JSClass *clasp, JSNative constructor, uintN nargs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { jsval value; char *private_name; JSObject *global; JSObject *prototype; if (clasp->name != NULL) { g_warning("Dynamic class should not have a name in the JSClass struct"); return NULL; } JS_BeginRequest(context); /* We use a special "fake" global object to store our constructors * in for future use. Using the actual global object of the context would * result in different contexts having different class definitions for * the same GObject class; since the proxies are shared between all * contexts, this would produce confusing results. */ global = gjs_get_import_global(context); /* JS_InitClass() wants to define the constructor in the global object, so * we give it a private and namespaced name... passing in the namespace * object instead of global object seems to break JS_ConstructObject() * which then can't find the constructor for the class. I am probably * missing something. */ private_name = g_strdup_printf("_private_%s_%s", ns_name, class_name); prototype = NULL; if (gjs_object_get_property(context, global, private_name, &value) && JSVAL_IS_OBJECT(value)) { jsval proto_val; g_free(private_name); /* don't need it anymore */ if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(value), NULL, "prototype", &proto_val) || !JSVAL_IS_OBJECT(proto_val)) { gjs_throw(context, "prototype was not defined or not an object?"); goto error; } prototype = JSVAL_TO_OBJECT(proto_val); } else { DynamicJSClass *class_copy; RuntimeData *rd; rd = get_data_from_context(context); class_copy = g_slice_new0(DynamicJSClass); class_copy->base = *clasp; class_copy->base.name = private_name; /* Pass ownership of memory */ class_copy->static_class = clasp; /* record the allocated class to be destroyed with the runtime and so * we can do an IS_DYNAMIC_CLASS check */ g_hash_table_replace(rd->dynamic_classes, class_copy, class_copy); gjs_debug(GJS_DEBUG_GREPO, "Initializing dynamic class %s %p", class_name, class_copy); prototype = JS_InitClass(context, global, parent_proto, &class_copy->base, constructor, nargs, ps, fs, static_ps, static_fs); if (prototype == NULL) goto error; /* Retrieve the property again so we can define it in * in_object */ if (!gjs_object_require_property(context, global, NULL, class_copy->base.name, &value)) goto error; } g_assert(!JSVAL_IS_VOID(value)); g_assert(prototype != NULL); /* Now manually define our constructor with a sane name, in the * namespace object. */ if (!JS_DefineProperty(context, in_object, class_name, value, NULL, NULL, GJS_MODULE_PROP_FLAGS)) goto error; JS_EndRequest(context); return prototype; error: JS_EndRequest(context); return NULL; }
static JSObject * load_module_init(JSContext *context, JSObject *in_object, const char *full_path) { char *script; gsize script_len; jsval script_retval; JSObject *module_obj; GError *error; JSBool found; jsid module_init_name; /* First we check if js module has already been loaded */ module_init_name = gjs_runtime_get_const_string(JS_GetRuntime(context), GJS_STRING_MODULE_INIT); if (JS_HasPropertyById(context, in_object, module_init_name, &found) && found) { jsval module_obj_val; if (JS_GetPropertyById(context, in_object, module_init_name, &module_obj_val)) { return JSVAL_TO_OBJECT(module_obj_val); } } module_obj = JS_NewObject(context, NULL, NULL, NULL); if (module_obj == NULL) { return JS_FALSE; } /* https://bugzilla.mozilla.org/show_bug.cgi?id=599651 means we * can't just pass in the global as the parent */ JS_SetParent(context, module_obj, gjs_get_import_global (context)); /* Define module in importer for future use and to avoid module_obj * object to be garbage collected during the evaluation of the script */ JS_DefinePropertyById(context, in_object, module_init_name, OBJECT_TO_JSVAL(module_obj), NULL, NULL, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT); script_len = 0; error = NULL; if (!g_file_get_contents(full_path, &script, &script_len, &error)) { if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_ISDIR) && !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR) && !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) gjs_throw_g_error(context, error); else g_error_free(error); return NULL; } g_assert(script != NULL); gjs_debug(GJS_DEBUG_IMPORTER, "Importing %s", full_path); if (!JS_EvaluateScript(context, module_obj, script, script_len, full_path, 1, /* line number */ &script_retval)) { g_free(script); /* If JSOPTION_DONT_REPORT_UNCAUGHT is set then the exception * would be left set after the evaluate and not go to the error * reporter function. */ if (JS_IsExceptionPending(context)) { gjs_debug(GJS_DEBUG_IMPORTER, "Module " MODULE_INIT_FILENAME " left an exception set"); gjs_log_and_keep_exception(context); } else { gjs_throw(context, "JS_EvaluateScript() returned FALSE but did not set exception"); } return NULL; } g_free(script); return module_obj; }
static void closure_marshal(GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { JSRuntime *runtime; JSContext *context; int argc; jsval *argv; jsval rval; int i; GSignalQuery signal_query = { 0, }; gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", closure); if (!gjs_closure_is_valid(closure)) { /* We were destroyed; become a no-op */ return; } runtime = gjs_closure_get_runtime(closure); context = gjs_runtime_get_current_context(runtime); JS_BeginRequest(context); argc = n_param_values; argv = g_newa(jsval, n_param_values); rval = JSVAL_VOID; gjs_set_values(context, argv, argc, JSVAL_VOID); gjs_root_value_locations(context, argv, argc); JS_AddValueRoot(context, &rval); if (marshal_data) { /* we are used for a signal handler */ guint signal_id; signal_id = GPOINTER_TO_UINT(marshal_data); g_signal_query(signal_id, &signal_query); if (!signal_query.signal_id) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called on invalid signal"); goto cleanup; } if (signal_query.n_params + 1 != n_param_values) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called with wrong number of parameters"); goto cleanup; } } for (i = 0; i < argc; ++i) { const GValue *gval = ¶m_values[i]; gboolean no_copy; no_copy = FALSE; if (i >= 1 && signal_query.signal_id) { no_copy = (signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0; } if (!gjs_value_from_g_value_internal(context, &argv[i], gval, no_copy, &signal_query, i)) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert arg %d in order to invoke closure", i); gjs_log_exception(context, NULL); goto cleanup; } } gjs_closure_invoke(closure, argc, argv, &rval); if (return_value != NULL) { if (JSVAL_IS_VOID(rval)) { /* something went wrong invoking, error should be set already */ goto cleanup; } if (!gjs_value_to_g_value(context, rval, return_value)) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert return value when invoking closure"); gjs_log_exception(context, NULL); goto cleanup; } } cleanup: gjs_unroot_value_locations(context, argv, argc); JS_RemoveValueRoot(context, &rval); JS_EndRequest(context); }
static JSBool do_import(JSContext *context, JSObject *obj, Importer *priv, const char *name) { char *filename; char *full_path; char *dirname = NULL; jsval search_path_val; JSObject *search_path; JSObject *module_obj = NULL; guint32 search_path_len; guint32 i; JSBool result; GPtrArray *directories; jsid search_path_name; search_path_name = gjs_runtime_get_const_string(JS_GetRuntime(context), GJS_STRING_SEARCH_PATH); if (!gjs_object_require_property(context, obj, "importer", search_path_name, &search_path_val)) { return JS_FALSE; } if (!JSVAL_IS_OBJECT(search_path_val)) { gjs_throw(context, "searchPath property on importer is not an object"); return JS_FALSE; } search_path = JSVAL_TO_OBJECT(search_path_val); if (!JS_IsArrayObject(context, search_path)) { gjs_throw(context, "searchPath property on importer is not an array"); return JS_FALSE; } if (!JS_GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return JS_FALSE; } result = JS_FALSE; filename = g_strdup_printf("%s.js", name); full_path = NULL; directories = NULL; /* First try importing an internal module like byteArray */ if (priv->is_root && gjs_is_registered_native_module(context, obj, name) && import_native_file(context, obj, name)) { gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name); result = JS_TRUE; goto out; } for (i = 0; i < search_path_len; ++i) { jsval elem; elem = JSVAL_VOID; if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem == JSVAL_VOID * means no element found */ goto out; } if (JSVAL_IS_VOID(elem)) continue; if (!JSVAL_IS_STRING(elem)) { gjs_throw(context, "importer searchPath contains non-string"); goto out; } g_free(dirname); dirname = NULL; if (!gjs_string_to_utf8(context, elem, &dirname)) goto out; /* Error message already set */ /* Ignore empty path elements */ if (dirname[0] == '\0') continue; /* Try importing __init__.js and loading the symbol from it */ if (full_path) g_free(full_path); full_path = g_build_filename(dirname, MODULE_INIT_FILENAME, NULL); module_obj = load_module_init(context, obj, full_path); if (module_obj != NULL) { jsval obj_val; if (JS_GetProperty(context, module_obj, name, &obj_val)) { if (!JSVAL_IS_VOID(obj_val) && JS_DefineProperty(context, obj, name, obj_val, NULL, NULL, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) { result = JS_TRUE; goto out; } } } /* Second try importing a directory (a sub-importer) */ if (full_path) g_free(full_path); full_path = g_build_filename(dirname, name, NULL); if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) { gjs_debug(GJS_DEBUG_IMPORTER, "Adding directory '%s' to child importer '%s'", full_path, name); if (directories == NULL) { directories = g_ptr_array_new(); } g_ptr_array_add(directories, full_path); /* don't free it twice - pass ownership to ptr array */ full_path = NULL; } /* If we just added to directories, we know we don't need to * check for a file. If we added to directories on an earlier * iteration, we want to ignore any files later in the * path. So, always skip the rest of the loop block if we have * directories. */ if (directories != NULL) { continue; } /* Third, if it's not a directory, try importing a file */ g_free(full_path); full_path = g_build_filename(dirname, filename, NULL); if (g_file_test(full_path, G_FILE_TEST_EXISTS)) { if (import_file(context, obj, name, full_path)) { gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name); result = JS_TRUE; } /* Don't keep searching path if we fail to load the file for * reasons other than it doesn't exist... i.e. broken files * block searching for nonbroken ones */ goto out; } gjs_debug(GJS_DEBUG_IMPORTER, "JS import '%s' not found in %s", name, dirname); } if (directories != NULL) { /* NULL-terminate the char** */ g_ptr_array_add(directories, NULL); if (import_directory(context, obj, name, (const char**) directories->pdata)) { gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported directory '%s'", name); result = JS_TRUE; } } out: if (directories != NULL) { char **str_array; /* NULL-terminate the char** * (maybe for a second time, but doesn't matter) */ g_ptr_array_add(directories, NULL); str_array = (char**) directories->pdata; g_ptr_array_free(directories, FALSE); g_strfreev(str_array); } g_free(full_path); g_free(filename); g_free(dirname); if (!result && !JS_IsExceptionPending(context)) { /* If no exception occurred, the problem is just that we got to the * end of the path. Be sure an exception is set. */ gjs_throw(context, "No JS module '%s' found in search path", name); } return result; }
JSObject* gjs_keep_alive_new(JSContext *context) { JSObject *keep_alive; JSObject *global; g_assert(context != NULL); JS_BeginRequest(context); /* put constructor in the global namespace */ global = JS_GetGlobalObject(context); g_assert(global != NULL); if (!gjs_object_has_property(context, global, gjs_keep_alive_class.name)) { JSObject *prototype; gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initializing keep-alive class in context %p global %p", context, global); prototype = JS_InitClass(context, global, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ NULL, &gjs_keep_alive_class, /* constructor for instances (NULL for * none - just name the prototype like * Math - rarely correct) */ keep_alive_constructor, /* number of constructor args */ 0, /* props of prototype */ &gjs_keep_alive_proto_props[0], /* funcs of prototype */ &gjs_keep_alive_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL); if (prototype == NULL) gjs_fatal("Can't init class %s", gjs_keep_alive_class.name); g_assert(gjs_object_has_property(context, global, gjs_keep_alive_class.name)); gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initialized class %s prototype %p", gjs_keep_alive_class.name, prototype); } gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Creating new keep-alive object for context %p global %p", context, global); /* Without the "global" parent object, this craters inside of * xulrunner because in jsobj.c:js_ConstructObject it looks up * VOID as the constructor. Exploring in gdb, it is walking up * the scope chain in a way that involves scary xpconnect-looking * stuff. Having "global" as parent seems to fix it. But, it would * not hurt to understand this better. */ keep_alive = JS_ConstructObject(context, &gjs_keep_alive_class, NULL, global); if (keep_alive == NULL) { gjs_log_exception(context, NULL); gjs_fatal("Failed to create keep_alive object"); } JS_EndRequest(context); return keep_alive; }
/* * Like JSResolveOp, but flags provide contextual information as follows: * * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence * JSRESOLVE_DECLARING var, const, or fundamental prolog declaration opcode * JSRESOLVE_CLASSNAME class name used when constructing * * The *objp out parameter, on success, should be null to indicate that id * was not resolved; and non-null, referring to obj or one of its prototypes, * if id was resolved. */ static JSBool fundamental_instance_new_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, unsigned flags, JS::MutableHandleObject objp) { FundamentalInstance *priv; char *name; JSBool ret = JS_FALSE; if (!gjs_get_string_id(context, id, &name)) return JS_TRUE; /* not resolved, but no error */ priv = priv_from_js(context, obj); gjs_debug_jsprop(GJS_DEBUG_GFUNDAMENTAL, "Resolve prop '%s' hook obj %p priv %p", name, *obj, priv); if (priv == NULL) goto out; /* wrong class */ if (priv->prototype == NULL) { /* We are the prototype, so look for methods and other class properties */ Fundamental *proto_priv = (Fundamental *) priv; GIFunctionInfo *method_info; method_info = g_object_info_find_method((GIStructInfo*) proto_priv->info, name); if (method_info != NULL) { const char *method_name; #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage((GIBaseInfo *) method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { method_name = g_base_info_get_name((GIBaseInfo *) method_info); /* we do not define deprecated methods in the prototype */ if (g_base_info_is_deprecated((GIBaseInfo *) method_info)) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Ignoring definition of deprecated method %s in prototype %s.%s", method_name, g_base_info_get_namespace((GIBaseInfo *) proto_priv->info), g_base_info_get_name((GIBaseInfo *) proto_priv->info)); g_base_info_unref((GIBaseInfo *) method_info); ret = JS_TRUE; goto out; } gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Defining method %s in prototype for %s.%s", method_name, g_base_info_get_namespace((GIBaseInfo *) proto_priv->info), g_base_info_get_name((GIBaseInfo *) proto_priv->info)); if (gjs_define_function(context, obj, proto_priv->gtype, method_info) == NULL) { g_base_info_unref((GIBaseInfo *) method_info); goto out; } objp.set(obj); } g_base_info_unref((GIBaseInfo *) method_info); } ret = fundamental_instance_new_resolve_interface(context, obj, objp, proto_priv, name); } else { /* We are an instance, not a prototype, so look for * per-instance props that we want to define on the * JSObject. Generally we do not want to cache these in JS, we * want to always pull them from the C object, or JS would not * see any changes made from C. So we use the get/set prop * hooks, not this resolve hook. */ } ret = JS_TRUE; out: g_free(name); return ret; }
gboolean gjs_context_eval(GjsContext *js_context, const char *script, gssize script_len, const char *filename, int *exit_status_p, GError **error) { int line_number; jsval retval; gboolean success; gunichar2 *u16_script; glong u16_script_len; g_object_ref(G_OBJECT(js_context)); if (exit_status_p) *exit_status_p = 1; /* "Failure" (like a shell script) */ /* whether we evaluated the script OK; not related to whether * script returned nonzero. We set GError if success = FALSE */ success = TRUE; /* handle scripts with UNIX shebangs */ line_number = 1; if (script != NULL && script[0] == '#' && script[1] == '!') { const char *s; s = (const char *) strstr (script, "\n"); if (s != NULL) { if (script_len > 0) script_len -= (s + 1 - script); script = s + 1; line_number = 2; } } if ((u16_script = g_utf8_to_utf16 (script, script_len, NULL, &u16_script_len, error)) == NULL) return FALSE; g_assert (u16_script_len < G_MAXUINT); /* log and clear exception if it's set (should not be, normally...) */ if (gjs_log_exception(js_context->context, NULL)) { gjs_debug(GJS_DEBUG_CONTEXT, "Exception was set prior to JS_EvaluateScript()"); } /* JS_EvaluateScript requires a request even though it sort of seems like * it means we're always in a request? */ gjs_runtime_push_context(js_context->runtime, js_context->context); JS_BeginRequest(js_context->context); retval = JSVAL_VOID; if (!JS_EvaluateUCScript(js_context->context, js_context->global, (const jschar*)u16_script, (guint) u16_script_len, filename, line_number, &retval)) { char *message; gjs_debug(GJS_DEBUG_CONTEXT, "Script evaluation failed"); /* if message is NULL then somehow exception wasn't set */ message = NULL; gjs_log_exception(js_context->context, &message); if (message) { g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "%s", message); g_free(message); } else { gjs_debug(GJS_DEBUG_CONTEXT, "JS_EvaluateScript() failed but no exception message?"); g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "JS_EvaluateScript() failed but no exception message?"); } success = FALSE; } g_free (u16_script); gjs_debug(GJS_DEBUG_CONTEXT, "Script evaluation succeeded"); if (gjs_log_exception(js_context->context, NULL)) { g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "Exception was set even though JS_EvaluateScript() returned true - did you gjs_throw() but not return false somewhere perhaps?"); success = FALSE; } if (success && exit_status_p) { if (JSVAL_IS_INT(retval)) { int code; if (JS_ValueToInt32(js_context->context, retval, &code)) { gjs_debug(GJS_DEBUG_CONTEXT, "Script returned integer code %d", code); *exit_status_p = code; } } else { /* Assume success if no integer was returned */ *exit_status_p = 0; } } JS_EndRequest(js_context->context); gjs_runtime_pop_context(js_context->runtime); g_object_unref(G_OBJECT(js_context)); return success; }
JSObject* gjs_keep_alive_new(JSContext *context) { JSObject *keep_alive; JSObject *global; /* This function creates an unattached KeepAlive object; following our * general strategy, we have a single KeepAlive class with a constructor * stored on our single "load global" pseudo-global object, and we create * instances with the load global as parent. */ g_assert(context != NULL); JS_BeginRequest(context); global = gjs_get_import_global(context); g_assert(global != NULL); if (!gjs_object_has_property(context, global, gjs_keep_alive_class.name)) { JSObject *prototype; gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initializing keep-alive class in context %p global %p", context, global); prototype = JS_InitClass(context, global, /* parent prototype JSObject* for * prototype; NULL for * Object.prototype */ NULL, &gjs_keep_alive_class, /* constructor for instances (NULL for * none - just name the prototype like * Math - rarely correct) */ gjs_keep_alive_constructor, /* number of constructor args */ 0, /* props of prototype */ &gjs_keep_alive_proto_props[0], /* funcs of prototype */ &gjs_keep_alive_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL); if (prototype == NULL) gjs_fatal("Can't init class %s", gjs_keep_alive_class.name); g_assert(gjs_object_has_property(context, global, gjs_keep_alive_class.name)); gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initialized class %s prototype %p", gjs_keep_alive_class.name, prototype); } gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Creating new keep-alive object for context %p global %p", context, global); keep_alive = JS_ConstructObject(context, &gjs_keep_alive_class, NULL, global); if (keep_alive == NULL) { gjs_log_exception(context, NULL); gjs_fatal("Failed to create keep_alive object"); } JS_EndRequest(context); return keep_alive; }
static JSBool log_and_maybe_keep_exception(JSContext *context, char **message_p, gboolean keep) { jsval exc = JSVAL_VOID; JSString *s; char *message; JSBool retval = JS_FALSE; JS_BeginRequest(context); if (message_p) *message_p = NULL; JS_AddRoot(context, &exc); if (!JS_GetPendingException(context, &exc)) goto out; JS_ClearPendingException(context); s = JS_ValueToString(context, exc); if (s == NULL) { gjs_debug(GJS_DEBUG_ERROR, "Failed to convert exception to string"); goto out; /* Exception should be thrown already */ } if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(s), &message)) { gjs_debug(GJS_DEBUG_ERROR, "Failed to convert exception string to UTF-8"); goto out; /* Error already set */ } gjs_debug(GJS_DEBUG_ERROR, "Exception was: %s", message); if (message_p) { *message_p = message; } else { g_free(message); } gjs_log_exception_props(context, exc); /* We clear above and then set it back so any exceptions * from the logging process don't overwrite the original */ if (keep) JS_SetPendingException(context, exc); retval = JS_TRUE; out: JS_RemoveRoot(context, &exc); JS_EndRequest(context); return retval; }