LCOV - code coverage report
Current view: top level - gjs - internal.cpp (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 215 283 76.0 %
Date: 2024-02-27 17:05:05 Functions: 20 23 87.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 47 98 48.0 %

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

Generated by: LCOV version 1.14