static JSObject* gjs_lookup_param_prototype(JSContext *context) { JS::RootedId gobject_name(context, gjs_intern_string_to_id(context, "GObject")); JS::RootedObject in_object(context, gjs_lookup_namespace_object_by_name(context, gobject_name)); if (G_UNLIKELY (!in_object)) return NULL; JS::RootedValue value(context); if (!JS_GetProperty(context, in_object, "ParamSpec", &value)) return NULL; if (G_UNLIKELY (!value.isObject())) return NULL; JS::RootedObject constructor(context, &value.toObject()); g_assert(constructor); if (!gjs_object_get_property(context, constructor, GJS_STRING_PROTOTYPE, &value)) return NULL; if (G_UNLIKELY (!value.isObjectOrNull())) return NULL; return value.toObjectOrNull(); }
/* Checks whether an object has a property; unlike JS_GetProperty(), * never sets an exception. Treats a property with a value of JSVAL_VOID * the same as an absent property and returns false in both cases. */ gboolean gjs_object_has_property(JSContext *context, JSObject *obj, const char *property_name) { return gjs_object_get_property(context, obj, property_name, NULL); }
void gjs_log_object_props(JSContext *context, JSObject *obj, GjsDebugTopic topic, const char *prefix) { JSObject *props_iter; jsid prop_id; JS_BeginRequest(context); /* We potentially create new strings, plus the property iterator, * that could get collected as we go through this process. So * create a local root scope. */ JS_EnterLocalRootScope(context); props_iter = JS_NewPropertyIterator(context, obj); if (props_iter == NULL) { gjs_debug(GJS_DEBUG_ERROR, "Failed to create property iterator for object props"); goto done; } prop_id = JSVAL_VOID; if (!JS_NextProperty(context, props_iter, &prop_id)) goto done; while (prop_id != JSVAL_VOID) { jsval nameval; const char *name; jsval propval; if (!JS_IdToValue(context, prop_id, &nameval)) goto next; if (!gjs_get_string_id(nameval, &name)) goto next; if (!gjs_object_get_property(context, obj, name, &propval)) goto next; gjs_debug(topic, "%s%s = '%s'", prefix, name, gjs_value_debug_string(context, propval)); next: prop_id = JSVAL_VOID; if (!JS_NextProperty(context, props_iter, &prop_id)) break; } done: JS_LeaveLocalRootScope(context); JS_EndRequest(context); }
static void try_to_chain_stack_trace(JSContext *src_context, JSContext *dst_context, jsval src_exc) { /* append current stack of dst_context to stack trace for src_exc. * we bail if anything goes wrong, just using the src_exc unmodified * in that case. */ jsval chained, src_stack, dst_stack, new_stack; JSString *new_stack_str; JS_BeginRequest(src_context); JS_BeginRequest(dst_context); if (!JSVAL_IS_OBJECT(src_exc)) goto out; // src_exc doesn't have a stack trace /* create a new exception in dst_context to get a stack trace */ gjs_throw_literal(dst_context, "Chained exception"); if (!(JS_GetPendingException(dst_context, &chained) && JSVAL_IS_OBJECT(chained))) goto out; // gjs_throw_literal didn't work?! JS_ClearPendingException(dst_context); /* get stack trace for src_exc and chained */ if (!(gjs_object_get_property(dst_context, JSVAL_TO_OBJECT(chained), "stack", &dst_stack) && JSVAL_IS_STRING(dst_stack))) goto out; // couldn't get chained stack if (!(gjs_object_get_property(src_context, JSVAL_TO_OBJECT(src_exc), "stack", &src_stack) && JSVAL_IS_STRING(src_stack))) goto out; // couldn't get source stack /* add chained exception's stack trace to src_exc */ new_stack_str = JS_ConcatStrings (dst_context, JSVAL_TO_STRING(src_stack), JSVAL_TO_STRING(dst_stack)); if (new_stack_str==NULL) goto out; // couldn't concatenate src and dst stacks?! new_stack = STRING_TO_JSVAL(new_stack_str); JS_SetProperty(dst_context, JSVAL_TO_OBJECT(src_exc), "stack", &new_stack); out: JS_EndRequest(dst_context); JS_EndRequest(src_context); }
static void _gjs_builder_connect_func (GtkBuilder *builder, GObject *object, const gchar *signal_name, const gchar *handler_name, GObject *connect_object, GConnectFlags flags, gpointer user_data) { builder_ud *priv = (builder_ud *)user_data; JSContext *ctx = priv->ctx; JSObject *obj = priv->obj; GClosure *closure; JSObject *callable; builder_cd *cd; closure_data *c; jsval func; if (!gjs_object_get_property (ctx, obj, handler_name, &func)) return; if (!JSVAL_IS_OBJECT(func)) return; callable = JSVAL_TO_OBJECT (func); if (!JS_ObjectIsFunction(ctx, callable)) return; /* Protect from garbage collection. */ cd = g_object_get_data (G_OBJECT (builder), GJS_BUILDER_CLOSURE_KEY); if (!cd) { cd = g_new0 (builder_cd, 1); cd->context = ctx; cd->closures = NULL; g_object_set_data_full (G_OBJECT (builder), GJS_BUILDER_CLOSURE_KEY, cd, (GDestroyNotify) _builder_cd_free); } g_assert (cd->context == ctx); c = g_new0 (closure_data, 1); c->jsobj = callable; cd->closures = g_slist_prepend (cd->closures, c); JS_AddObjectRoot (ctx, &c->jsobj); closure = gjs_closure_new_for_signal (ctx, callable, "signal handler (GtkBuilder)", 0); if (connect_object != NULL) g_object_watch_closure (connect_object, closure); c->object = g_object_ref (object); c->handler_id = g_signal_connect_closure (object, signal_name, closure, FALSE); }
static char * get_module_name(JSContext *context, JSObject *module_obj, jsval *tmp) { if (gjs_object_get_property(context, module_obj, "__moduleName__", tmp) && JSVAL_IS_STRING(*tmp)) return gjs_string_get_ascii(context, *tmp); else return NULL; }
static JSObject* gjs_keep_alive_get_from_parent(JSContext *context, JSObject *parent) { jsval value; gjs_object_get_property(context, parent, GLOBAL_KEEP_ALIVE_NAME, &value); if (JSVAL_IS_OBJECT(value)) return JSVAL_TO_OBJECT(value); else return NULL; }
static JSObject* module_get_parent(JSContext *context, JSObject *module_obj) { jsval value; if (gjs_object_get_property(context, module_obj, "__parentModule__", &value) && !JSVAL_IS_NULL(value) && JSVAL_IS_OBJECT(value)) { return JSVAL_TO_OBJECT(value); } else { return NULL; } }
static char * get_module_name(JSContext *context, JSObject *module_obj, jsval *tmp) { char *name; if (gjs_object_get_property(context, module_obj, "__moduleName__", tmp) && JSVAL_IS_STRING(*tmp)) if (gjs_string_to_utf8(context, *tmp, &name)) return name; else return NULL; else return NULL; }
JSObject* gjs_keep_alive_get_global(JSContext *context) { jsval value; JSObject *global; JSObject *result; JS_BeginRequest(context); global = JS_GetGlobalObject(context); gjs_object_get_property(context, global, GLOBAL_KEEP_ALIVE_NAME, &value); if (JSVAL_IS_OBJECT(value)) result = JSVAL_TO_OBJECT(value); else result = NULL; JS_EndRequest(context); return result; }
JSBool gjs_define_interface_class(JSContext *context, JSObject *in_object, GIInterfaceInfo *info, JSObject **prototype_p) { Interface *priv; const char *constructor_name; JSObject *constructor; JSObject *prototype; jsval value; constructor_name = g_base_info_get_name((GIBaseInfo*)info); gjs_object_get_property(context, in_object, constructor_name, &value); if (!JSVAL_IS_VOID(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, "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; } if (!gjs_init_class_dynamic(context, in_object, NULL, g_base_info_get_namespace((GIBaseInfo*)info), constructor_name, &gjs_interface_class, gjs_interface_constructor, 0, /* props of prototype */ &gjs_interface_proto_props[0], /* funcs of prototype */ &gjs_interface_proto_funcs[0], /* props of constructor, MyConstructor.myprop */ NULL, /* funcs of constructor, MyConstructor.myfunc() */ NULL, &prototype, &constructor)) { gjs_fatal("Can't init class %s", constructor_name); } GJS_INC_COUNTER(interface); priv = g_slice_new0(Interface); priv->info = info; priv->gtype = g_registered_type_info_get_g_type(priv->info); g_base_info_ref((GIBaseInfo*)priv->info); JS_SetPrivate(context, prototype, priv); 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 (prototype_p) *prototype_p = prototype; return JS_TRUE; }
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; /* First we check if js module has already been loaded */ if (gjs_object_has_property(context, in_object, MODULE_INIT_PROPERTY)) { jsval module_obj_val; if (gjs_object_get_property(context, in_object, MODULE_INIT_PROPERTY, &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_DefineProperty(context, in_object, MODULE_INIT_PROPERTY, OBJECT_TO_JSVAL(module_obj), NULL, NULL, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT); script = NULL; script_len = 0; if (!g_file_get_contents(full_path, &script, &script_len, NULL)) { 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, NULL); } else { gjs_throw(context, "JS_EvaluateScript() returned FALSE but did not set exception"); } return NULL; } g_free(script); return module_obj; }
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; }
static JSBool do_import(JSContext *context, JSObject *obj, Importer *priv, const char *name) { char *filename; char *native_filename; char *full_path; char *dirname = NULL; jsval search_path_val; JSObject *search_path; JSObject *module_obj = NULL; jsuint search_path_len; jsuint i; JSBool result; GPtrArray *directories; if (strcmp(name, MODULE_INIT_PROPERTY) == 0) { return JS_FALSE; } if (!gjs_object_require_property(context, obj, "importer", "searchPath", &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); native_filename = g_strdup_printf("%s."G_MODULE_SUFFIX, name); full_path = NULL; directories = NULL; /* First try importing an internal module like byteArray */ if (gjs_is_registered_native_module(context, obj, name) && import_native_file(context, obj, name, NULL)) { 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 (gjs_object_get_property(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; } /* Finally see if it's a native module */ g_free(full_path); full_path = g_build_filename(dirname, native_filename, NULL); if (g_file_test(full_path, G_FILE_TEST_EXISTS)) { if (import_native_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(native_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_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; }
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; }
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; }
/* * 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; }