LCOV - code coverage report
Current view: top level - gjs - internal.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 83.6 % 311 260
Test Date: 2025-07-16 04:58:21 Functions: 95.8 % 24 23
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 56.8 % 118 67

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

Generated by: LCOV version 2.0-1