LCOV - code coverage report
Current view: top level - gjs - internal.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 79.6 % 289 230
Test Date: 2024-09-08 20:50:23 Functions: 87.0 % 23 20
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 56.9 % 102 58

             Branch data     Line data    Source code
       1                 :             : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       2                 :             : // SPDX-FileCopyrightText: 2020 Evan Welsh <contact@evanwelsh.com>
       3                 :             : 
       4                 :             : #include <config.h>
       5                 :             : 
       6                 :             : #include <stddef.h>  // for size_t
       7                 :             : #include <string.h>
       8                 :             : 
       9                 :             : #include <memory>  // for unique_ptr
      10                 :             : 
      11                 :             : #include <gio/gio.h>
      12                 :             : #include <glib-object.h>
      13                 :             : #include <glib.h>
      14                 :             : 
      15                 :             : #include <js/CallAndConstruct.h>  // for JS_CallFunction
      16                 :             : #include <js/CallArgs.h>
      17                 :             : #include <js/CharacterEncoding.h>
      18                 :             : #include <js/CompilationAndEvaluation.h>
      19                 :             : #include <js/CompileOptions.h>
      20                 :             : #include <js/ErrorReport.h>  // for JSEXN_ERR
      21                 :             : #include <js/Exception.h>
      22                 :             : #include <js/GCAPI.h>  // for JS_AddExtraGCRootsTracer
      23                 :             : #include <js/Modules.h>
      24                 :             : #include <js/Promise.h>
      25                 :             : #include <js/PropertyAndElement.h>
      26                 :             : #include <js/PropertyDescriptor.h>
      27                 :             : #include <js/Realm.h>
      28                 :             : #include <js/RootingAPI.h>
      29                 :             : #include <js/SourceText.h>
      30                 :             : #include <js/String.h>
      31                 :             : #include <js/TracingAPI.h>
      32                 :             : #include <js/TypeDecls.h>
      33                 :             : #include <js/Utility.h>  // for UniqueChars
      34                 :             : #include <js/Value.h>
      35                 :             : #include <js/ValueArray.h>
      36                 :             : #include <jsapi.h>        // for JS_NewPlainObject, JS_ObjectIsFunction
      37                 :             : #include <jsfriendapi.h>  // for JS_GetObjectFunction, SetFunctionNativeReserved
      38                 :             : 
      39                 :             : #include "gjs/context-private.h"
      40                 :             : #include "gjs/engine.h"
      41                 :             : #include "gjs/global.h"
      42                 :             : #include "gjs/internal.h"
      43                 :             : #include "gjs/jsapi-util-args.h"
      44                 :             : #include "gjs/jsapi-util.h"
      45                 :             : #include "gjs/macros.h"
      46                 :             : #include "gjs/module.h"
      47                 :             : #include "util/log.h"
      48                 :             : #include "util/misc.h"
      49                 :             : 
      50                 :             : namespace mozilla {
      51                 :             : union Utf8Unit;
      52                 :             : }
      53                 :             : 
      54                 :             : // NOTE: You have to be very careful in this file to only do operations within
      55                 :             : // the correct global!
      56                 :             : 
      57                 :             : /**
      58                 :             :  * gjs_load_internal_module:
      59                 :             :  *
      60                 :             :  * @brief Loads a module source from an internal resource,
      61                 :             :  * resource:///org/gnome/gjs/modules/internal/{#identifier}.js, registers it in
      62                 :             :  * the internal global's module registry, and proceeds to compile, initialize,
      63                 :             :  * and evaluate the module.
      64                 :             :  *
      65                 :             :  * @param cx the current JSContext
      66                 :             :  * @param identifier the identifier of the internal module
      67                 :             :  *
      68                 :             :  * @returns whether an error occurred while loading or evaluating the module.
      69                 :             :  */
      70                 :         235 : bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
      71                 :             :     GjsAutoChar full_path(g_strdup_printf(
      72                 :         235 :         "resource:///org/gnome/gjs/modules/internal/%s.js", identifier));
      73                 :             : 
      74                 :         235 :     gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)",
      75                 :             :               identifier, full_path.get());
      76                 :             : 
      77                 :         235 :     GjsAutoChar script;
      78                 :             :     size_t script_len;
      79         [ -  + ]:         235 :     if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len))
      80                 :           0 :         return false;
      81                 :             : 
      82                 :         235 :     JS::SourceText<mozilla::Utf8Unit> buf;
      83         [ -  + ]:         235 :     if (!buf.init(cx, script.get(), script_len, JS::SourceOwnership::Borrowed))
      84                 :           0 :         return false;
      85                 :             : 
      86                 :         235 :     JS::CompileOptions options(cx);
      87                 :         235 :     options.setIntroductionType("Internal Module Bootstrap");
      88                 :         235 :     options.setFileAndLine(full_path, 1);
      89                 :         235 :     options.setSelfHostingMode(false);
      90                 :             : 
      91                 :         235 :     Gjs::AutoInternalRealm ar{cx};
      92                 :             : 
      93                 :         235 :     JS::RootedValue ignored(cx);
      94                 :         235 :     return JS::Evaluate(cx, options, buf, &ignored);
      95                 :         235 : }
      96                 :             : 
      97                 :           0 : static bool handle_wrong_args(JSContext* cx) {
      98                 :           0 :     gjs_log_exception(cx);
      99                 :           0 :     g_error("Wrong invocation of internal code");
     100                 :             :     return false;
     101                 :             : }
     102                 :             : 
     103                 :             : /**
     104                 :             :  * gjs_internal_set_global_module_loader:
     105                 :             :  *
     106                 :             :  * @brief Sets the MODULE_LOADER slot of the passed global object.
     107                 :             :  * The second argument should be an instance of ModuleLoader or
     108                 :             :  * InternalModuleLoader. Its moduleResolveHook and moduleLoadHook properties
     109                 :             :  * will be called.
     110                 :             :  *
     111                 :             :  * @returns guaranteed to return true or assert.
     112                 :             :  */
     113                 :         235 : bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc,
     114                 :             :                                            JS::Value* vp) {
     115                 :         235 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     116                 :         235 :     JS::RootedObject global(cx), loader(cx);
     117         [ -  + ]:         235 :     if (!gjs_parse_call_args(cx, "setGlobalModuleLoader", args, "oo", "global",
     118                 :             :                              &global, "loader", &loader))
     119                 :           0 :         return handle_wrong_args(cx);
     120                 :             : 
     121                 :         235 :     gjs_set_global_slot(global, GjsGlobalSlot::MODULE_LOADER,
     122                 :         235 :                         JS::ObjectValue(*loader));
     123                 :             : 
     124                 :         235 :     args.rval().setUndefined();
     125                 :         235 :     return true;
     126                 :         235 : }
     127                 :             : 
     128                 :             : /**
     129                 :             :  * compile_module:
     130                 :             :  *
     131                 :             :  * @brief Compiles the a module source text into an internal #Module object
     132                 :             :  * given the module's URI as the first argument.
     133                 :             :  *
     134                 :             :  * @param cx the current JSContext
     135                 :             :  * @param uri The URI of the module
     136                 :             :  * @param source The source text of the module
     137                 :             :  * @param v_module_out Return location for the module as a JS value
     138                 :             :  *
     139                 :             :  * @returns whether an error occurred while compiling the module.
     140                 :             :  */
     141                 :        1676 : static bool compile_module(JSContext* cx, const JS::UniqueChars& uri,
     142                 :             :                            JS::HandleString source,
     143                 :             :                            JS::MutableHandleValue v_module_out) {
     144                 :        1676 :     JS::CompileOptions options(cx);
     145                 :        1676 :     options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false);
     146                 :             : 
     147                 :             :     size_t text_len;
     148                 :             :     char16_t* text;
     149         [ -  + ]:        1676 :     if (!gjs_string_get_char16_data(cx, source, &text, &text_len))
     150                 :           0 :         return false;
     151                 :             : 
     152                 :        1676 :     JS::SourceText<char16_t> buf;
     153         [ -  + ]:        1676 :     if (!buf.init(cx, text, text_len, JS::SourceOwnership::TakeOwnership))
     154                 :           0 :         return false;
     155                 :             : 
     156                 :        1676 :     JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf));
     157         [ +  + ]:        1676 :     if (!new_module)
     158                 :           1 :         return false;
     159                 :             : 
     160                 :        1675 :     v_module_out.setObject(*new_module);
     161                 :        1675 :     return true;
     162                 :        1676 : }
     163                 :             : 
     164                 :             : /**
     165                 :             :  * gjs_internal_compile_internal_module:
     166                 :             :  *
     167                 :             :  * @brief Compiles a module source text within the internal global's realm.
     168                 :             :  *
     169                 :             :  * NOTE: Modules compiled with this function can only be executed
     170                 :             :  * within the internal global's realm.
     171                 :             :  *
     172                 :             :  * @param cx the current JSContext
     173                 :             :  * @param argc
     174                 :             :  * @param vp
     175                 :             :  *
     176                 :             :  * @returns whether an error occurred while compiling the module.
     177                 :             :  */
     178                 :           0 : bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc,
     179                 :             :                                           JS::Value* vp) {
     180                 :           0 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     181                 :             : 
     182                 :           0 :     Gjs::AutoInternalRealm ar{cx};
     183                 :             : 
     184                 :           0 :     JS::UniqueChars uri;
     185                 :           0 :     JS::RootedString source(cx);
     186         [ #  # ]:           0 :     if (!gjs_parse_call_args(cx, "compileInternalModule", args, "sS", "uri",
     187                 :             :                              &uri, "source", &source))
     188                 :           0 :         return handle_wrong_args(cx);
     189                 :             : 
     190                 :           0 :     return compile_module(cx, uri, source, args.rval());
     191                 :           0 : }
     192                 :             : 
     193                 :             : /**
     194                 :             :  * gjs_internal_compile_module:
     195                 :             :  *
     196                 :             :  * @brief Compiles a module source text within the main realm.
     197                 :             :  *
     198                 :             :  * NOTE: Modules compiled with this function can only be executed
     199                 :             :  * within the main realm.
     200                 :             :  *
     201                 :             :  * @param cx the current JSContext
     202                 :             :  * @param argc
     203                 :             :  * @param vp
     204                 :             :  *
     205                 :             :  * @returns whether an error occurred while compiling the module.
     206                 :             :  */
     207                 :        1676 : bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) {
     208                 :        1676 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     209                 :             : 
     210                 :        1676 :     Gjs::AutoMainRealm ar{cx};
     211                 :             : 
     212                 :        1676 :     JS::UniqueChars uri;
     213                 :        1676 :     JS::RootedString source(cx);
     214         [ -  + ]:        1676 :     if (!gjs_parse_call_args(cx, "compileModule", args, "sS", "uri", &uri,
     215                 :             :                              "source", &source))
     216                 :           0 :         return handle_wrong_args(cx);
     217                 :             : 
     218                 :        1676 :     return compile_module(cx, uri, source, args.rval());
     219                 :        1676 : }
     220                 :             : 
     221                 :             : /**
     222                 :             :  * gjs_internal_set_module_private:
     223                 :             :  *
     224                 :             :  * @brief Sets the private object of an internal #Module object.
     225                 :             :  * The private object must be a #JSObject.
     226                 :             :  *
     227                 :             :  * @param cx the current JSContext
     228                 :             :  * @param argc
     229                 :             :  * @param vp
     230                 :             :  *
     231                 :             :  * @returns whether an error occurred while setting the private.
     232                 :             :  */
     233                 :        1675 : bool gjs_internal_set_module_private(JSContext* cx, unsigned argc,
     234                 :             :                                      JS::Value* vp) {
     235                 :        1675 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     236                 :        1675 :     JS::RootedObject module(cx), private_obj(cx);
     237         [ -  + ]:        1675 :     if (!gjs_parse_call_args(cx, "setModulePrivate", args, "oo", "module",
     238                 :             :                              &module, "private", &private_obj))
     239                 :           0 :         return handle_wrong_args(cx);
     240                 :             : 
     241                 :        1675 :     JS::SetModulePrivate(module, JS::ObjectValue(*private_obj));
     242                 :        1675 :     return true;
     243                 :        1675 : }
     244                 :             : 
     245                 :             : /**
     246                 :             :  * gjs_internal_get_registry:
     247                 :             :  *
     248                 :             :  * @brief Retrieves the module registry for the passed global object.
     249                 :             :  *
     250                 :             :  * @param cx the current JSContext
     251                 :             :  * @param argc
     252                 :             :  * @param vp
     253                 :             :  *
     254                 :             :  * @returns whether an error occurred while retrieving the registry.
     255                 :             :  */
     256                 :        4936 : bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) {
     257                 :        4936 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     258                 :        4936 :     JS::RootedObject global(cx);
     259         [ -  + ]:        4936 :     if (!gjs_parse_call_args(cx, "getRegistry", args, "o", "global", &global))
     260                 :           0 :         return handle_wrong_args(cx);
     261                 :             : 
     262                 :        4936 :     JSAutoRealm ar(cx, global);
     263                 :             : 
     264                 :        4936 :     JS::RootedObject registry(cx, gjs_get_module_registry(global));
     265                 :        4936 :     args.rval().setObject(*registry);
     266                 :        4936 :     return true;
     267                 :        4936 : }
     268                 :             : 
     269                 :        7162 : bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) {
     270                 :             :     using AutoHashTable =
     271                 :           0 :         GjsAutoPointer<GHashTable, GHashTable, g_hash_table_destroy>;
     272                 :        4641 :     using AutoURI = GjsAutoPointer<GUri, GUri, g_uri_unref>;
     273                 :             : 
     274                 :        7162 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     275                 :        7162 :     JS::RootedString string_arg(cx);
     276         [ -  + ]:        7162 :     if (!gjs_parse_call_args(cx, "parseUri", args, "S", "uri", &string_arg))
     277                 :           0 :         return handle_wrong_args(cx);
     278                 :             : 
     279                 :        7162 :     JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg);
     280         [ -  + ]:        7162 :     if (!uri)
     281                 :           0 :         return false;
     282                 :             : 
     283                 :        7162 :     GjsAutoError error;
     284                 :        7162 :     AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error);
     285         [ +  + ]:        7162 :     if (!parsed) {
     286                 :        2521 :         Gjs::AutoMainRealm ar{cx};
     287                 :             : 
     288                 :        2521 :         gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
     289                 :             :                          "Attempted to import invalid URI: %s (%s)", uri.get(),
     290                 :        2521 :                          error->message);
     291                 :        2521 :         return false;
     292                 :        2521 :     }
     293                 :             : 
     294                 :        4641 :     JS::RootedObject query_obj(cx, JS_NewPlainObject(cx));
     295         [ -  + ]:        4641 :     if (!query_obj)
     296                 :           0 :         return false;
     297                 :             : 
     298                 :        4641 :     const char* raw_query = g_uri_get_query(parsed);
     299         [ +  + ]:        4641 :     if (raw_query) {
     300                 :             :         AutoHashTable query =
     301                 :           7 :             g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error);
     302         [ -  + ]:           7 :         if (!query) {
     303                 :           0 :             Gjs::AutoMainRealm ar{cx};
     304                 :             : 
     305                 :           0 :             gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
     306                 :             :                              "Attempted to import invalid URI: %s (%s)",
     307                 :           0 :                              uri.get(), error->message);
     308                 :           0 :             return false;
     309                 :           0 :         }
     310                 :             : 
     311                 :             :         GHashTableIter iter;
     312                 :           7 :         g_hash_table_iter_init(&iter, query);
     313                 :             : 
     314                 :             :         void* key_ptr;
     315                 :             :         void* value_ptr;
     316         [ +  + ]:          17 :         while (g_hash_table_iter_next(&iter, &key_ptr, &value_ptr)) {
     317                 :          10 :             auto* key = static_cast<const char*>(key_ptr);
     318                 :          10 :             auto* value = static_cast<const char*>(value_ptr);
     319                 :             : 
     320                 :          10 :             JS::ConstUTF8CharsZ value_chars{value, strlen(value)};
     321                 :             :             JS::RootedString value_str(cx,
     322                 :          10 :                                        JS_NewStringCopyUTF8Z(cx, value_chars));
     323   [ +  -  -  +  :          10 :             if (!value_str || !JS_DefineProperty(cx, query_obj, key, value_str,
                   -  + ]
     324                 :             :                                                  JSPROP_ENUMERATE))
     325                 :           0 :                 return false;
     326         [ +  - ]:          10 :         }
     327         [ +  - ]:           7 :     }
     328                 :             : 
     329                 :        4641 :     JS::RootedObject return_obj(cx, JS_NewPlainObject(cx));
     330         [ -  + ]:        4641 :     if (!return_obj)
     331                 :           0 :         return false;
     332                 :             : 
     333                 :             :     // JS_NewStringCopyZ() used here and below because the URI components are
     334                 :             :     // %-encoded, meaning ASCII-only
     335                 :             :     JS::RootedString scheme(cx,
     336                 :        4641 :                             JS_NewStringCopyZ(cx, g_uri_get_scheme(parsed)));
     337         [ -  + ]:        4641 :     if (!scheme)
     338                 :           0 :         return false;
     339                 :             : 
     340                 :        4641 :     JS::RootedString host(cx, JS_NewStringCopyZ(cx, g_uri_get_host(parsed)));
     341         [ -  + ]:        4641 :     if (!host)
     342                 :           0 :         return false;
     343                 :             : 
     344                 :        4641 :     JS::RootedString path(cx, JS_NewStringCopyZ(cx, g_uri_get_path(parsed)));
     345         [ -  + ]:        4641 :     if (!path)
     346                 :           0 :         return false;
     347                 :             : 
     348                 :             :     GjsAutoChar no_query_str =
     349                 :        4641 :         g_uri_to_string_partial(parsed, G_URI_HIDE_QUERY);
     350                 :        4641 :     JS::RootedString uri_no_query{cx, JS_NewStringCopyZ(cx, no_query_str)};
     351         [ -  + ]:        4641 :     if (!uri_no_query)
     352                 :           0 :         return false;
     353                 :             : 
     354                 :        4641 :     if (!JS_DefineProperty(cx, return_obj, "uri", uri_no_query,
     355                 :        4641 :                            JSPROP_ENUMERATE) ||
     356         [ +  - ]:        4641 :         !JS_DefineProperty(cx, return_obj, "uriWithQuery", string_arg,
     357                 :        4641 :                            JSPROP_ENUMERATE) ||
     358         [ +  - ]:        4641 :         !JS_DefineProperty(cx, return_obj, "scheme", scheme,
     359                 :        4641 :                            JSPROP_ENUMERATE) ||
     360         [ +  - ]:        4641 :         !JS_DefineProperty(cx, return_obj, "host", host, JSPROP_ENUMERATE) ||
     361   [ +  -  +  - ]:       13923 :         !JS_DefineProperty(cx, return_obj, "path", path, JSPROP_ENUMERATE) ||
     362   [ -  +  -  + ]:        9282 :         !JS_DefineProperty(cx, return_obj, "query", query_obj,
     363                 :             :                            JSPROP_ENUMERATE))
     364                 :           0 :         return false;
     365                 :             : 
     366                 :        4641 :     args.rval().setObject(*return_obj);
     367                 :        4641 :     return true;
     368                 :        7162 : }
     369                 :             : 
     370                 :        1722 : bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx,
     371                 :             :                                                     unsigned argc,
     372                 :             :                                                     JS::Value* vp) {
     373                 :        1722 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     374                 :        1722 :     JS::UniqueChars uri, relative_path;
     375         [ -  + ]:        1722 :     if (!gjs_parse_call_args(cx, "resolveRelativeResourceOrFile", args, "ss",
     376                 :             :                              "uri", &uri, "relativePath", &relative_path))
     377                 :           0 :         return handle_wrong_args(cx);
     378                 :             : 
     379                 :        1722 :     GjsAutoUnref<GFile> module_file = g_file_new_for_uri(uri.get());
     380                 :             : 
     381         [ +  - ]:        1722 :     if (module_file) {
     382                 :             :         GjsAutoChar output_uri = g_uri_resolve_relative(
     383                 :        1722 :             uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr);
     384                 :             : 
     385                 :        1722 :         JS::ConstUTF8CharsZ uri_chars(output_uri, strlen(output_uri));
     386                 :        1722 :         JS::RootedString retval(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
     387         [ -  + ]:        1722 :         if (!retval)
     388                 :           0 :             return false;
     389                 :             : 
     390                 :        1722 :         args.rval().setString(retval);
     391                 :        1722 :         return true;
     392                 :        1722 :     }
     393                 :             : 
     394                 :           0 :     args.rval().setNull();
     395                 :           0 :     return true;
     396                 :        1722 : }
     397                 :             : 
     398                 :        1616 : bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc,
     399                 :             :                                         JS::Value* vp) {
     400                 :        1616 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     401                 :        1616 :     JS::UniqueChars uri;
     402         [ -  + ]:        1616 :     if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri))
     403                 :           0 :         return handle_wrong_args(cx);
     404                 :             : 
     405                 :        1616 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     406                 :             : 
     407                 :             :     char* contents;
     408                 :             :     size_t length;
     409                 :        1616 :     GjsAutoError error;
     410         [ -  + ]:        1616 :     if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents,
     411                 :             :                               &length, /* etag_out = */ nullptr, &error)) {
     412                 :           0 :         Gjs::AutoMainRealm ar{cx};
     413                 :             : 
     414                 :           0 :         gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
     415                 :             :                          "Unable to load file from: %s (%s)", uri.get(),
     416                 :           0 :                          error->message);
     417                 :           0 :         return false;
     418                 :           0 :     }
     419                 :             : 
     420                 :        1616 :     JS::ConstUTF8CharsZ contents_chars{contents, length};
     421                 :             :     JS::RootedString contents_str(cx,
     422                 :        1616 :                                   JS_NewStringCopyUTF8Z(cx, contents_chars));
     423                 :        1616 :     g_free(contents);
     424         [ -  + ]:        1616 :     if (!contents_str)
     425                 :           0 :         return false;
     426                 :             : 
     427                 :        1616 :     args.rval().setString(contents_str);
     428                 :        1616 :     return true;
     429                 :        1616 : }
     430                 :             : 
     431                 :         797 : bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) {
     432                 :         797 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     433                 :         797 :     JS::UniqueChars uri;
     434         [ -  + ]:         797 :     if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri))
     435                 :           0 :         return handle_wrong_args(cx);
     436                 :             : 
     437                 :         797 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     438                 :             : 
     439                 :         797 :     args.rval().setBoolean(g_file_query_exists(file, nullptr));
     440                 :         797 :     return true;
     441                 :         797 : }
     442                 :             : 
     443                 :             : class PromiseData {
     444                 :             :  public:
     445                 :             :     JSContext* cx;
     446                 :             : 
     447                 :             :  private:
     448                 :             :     JS::Heap<JSFunction*> m_resolve;
     449                 :             :     JS::Heap<JSFunction*> m_reject;
     450                 :             : 
     451                 :          14 :     JS::HandleFunction resolver() {
     452                 :          14 :         return JS::HandleFunction::fromMarkedLocation(m_resolve.address());
     453                 :             :     }
     454                 :           1 :     JS::HandleFunction rejecter() {
     455                 :           1 :         return JS::HandleFunction::fromMarkedLocation(m_reject.address());
     456                 :             :     }
     457                 :             : 
     458                 :           0 :     static void trace(JSTracer* trc, void* data) {
     459                 :           0 :         auto* self = PromiseData::from_ptr(data);
     460                 :           0 :         JS::TraceEdge(trc, &self->m_resolve, "loadResourceOrFileAsync resolve");
     461                 :           0 :         JS::TraceEdge(trc, &self->m_reject, "loadResourceOrFileAsync reject");
     462                 :           0 :     }
     463                 :             : 
     464                 :             :  public:
     465                 :          15 :     explicit PromiseData(JSContext* a_cx, JSFunction* resolve,
     466                 :             :                          JSFunction* reject)
     467                 :          15 :         : cx(a_cx), m_resolve(resolve), m_reject(reject) {
     468                 :          15 :         JS_AddExtraGCRootsTracer(cx, &PromiseData::trace, this);
     469                 :          15 :     }
     470                 :             : 
     471                 :          15 :     ~PromiseData() {
     472                 :          15 :         JS_RemoveExtraGCRootsTracer(cx, &PromiseData::trace, this);
     473                 :          15 :     }
     474                 :             : 
     475                 :          15 :     static PromiseData* from_ptr(void* ptr) {
     476                 :          15 :         return static_cast<PromiseData*>(ptr);
     477                 :             :     }
     478                 :             : 
     479                 :             :     // Adapted from SpiderMonkey js::RejectPromiseWithPendingError()
     480                 :             :     // https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435
     481                 :           1 :     void reject_with_pending_exception() {
     482                 :           1 :         JS::RootedValue exception(cx);
     483                 :           1 :         bool ok GJS_USED_ASSERT = JS_GetPendingException(cx, &exception);
     484                 :           1 :         g_assert(ok && "Cannot reject a promise with an uncatchable exception");
     485                 :             : 
     486                 :           1 :         JS::RootedValueArray<1> args(cx);
     487                 :           1 :         args[0].set(exception);
     488                 :           1 :         JS::RootedValue ignored_rval(cx);
     489                 :           1 :         ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args,
     490                 :             :                              &ignored_rval);
     491                 :           1 :         g_assert(ok && "Failed rejecting promise");
     492                 :           1 :     }
     493                 :             : 
     494                 :          14 :     void resolve(JS::Value result) {
     495                 :          14 :         JS::RootedValueArray<1> args(cx);
     496                 :          14 :         args[0].set(result);
     497                 :          14 :         JS::RootedValue ignored_rval(cx);
     498                 :          14 :         bool ok GJS_USED_ASSERT = JS_CallFunction(
     499                 :             :             cx, /* this_obj = */ nullptr, resolver(), args, &ignored_rval);
     500                 :          14 :         g_assert(ok && "Failed resolving promise");
     501                 :          14 :     }
     502                 :             : };
     503                 :             : 
     504                 :          15 : static void load_async_callback(GObject* file, GAsyncResult* res, void* data) {
     505                 :          15 :     std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data));
     506                 :             : 
     507                 :          15 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx);
     508                 :          15 :     gjs->main_loop_release();
     509                 :             : 
     510                 :          15 :     Gjs::AutoMainRealm ar{gjs};
     511                 :             : 
     512                 :             :     char* contents;
     513                 :             :     size_t length;
     514                 :          15 :     GjsAutoError error;
     515         [ +  + ]:          15 :     if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length,
     516                 :             :                                      /* etag_out = */ nullptr, &error)) {
     517                 :           1 :         GjsAutoChar uri = g_file_get_uri(G_FILE(file));
     518                 :           1 :         gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError",
     519                 :             :                          "Unable to load file async from: %s (%s)", uri.get(),
     520                 :           1 :                          error->message);
     521                 :           1 :         promise->reject_with_pending_exception();
     522                 :           1 :         return;
     523                 :           1 :     }
     524                 :             : 
     525                 :          14 :     JS::RootedValue text(promise->cx);
     526                 :          14 :     bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text);
     527                 :          14 :     g_free(contents);
     528         [ -  + ]:          14 :     if (!ok) {
     529                 :           0 :         promise->reject_with_pending_exception();
     530                 :           0 :         return;
     531                 :             :     }
     532                 :             : 
     533                 :          14 :     promise->resolve(text);
     534   [ +  -  +  +  :          17 : }
             +  +  +  + ]
     535                 :             : 
     536                 :             : GJS_JSAPI_RETURN_CONVENTION
     537                 :          15 : static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) {
     538                 :          15 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     539                 :          15 :     JS::RootedObject resolve(cx), reject(cx);
     540         [ -  + ]:          15 :     if (!gjs_parse_call_args(cx, "executor", args, "oo", "resolve", &resolve,
     541                 :             :                              "reject", &reject))
     542                 :           0 :         return handle_wrong_args(cx);
     543                 :             : 
     544                 :          15 :     g_assert(JS_ObjectIsFunction(resolve) && "Executor called weirdly");
     545                 :          15 :     g_assert(JS_ObjectIsFunction(reject) && "Executor called weirdly");
     546                 :             : 
     547                 :          15 :     JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
     548                 :          15 :     g_assert(!priv_value.isNull() && "Executor called twice");
     549                 :          15 :     GjsAutoUnref<GFile> file = G_FILE(priv_value.toPrivate());
     550                 :          15 :     g_assert(file && "Executor called twice");
     551                 :             :     // We now own the GFile, and will pass the reference to the GAsyncResult, so
     552                 :             :     // remove it from the executor's private slot so it doesn't become dangling
     553                 :          15 :     js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
     554                 :             : 
     555                 :          15 :     auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve),
     556                 :          30 :                                  JS_GetObjectFunction(reject));
     557                 :             : 
     558                 :             :     // Hold the main loop until this function resolves...
     559                 :          15 :     GjsContextPrivate::from_cx(cx)->main_loop_hold();
     560                 :          15 :     g_file_load_contents_async(file, nullptr, load_async_callback, data);
     561                 :             : 
     562                 :          15 :     args.rval().setUndefined();
     563                 :          15 :     return true;
     564                 :          15 : }
     565                 :             : 
     566                 :          15 : bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc,
     567                 :             :                                               JS::Value* vp) {
     568                 :          15 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     569                 :          15 :     JS::UniqueChars uri;
     570         [ -  + ]:          15 :     if (!gjs_parse_call_args(cx, "loadResourceOrFileAsync", args, "s", "uri",
     571                 :             :                              &uri))
     572                 :           0 :         return handle_wrong_args(cx);
     573                 :             : 
     574                 :          15 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     575                 :             : 
     576                 :             :     JS::RootedObject executor(cx,
     577                 :          15 :                               JS_GetFunctionObject(js::NewFunctionWithReserved(
     578                 :             :                                   cx, load_async_executor, 2, 0,
     579                 :          15 :                                   "loadResourceOrFileAsync executor")));
     580         [ -  + ]:          15 :     if (!executor)
     581                 :           0 :         return false;
     582                 :             : 
     583                 :             :     // Stash the file object for the callback to find later; executor owns it
     584                 :          15 :     js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy()));
     585                 :             : 
     586                 :          15 :     JSObject* promise = JS::NewPromiseObject(cx, executor);
     587         [ -  + ]:          15 :     if (!promise)
     588                 :           0 :         return false;
     589                 :             : 
     590                 :          15 :     args.rval().setObject(*promise);
     591                 :          15 :     return true;
     592                 :          15 : }
        

Generated by: LCOV version 2.0-1