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

             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                 :         241 : bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
      71                 :             :     GjsAutoChar full_path(g_strdup_printf(
      72                 :         241 :         "resource:///org/gnome/gjs/modules/internal/%s.js", identifier));
      73                 :             : 
      74                 :         241 :     gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)",
      75                 :             :               identifier, full_path.get());
      76                 :             : 
      77                 :         241 :     GjsAutoChar script;
      78                 :             :     size_t script_len;
      79         [ -  + ]:         241 :     if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len))
      80                 :           0 :         return false;
      81                 :             : 
      82                 :         241 :     JS::SourceText<mozilla::Utf8Unit> buf;
      83         [ -  + ]:         241 :     if (!buf.init(cx, script.get(), script_len, JS::SourceOwnership::Borrowed))
      84                 :           0 :         return false;
      85                 :             : 
      86                 :         241 :     JS::CompileOptions options(cx);
      87                 :         241 :     options.setIntroductionType("Internal Module Bootstrap");
      88                 :         241 :     options.setFileAndLine(full_path, 1);
      89                 :         241 :     options.setSelfHostingMode(false);
      90                 :             : 
      91                 :         241 :     Gjs::AutoInternalRealm ar{cx};
      92                 :             : 
      93                 :         241 :     JS::RootedValue ignored(cx);
      94                 :         241 :     return JS::Evaluate(cx, options, buf, &ignored);
      95                 :         241 : }
      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                 :         241 : bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc,
     114                 :             :                                            JS::Value* vp) {
     115                 :         241 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     116                 :         241 :     JS::RootedObject global(cx), loader(cx);
     117         [ -  + ]:         241 :     if (!gjs_parse_call_args(cx, "setGlobalModuleLoader", args, "oo", "global",
     118                 :             :                              &global, "loader", &loader))
     119                 :           0 :         return handle_wrong_args(cx);
     120                 :             : 
     121                 :         241 :     gjs_set_global_slot(global, GjsGlobalSlot::MODULE_LOADER,
     122                 :         241 :                         JS::ObjectValue(*loader));
     123                 :             : 
     124                 :         241 :     args.rval().setUndefined();
     125                 :         241 :     return true;
     126                 :         241 : }
     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                 :        1735 : static bool compile_module(JSContext* cx, const JS::UniqueChars& uri,
     142                 :             :                            JS::HandleString source,
     143                 :             :                            JS::MutableHandleValue v_module_out) {
     144                 :        1735 :     JS::CompileOptions options(cx);
     145                 :        1735 :     options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false);
     146                 :             : 
     147                 :             :     size_t text_len;
     148                 :             :     char16_t* text;
     149         [ -  + ]:        1735 :     if (!gjs_string_get_char16_data(cx, source, &text, &text_len))
     150                 :           0 :         return false;
     151                 :             : 
     152                 :        1735 :     JS::SourceText<char16_t> buf;
     153         [ -  + ]:        1735 :     if (!buf.init(cx, text, text_len, JS::SourceOwnership::TakeOwnership))
     154                 :           0 :         return false;
     155                 :             : 
     156                 :        1735 :     JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf));
     157         [ +  + ]:        1735 :     if (!new_module)
     158                 :           1 :         return false;
     159                 :             : 
     160                 :        1734 :     v_module_out.setObject(*new_module);
     161                 :        1734 :     return true;
     162                 :        1735 : }
     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                 :        1735 : bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) {
     208                 :        1735 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     209                 :             : 
     210                 :        1735 :     Gjs::AutoMainRealm ar{cx};
     211                 :             : 
     212                 :        1735 :     JS::UniqueChars uri;
     213                 :        1735 :     JS::RootedString source(cx);
     214         [ -  + ]:        1735 :     if (!gjs_parse_call_args(cx, "compileModule", args, "sS", "uri", &uri,
     215                 :             :                              "source", &source))
     216                 :           0 :         return handle_wrong_args(cx);
     217                 :             : 
     218                 :        1735 :     return compile_module(cx, uri, source, args.rval());
     219                 :        1735 : }
     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                 :        1734 : bool gjs_internal_set_module_private(JSContext* cx, unsigned argc,
     234                 :             :                                      JS::Value* vp) {
     235                 :        1734 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     236                 :        1734 :     JS::RootedObject module(cx), private_obj(cx);
     237         [ -  + ]:        1734 :     if (!gjs_parse_call_args(cx, "setModulePrivate", args, "oo", "module",
     238                 :             :                              &module, "private", &private_obj))
     239                 :           0 :         return handle_wrong_args(cx);
     240                 :             : 
     241                 :        1734 :     JS::SetModulePrivate(module, JS::ObjectValue(*private_obj));
     242                 :        1734 :     return true;
     243                 :        1734 : }
     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                 :        5142 : bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) {
     257                 :        5142 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     258                 :        5142 :     JS::RootedObject global(cx);
     259         [ -  + ]:        5142 :     if (!gjs_parse_call_args(cx, "getRegistry", args, "o", "global", &global))
     260                 :           0 :         return handle_wrong_args(cx);
     261                 :             : 
     262                 :        5142 :     JSAutoRealm ar(cx, global);
     263                 :             : 
     264                 :        5142 :     JS::RootedObject registry(cx, gjs_get_module_registry(global));
     265                 :        5142 :     args.rval().setObject(*registry);
     266                 :        5142 :     return true;
     267                 :        5142 : }
     268                 :             : 
     269                 :        7468 : bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) {
     270                 :             :     using AutoHashTable =
     271                 :             :         GjsAutoPointer<GHashTable, GHashTable, g_hash_table_destroy>;
     272                 :             :     using AutoURI = GjsAutoPointer<GUri, GUri, g_uri_unref>;
     273                 :             : 
     274                 :        7468 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     275                 :        7468 :     JS::RootedString string_arg(cx);
     276         [ -  + ]:        7468 :     if (!gjs_parse_call_args(cx, "parseUri", args, "S", "uri", &string_arg))
     277                 :           0 :         return handle_wrong_args(cx);
     278                 :             : 
     279                 :        7468 :     JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg);
     280         [ -  + ]:        7468 :     if (!uri)
     281                 :           0 :         return false;
     282                 :             : 
     283                 :        7468 :     GjsAutoError error;
     284                 :        7468 :     AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error);
     285         [ +  + ]:        7468 :     if (!parsed) {
     286                 :        2627 :         Gjs::AutoMainRealm ar{cx};
     287                 :             : 
     288                 :        2627 :         gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
     289                 :             :                          "Attempted to import invalid URI: %s (%s)", uri.get(),
     290                 :        2627 :                          error->message);
     291                 :        2627 :         return false;
     292                 :        2627 :     }
     293                 :             : 
     294                 :        4841 :     JS::RootedObject query_obj(cx, JS_NewPlainObject(cx));
     295         [ -  + ]:        4841 :     if (!query_obj)
     296                 :           0 :         return false;
     297                 :             : 
     298                 :        4841 :     const char* raw_query = g_uri_get_query(parsed);
     299         [ -  + ]:        4841 :     if (raw_query) {
     300                 :             :         AutoHashTable query =
     301                 :           0 :             g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error);
     302         [ #  # ]:           0 :         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                 :           0 :         g_hash_table_iter_init(&iter, query);
     313                 :             : 
     314                 :             :         void* key_ptr;
     315                 :             :         void* value_ptr;
     316         [ #  # ]:           0 :         while (g_hash_table_iter_next(&iter, &key_ptr, &value_ptr)) {
     317                 :           0 :             auto* key = static_cast<const char*>(key_ptr);
     318                 :           0 :             auto* value = static_cast<const char*>(value_ptr);
     319                 :             : 
     320                 :           0 :             JS::ConstUTF8CharsZ value_chars{value, strlen(value)};
     321                 :             :             JS::RootedString value_str(cx,
     322                 :           0 :                                        JS_NewStringCopyUTF8Z(cx, value_chars));
     323   [ #  #  #  #  :           0 :             if (!value_str || !JS_DefineProperty(cx, query_obj, key, value_str,
                   #  # ]
     324                 :             :                                                  JSPROP_ENUMERATE))
     325                 :           0 :                 return false;
     326         [ #  # ]:           0 :         }
     327         [ #  # ]:           0 :     }
     328                 :             : 
     329                 :        4841 :     JS::RootedObject return_obj(cx, JS_NewPlainObject(cx));
     330         [ -  + ]:        4841 :     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                 :        4841 :                             JS_NewStringCopyZ(cx, g_uri_get_scheme(parsed)));
     337         [ -  + ]:        4841 :     if (!scheme)
     338                 :           0 :         return false;
     339                 :             : 
     340                 :        4841 :     JS::RootedString host(cx, JS_NewStringCopyZ(cx, g_uri_get_host(parsed)));
     341         [ -  + ]:        4841 :     if (!host)
     342                 :           0 :         return false;
     343                 :             : 
     344                 :        4841 :     JS::RootedString path(cx, JS_NewStringCopyZ(cx, g_uri_get_path(parsed)));
     345         [ -  + ]:        4841 :     if (!path)
     346                 :           0 :         return false;
     347                 :             : 
     348                 :        4841 :     if (!JS_DefineProperty(cx, return_obj, "uri", string_arg,
     349                 :        4841 :                            JSPROP_ENUMERATE) ||
     350         [ +  - ]:        4841 :         !JS_DefineProperty(cx, return_obj, "scheme", scheme,
     351                 :        4841 :                            JSPROP_ENUMERATE) ||
     352         [ +  - ]:        4841 :         !JS_DefineProperty(cx, return_obj, "host", host, JSPROP_ENUMERATE) ||
     353   [ +  -  +  - ]:       14523 :         !JS_DefineProperty(cx, return_obj, "path", path, JSPROP_ENUMERATE) ||
     354   [ -  +  -  + ]:        9682 :         !JS_DefineProperty(cx, return_obj, "query", query_obj,
     355                 :             :                            JSPROP_ENUMERATE))
     356                 :           0 :         return false;
     357                 :             : 
     358                 :        4841 :     args.rval().setObject(*return_obj);
     359                 :        4841 :     return true;
     360                 :        7468 : }
     361                 :             : 
     362                 :        1798 : bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx,
     363                 :             :                                                     unsigned argc,
     364                 :             :                                                     JS::Value* vp) {
     365                 :        1798 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     366                 :        1798 :     JS::UniqueChars uri, relative_path;
     367         [ -  + ]:        1798 :     if (!gjs_parse_call_args(cx, "resolveRelativeResourceOrFile", args, "ss",
     368                 :             :                              "uri", &uri, "relativePath", &relative_path))
     369                 :           0 :         return handle_wrong_args(cx);
     370                 :             : 
     371                 :        1798 :     GjsAutoUnref<GFile> module_file = g_file_new_for_uri(uri.get());
     372                 :        1798 :     GjsAutoUnref<GFile> module_parent_file = g_file_get_parent(module_file);
     373                 :             : 
     374         [ +  - ]:        1798 :     if (module_parent_file) {
     375                 :             :         GjsAutoUnref<GFile> output = g_file_resolve_relative_path(
     376                 :        1798 :             module_parent_file, relative_path.get());
     377                 :        1798 :         GjsAutoChar output_uri = g_file_get_uri(output);
     378                 :             : 
     379                 :        1798 :         JS::ConstUTF8CharsZ uri_chars(output_uri, strlen(output_uri));
     380                 :        1798 :         JS::RootedString retval(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
     381         [ -  + ]:        1798 :         if (!retval)
     382                 :           0 :             return false;
     383                 :             : 
     384                 :        1798 :         args.rval().setString(retval);
     385                 :        1798 :         return true;
     386                 :        1798 :     }
     387                 :             : 
     388                 :           0 :     args.rval().setNull();
     389                 :           0 :     return true;
     390                 :        1798 : }
     391                 :             : 
     392                 :        1672 : bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc,
     393                 :             :                                         JS::Value* vp) {
     394                 :        1672 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     395                 :        1672 :     JS::UniqueChars uri;
     396         [ -  + ]:        1672 :     if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri))
     397                 :           0 :         return handle_wrong_args(cx);
     398                 :             : 
     399                 :        1672 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     400                 :             : 
     401                 :             :     char* contents;
     402                 :             :     size_t length;
     403                 :        1672 :     GjsAutoError error;
     404         [ -  + ]:        1672 :     if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents,
     405                 :             :                               &length, /* etag_out = */ nullptr, &error)) {
     406                 :           0 :         Gjs::AutoMainRealm ar{cx};
     407                 :             : 
     408                 :           0 :         gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
     409                 :             :                          "Unable to load file from: %s (%s)", uri.get(),
     410                 :           0 :                          error->message);
     411                 :           0 :         return false;
     412                 :           0 :     }
     413                 :             : 
     414                 :        1672 :     JS::ConstUTF8CharsZ contents_chars{contents, length};
     415                 :             :     JS::RootedString contents_str(cx,
     416                 :        1672 :                                   JS_NewStringCopyUTF8Z(cx, contents_chars));
     417                 :        1672 :     g_free(contents);
     418         [ -  + ]:        1672 :     if (!contents_str)
     419                 :           0 :         return false;
     420                 :             : 
     421                 :        1672 :     args.rval().setString(contents_str);
     422                 :        1672 :     return true;
     423                 :        1672 : }
     424                 :             : 
     425                 :         827 : bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) {
     426                 :         827 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     427                 :         827 :     JS::UniqueChars uri;
     428         [ -  + ]:         827 :     if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri))
     429                 :           0 :         return handle_wrong_args(cx);
     430                 :             : 
     431                 :         827 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     432                 :             : 
     433                 :         827 :     args.rval().setBoolean(g_file_query_exists(file, nullptr));
     434                 :         827 :     return true;
     435                 :         827 : }
     436                 :             : 
     437                 :             : class PromiseData {
     438                 :             :  public:
     439                 :             :     JSContext* cx;
     440                 :             : 
     441                 :             :  private:
     442                 :             :     JS::Heap<JSFunction*> m_resolve;
     443                 :             :     JS::Heap<JSFunction*> m_reject;
     444                 :             : 
     445                 :          11 :     JS::HandleFunction resolver() {
     446                 :          11 :         return JS::HandleFunction::fromMarkedLocation(m_resolve.address());
     447                 :             :     }
     448                 :           1 :     JS::HandleFunction rejecter() {
     449                 :           1 :         return JS::HandleFunction::fromMarkedLocation(m_reject.address());
     450                 :             :     }
     451                 :             : 
     452                 :           0 :     static void trace(JSTracer* trc, void* data) {
     453                 :           0 :         auto* self = PromiseData::from_ptr(data);
     454                 :           0 :         JS::TraceEdge(trc, &self->m_resolve, "loadResourceOrFileAsync resolve");
     455                 :           0 :         JS::TraceEdge(trc, &self->m_reject, "loadResourceOrFileAsync reject");
     456                 :           0 :     }
     457                 :             : 
     458                 :             :  public:
     459                 :          12 :     explicit PromiseData(JSContext* a_cx, JSFunction* resolve,
     460                 :             :                          JSFunction* reject)
     461                 :          12 :         : cx(a_cx), m_resolve(resolve), m_reject(reject) {
     462                 :          12 :         JS_AddExtraGCRootsTracer(cx, &PromiseData::trace, this);
     463                 :          12 :     }
     464                 :             : 
     465                 :          12 :     ~PromiseData() {
     466                 :          12 :         JS_RemoveExtraGCRootsTracer(cx, &PromiseData::trace, this);
     467                 :          12 :     }
     468                 :             : 
     469                 :          12 :     static PromiseData* from_ptr(void* ptr) {
     470                 :          12 :         return static_cast<PromiseData*>(ptr);
     471                 :             :     }
     472                 :             : 
     473                 :             :     // Adapted from SpiderMonkey js::RejectPromiseWithPendingError()
     474                 :             :     // https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435
     475                 :           1 :     void reject_with_pending_exception() {
     476                 :           1 :         JS::RootedValue exception(cx);
     477                 :           1 :         bool ok GJS_USED_ASSERT = JS_GetPendingException(cx, &exception);
     478                 :           1 :         g_assert(ok && "Cannot reject a promise with an uncatchable exception");
     479                 :             : 
     480                 :           1 :         JS::RootedValueArray<1> args(cx);
     481                 :           1 :         args[0].set(exception);
     482                 :           1 :         JS::RootedValue ignored_rval(cx);
     483                 :           1 :         ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args,
     484                 :             :                              &ignored_rval);
     485                 :           1 :         g_assert(ok && "Failed rejecting promise");
     486                 :           1 :     }
     487                 :             : 
     488                 :          11 :     void resolve(JS::Value result) {
     489                 :          11 :         JS::RootedValueArray<1> args(cx);
     490                 :          11 :         args[0].set(result);
     491                 :          11 :         JS::RootedValue ignored_rval(cx);
     492                 :          11 :         bool ok GJS_USED_ASSERT = JS_CallFunction(
     493                 :             :             cx, /* this_obj = */ nullptr, resolver(), args, &ignored_rval);
     494                 :          11 :         g_assert(ok && "Failed resolving promise");
     495                 :          11 :     }
     496                 :             : };
     497                 :             : 
     498                 :          12 : static void load_async_callback(GObject* file, GAsyncResult* res, void* data) {
     499                 :          12 :     std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data));
     500                 :             : 
     501                 :          12 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx);
     502                 :          12 :     gjs->main_loop_release();
     503                 :             : 
     504                 :          12 :     Gjs::AutoMainRealm ar{gjs};
     505                 :             : 
     506                 :             :     char* contents;
     507                 :             :     size_t length;
     508                 :          12 :     GjsAutoError error;
     509         [ +  + ]:          12 :     if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length,
     510                 :             :                                      /* etag_out = */ nullptr, &error)) {
     511                 :           1 :         GjsAutoChar uri = g_file_get_uri(G_FILE(file));
     512                 :           1 :         gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError",
     513                 :             :                          "Unable to load file from: %s (%s)", uri.get(),
     514                 :           1 :                          error->message);
     515                 :           1 :         promise->reject_with_pending_exception();
     516                 :           1 :         return;
     517                 :           1 :     }
     518                 :             : 
     519                 :          11 :     JS::RootedValue text(promise->cx);
     520                 :          11 :     bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text);
     521                 :          11 :     g_free(contents);
     522         [ -  + ]:          11 :     if (!ok) {
     523                 :           0 :         promise->reject_with_pending_exception();
     524                 :           0 :         return;
     525                 :             :     }
     526                 :             : 
     527                 :          11 :     promise->resolve(text);
     528   [ +  -  +  +  :          14 : }
             +  +  +  + ]
     529                 :             : 
     530                 :             : GJS_JSAPI_RETURN_CONVENTION
     531                 :          12 : static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) {
     532                 :          12 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     533                 :          12 :     JS::RootedObject resolve(cx), reject(cx);
     534         [ -  + ]:          12 :     if (!gjs_parse_call_args(cx, "executor", args, "oo", "resolve", &resolve,
     535                 :             :                              "reject", &reject))
     536                 :           0 :         return handle_wrong_args(cx);
     537                 :             : 
     538                 :          12 :     g_assert(JS_ObjectIsFunction(resolve) && "Executor called weirdly");
     539                 :          12 :     g_assert(JS_ObjectIsFunction(reject) && "Executor called weirdly");
     540                 :             : 
     541                 :          12 :     JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
     542                 :          12 :     g_assert(!priv_value.isNull() && "Executor called twice");
     543                 :          12 :     GjsAutoUnref<GFile> file = G_FILE(priv_value.toPrivate());
     544                 :          12 :     g_assert(file && "Executor called twice");
     545                 :             :     // We now own the GFile, and will pass the reference to the GAsyncResult, so
     546                 :             :     // remove it from the executor's private slot so it doesn't become dangling
     547                 :          12 :     js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
     548                 :             : 
     549                 :          12 :     auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve),
     550                 :          24 :                                  JS_GetObjectFunction(reject));
     551                 :             : 
     552                 :             :     // Hold the main loop until this function resolves...
     553                 :          12 :     GjsContextPrivate::from_cx(cx)->main_loop_hold();
     554                 :          12 :     g_file_load_contents_async(file, nullptr, load_async_callback, data);
     555                 :             : 
     556                 :          12 :     args.rval().setUndefined();
     557                 :          12 :     return true;
     558                 :          12 : }
     559                 :             : 
     560                 :          12 : bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc,
     561                 :             :                                               JS::Value* vp) {
     562                 :          12 :     JS::CallArgs args = CallArgsFromVp(argc, vp);
     563                 :          12 :     JS::UniqueChars uri;
     564         [ -  + ]:          12 :     if (!gjs_parse_call_args(cx, "loadResourceOrFileAsync", args, "s", "uri",
     565                 :             :                              &uri))
     566                 :           0 :         return handle_wrong_args(cx);
     567                 :             : 
     568                 :          12 :     GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
     569                 :             : 
     570                 :             :     JS::RootedObject executor(cx,
     571                 :          12 :                               JS_GetFunctionObject(js::NewFunctionWithReserved(
     572                 :             :                                   cx, load_async_executor, 2, 0,
     573                 :          12 :                                   "loadResourceOrFileAsync executor")));
     574         [ -  + ]:          12 :     if (!executor)
     575                 :           0 :         return false;
     576                 :             : 
     577                 :             :     // Stash the file object for the callback to find later; executor owns it
     578                 :          12 :     js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy()));
     579                 :             : 
     580                 :          12 :     JSObject* promise = JS::NewPromiseObject(cx, executor);
     581         [ -  + ]:          12 :     if (!promise)
     582                 :           0 :         return false;
     583                 :             : 
     584                 :          12 :     args.rval().setObject(*promise);
     585                 :          12 :     return true;
     586                 :          12 : }
        

Generated by: LCOV version 2.0-1