LCOV - code coverage report
Current view: top level - gjs - internal.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 83.1 % 313 260
Test Date: 2025-02-15 06:20:10 Functions: 95.7 % 23 22
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 56.7 % 120 68

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

Generated by: LCOV version 2.0-1