LCOV - code coverage report
Current view: top level - gjs - importer.cpp (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 342 425 80.5 %
Date: 2024-02-27 17:05:05 Functions: 21 21 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 191 299 63.9 %

           Branch data     Line data    Source code
       1                 :            : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
       2                 :            : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       3                 :            : // SPDX-FileCopyrightText: 2008-2010 litl, LLC
       4                 :            : 
       5                 :            : #include <config.h>
       6                 :            : 
       7                 :            : #include <string.h>  // for size_t, strcmp, strlen
       8                 :            : 
       9                 :            : #ifdef _WIN32
      10                 :            : #    include <windows.h>
      11                 :            : #endif
      12                 :            : 
      13                 :            : #include <string>
      14                 :            : #include <vector>   // for vector
      15                 :            : 
      16                 :            : #include <gio/gio.h>
      17                 :            : #include <glib-object.h>
      18                 :            : #include <glib.h>
      19                 :            : 
      20                 :            : #include <js/Array.h>
      21                 :            : #include <js/CallArgs.h>
      22                 :            : #include <js/CharacterEncoding.h>
      23                 :            : #include <js/Class.h>
      24                 :            : #include <js/ComparisonOperators.h>
      25                 :            : #include <js/ErrorReport.h>  // for JS_ReportOutOfMemory, JSEXN_ERR
      26                 :            : #include <js/Exception.h>
      27                 :            : #include <js/GlobalObject.h>  // for CurrentGlobalOrNull
      28                 :            : #include <js/Id.h>  // for PropertyKey
      29                 :            : #include <js/Object.h>  // for GetClass
      30                 :            : #include <js/PropertyAndElement.h>
      31                 :            : #include <js/PropertyDescriptor.h>
      32                 :            : #include <js/PropertySpec.h>
      33                 :            : #include <js/RootingAPI.h>
      34                 :            : #include <js/String.h>
      35                 :            : #include <js/Symbol.h>
      36                 :            : #include <js/TypeDecls.h>
      37                 :            : #include <js/Utility.h>  // for UniqueChars
      38                 :            : #include <js/Value.h>
      39                 :            : #include <jsapi.h>  // for JS_NewPlainObject, IdVector, JS_...
      40                 :            : #include <mozilla/Maybe.h>
      41                 :            : #include <mozilla/UniquePtr.h>
      42                 :            : 
      43                 :            : #include "gjs/atoms.h"
      44                 :            : #include "gjs/context-private.h"
      45                 :            : #include "gjs/global.h"
      46                 :            : #include "gjs/importer.h"
      47                 :            : #include "gjs/jsapi-util.h"
      48                 :            : #include "gjs/macros.h"
      49                 :            : #include "gjs/module.h"
      50                 :            : #include "gjs/native.h"
      51                 :            : #include "util/log.h"
      52                 :            : 
      53                 :            : #define MODULE_INIT_FILENAME "__init__.js"
      54                 :            : 
      55                 :            : extern const JSClass gjs_importer_class;
      56                 :            : 
      57                 :            : GJS_JSAPI_RETURN_CONVENTION
      58                 :            : static JSObject* gjs_define_importer(JSContext*, JS::HandleObject, const char*,
      59                 :            :                                      const std::vector<std::string>&, bool);
      60                 :            : 
      61                 :            : GJS_JSAPI_RETURN_CONVENTION
      62                 :            : static bool
      63                 :         14 : importer_to_string(JSContext *cx,
      64                 :            :                    unsigned   argc,
      65                 :            :                    JS::Value *vp)
      66                 :            : {
      67         [ -  + ]:         14 :     GJS_GET_THIS(cx, argc, vp, args, importer);
      68                 :            : 
      69                 :         14 :     GjsAutoChar output;
      70                 :            : 
      71                 :         14 :     const JSClass* klass = JS::GetClass(importer);
      72                 :         14 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
      73                 :         14 :     JS::RootedValue module_path(cx);
      74         [ -  + ]:         14 :     if (!JS_GetPropertyById(cx, importer, atoms.module_path(), &module_path))
      75                 :          0 :         return false;
      76                 :            : 
      77         [ +  + ]:         14 :     if (module_path.isNull()) {
      78                 :          7 :         output = g_strdup_printf("[%s root]", klass->name);
      79                 :            :     } else {
      80                 :          7 :         g_assert(module_path.isString() && "Bad importer.__modulePath__");
      81                 :          7 :         JS::UniqueChars path = gjs_string_to_utf8(cx, module_path);
      82         [ -  + ]:          7 :         if (!path)
      83                 :          0 :             return false;
      84                 :          7 :         output = g_strdup_printf("[%s %s]", klass->name, path.get());
      85         [ +  - ]:          7 :     }
      86                 :            : 
      87                 :         14 :     args.rval().setString(JS_NewStringCopyZ(cx, output));
      88                 :         14 :     return true;
      89                 :         14 : }
      90                 :            : 
      91                 :            : GJS_JSAPI_RETURN_CONVENTION
      92                 :            : static bool
      93                 :       1306 : define_meta_properties(JSContext       *context,
      94                 :            :                        JS::HandleObject module_obj,
      95                 :            :                        const char      *parse_name,
      96                 :            :                        const char      *module_name,
      97                 :            :                        JS::HandleObject parent)
      98                 :            : {
      99                 :            :     bool parent_is_module;
     100                 :       1306 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     101                 :            : 
     102                 :            :     /* For these meta-properties, don't set ENUMERATE since we wouldn't want to
     103                 :            :      * copy these symbols to any other object for example. RESOLVING is used to
     104                 :            :      * make sure we don't try to invoke a "resolve" operation, since this
     105                 :            :      * function may be called from inside one. */
     106                 :       1306 :     unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING;
     107                 :            : 
     108                 :            :     /* We define both __moduleName__ and __parentModule__ to null
     109                 :            :      * on the root importer
     110                 :            :      */
     111         [ +  - ]:       1306 :     parent_is_module = parent && JS_InstanceOf(context, parent,
     112         [ +  + ]:       1306 :                                                &gjs_importer_class, nullptr);
     113                 :            : 
     114         [ +  - ]:       2612 :     gjs_debug(GJS_DEBUG_IMPORTER, "Defining parent %p of %p '%s' is mod %d",
     115                 :       1306 :               parent.get(), module_obj.get(),
     116                 :            :               module_name ? module_name : "<root>", parent_is_module);
     117                 :            : 
     118         [ +  + ]:       1306 :     if (parse_name != nullptr) {
     119                 :        444 :         JS::RootedValue file(context);
     120         [ -  + ]:        444 :         if (!gjs_string_from_utf8(context, parse_name, &file))
     121                 :          0 :             return false;
     122         [ -  + ]:        444 :         if (!JS_DefinePropertyById(context, module_obj, atoms.file(), file,
     123                 :            :                                    attrs))
     124                 :          0 :             return false;
     125         [ +  - ]:        444 :     }
     126                 :            : 
     127                 :            :     /* Null is used instead of undefined for backwards compatibility with code
     128                 :            :      * that explicitly checks for null. */
     129                 :       1306 :     JS::RootedValue module_name_val(context, JS::NullValue());
     130                 :       1306 :     JS::RootedValue parent_module_val(context, JS::NullValue());
     131                 :       1306 :     JS::RootedValue module_path(context, JS::NullValue());
     132                 :       1306 :     JS::RootedValue to_string_tag(context);
     133         [ +  + ]:       1306 :     if (parent_is_module) {
     134         [ -  + ]:       1064 :         if (!gjs_string_from_utf8(context, module_name, &module_name_val))
     135                 :          0 :             return false;
     136                 :       1064 :         parent_module_val.setObject(*parent);
     137                 :            : 
     138                 :       1064 :         JS::RootedValue parent_module_path(context);
     139         [ -  + ]:       1064 :         if (!JS_GetPropertyById(context, parent, atoms.module_path(),
     140                 :            :                                 &parent_module_path))
     141                 :          0 :             return false;
     142                 :            : 
     143                 :       1064 :         GjsAutoChar module_path_buf;
     144         [ +  + ]:       1064 :         if (parent_module_path.isNull()) {
     145                 :        895 :             module_path_buf = g_strdup(module_name);
     146                 :            :         } else {
     147                 :            :             JS::UniqueChars parent_path =
     148                 :        169 :                 gjs_string_to_utf8(context, parent_module_path);
     149         [ -  + ]:        169 :             if (!parent_path)
     150                 :          0 :                 return false;
     151                 :        169 :             module_path_buf = g_strdup_printf("%s.%s", parent_path.get(), module_name);
     152         [ +  - ]:        169 :         }
     153         [ -  + ]:       1064 :         if (!gjs_string_from_utf8(context, module_path_buf, &module_path))
     154                 :          0 :             return false;
     155                 :            : 
     156                 :            :         GjsAutoChar to_string_tag_buf = g_strdup_printf("GjsModule %s",
     157                 :       1064 :                                                         module_path_buf.get());
     158         [ -  + ]:       1064 :         if (!gjs_string_from_utf8(context, to_string_tag_buf, &to_string_tag))
     159                 :          0 :             return false;
     160   [ +  -  +  -  :       1064 :     } else {
                   +  - ]
     161                 :        242 :         to_string_tag.setString(JS_AtomizeString(context, "GjsModule"));
     162                 :            :     }
     163                 :            : 
     164         [ -  + ]:       1306 :     if (!JS_DefinePropertyById(context, module_obj, atoms.module_name(),
     165                 :            :                                module_name_val, attrs))
     166                 :          0 :         return false;
     167                 :            : 
     168         [ -  + ]:       1306 :     if (!JS_DefinePropertyById(context, module_obj, atoms.parent_module(),
     169                 :            :                                parent_module_val, attrs))
     170                 :          0 :         return false;
     171                 :            : 
     172         [ -  + ]:       1306 :     if (!JS_DefinePropertyById(context, module_obj, atoms.module_path(),
     173                 :            :                                module_path, attrs))
     174                 :          0 :         return false;
     175                 :            : 
     176                 :            :     JS::RootedId to_string_tag_name(
     177                 :       1306 :         context, JS::PropertyKey::Symbol(JS::GetWellKnownSymbol(
     178                 :       1306 :                      context, JS::SymbolCode::toStringTag)));
     179                 :       1306 :     return JS_DefinePropertyById(context, module_obj, to_string_tag_name,
     180                 :       1306 :                                  to_string_tag, attrs);
     181                 :       1306 : }
     182                 :            : 
     183                 :            : GJS_JSAPI_RETURN_CONVENTION
     184                 :         69 : static bool import_directory(JSContext* context, JS::HandleObject obj,
     185                 :            :                              const char* name,
     186                 :            :                              const std::vector<std::string>& full_paths) {
     187                 :         69 :     gjs_debug(GJS_DEBUG_IMPORTER,
     188                 :            :               "Importing directory '%s'",
     189                 :            :               name);
     190                 :            : 
     191                 :            :     // We define a sub-importer that has only the given directories on its
     192                 :            :     // search path.
     193                 :         69 :     return !!gjs_define_importer(context, obj, name, full_paths, false);
     194                 :            : }
     195                 :            : 
     196                 :            : /* Make the property we set in gjs_module_import() permanent;
     197                 :            :  * we do this after the import successfully completes.
     198                 :            :  */
     199                 :            : GJS_JSAPI_RETURN_CONVENTION
     200                 :            : static bool
     201                 :        444 : seal_import(JSContext       *cx,
     202                 :            :             JS::HandleObject obj,
     203                 :            :             JS::HandleId     id,
     204                 :            :             const char      *name)
     205                 :            : {
     206                 :        444 :     JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> maybe_descr(cx);
     207                 :            : 
     208   [ +  -  -  +  :        888 :     if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &maybe_descr) ||
                   -  + ]
     209                 :        444 :         maybe_descr.isNothing()) {
     210                 :          0 :         gjs_debug(GJS_DEBUG_IMPORTER,
     211                 :            :                   "Failed to get attributes to seal '%s' in importer",
     212                 :            :                   name);
     213                 :          0 :         return false;
     214                 :            :     }
     215                 :            : 
     216                 :        444 :     JS::Rooted<JS::PropertyDescriptor> descr(cx, maybe_descr.value());
     217                 :            : 
     218                 :        444 :     descr.setConfigurable(false);
     219                 :            : 
     220         [ -  + ]:        444 :     if (!JS_DefinePropertyById(cx, obj, id, descr)) {
     221                 :          0 :         gjs_debug(GJS_DEBUG_IMPORTER,
     222                 :            :                   "Failed to redefine attributes to seal '%s' in importer",
     223                 :            :                   name);
     224                 :          0 :         return false;
     225                 :            :     }
     226                 :            : 
     227                 :        444 :     return true;
     228                 :        444 : }
     229                 :            : 
     230                 :            : /* An import failed. Delete the property pointing to the import
     231                 :            :  * from the parent namespace. In complicated situations this might
     232                 :            :  * not be sufficient to get us fully back to a sane state. If:
     233                 :            :  *
     234                 :            :  *  - We import module A
     235                 :            :  *  - module A imports module B
     236                 :            :  *  - module B imports module A, storing a reference to the current
     237                 :            :  *    module A module object
     238                 :            :  *  - module A subsequently throws an exception
     239                 :            :  *
     240                 :            :  * Then module B is left imported, but the imported module B has
     241                 :            :  * a reference to the failed module A module object. To handle this
     242                 :            :  * we could could try to track the entire "import operation" and
     243                 :            :  * roll back *all* modifications made to the namespace objects.
     244                 :            :  * It's not clear that the complexity would be worth the small gain
     245                 :            :  * in robustness. (You can still come up with ways of defeating
     246                 :            :  * the attempt to clean up.)
     247                 :            :  */
     248                 :            : static void
     249                 :          4 : cancel_import(JSContext       *context,
     250                 :            :               JS::HandleObject obj,
     251                 :            :               const char      *name)
     252                 :            : {
     253                 :          4 :     gjs_debug(GJS_DEBUG_IMPORTER,
     254                 :            :               "Cleaning up from failed import of '%s'",
     255                 :            :               name);
     256                 :            : 
     257         [ -  + ]:          4 :     if (!JS_DeleteProperty(context, obj, name)) {
     258                 :          0 :         gjs_debug(GJS_DEBUG_IMPORTER,
     259                 :            :                   "Failed to delete '%s' in importer",
     260                 :            :                   name);
     261                 :            :     }
     262                 :          4 : }
     263                 :            : 
     264                 :            : /*
     265                 :            :  * gjs_import_native_module:
     266                 :            :  * @cx: the #JSContext
     267                 :            :  * @importer: the root importer
     268                 :            :  * @id_str: Name under which the module was registered with add()
     269                 :            :  *
     270                 :            :  * Imports a builtin native-code module so that it is available to JS code as
     271                 :            :  * `imports[id_str]`.
     272                 :            :  *
     273                 :            :  * Returns: true on success, false if an exception was thrown.
     274                 :            :  */
     275                 :        551 : bool gjs_import_native_module(JSContext* cx, JS::HandleObject importer,
     276                 :            :                               const char* id_str) {
     277                 :        551 :     gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", id_str);
     278                 :            : 
     279                 :            :     JS::RootedObject native_registry(
     280                 :        551 :         cx, gjs_get_native_registry(JS::CurrentGlobalOrNull(cx)));
     281                 :            : 
     282                 :        551 :     JS::RootedId id(cx, gjs_intern_string_to_id(cx, id_str));
     283         [ -  + ]:        551 :     if (id.isVoid())
     284                 :          0 :         return false;
     285                 :            : 
     286                 :        551 :     JS::RootedObject module(cx);
     287         [ -  + ]:        551 :     if (!gjs_global_registry_get(cx, native_registry, id, &module))
     288                 :          0 :         return false;
     289                 :            : 
     290         [ +  + ]:       1052 :     if (!module &&
     291         [ +  - ]:        501 :         (!Gjs::NativeModuleDefineFuncs::get().define(cx, id_str, &module) ||
     292   [ -  +  -  + ]:       1052 :          !gjs_global_registry_set(cx, native_registry, id, module)))
     293                 :          0 :         return false;
     294                 :            : 
     295   [ +  -  +  - ]:       1102 :     return define_meta_properties(cx, module, nullptr, id_str, importer) &&
     296                 :        551 :            JS_DefineProperty(cx, importer, id_str, module,
     297                 :        551 :                              GJS_MODULE_PROP_FLAGS);
     298                 :        551 : }
     299                 :            : 
     300                 :            : GJS_JSAPI_RETURN_CONVENTION
     301                 :            : static bool
     302                 :        135 : import_module_init(JSContext       *context,
     303                 :            :                    GFile           *file,
     304                 :            :                    JS::HandleObject module_obj)
     305                 :            : {
     306                 :        135 :     gsize script_len = 0;
     307                 :        135 :     GjsAutoError error;
     308                 :            : 
     309                 :        135 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
     310                 :        135 :     JS::RootedValue ignored(context);
     311                 :            : 
     312                 :        135 :     GjsAutoChar script;
     313         [ +  + ]:        135 :     if (!g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr,
     314                 :            :                               &error)) {
     315         [ +  - ]:        264 :         if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) &&
     316   [ +  -  -  +  :        264 :             !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) &&
                   -  + ]
     317                 :        132 :             !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
     318                 :          0 :             gjs_throw_gerror_message(context, error);
     319                 :          0 :             return false;
     320                 :            :         }
     321                 :            : 
     322                 :        132 :         return true;
     323                 :            :     }
     324                 :          3 :     g_assert(script);
     325                 :            : 
     326                 :          3 :     GjsAutoChar full_path = g_file_get_parse_name(file);
     327                 :            : 
     328                 :          3 :     return gjs->eval_with_scope(module_obj, script, script_len, full_path,
     329                 :          3 :                                 &ignored);
     330                 :        135 : }
     331                 :            : 
     332                 :            : GJS_JSAPI_RETURN_CONVENTION
     333                 :       1191 : static JSObject* load_module_init(JSContext* cx, JS::HandleObject in_object,
     334                 :            :                                   GFile* file) {
     335                 :            :     bool found;
     336                 :       1191 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     337                 :            : 
     338                 :            :     /* First we check if js module has already been loaded  */
     339         [ -  + ]:       1191 :     if (!JS_HasPropertyById(cx, in_object, atoms.module_init(), &found))
     340                 :          0 :         return nullptr;
     341         [ +  + ]:       1191 :     if (found) {
     342                 :       1056 :         JS::RootedValue v_module(cx);
     343         [ -  + ]:       1056 :         if (!JS_GetPropertyById(cx, in_object, atoms.module_init(),
     344                 :            :                                 &v_module))
     345                 :          0 :             return nullptr;
     346         [ +  - ]:       1056 :         if (v_module.isObject())
     347                 :       1056 :             return &v_module.toObject();
     348                 :            : 
     349                 :          0 :         GjsAutoChar full_path = g_file_get_parse_name(file);
     350                 :          0 :         gjs_throw(cx, "Unexpected non-object module __init__ imported from %s",
     351                 :            :                   full_path.get());
     352                 :          0 :         return nullptr;
     353                 :       1056 :     }
     354                 :            : 
     355                 :        135 :     JS::RootedObject module_obj(cx, JS_NewPlainObject(cx));
     356         [ -  + ]:        135 :     if (!module_obj)
     357                 :          0 :         return nullptr;
     358                 :            : 
     359         [ +  + ]:        135 :     if (!import_module_init(cx, file, module_obj))
     360                 :          2 :         return nullptr;
     361                 :            : 
     362         [ -  + ]:        133 :     if (!JS_DefinePropertyById(cx, in_object, atoms.module_init(), module_obj,
     363                 :            :                                GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
     364                 :          0 :         return nullptr;
     365                 :            : 
     366                 :        133 :     return module_obj;
     367                 :        135 : }
     368                 :            : 
     369                 :            : GJS_JSAPI_RETURN_CONVENTION
     370                 :          3 : static bool load_module_elements(JSContext* cx, JS::HandleObject in_object,
     371                 :            :                                  JS::MutableHandleIdVector prop_ids,
     372                 :            :                                  GFile* file) {
     373                 :          3 :     JS::RootedObject module_obj(cx, load_module_init(cx, in_object, file));
     374         [ -  + ]:          3 :     if (!module_obj)
     375                 :          0 :         return false;
     376                 :            : 
     377                 :          3 :     JS::Rooted<JS::IdVector> ids(cx, cx);
     378         [ -  + ]:          3 :     if (!JS_Enumerate(cx, module_obj, &ids))
     379                 :          0 :         return false;
     380                 :            : 
     381         [ -  + ]:          3 :     if (!prop_ids.appendAll(ids)) {
     382                 :          0 :         JS_ReportOutOfMemory(cx);
     383                 :          0 :         return false;
     384                 :            :     }
     385                 :            : 
     386                 :          3 :     return true;
     387                 :          3 : }
     388                 :            : 
     389                 :            : /* If error, returns false. If not found, returns true but does not touch
     390                 :            :  * the value at *result. If found, returns true and sets *result = true.
     391                 :            :  */
     392                 :            : GJS_JSAPI_RETURN_CONVENTION
     393                 :       1188 : static bool import_symbol_from_init_js(JSContext* cx, JS::HandleObject importer,
     394                 :            :                                        GFile* directory, const char* name,
     395                 :            :                                        bool* result) {
     396                 :            :     bool found;
     397                 :            :     GjsAutoUnref<GFile> file =
     398                 :       1188 :         g_file_get_child(directory, MODULE_INIT_FILENAME);
     399                 :            : 
     400                 :       1188 :     JS::RootedObject module_obj(cx, load_module_init(cx, importer, file));
     401   [ +  +  -  +  :       1188 :     if (!module_obj || !JS_AlreadyHasOwnProperty(cx, module_obj, name, &found))
                   +  + ]
     402                 :          2 :         return false;
     403                 :            : 
     404         [ +  + ]:       1186 :     if (!found)
     405                 :       1184 :         return true;
     406                 :            : 
     407                 :          2 :     JS::RootedValue obj_val(cx);
     408         [ -  + ]:          2 :     if (!JS_GetProperty(cx, module_obj, name, &obj_val))
     409                 :          0 :         return false;
     410                 :            : 
     411         [ -  + ]:          2 :     if (obj_val.isUndefined())
     412                 :          0 :         return true;
     413                 :            : 
     414         [ -  + ]:          2 :     if (!JS_DefineProperty(cx, importer, name, obj_val,
     415                 :            :                            GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
     416                 :          0 :         return false;
     417                 :            : 
     418                 :          2 :     *result = true;
     419                 :          2 :     return true;
     420                 :       1188 : }
     421                 :            : 
     422                 :            : GJS_JSAPI_RETURN_CONVENTION
     423                 :        448 : static bool attempt_import(JSContext* cx, JS::HandleObject obj,
     424                 :            :                            JS::HandleId module_id, const char* module_name,
     425                 :            :                            GFile* file) {
     426                 :            :     JS::RootedObject module_obj(
     427                 :        448 :         cx, gjs_module_import(cx, obj, module_id, module_name, file));
     428         [ +  + ]:        448 :     if (!module_obj)
     429                 :          4 :         return false;
     430                 :            : 
     431                 :        444 :     GjsAutoChar full_path = g_file_get_parse_name(file);
     432                 :            : 
     433                 :        444 :     return define_meta_properties(cx, module_obj, full_path, module_name,
     434   [ +  -  +  - ]:        888 :                                   obj) &&
     435                 :        444 :            seal_import(cx, obj, module_id, module_name);
     436                 :        448 : }
     437                 :            : 
     438                 :            : GJS_JSAPI_RETURN_CONVENTION
     439                 :            : static bool
     440                 :        448 : import_file_on_module(JSContext       *context,
     441                 :            :                       JS::HandleObject obj,
     442                 :            :                       JS::HandleId     id,
     443                 :            :                       const char      *name,
     444                 :            :                       GFile           *file)
     445                 :            : {
     446         [ +  + ]:        448 :     if (!attempt_import(context, obj, id, name, file)) {
     447                 :          4 :         cancel_import(context, obj, name);
     448                 :          4 :         return false;
     449                 :            :     }
     450                 :            : 
     451                 :        444 :     return true;
     452                 :            : }
     453                 :            : 
     454                 :            : GJS_JSAPI_RETURN_CONVENTION
     455                 :       1153 : static bool do_import(JSContext* context, JS::HandleObject obj,
     456                 :            :                       JS::HandleId id) {
     457                 :       1153 :     JS::RootedObject search_path(context);
     458                 :            :     guint32 search_path_len;
     459                 :            :     guint32 i;
     460                 :            :     bool exists, is_array;
     461                 :       1153 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     462                 :            : 
     463         [ -  + ]:       1153 :     if (!gjs_object_require_property(context, obj, "importer",
     464                 :            :                                      atoms.search_path(), &search_path))
     465                 :          0 :         return false;
     466                 :            : 
     467         [ -  + ]:       1153 :     if (!JS::IsArrayObject(context, search_path, &is_array))
     468                 :          0 :         return false;
     469         [ -  + ]:       1153 :     if (!is_array) {
     470                 :          0 :         gjs_throw(context, "searchPath property on importer is not an array");
     471                 :          0 :         return false;
     472                 :            :     }
     473                 :            : 
     474         [ -  + ]:       1153 :     if (!JS::GetArrayLength(context, search_path, &search_path_len)) {
     475                 :          0 :         gjs_throw(context, "searchPath array has no length");
     476                 :          0 :         return false;
     477                 :            :     }
     478                 :            : 
     479                 :       1153 :     JS::UniqueChars name;
     480         [ -  + ]:       1153 :     if (!gjs_get_string_id(context, id, &name))
     481                 :          0 :         return false;
     482         [ -  + ]:       1153 :     if (!name) {
     483                 :          0 :         gjs_throw(context, "Importing invalid module name");
     484                 :          0 :         return false;
     485                 :            :     }
     486                 :            : 
     487                 :            :     // null if this is the root importer
     488                 :       1153 :     JS::RootedValue parent(context);
     489         [ -  + ]:       1153 :     if (!JS_GetPropertyById(context, obj, atoms.parent_module(), &parent))
     490                 :          0 :         return false;
     491                 :            : 
     492                 :            :     /* First try importing an internal module like gi */
     493   [ +  +  +  +  :       2051 :     if (parent.isNull() &&
                   +  + ]
     494                 :        898 :         Gjs::NativeModuleDefineFuncs::get().is_registered(name.get())) {
     495         [ -  + ]:        551 :         if (!gjs_import_native_module(context, obj, name.get()))
     496                 :          0 :             return false;
     497                 :            : 
     498                 :        551 :         gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'",
     499                 :            :                   name.get());
     500                 :        551 :         return true;
     501                 :            :     }
     502                 :            : 
     503                 :        602 :     GjsAutoChar filename = g_strdup_printf("%s.js", name.get());
     504                 :        602 :     std::vector<std::string> directories;
     505                 :        602 :     JS::RootedValue elem(context);
     506                 :        602 :     JS::RootedString str(context);
     507                 :            : 
     508         [ +  + ]:       1338 :     for (i = 0; i < search_path_len; ++i) {
     509                 :       1188 :         elem.setUndefined();
     510         [ -  + ]:       1188 :         if (!JS_GetElement(context, search_path, i, &elem)) {
     511                 :            :             /* this means there was an exception, while elem.isUndefined()
     512                 :            :              * means no element found
     513                 :            :              */
     514                 :        452 :             return false;
     515                 :            :         }
     516                 :            : 
     517         [ -  + ]:       1188 :         if (elem.isUndefined())
     518                 :        736 :             continue;
     519                 :            : 
     520         [ -  + ]:       1188 :         if (!elem.isString()) {
     521                 :          0 :             gjs_throw(context, "importer searchPath contains non-string");
     522                 :          0 :             return false;
     523                 :            :         }
     524                 :            : 
     525                 :       1188 :         str = elem.toString();
     526                 :       1188 :         JS::UniqueChars dirname(JS_EncodeStringToUTF8(context, str));
     527         [ -  + ]:       1188 :         if (!dirname)
     528                 :          0 :             return false;
     529                 :            : 
     530                 :            :         /* Ignore empty path elements */
     531         [ -  + ]:       1188 :         if (dirname[0] == '\0')
     532                 :          0 :             continue;
     533                 :            : 
     534                 :            :         GjsAutoUnref<GFile> directory =
     535                 :       1188 :             g_file_new_for_commandline_arg(dirname.get());
     536                 :            : 
     537                 :            :         /* Try importing __init__.js and loading the symbol from it */
     538                 :       1188 :         bool found = false;
     539         [ +  + ]:       1188 :         if (!import_symbol_from_init_js(context, obj, directory, name.get(),
     540                 :            :                                         &found))
     541                 :          2 :             return false;
     542         [ +  + ]:       1186 :         if (found)
     543                 :          2 :             return true;
     544                 :            : 
     545                 :            :         /* Second try importing a directory (a sub-importer) */
     546                 :       1184 :         GjsAutoUnref<GFile> file = g_file_get_child(directory, name.get());
     547                 :            : 
     548         [ +  + ]:       1184 :         if (g_file_query_file_type(file, GFileQueryInfoFlags(0), nullptr) ==
     549                 :            :             G_FILE_TYPE_DIRECTORY) {
     550                 :         69 :             GjsAutoChar full_path = g_file_get_parse_name(file);
     551                 :         69 :             gjs_debug(GJS_DEBUG_IMPORTER,
     552                 :            :                       "Adding directory '%s' to child importer '%s'",
     553                 :            :                       full_path.get(), name.get());
     554                 :         69 :             directories.push_back(full_path.get());
     555                 :         69 :         }
     556                 :            : 
     557                 :            :         /* If we just added to directories, we know we don't need to
     558                 :            :          * check for a file.  If we added to directories on an earlier
     559                 :            :          * iteration, we want to ignore any files later in the
     560                 :            :          * path. So, always skip the rest of the loop block if we have
     561                 :            :          * directories.
     562                 :            :          */
     563         [ +  + ]:       1184 :         if (!directories.empty())
     564                 :        262 :             continue;
     565                 :            : 
     566                 :            :         /* Third, if it's not a directory, try importing a file */
     567                 :        922 :         file = g_file_get_child(directory, filename.get());
     568                 :        922 :         exists = g_file_query_exists(file, nullptr);
     569                 :            : 
     570         [ +  + ]:        922 :         if (!exists) {
     571                 :        474 :             GjsAutoChar full_path = g_file_get_parse_name(file);
     572                 :        474 :             gjs_debug(GJS_DEBUG_IMPORTER,
     573                 :            :                       "JS import '%s' not found in %s at %s", name.get(),
     574                 :            :                       dirname.get(), full_path.get());
     575                 :        474 :             continue;
     576                 :        474 :         }
     577                 :            : 
     578         [ +  + ]:        448 :         if (import_file_on_module(context, obj, id, name.get(), file)) {
     579                 :        444 :             gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'",
     580                 :            :                       name.get());
     581                 :        444 :             return true;
     582                 :            :         }
     583                 :            : 
     584                 :            :         /* Don't keep searching path if we fail to load the file for
     585                 :            :          * reasons other than it doesn't exist... i.e. broken files
     586                 :            :          * block searching for nonbroken ones
     587                 :            :          */
     588                 :          4 :         return false;
     589   [ +  +  +  +  :       3560 :     }
                   +  + ]
     590                 :            : 
     591         [ +  + ]:        150 :     if (!directories.empty()) {
     592         [ -  + ]:         69 :         if (!import_directory(context, obj, name.get(), directories))
     593                 :          0 :             return false;
     594                 :            : 
     595                 :         69 :         gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported directory '%s'",
     596                 :            :                   name.get());
     597                 :         69 :         return true;
     598                 :            :     }
     599                 :            : 
     600                 :            :     /* If no exception occurred, the problem is just that we got to the
     601                 :            :      * end of the path. Be sure an exception is set. */
     602                 :         81 :     g_assert(!JS_IsExceptionPending(context));
     603                 :         81 :     gjs_throw_custom(context, JSEXN_ERR, "ImportError",
     604                 :            :                      "No JS module '%s' found in search path", name.get());
     605                 :         81 :     return false;
     606                 :       1153 : }
     607                 :            : 
     608                 :            : GJS_JSAPI_RETURN_CONVENTION
     609                 :          3 : static bool importer_new_enumerate(JSContext* context, JS::HandleObject object,
     610                 :            :                                    JS::MutableHandleIdVector properties,
     611                 :            :                                    bool enumerable_only [[maybe_unused]]) {
     612                 :            :     guint32 search_path_len;
     613                 :            :     guint32 i;
     614                 :            :     bool is_array;
     615                 :          3 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     616                 :            : 
     617                 :          3 :     JS::RootedObject search_path(context);
     618         [ -  + ]:          3 :     if (!gjs_object_require_property(context, object, "importer",
     619                 :            :                                      atoms.search_path(), &search_path))
     620                 :          0 :         return false;
     621                 :            : 
     622         [ -  + ]:          3 :     if (!JS::IsArrayObject(context, search_path, &is_array))
     623                 :          0 :         return false;
     624         [ -  + ]:          3 :     if (!is_array) {
     625                 :          0 :         gjs_throw(context, "searchPath property on importer is not an array");
     626                 :          0 :         return false;
     627                 :            :     }
     628                 :            : 
     629         [ -  + ]:          3 :     if (!JS::GetArrayLength(context, search_path, &search_path_len)) {
     630                 :          0 :         gjs_throw(context, "searchPath array has no length");
     631                 :          0 :         return false;
     632                 :            :     }
     633                 :            : 
     634                 :          3 :     JS::RootedValue elem(context);
     635                 :          3 :     JS::RootedString str(context);
     636         [ +  + ]:          6 :     for (i = 0; i < search_path_len; ++i) {
     637                 :          3 :         elem.setUndefined();
     638         [ -  + ]:          3 :         if (!JS_GetElement(context, search_path, i, &elem)) {
     639                 :            :             /* this means there was an exception, while elem.isUndefined()
     640                 :            :              * means no element found
     641                 :            :              */
     642                 :          0 :             return false;
     643                 :            :         }
     644                 :            : 
     645         [ -  + ]:          3 :         if (elem.isUndefined())
     646                 :          0 :             continue;
     647                 :            : 
     648         [ -  + ]:          3 :         if (!elem.isString()) {
     649                 :          0 :             gjs_throw(context, "importer searchPath contains non-string");
     650                 :          0 :             return false;
     651                 :            :         }
     652                 :            : 
     653                 :          3 :         str = elem.toString();
     654                 :          3 :         JS::UniqueChars dirname(JS_EncodeStringToUTF8(context, str));
     655         [ -  + ]:          3 :         if (!dirname)
     656                 :          0 :             return false;
     657                 :            : 
     658                 :            :         GjsAutoUnref<GFile> directory =
     659                 :          3 :             g_file_new_for_commandline_arg(dirname.get());
     660                 :            :         GjsAutoUnref<GFile> file =
     661                 :          3 :             g_file_get_child(directory, MODULE_INIT_FILENAME);
     662                 :            : 
     663         [ -  + ]:          3 :         if (!load_module_elements(context, object, properties, file))
     664                 :          0 :             return false;
     665                 :            : 
     666                 :            :         /* new_for_commandline_arg handles resource:/// paths */
     667                 :            :         GjsAutoUnref<GFileEnumerator> direnum = g_file_enumerate_children(
     668                 :            :             directory, "standard::name,standard::type", G_FILE_QUERY_INFO_NONE,
     669                 :          3 :             nullptr, nullptr);
     670                 :            : 
     671                 :            :         while (true) {
     672                 :            :             GFileInfo *info;
     673                 :            :             GFile *file;
     674         [ -  + ]:         60 :             if (!g_file_enumerator_iterate(direnum, &info, &file, NULL, NULL))
     675                 :          0 :                 break;
     676   [ +  +  +  - ]:         60 :             if (info == NULL || file == NULL)
     677                 :            :                 break;
     678                 :            : 
     679                 :         57 :             GjsAutoChar filename = g_file_get_basename(file);
     680                 :            : 
     681                 :            :             /* skip hidden files and directories (.svn, .git, ...) */
     682         [ -  + ]:         57 :             if (filename.get()[0] == '.')
     683                 :          0 :                 continue;
     684                 :            : 
     685                 :            :             /* skip module init file */
     686         [ -  + ]:         57 :             if (strcmp(filename, MODULE_INIT_FILENAME) == 0)
     687                 :          0 :                 continue;
     688                 :            : 
     689         [ +  + ]:         57 :             if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY) {
     690                 :         21 :                 jsid id = gjs_intern_string_to_id(context, filename);
     691         [ -  + ]:         21 :                 if (id.isVoid())
     692                 :          0 :                     return false;
     693         [ -  + ]:         21 :                 if (!properties.append(id)) {
     694                 :          0 :                     JS_ReportOutOfMemory(context);
     695                 :          0 :                     return false;
     696                 :            :                 }
     697   [ -  +  +  -  :         36 :             } else if (g_str_has_suffix(filename, ".js")) {
                   +  + ]
     698                 :            :                 GjsAutoChar filename_noext =
     699                 :         30 :                     g_strndup(filename, strlen(filename) - 3);
     700                 :         30 :                 jsid id = gjs_intern_string_to_id(context, filename_noext);
     701         [ -  + ]:         30 :                 if (id.isVoid())
     702                 :          0 :                     return false;
     703         [ -  + ]:         30 :                 if (!properties.append(id)) {
     704                 :          0 :                     JS_ReportOutOfMemory(context);
     705                 :          0 :                     return false;
     706                 :            :                 }
     707         [ +  - ]:         30 :             }
     708      [ +  -  - ]:        114 :         }
     709   [ +  -  +  -  :          3 :     }
             +  -  +  - ]
     710                 :          3 :     return true;
     711                 :          3 : }
     712                 :            : 
     713                 :            : /* The *resolved out parameter, on success, should be false to indicate that id
     714                 :            :  * was not resolved; and true if id was resolved. */
     715                 :            : GJS_JSAPI_RETURN_CONVENTION
     716                 :            : static bool
     717                 :       1440 : importer_resolve(JSContext        *context,
     718                 :            :                  JS::HandleObject  obj,
     719                 :            :                  JS::HandleId      id,
     720                 :            :                  bool             *resolved)
     721                 :            : {
     722         [ +  + ]:       1440 :     if (!id.isString()) {
     723                 :          1 :         *resolved = false;
     724                 :          1 :         return true;
     725                 :            :     }
     726                 :            : 
     727                 :       1439 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     728   [ +  +  +  +  :       2592 :     if (id == atoms.module_init() || id == atoms.to_string() ||
                   -  + ]
     729         [ +  + ]:       2592 :         id == atoms.value_of()) {
     730                 :        286 :         *resolved = false;
     731                 :        286 :         return true;
     732                 :            :     }
     733                 :            : 
     734                 :            :     gjs_debug_jsprop(GJS_DEBUG_IMPORTER, "Resolve prop '%s' hook, obj %s",
     735                 :            :                      gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str());
     736                 :            : 
     737         [ -  + ]:       1153 :     if (!id.isString()) {
     738                 :          0 :         *resolved = false;
     739                 :          0 :         return true;
     740                 :            :     }
     741                 :            : 
     742         [ +  + ]:       1153 :     if (!do_import(context, obj, id))
     743                 :         87 :         return false;
     744                 :            : 
     745                 :       1066 :     *resolved = true;
     746                 :       1066 :     return true;
     747                 :            : }
     748                 :            : 
     749                 :            : static const JSClassOps gjs_importer_class_ops = {
     750                 :            :     nullptr,  // addProperty
     751                 :            :     nullptr,  // deleteProperty
     752                 :            :     nullptr,  // enumerate
     753                 :            :     importer_new_enumerate,
     754                 :            :     importer_resolve,
     755                 :            : };
     756                 :            : 
     757                 :            : const JSClass gjs_importer_class = {
     758                 :            :     "GjsFileImporter",
     759                 :            :     0,
     760                 :            :     &gjs_importer_class_ops,
     761                 :            : };
     762                 :            : 
     763                 :            : static const JSPropertySpec gjs_importer_proto_props[] = {
     764                 :            :     JS_STRING_SYM_PS(toStringTag, "GjsFileImporter", JSPROP_READONLY),
     765                 :            :     JS_PS_END};
     766                 :            : 
     767                 :            : JSFunctionSpec gjs_importer_proto_funcs[] = {
     768                 :            :     JS_FN("toString", importer_to_string, 0, 0),
     769                 :            :     JS_FS_END};
     770                 :            : 
     771                 :        242 : [[nodiscard]] static const std::vector<std::string>& gjs_get_search_path() {
     772   [ +  +  +  - ]:        242 :     static std::vector<std::string> gjs_search_path;
     773                 :            :     static bool search_path_initialized = false;
     774                 :            : 
     775                 :            :     /* not thread safe */
     776                 :            : 
     777         [ +  + ]:        242 :     if (!search_path_initialized) {
     778                 :            :         const char* const* system_data_dirs;
     779                 :            :         const char *envstr;
     780                 :            :         gsize i;
     781                 :            : 
     782                 :            :         /* in order of priority */
     783                 :            : 
     784                 :            :         /* $GJS_PATH */
     785                 :        103 :         envstr = g_getenv("GJS_PATH");
     786         [ +  - ]:        103 :         if (envstr) {
     787                 :            :             char **dirs, **d;
     788                 :        103 :             dirs = g_strsplit(envstr, G_SEARCHPATH_SEPARATOR_S, 0);
     789         [ -  + ]:        103 :             for (d = dirs; *d != NULL; d++)
     790                 :          0 :                 gjs_search_path.push_back(*d);
     791                 :            :             /* we assume the array and strings are allocated separately */
     792                 :        103 :             g_free(dirs);
     793                 :            :         }
     794                 :            : 
     795                 :        206 :         gjs_search_path.push_back("resource:///org/gnome/gjs/modules/script/");
     796                 :        103 :         gjs_search_path.push_back("resource:///org/gnome/gjs/modules/core/");
     797                 :            : 
     798                 :            :         /* $XDG_DATA_DIRS /gjs-1.0 */
     799                 :        103 :         system_data_dirs = g_get_system_data_dirs();
     800         [ +  + ]:        309 :         for (i = 0; system_data_dirs[i] != NULL; ++i) {
     801                 :            :             GjsAutoChar s =
     802                 :        206 :                 g_build_filename(system_data_dirs[i], "gjs-1.0", nullptr);
     803                 :        206 :             gjs_search_path.push_back(s.get());
     804                 :        206 :         }
     805                 :            : 
     806                 :            :         /* ${datadir}/share/gjs-1.0 */
     807                 :            : #ifdef G_OS_WIN32
     808                 :            :         extern HMODULE gjs_dll;
     809                 :            :         char *basedir = g_win32_get_package_installation_directory_of_module (gjs_dll);
     810                 :            :         GjsAutoChar gjs_data_dir =
     811                 :            :             g_build_filename(basedir, "share", "gjs-1.0", nullptr);
     812                 :            :         gjs_search_path.push_back(gjs_data_dir.get());
     813                 :            :         g_free (basedir);
     814                 :            : #else
     815                 :        103 :         gjs_search_path.push_back(GJS_JS_DIR);
     816                 :            : #endif
     817                 :            : 
     818                 :        103 :         search_path_initialized = true;
     819                 :            :     }
     820                 :            : 
     821                 :        242 :     return gjs_search_path;
     822                 :            : }
     823                 :            : 
     824                 :            : GJS_JSAPI_RETURN_CONVENTION
     825                 :          1 : static bool no_construct(JSContext* cx, unsigned argc, JS::Value* vp) {
     826                 :          1 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     827                 :          1 :     gjs_throw_abstract_constructor_error(cx, args);
     828                 :          1 :     return false;
     829                 :            : }
     830                 :            : 
     831                 :            : GJS_JSAPI_RETURN_CONVENTION
     832                 :        311 : static JSObject* gjs_importer_define_proto(JSContext* cx) {
     833                 :        311 :     JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
     834                 :        311 :     g_assert(global && "Must enter a realm before defining importer");
     835                 :            : 
     836                 :            :     // If we've been here more than once, we already have the proto
     837                 :            :     JS::Value v_proto =
     838                 :        311 :         gjs_get_global_slot(global, GjsGlobalSlot::PROTOTYPE_importer);
     839         [ +  + ]:        311 :     if (!v_proto.isUndefined()) {
     840                 :         69 :         g_assert(v_proto.isObject() &&
     841                 :            :                  "Someone stored some weird value in a global slot");
     842                 :         69 :         return &v_proto.toObject();
     843                 :            :     }
     844                 :            : 
     845                 :        242 :     JS::RootedObject proto(cx, JS_NewPlainObject(cx));
     846   [ +  -  +  - ]:        484 :     if (!proto || !JS_DefineFunctions(cx, proto, gjs_importer_proto_funcs) ||
     847   [ -  +  -  + ]:        484 :         !JS_DefineProperties(cx, proto, gjs_importer_proto_props))
     848                 :          0 :         return nullptr;
     849                 :        242 :     gjs_set_global_slot(global, GjsGlobalSlot::PROTOTYPE_importer,
     850                 :        242 :                         JS::ObjectValue(*proto));
     851                 :            : 
     852                 :            :     // For backwards compatibility
     853                 :        242 :     JSFunction* constructor = JS_NewFunction(
     854                 :            :         cx, no_construct, 0, JSFUN_CONSTRUCTOR, "GjsFileImporter");
     855                 :        242 :     JS::RootedObject ctor_obj(cx, JS_GetFunctionObject(constructor));
     856         [ +  - ]:        484 :     if (!JS_LinkConstructorAndPrototype(cx, ctor_obj, proto) ||
     857   [ -  +  -  + ]:        484 :         !JS_DefineProperty(cx, global, "GjsFileImporter", ctor_obj, 0))
     858                 :          0 :         return nullptr;
     859                 :            : 
     860                 :        242 :     gjs_debug(GJS_DEBUG_CONTEXT, "Initialized class %s prototype %p",
     861                 :        242 :               gjs_importer_class.name, proto.get());
     862                 :        242 :     return proto;
     863                 :        311 : }
     864                 :            : 
     865                 :            : GJS_JSAPI_RETURN_CONVENTION
     866                 :        311 : static JSObject* gjs_create_importer(
     867                 :            :     JSContext* context, const char* importer_name,
     868                 :            :     const std::vector<std::string>& initial_search_path,
     869                 :            :     bool add_standard_search_path, JS::HandleObject in_object) {
     870                 :        311 :     std::vector<std::string> search_paths = initial_search_path;
     871         [ +  + ]:        311 :     if (add_standard_search_path) {
     872                 :            :         /* Stick the "standard" shared search path after the provided one. */
     873                 :        242 :         const std::vector<std::string>& gjs_search_path = gjs_get_search_path();
     874                 :        242 :         search_paths.insert(search_paths.end(), gjs_search_path.begin(),
     875                 :            :                             gjs_search_path.end());
     876                 :            :     }
     877                 :            : 
     878                 :        311 :     JS::RootedObject proto(context, gjs_importer_define_proto(context));
     879         [ -  + ]:        311 :     if (!proto)
     880                 :          0 :         return nullptr;
     881                 :            : 
     882                 :            :     JS::RootedObject importer(
     883                 :            :         context,
     884                 :        311 :         JS_NewObjectWithGivenProto(context, &gjs_importer_class, proto));
     885         [ -  + ]:        311 :     if (!importer)
     886                 :          0 :         return nullptr;
     887                 :            : 
     888                 :            :     gjs_debug_lifecycle(GJS_DEBUG_IMPORTER, "importer constructor, obj %p",
     889                 :            :                         importer.get());
     890                 :            : 
     891                 :            :     /* API users can replace this property from JS, is the idea */
     892         [ -  + ]:        311 :     if (!gjs_define_string_array(
     893                 :            :             context, importer, "searchPath", search_paths,
     894                 :            :             // settable (no READONLY) but not deletable (PERMANENT)
     895                 :            :             JSPROP_PERMANENT | JSPROP_RESOLVING))
     896                 :          0 :         return nullptr;
     897                 :            : 
     898         [ -  + ]:        311 :     if (!define_meta_properties(context, importer, NULL, importer_name, in_object))
     899                 :          0 :         return nullptr;
     900                 :            : 
     901                 :        311 :     return importer;
     902                 :        311 : }
     903                 :            : 
     904                 :            : GJS_JSAPI_RETURN_CONVENTION
     905                 :         69 : static JSObject* gjs_define_importer(
     906                 :            :     JSContext* context, JS::HandleObject in_object, const char* importer_name,
     907                 :            :     const std::vector<std::string>& initial_search_path,
     908                 :            :     bool add_standard_search_path) {
     909                 :            :     JS::RootedObject importer(
     910                 :            :         context,
     911                 :         69 :         gjs_create_importer(context, importer_name, initial_search_path,
     912                 :         69 :                             add_standard_search_path, in_object));
     913                 :            : 
     914         [ -  + ]:         69 :     if (!JS_DefineProperty(context, in_object, importer_name, importer,
     915                 :            :                            GJS_MODULE_PROP_FLAGS))
     916                 :          0 :         return nullptr;
     917                 :            : 
     918                 :         69 :     gjs_debug(GJS_DEBUG_IMPORTER,
     919                 :         69 :               "Defined importer '%s' %p in %p", importer_name, importer.get(),
     920                 :         69 :               in_object.get());
     921                 :            : 
     922                 :         69 :     return importer;
     923                 :         69 : }
     924                 :            : 
     925                 :        242 : JSObject* gjs_create_root_importer(
     926                 :            :     JSContext* cx, const std::vector<std::string>& search_path) {
     927                 :        242 :     return gjs_create_importer(cx, "imports", search_path, true, nullptr);
     928                 :            : }

Generated by: LCOV version 1.14