[gjs] js: Add convenience gjs_object_require_property_value



commit 1a5cd77b01f78d960c83db8e5837168a62463d1f
Author: Philip Chimento <philip endlessm com>
Date:   Tue Oct 25 14:33:29 2016 -0700

    js: Add convenience gjs_object_require_property_value
    
    This adds several functions in an overloaded
    gjs_object_require_property_value() family, as well as a
    gjs_object_require_converted_property_value() that can be overloaded when
    the opportunity arises.
    
    One operation that is done many times across the codebase is to call
    gjs_object_require_property() and then typecheck the JS::Value that it
    returns, throwing a custom exception if the property was the wrong type.
    We introduce gjs_object_require_property_value() which takes a pointer to
    whatever type we expect in the JS::Value, and takes care of all the
    rooting and exception throwing. Basically it calls the equivalent of
    isWhateverType() and toWhateverType() while skipping JSString * in favour
    of returning a UTF-8 encoded string.
    
    For not only type-checking but also converting a JS::Value of the wrong
    type to the requested type, there is
    gjs_object_require_converted_property_value(). Currently in the codebase
    this operation is only done on one type, uint32, but it can be overloaded
    with more types later.
    
    This reduces a lot of code duplication that would otherwise all have to
    be converted to rooting by hand in the next commit.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=742249

 gi/arg.cpp                  |   14 ++---
 gi/boxed.cpp                |    9 ++-
 gi/fundamental.cpp          |   14 ++--
 gi/gerror.cpp               |   31 +++++------
 gi/repo.cpp                 |   68 ++++++++--------------
 gi/value.cpp                |    9 +--
 gjs/coverage.cpp            |  110 +++++++++++++++---------------------
 gjs/importer.cpp            |   23 ++------
 gjs/jsapi-dynamic-class.cpp |   19 ++----
 gjs/jsapi-util.cpp          |  131 ++++++++++++++++++++++++++++++++++++++-----
 gjs/jsapi-util.h            |   34 +++++++++++
 11 files changed, 267 insertions(+), 195 deletions(-)
---
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 3df4437..7f9ed7b 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -1119,13 +1119,11 @@ gjs_array_to_explicit_array_internal(JSContext       *context,
             goto out;
     } else if (JS_HasPropertyById(context, &value.toObject(), length_name, &found_length) &&
                found_length) {
-        JS::RootedValue length_value(context);
         JS::RootedObject array_obj(context, &value.toObject());
         guint32 length;
 
-        if (!gjs_object_require_property(context, array_obj, NULL, length_name,
-                                         &length_value) ||
-            !JS::ToUint32(context, length_value, &length)) {
+        if (!gjs_object_require_converted_property_value(context, array_obj, NULL,
+                                                         length_name, &length)) {
             goto out;
         } else {
             if (!gjs_array_to_array(context,
@@ -1648,14 +1646,12 @@ gjs_value_to_g_argument(JSContext      *context,
         if (value.isObject() &&
             JS_HasPropertyById(context, &value.toObject(), length_name, &found_length) &&
             found_length) {
-            JS::RootedValue length_value(context);
             JS::RootedObject array_obj(context, &value.toObject());
             guint32 length;
 
-            if (!gjs_object_require_property(context, array_obj, NULL,
-                                             length_name,
-                                             &length_value) ||
-                !JS::ToUint32(context, length_value, &length)) {
+            if (!gjs_object_require_converted_property_value(context, array_obj,
+                                                             NULL, length_name,
+                                                             &length)) {
                 wrong = true;
             } else {
                 GList *list;
diff --git a/gi/boxed.cpp b/gi/boxed.cpp
index 977622b..3cf4964 100644
--- a/gi/boxed.cpp
+++ b/gi/boxed.cpp
@@ -328,15 +328,16 @@ boxed_invoke_constructor(JSContext             *context,
                          JS::HandleId           constructor_name,
                          JS::CallArgs&          args)
 {
-    JS::RootedValue js_constructor(context), js_constructor_func(context);
     JS::RootedId constructor_const(context,
         gjs_context_get_const_string(context, GJS_STRING_CONSTRUCTOR));
+    JS::RootedObject js_constructor(context);
 
-    if (!gjs_object_require_property(context, obj, NULL, constructor_const, &js_constructor))
+    if (!gjs_object_require_property_value(context, obj, NULL, constructor_const,
+                                           &js_constructor))
         return false;
 
-    JS::RootedObject js_constructor_obj(context, &js_constructor.toObject());
-    if (!gjs_object_require_property(context, js_constructor_obj, NULL,
+    JS::RootedValue js_constructor_func(context);
+    if (!gjs_object_require_property(context, js_constructor, NULL,
                                      constructor_name, &js_constructor_func))
         return false;
 
diff --git a/gi/fundamental.cpp b/gi/fundamental.cpp
index 569ae26..34a881a 100644
--- a/gi/fundamental.cpp
+++ b/gi/fundamental.cpp
@@ -388,12 +388,12 @@ fundamental_invoke_constructor(FundamentalInstance *priv,
                                JS::Value           *argv,
                                GArgument           *rvalue)
 {
-    JS::RootedValue js_constructor(context), js_constructor_func(context);
     JS::RootedId constructor_const(context,
         gjs_context_get_const_string(context, GJS_STRING_CONSTRUCTOR));
+    JS::RootedObject js_constructor(context);
 
-    if (!gjs_object_require_property(context, obj, NULL,
-                                     constructor_const, &js_constructor) ||
+    if (!gjs_object_require_property_value(context, obj, NULL,
+                                           constructor_const, &js_constructor) ||
         priv->prototype->constructor_name == JSID_VOID) {
         gjs_throw (context,
                    "Couldn't find a constructor for type %s.%s",
@@ -402,9 +402,10 @@ fundamental_invoke_constructor(FundamentalInstance *priv,
         return false;
     }
 
-    JS::RootedObject js_constructor_obj(context, js_constructor.toObjectOrNull());
-    if (!gjs_object_require_property(context, js_constructor_obj, NULL,
-                                     priv->prototype->constructor_name, &js_constructor_func)) {
+    JS::RootedObject constructor(context);
+    if (!gjs_object_require_property_value(context, js_constructor, NULL,
+                                           priv->prototype->constructor_name,
+                                           &constructor)) {
         gjs_throw (context,
                    "Couldn't find a constructor for type %s.%s",
                    g_base_info_get_namespace((GIBaseInfo*) priv->prototype->info),
@@ -412,7 +413,6 @@ fundamental_invoke_constructor(FundamentalInstance *priv,
         return false;
     }
 
-    JS::RootedObject constructor(context, js_constructor_func.toObjectOrNull());
     return gjs_invoke_constructor_from_c(context, constructor, obj, argc, argv, rvalue);
 }
 
diff --git a/gi/gerror.cpp b/gi/gerror.cpp
index c0042c7..4c38953 100644
--- a/gi/gerror.cpp
+++ b/gi/gerror.cpp
@@ -54,6 +54,7 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(error)
     Error *priv;
     Error *proto_priv;
     gchar *message;
+    int32_t code;
 
     /* Check early to avoid allocating memory for nothing */
     if (argc != 1 || !argv[0].isObject()) {
@@ -94,25 +95,23 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(error)
     g_base_info_ref( (GIBaseInfo*) priv->info);
     priv->domain = proto_priv->domain;
 
-    JS::RootedValue v_message(context), v_code(context);
     JS::RootedObject params_obj(context, &argv[0].toObject());
     JS::RootedId message_name(context,
         gjs_context_get_const_string(context, GJS_STRING_MESSAGE));
     JS::RootedId code_name(context,
         gjs_context_get_const_string(context, GJS_STRING_CODE));
-    if (!gjs_object_require_property(context, params_obj,
-                                     "GError constructor", message_name, &v_message))
+    if (!gjs_object_require_property_value(context, params_obj,
+                                           "GError constructor", message_name,
+                                           &message))
         return false;
-    if (!gjs_object_require_property(context, params_obj,
-                                     "GError constructor", code_name, &v_code))
-        return false;
-    if (!gjs_string_to_utf8 (context, v_message, &message))
+    if (!gjs_object_require_property_value(context, params_obj,
+                                           "GError constructor", code_name,
+                                           &code))
         return false;
 
-    priv->gerror = g_error_new_literal(priv->domain, v_code.toInt32(),
-                                       message);
+    priv->gerror = g_error_new_literal(priv->domain, code, message);
 
-    g_free (message);
+    JS_free(context, message);
 
     /* We assume this error will be thrown in the same line as the constructor */
     define_error_properties(context, object);
@@ -246,22 +245,20 @@ error_constructor_value_of(JSContext *context,
                            JS::Value *vp)
 {
     GJS_GET_THIS(context, argc, vp, rec, self);
-    JS::RootedValue v_prototype(context);
     Error *priv;
     JS::RootedId prototype_name(context,
         gjs_context_get_const_string(context, GJS_STRING_PROTOTYPE));
+    JS::RootedObject prototype(context);
 
-    if (!gjs_object_require_property(context, self, "constructor",
-                                     prototype_name, &v_prototype))
-        return false;
-
-    if (!v_prototype.isObject()) {
+    if (!gjs_object_require_property_value(context, self, "constructor",
+                                           prototype_name, &prototype)) {
+        /* This error message will be more informative */
+        JS_ClearPendingException(context);
         gjs_throw(context, "GLib.Error.valueOf() called on something that is not"
                   " a constructor");
         return false;
     }
 
-    JS::RootedObject prototype(context, &v_prototype.toObject());
     priv = priv_from_js(context, prototype);
 
     if (priv == NULL)
diff --git a/gi/repo.cpp b/gi/repo.cpp
index 9a3b6f9..b712bd2 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -59,27 +59,23 @@ static JSObject *lookup_override_function(JSContext *, JS::HandleId);
 static bool
 get_version_for_ns (JSContext       *context,
                     JS::HandleObject repo_obj,
-                    jsid             ns_id,
+                    JS::HandleId     ns_id,
                     char           **version)
 {
-    JS::RootedValue versions_val(context);
-    JSObject *versions;
+    JS::RootedObject versions(context);
     JS::RootedValue version_val(context);
     JS::RootedId versions_name(context,
         gjs_context_get_const_string(context, GJS_STRING_GI_VERSIONS));
 
-    if (!gjs_object_require_property(context, repo_obj, "GI repository object", versions_name, 
&versions_val) ||
-        !versions_val.isObject()) {
-        gjs_throw(context, "No 'versions' property in GI repository object");
+    if (!gjs_object_require_property_value(context, repo_obj,
+                                           "GI repository object", versions_name,
+                                           &versions))
         return false;
-    }
-
-    versions = &versions_val.toObject();
 
-    *version = NULL;
-    if (JS_GetPropertyById(context, versions, ns_id, version_val.address()) &&
-        version_val.isString()) {
-        gjs_string_to_utf8(context, version_val, version);
+    if (!gjs_object_require_property_value(context, versions, NULL, ns_id, version)) {
+        /* Property not actually required, so clear an exception */
+        JS_ClearPendingException(context);
+        *version = NULL;
     }
 
     return true;
@@ -588,27 +584,23 @@ lookup_override_function(JSContext   *cx,
     JS::RootedValue importer(cx, gjs_get_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS));
     g_assert(importer.isObject());
 
-    JS::RootedValue overridespkg(cx), module(cx), function(cx);
+    JS::RootedValue function(cx);
     JS::RootedId overrides_name(cx,
         gjs_context_get_const_string(cx, GJS_STRING_GI_OVERRIDES));
     JS::RootedId object_init_name(cx,
         gjs_context_get_const_string(cx, GJS_STRING_GOBJECT_INIT));
-    JS::RootedObject overridespkg_obj(cx), module_obj(cx);
+    JS::RootedObject overridespkg(cx), module(cx);
     JS::RootedObject importer_obj(cx, &importer.toObject());
 
-    if (!gjs_object_require_property(cx, importer_obj, "importer",
-                                     overrides_name, &overridespkg) ||
-        !overridespkg.isObject())
+    if (!gjs_object_require_property_value(cx, importer_obj, "importer",
+                                           overrides_name, &overridespkg))
         goto fail;
 
-    overridespkg_obj = &overridespkg.toObject();
-    if (!gjs_object_require_property(cx, overridespkg_obj,
-                                     "GI repository object", ns_name, &module)
-        || !module.isObject())
+    if (!gjs_object_require_property_value(cx, overridespkg, "GI repository object",
+                                           ns_name, &module))
         goto fail;
 
-    module_obj = &module.toObject();
-    if (!gjs_object_require_property(cx, module_obj, "override module",
+    if (!gjs_object_require_property(cx, module, "override module",
                                      object_init_name, &function) ||
         !function.isObjectOrNull())
         goto fail;
@@ -633,33 +625,21 @@ gjs_lookup_namespace_object_by_name(JSContext      *context,
 
     JS::RootedId gi_name(context,
         gjs_context_get_const_string(context, GJS_STRING_GI_MODULE));
-    JS::RootedObject importer_obj(context, &importer.toObject());
-    JS::RootedValue girepository(context);
-    if (!gjs_object_require_property(context, importer_obj, "importer",
-                                     gi_name, &girepository) ||
-        !girepository.isObject()) {
+    JS::RootedObject repo(context), importer_obj(context, &importer.toObject());
+
+    if (!gjs_object_require_property_value(context, importer_obj, "importer",
+                                           gi_name, &repo)) {
         gjs_log_exception(context);
         gjs_throw(context, "No gi property in importer");
         return NULL;
     }
 
-    JS::RootedObject repo_obj(context, &girepository.toObject());
-    JS::RootedValue ns_obj(context);
-    if (!gjs_object_require_property(context, repo_obj, "GI repository object", ns_name, &ns_obj)) {
-        return NULL;
-    }
-
-    if (!ns_obj.isObject()) {
-        char *name;
-
-        gjs_get_string_id(context, ns_name, &name);
-        gjs_throw(context, "Namespace '%s' is not an object?", name);
-
-        g_free(name);
+    JS::RootedObject retval(context);
+    if (!gjs_object_require_property_value(context, repo, "GI repository object",
+                                           ns_name, &retval))
         return NULL;
-    }
 
-    return &ns_obj.toObject();
+    return retval;
 }
 
 const char*
diff --git a/gi/value.cpp b/gi/value.cpp
index 0be77ab..85099ee 100644
--- a/gi/value.cpp
+++ b/gi/value.cpp
@@ -493,14 +493,13 @@ gjs_value_to_g_value_internal(JSContext    *context,
             /* do nothing */
         } else if (JS_HasPropertyById(context, &value.toObject(), length_name, &found_length) &&
                    found_length) {
-            JS::RootedValue length_value(context);
             JS::RootedObject array_obj(context, &value.toObject());
             guint32 length;
 
-            if (!gjs_object_require_property(context, array_obj, NULL,
-                                             length_name,
-                                             &length_value) ||
-                !JS::ToUint32(context, length_value, &length)) {
+            if (!gjs_object_require_converted_property_value(context, array_obj,
+                                                             NULL, length_name,
+                                                             &length)) {
+                JS_ClearPendingException(context);
                 gjs_throw(context,
                           "Wrong type %s; strv expected",
                           gjs_get_type_name(value));
diff --git a/gjs/coverage.cpp b/gjs/coverage.cpp
index 06237c7..135f71d 100644
--- a/gjs/coverage.cpp
+++ b/gjs/coverage.cpp
@@ -506,24 +506,40 @@ clear_coverage_function(gpointer info_location)
 }
 
 static bool
+get_hit_count_and_line_data(JSContext       *cx,
+                            JS::HandleObject obj,
+                            const char      *description,
+                            int32_t         *hit_count,
+                            int32_t         *line)
+{
+    JS::RootedId hit_count_name(cx, gjs_intern_string_to_id(cx, "hitCount"));
+    if (!gjs_object_require_property_value(cx, obj, "function element",
+                                           hit_count_name, hit_count))
+        return false;
+
+    JS::RootedId line_number_name(cx, gjs_intern_string_to_id(cx, "line"));
+    return gjs_object_require_property_value(cx, obj, "function_element",
+                                             line_number_name, line);
+}
+
+static bool
 convert_and_insert_function_decl(GArray    *array,
                                  JSContext *context,
                                  JS::Value *element)
 {
-    JSObject *object;
-
     if (!element->isObject()) {
         gjs_throw(context, "Function element is not an object");
         return false;
     }
-    object = &element->toObject();
 
-    JS::Value function_name_property_value;
+    JS::RootedObject object(context, &element->toObject());
+    JS::RootedValue function_name_property_value(context);
+    JS::RootedId function_name(context,
+        gjs_context_get_const_string(context, GJS_STRING_NAME));
 
-    if (!JS_GetProperty(context, object, "name", &function_name_property_value)) {
-        gjs_throw(context, "Failed to get name property for function object");
+    if (!gjs_object_require_property(context, object, NULL, function_name,
+                                     &function_name_property_value))
         return false;
-    }
 
     char *utf8_string;
 
@@ -541,22 +557,11 @@ convert_and_insert_function_decl(GArray    *array,
         return false;
     }
 
-    JS::Value hit_count_property_value;
-    if (!JS_GetProperty(context, object, "hitCount", &hit_count_property_value) ||
-        !hit_count_property_value.isInt32()) {
-        gjs_throw(context, "Failed to get hitCount property for function object");
-        return false;
-    }
-
-    JS::Value line_number_property_value;
-    if (!JS_GetProperty(context, object, "line", &line_number_property_value) ||
-        !line_number_property_value.isInt32()) {
-        gjs_throw(context, "Failed to get line property for function object");
+    int32_t hit_count;
+    int32_t line_number;
+    if (!get_hit_count_and_line_data(context, object, "function element",
+                                     &hit_count, &line_number))
         return false;
-    }
-
-    unsigned int line_number = line_number_property_value.toInt32();
-    unsigned int hit_count = hit_count_property_value.toInt32();
 
     GjsCoverageFunction info;
     init_covered_function(&info,
@@ -618,29 +623,13 @@ convert_and_insert_branch_exit(GArray    *array,
         return false;
     }
 
-    JSObject *object = &element->toObject();
-
-    JS::Value line_value;
-    int32_t line;
+    JS::RootedObject object(context, &element->toObject());
 
-    if (!JS_GetProperty(context, object, "line", &line_value) ||
-        !line_value.isInt32()) {
-        gjs_throw(context, "Failed to get line property from element");
-        return false;
-    }
-
-    line = line_value.toInt32();
-
-    JS::Value hit_count_value;
     int32_t hit_count;
-
-    if (!JS_GetProperty(context, object, "hitCount", &hit_count_value) ||
-        !hit_count_value.isInt32()) {
-        gjs_throw(context, "Failed to get hitCount property from element");
+    int32_t line;
+    if (!get_hit_count_and_line_data(context, object, "branch exit array element",
+                                     &hit_count, &line))
         return false;
-    }
-
-    hit_count = hit_count_value.toInt32();
 
     GjsCoverageBranchExit exit = {
         (unsigned int) line,
@@ -663,28 +652,21 @@ convert_and_insert_branch_info(GArray    *array,
     }
 
     if (element->isObject()) {
-        JSObject *object = &element->toObject();
-        JS::Value branch_point_value;
+        JS::RootedObject object(context, &element->toObject());
+
         int32_t branch_point;
+        JS::RootedId point_name(context, gjs_intern_string_to_id(context, "point"));
 
-        if (!JS_GetProperty(context, object, "point", &branch_point_value) ||
-            !branch_point_value.isInt32()) {
-            gjs_throw(context, "Failed to get point property from element");
+        if (!gjs_object_require_property_value(context, object, "branch array element",
+                                               point_name, &branch_point))
             return false;
-        }
 
-        branch_point = branch_point_value.toInt32();
-
-        JS::Value was_hit_value;
         bool was_hit;
+        JS::RootedId hit_name(context, gjs_intern_string_to_id(context, "hit"));
 
-        if (!JS_GetProperty(context, object, "hit", &was_hit_value) ||
-            !was_hit_value.isBoolean()) {
-            gjs_throw(context, "Failed to get point property from element");
+        if (!gjs_object_require_property_value(context, object, "branch array element",
+                                               hit_name, &was_hit))
             return false;
-        }
-
-        was_hit = was_hit_value.toBoolean();
 
         JS::Value branch_exits_value;
         GArray *branch_exits_array = NULL;
@@ -1633,12 +1615,14 @@ bootstrap_coverage(GjsCoverage *coverage)
                                                   &error))
             g_error("Failed to eval coverage script: %s\n", error->message);
 
-        JS::Value coverage_statistics_prototype_value;
-        if (!JS_GetProperty(context, debugger_compartment, "CoverageStatistics", 
&coverage_statistics_prototype_value) ||
-            !coverage_statistics_prototype_value.isObject()) {
-            gjs_throw(context, "Failed to get CoverageStatistics prototype");
+        JS::RootedObject coverage_statistics_constructor(context);
+        JS::RootedId coverage_statistics_name(context,
+            gjs_intern_string_to_id(context, "CoverageStatistics"));
+        if (!gjs_object_require_property_value(context, debugger_compartment,
+                                               "debugger compartment",
+                                               coverage_statistics_name,
+                                               &coverage_statistics_constructor))
             return false;
-        }
 
         /* Create value for holding the cache. This will be undefined if
          * the cache does not exist, otherwise it will be an object set
@@ -1658,8 +1642,6 @@ bootstrap_coverage(GjsCoverage *coverage)
             cache_value.set(JS::UndefinedValue());
         }
 
-        JSObject *coverage_statistics_constructor = &coverage_statistics_prototype_value.toObject();
-
         /* Now create the array to pass the desired prefixes over */
         JSObject *prefixes = gjs_build_string_array(context, -1, priv->prefixes);
 
diff --git a/gjs/importer.cpp b/gjs/importer.cpp
index bf022e6..8b548fe 100644
--- a/gjs/importer.cpp
+++ b/gjs/importer.cpp
@@ -411,7 +411,7 @@ do_import(JSContext       *context,
     char *filename;
     char *full_path;
     char *dirname = NULL;
-    JS::RootedValue search_path_val(context);
+    JS::RootedObject search_path(context);
     guint32 search_path_len;
     guint32 i;
     bool result;
@@ -421,17 +421,12 @@ do_import(JSContext       *context,
 
     JS::RootedId search_path_name(context,
         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 false;
-    }
 
-    if (!search_path_val.isObject()) {
-        gjs_throw(context, "searchPath property on importer is not an object");
+    if (!gjs_object_require_property_value(context, obj, "importer",
+                                           search_path_name, &search_path)) {
         return false;
     }
 
-    JS::RootedObject search_path(context, &search_path_val.toObject());
-
     if (!JS_IsArrayObject(context, search_path)) {
         gjs_throw(context, "searchPath property on importer is not an array");
         return false;
@@ -662,7 +657,7 @@ importer_new_enumerate(JSContext  *context,
     case JSENUMERATE_INIT_ALL:
     case JSENUMERATE_INIT: {
         Importer *priv;
-        JS::RootedValue search_path_val(context);
+        JS::RootedObject search_path(context);
         guint32 search_path_len;
         guint32 i;
 
@@ -678,16 +673,10 @@ importer_new_enumerate(JSContext  *context,
 
         JS::RootedId search_path_name(context,
             gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH));
-        if (!gjs_object_require_property(context, object, "importer", search_path_name, &search_path_val))
+        if (!gjs_object_require_property_value(context, object, "importer",
+                                               search_path_name, &search_path))
             return false;
 
-        if (!search_path_val.isObject()) {
-            gjs_throw(context, "searchPath property on importer is not an object");
-            return false;
-        }
-
-        JS::RootedObject search_path(context, &search_path_val.toObject());
-
         if (!JS_IsArrayObject(context, search_path)) {
             gjs_throw(context, "searchPath property on importer is not an array");
             return false;
diff --git a/gjs/jsapi-dynamic-class.cpp b/gjs/jsapi-dynamic-class.cpp
index 3965f59..f85c57e 100644
--- a/gjs/jsapi-dynamic-class.cpp
+++ b/gjs/jsapi-dynamic-class.cpp
@@ -172,22 +172,15 @@ gjs_construct_object_dynamic(JSContext       *context,
                              unsigned         argc,
                              JS::Value       *argv)
 {
-    JSObject *constructor;
-    JSObject *result = NULL;
-    JS::RootedValue value(context);
-
-    JS_BeginRequest(context);
+    JSAutoRequest ar(context);
 
     JS::RootedId constructor_name(context,
         gjs_context_get_const_string(context, GJS_STRING_CONSTRUCTOR));
-    if (!gjs_object_require_property(context, proto, "prototype",
-                                     constructor_name, &value))
-        goto out;
+    JS::RootedObject constructor(context);
 
-    constructor = &value.toObject();
-    result = JS_New(context, constructor, argc, argv);
+    if (!gjs_object_require_property_value(context, proto, "prototype",
+                                           constructor_name, &constructor))
+        return NULL;
 
- out:
-    JS_EndRequest(context);
-    return result;
+    return JS_New(context, constructor, argc, argv);
 }
diff --git a/gjs/jsapi-util.cpp b/gjs/jsapi-util.cpp
index b33e1d8..6213842 100644
--- a/gjs/jsapi-util.cpp
+++ b/gjs/jsapi-util.cpp
@@ -128,6 +128,29 @@ gjs_get_global_slot (JSContext     *context,
     return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
 }
 
+static void
+throw_property_lookup_error(JSContext       *cx,
+                            JS::HandleObject obj,
+                            const char      *description,
+                            JS::HandleId     property_name,
+                            const char      *reason)
+{
+    /* remember gjs_throw() is a no-op if JS_GetProperty()
+     * already set an exception
+     */
+    char *name;
+    gjs_get_string_id(cx, property_name, &name);
+
+    if (description)
+        gjs_throw(cx, "No property '%s' in %s (or %s)", name, description,
+                  reason);
+    else
+        gjs_throw(cx, "No property '%s' in object %p (or %s)", name,
+                  obj.address(), reason);
+
+    g_free(name);
+}
+
 /* Returns whether the object had the property; if the object did
  * not have the property, always sets an exception. Treats
  * "the property's value is undefined" the same as "no such property,".
@@ -143,8 +166,6 @@ gjs_object_require_property(JSContext             *context,
                             JS::HandleId           property_name,
                             JS::MutableHandleValue value)
 {
-    char *name;
-
     value.setUndefined();
 
     if (G_UNLIKELY(!JS_GetPropertyById(context, obj, property_name, value.address())))
@@ -153,22 +174,102 @@ gjs_object_require_property(JSContext             *context,
     if (G_LIKELY(!value.isUndefined()))
         return true;
 
-    /* remember gjs_throw() is a no-op if JS_GetProperty()
-     * already set an exception
-     */
+    throw_property_lookup_error(context, obj, obj_description, property_name,
+                                "its value was undefined");
+    return false;
+}
 
-    gjs_get_string_id(context, property_name, &name);
+bool
+gjs_object_require_property_value(JSContext       *cx,
+                                  JS::HandleObject obj,
+                                  const char      *description,
+                                  JS::HandleId     property_name,
+                                  bool            *value)
+{
+    JS::RootedValue prop_value(cx);
+    if (JS_GetPropertyById(cx, obj, property_name, prop_value.address()) &&
+        prop_value.isBoolean()) {
+        *value = prop_value.toBoolean();
+        return true;
+    }
 
-    if (obj_description)
-        gjs_throw(context,
-                  "No property '%s' in %s (or its value was undefined)",
-                  name, obj_description);
-    else
-        gjs_throw(context,
-                  "No property '%s' in object %p (or its value was undefined)",
-                  name, obj.address());
+    throw_property_lookup_error(cx, obj, description, property_name,
+                                "it was not a boolean");
+    return false;
+}
 
-    g_free(name);
+bool
+gjs_object_require_property_value(JSContext       *cx,
+                                  JS::HandleObject obj,
+                                  const char      *description,
+                                  JS::HandleId     property_name,
+                                  int32_t         *value)
+{
+    JS::RootedValue prop_value(cx);
+    if (JS_GetPropertyById(cx, obj, property_name, prop_value.address()) &&
+        prop_value.isInt32()) {
+        *value = prop_value.toInt32();
+        return true;
+    }
+
+    throw_property_lookup_error(cx, obj, description, property_name,
+                                "it was not a 32-bit integer");
+    return false;
+}
+
+/* Converts JS string value to UTF-8 string. value must be freed with JS_free. */
+bool
+gjs_object_require_property_value(JSContext       *cx,
+                                  JS::HandleObject obj,
+                                  const char      *description,
+                                  JS::HandleId     property_name,
+                                  char           **value)
+{
+    JS::RootedValue prop_value(cx);
+    if (JS_GetPropertyById(cx, obj, property_name, prop_value.address()) &&
+        gjs_string_to_utf8(cx, prop_value, value)) {
+        return true;
+    }
+
+    throw_property_lookup_error(cx, obj, description, property_name,
+                                "it was not a valid string");
+    return false;
+}
+
+bool
+gjs_object_require_property_value(JSContext              *cx,
+                                  JS::HandleObject        obj,
+                                  const char             *description,
+                                  JS::HandleId            property_name,
+                                  JS::MutableHandleObject value)
+{
+    JS::RootedValue prop_value(cx);
+    if (JS_GetPropertyById(cx, obj, property_name, prop_value.address()) &&
+        prop_value.isObject()) {
+        value.set(&prop_value.toObject());
+        return true;
+    }
+
+    throw_property_lookup_error(cx, obj, description, property_name,
+                                "it was not an object");
+    return false;
+}
+
+bool
+gjs_object_require_converted_property_value(JSContext       *cx,
+                                            JS::HandleObject obj,
+                                            const char      *description,
+                                            JS::HandleId     property_name,
+                                            uint32_t        *value)
+{
+    JS::RootedValue prop_value(cx);
+    if (JS_GetPropertyById(cx, obj, property_name, prop_value.address()) &&
+        JS::ToUint32(cx, prop_value, value)) {
+        return true;
+    }
+
+    throw_property_lookup_error(cx, obj, description, property_name,
+                                "it couldn't be converted to uint32");
     return false;
 }
 
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index 45b6503..9f39be2 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -309,6 +309,13 @@ bool gjs_object_require_property(JSContext             *context,
                                  JS::HandleId           property_name,
                                  JS::MutableHandleValue value);
 
+/* This is intended to be overloaded with more types as the opportunity arises */
+bool gjs_object_require_converted_property_value(JSContext       *context,
+                                                 JS::HandleObject obj,
+                                                 const char      *description,
+                                                 JS::HandleId     property_name,
+                                                 uint32_t        *value);
+
 bool gjs_init_class_dynamic(JSContext              *context,
                             JSObject               *in_object,
                             JSObject               *parent_proto,
@@ -483,4 +490,31 @@ const char * gjs_strip_unix_shebang(const char *script,
 
 G_END_DECLS
 
+/* Overloaded functions, must be outside G_DECLS. More types are intended to be
+ * added as the opportunity arises. */
+
+bool gjs_object_require_property_value(JSContext       *cx,
+                                       JS::HandleObject obj,
+                                       const char      *description,
+                                       JS::HandleId     property_name,
+                                       bool            *value);
+
+bool gjs_object_require_property_value(JSContext       *cx,
+                                       JS::HandleObject obj,
+                                       const char      *description,
+                                       JS::HandleId     property_name,
+                                       int32_t         *value);
+
+bool gjs_object_require_property_value(JSContext       *cx,
+                                       JS::HandleObject obj,
+                                       const char      *description,
+                                       JS::HandleId     property_name,
+                                       char           **value);
+
+bool gjs_object_require_property_value(JSContext              *cx,
+                                       JS::HandleObject        obj,
+                                       const char             *description,
+                                       JS::HandleId            property_name,
+                                       JS::MutableHandleObject value);
+
 #endif  /* __GJS_JSAPI_UTIL_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]