Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2017 Philip Chimento <philip.chimento@gmail.com>
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <stddef.h> // for size_t
8 : : #include <string.h>
9 : :
10 : : #include <string>
11 : : #include <vector> // for vector
12 : :
13 : : #include <gio/gio.h>
14 : : #include <glib-object.h>
15 : : #include <glib.h>
16 : :
17 : : #include <js/CallAndConstruct.h>
18 : : #include <js/CallArgs.h>
19 : : #include <js/CharacterEncoding.h> // for ConstUTF8CharsZ
20 : : #include <js/Class.h>
21 : : #include <js/CompilationAndEvaluation.h>
22 : : #include <js/CompileOptions.h>
23 : : #include <js/Conversions.h>
24 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
25 : : #include <js/Exception.h>
26 : : #include <js/GCVector.h> // for RootedVector
27 : : #include <js/GlobalObject.h> // for CurrentGlobalOrNull
28 : : #include <js/Id.h>
29 : : #include <js/Modules.h>
30 : : #include <js/Object.h>
31 : : #include <js/Promise.h>
32 : : #include <js/PropertyAndElement.h>
33 : : #include <js/PropertyDescriptor.h>
34 : : #include <js/RootingAPI.h>
35 : : #include <js/ScriptPrivate.h>
36 : : #include <js/SourceText.h>
37 : : #include <js/String.h>
38 : : #include <js/TypeDecls.h>
39 : : #include <js/Utility.h> // for UniqueChars
40 : : #include <js/Value.h>
41 : : #include <js/ValueArray.h>
42 : : #include <jsapi.h> // for JS_GetFunctionObject, JS_Ne...
43 : : #include <jsfriendapi.h> // for NewFunctionWithReserved
44 : : #include <mozilla/Maybe.h>
45 : :
46 : : #include "gjs/atoms.h"
47 : : #include "gjs/auto.h"
48 : : #include "gjs/context-private.h"
49 : : #include "gjs/deprecation.h"
50 : : #include "gjs/gerror-result.h"
51 : : #include "gjs/global.h"
52 : : #include "gjs/jsapi-util-args.h"
53 : : #include "gjs/jsapi-util.h"
54 : : #include "gjs/macros.h"
55 : : #include "gjs/mem-private.h"
56 : : #include "gjs/module.h"
57 : : #include "gjs/native.h"
58 : : #include "util/log.h"
59 : : #include "util/misc.h"
60 : :
61 : : namespace mozilla {
62 : : union Utf8Unit;
63 : : }
64 : :
65 : : class GjsScriptModule {
66 : : Gjs::AutoChar m_name;
67 : :
68 : : // Reserved slots
69 : : static const size_t POINTER = 0;
70 : :
71 : 908 : GjsScriptModule(const char* name) : m_name(g_strdup(name)) {
72 : 454 : GJS_INC_COUNTER(module);
73 : 454 : }
74 : :
75 : 449 : ~GjsScriptModule() { GJS_DEC_COUNTER(module); }
76 : :
77 : : GjsScriptModule(GjsScriptModule&) = delete;
78 : : GjsScriptModule& operator=(GjsScriptModule&) = delete;
79 : :
80 : : /* Private data accessors */
81 : :
82 : 293083 : [[nodiscard]] static inline GjsScriptModule* priv(JSObject* module) {
83 : 293083 : return JS::GetMaybePtrFromReservedSlot<GjsScriptModule>(
84 : 293083 : module, GjsScriptModule::POINTER);
85 : : }
86 : :
87 : : /* Creates a JS module object. Use instead of the class's constructor */
88 : 454 : [[nodiscard]] static JSObject* create(JSContext* cx, const char* name) {
89 : 454 : JSObject* module = JS_NewObject(cx, &GjsScriptModule::klass);
90 : 454 : JS::SetReservedSlot(module, GjsScriptModule::POINTER,
91 : 454 : JS::PrivateValue(new GjsScriptModule(name)));
92 : 454 : return module;
93 : : }
94 : :
95 : : /* Defines the empty module as a property on the importer */
96 : : GJS_JSAPI_RETURN_CONVENTION
97 : : bool
98 : 454 : define_import(JSContext *cx,
99 : : JS::HandleObject module,
100 : : JS::HandleObject importer,
101 : : JS::HandleId name) const
102 : : {
103 [ - + ]: 454 : if (!JS_DefinePropertyById(cx, importer, name, module,
104 : : GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) {
105 : 0 : gjs_debug(GJS_DEBUG_IMPORTER, "Failed to define '%s' in importer",
106 : : m_name.get());
107 : 0 : return false;
108 : : }
109 : :
110 : 454 : return true;
111 : : }
112 : :
113 : : /* Carries out the actual execution of the module code */
114 : : GJS_JSAPI_RETURN_CONVENTION
115 : 454 : bool evaluate_import(JSContext* cx, JS::HandleObject module,
116 : : const char* source, size_t source_len,
117 : : const char* filename, const char* uri) {
118 : 454 : JS::SourceText<mozilla::Utf8Unit> buf;
119 [ - + ]: 454 : if (!buf.init(cx, source, source_len, JS::SourceOwnership::Borrowed))
120 : 0 : return false;
121 : :
122 : 454 : JS::RootedObjectVector scope_chain(cx);
123 [ - + ]: 454 : if (!scope_chain.append(module)) {
124 : 0 : JS_ReportOutOfMemory(cx);
125 : 0 : return false;
126 : : }
127 : :
128 : 454 : JS::CompileOptions options(cx);
129 : 454 : options.setFileAndLine(filename, 1).setNonSyntacticScope(true);
130 : :
131 : 454 : JS::RootedObject priv(cx, build_private(cx, uri));
132 [ - + ]: 454 : if (!priv)
133 : 0 : return false;
134 : :
135 : 454 : JS::RootedScript script(cx, JS::Compile(cx, options, buf));
136 [ + + ]: 454 : if (!script)
137 : 1 : return false;
138 : :
139 : 453 : JS::SetScriptPrivate(script, JS::ObjectValue(*priv));
140 : 453 : JS::RootedValue ignored_retval(cx);
141 [ + + ]: 453 : if (!JS_ExecuteScript(cx, scope_chain, script, &ignored_retval))
142 : 3 : return false;
143 : :
144 : 450 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
145 : 450 : gjs->schedule_gc_if_needed();
146 : :
147 : 450 : gjs_debug(GJS_DEBUG_IMPORTER, "Importing module %s succeeded",
148 : : m_name.get());
149 : :
150 : 450 : return true;
151 : 454 : }
152 : :
153 : : /* Loads JS code from a file and imports it */
154 : : GJS_JSAPI_RETURN_CONVENTION
155 : : bool
156 : 454 : import_file(JSContext *cx,
157 : : JS::HandleObject module,
158 : : GFile *file)
159 : : {
160 : 454 : Gjs::AutoError error;
161 : 454 : Gjs::AutoChar script;
162 : 454 : size_t script_len = 0;
163 : :
164 [ - + ]: 454 : if (!(g_file_load_contents(file, nullptr, script.out(), &script_len,
165 : : nullptr, &error)))
166 : 0 : return gjs_throw_gerror_message(cx, error);
167 : 454 : g_assert(script);
168 : :
169 : 454 : Gjs::AutoChar full_path{g_file_get_parse_name(file)};
170 : 454 : Gjs::AutoChar uri{g_file_get_uri(file)};
171 : 454 : return evaluate_import(cx, module, script, script_len, full_path, uri);
172 : 454 : }
173 : :
174 : : /* JSClass operations */
175 : :
176 : : GJS_JSAPI_RETURN_CONVENTION
177 : : bool
178 : 291726 : resolve_impl(JSContext *cx,
179 : : JS::HandleObject module,
180 : : JS::HandleId id,
181 : : bool *resolved)
182 : : {
183 : 291726 : JS::RootedObject lexical(cx, JS_ExtensibleLexicalEnvironment(module));
184 [ - + ]: 291726 : if (!lexical) {
185 : 0 : *resolved = false;
186 : 0 : return true; /* nothing imported yet */
187 : : }
188 : :
189 : 291726 : JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> maybe_desc(cx);
190 : 291726 : JS::RootedObject holder(cx);
191 [ - + ]: 291726 : if (!JS_GetPropertyDescriptorById(cx, lexical, id, &maybe_desc,
192 : : &holder))
193 : 0 : return false;
194 [ + + ]: 291726 : if (maybe_desc.isNothing())
195 : 291724 : return true;
196 : :
197 : : /* The property is present in the lexical environment. This should not
198 : : * be supported according to ES6. For compatibility with earlier GJS,
199 : : * we treat it as if it were a real property, but warn about it. */
200 : :
201 [ + + ]: 12 : _gjs_warn_deprecated_once_per_callsite(
202 : : cx, GjsDeprecationMessageId::ModuleExportedLetOrConst,
203 : 4 : {gjs_debug_id(id), m_name.get()});
204 : :
205 : 2 : JS::Rooted<JS::PropertyDescriptor> desc(cx, maybe_desc.value());
206 : 2 : return JS_DefinePropertyById(cx, module, id, desc);
207 : 291726 : }
208 : :
209 : : GJS_JSAPI_RETURN_CONVENTION
210 : : static bool
211 : 291726 : resolve(JSContext *cx,
212 : : JS::HandleObject module,
213 : : JS::HandleId id,
214 : : bool *resolved)
215 : : {
216 : 291726 : return priv(module)->resolve_impl(cx, module, id, resolved);
217 : : }
218 : :
219 : 449 : static void finalize(JS::GCContext*, JSObject* module) {
220 [ + - ]: 449 : delete priv(module);
221 : 449 : }
222 : :
223 : : static constexpr JSClassOps class_ops = {
224 : : nullptr, // addProperty
225 : : nullptr, // deleteProperty
226 : : nullptr, // enumerate
227 : : nullptr, // newEnumerate
228 : : &GjsScriptModule::resolve,
229 : : nullptr, // mayResolve
230 : : &GjsScriptModule::finalize,
231 : : };
232 : :
233 : : static constexpr JSClass klass = {
234 : : "GjsScriptModule",
235 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE,
236 : : &GjsScriptModule::class_ops,
237 : : };
238 : :
239 : : public:
240 : : /*
241 : : * Creates a JS object to pass to JS::SetScriptPrivate as a script's
242 : : * private.
243 : : */
244 : : GJS_JSAPI_RETURN_CONVENTION
245 : 712 : static JSObject* build_private(JSContext* cx, const char* script_uri) {
246 : 712 : JS::RootedObject priv(cx, JS_NewPlainObject(cx));
247 : 712 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
248 : :
249 : 712 : JS::RootedValue val(cx);
250 [ + - ]: 1424 : if (!gjs_string_from_utf8(cx, script_uri, &val) ||
251 [ - + - + ]: 1424 : !JS_SetPropertyById(cx, priv, atoms.uri(), val))
252 : 0 : return nullptr;
253 : :
254 : 712 : return priv;
255 : 712 : }
256 : :
257 : : /* Carries out the import operation */
258 : : GJS_JSAPI_RETURN_CONVENTION
259 : : static JSObject *
260 : 454 : import(JSContext *cx,
261 : : JS::HandleObject importer,
262 : : JS::HandleId id,
263 : : const char *name,
264 : : GFile *file)
265 : : {
266 : 454 : JS::RootedObject module(cx, GjsScriptModule::create(cx, name));
267 : 454 : if (!module ||
268 [ + - + - ]: 908 : !priv(module)->define_import(cx, module, importer, id) ||
269 [ + + + + ]: 908 : !priv(module)->import_file(cx, module, file))
270 : 4 : return nullptr;
271 : :
272 : 450 : return module;
273 : 454 : }
274 : : };
275 : :
276 : : /**
277 : : * gjs_script_module_build_private:
278 : : * @param cx the #JSContext
279 : : * @param uri the URI this script module is loaded from
280 : : *
281 : : * @brief To support dynamic imports from scripts, we need to provide private
282 : : * data when we compile scripts which is compatible with our module resolution
283 : : * hooks in modules/internal/loader.js
284 : : *
285 : : * @returns a JSObject which can be used for a JSScript's private data.
286 : : */
287 : 258 : JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri) {
288 : 258 : return GjsScriptModule::build_private(cx, uri);
289 : : }
290 : :
291 : : /**
292 : : * gjs_module_import:
293 : : * @cx: the JS context
294 : : * @importer: the JS importer object, parent of the module to be imported
295 : : * @id: module name in the form of a jsid
296 : : * @name: module name, used for logging and identification
297 : : * @file: location of the file to import
298 : : *
299 : : * Carries out an import of a GJS module.
300 : : * Defines a property @name on @importer pointing to the module object, which
301 : : * is necessary in the case of cyclic imports.
302 : : * This property is not permanent; the caller is responsible for making it
303 : : * permanent if the import succeeds.
304 : : *
305 : : * Returns: the JS module object, or nullptr on failure.
306 : : */
307 : : JSObject *
308 : 454 : gjs_module_import(JSContext *cx,
309 : : JS::HandleObject importer,
310 : : JS::HandleId id,
311 : : const char *name,
312 : : GFile *file)
313 : : {
314 : 454 : return GjsScriptModule::import(cx, importer, id, name, file);
315 : : }
316 : :
317 : : decltype(GjsScriptModule::klass) constexpr GjsScriptModule::klass;
318 : : decltype(GjsScriptModule::class_ops) constexpr GjsScriptModule::class_ops;
319 : :
320 : : /**
321 : : * gjs_get_native_registry:
322 : : *
323 : : * @brief Retrieves a global's native registry from the NATIVE_REGISTRY slot.
324 : : * Registries are JS Map objects created with JS::NewMapObject instead
325 : : * of GCHashMaps (used elsewhere in GJS) because the objects need to be
326 : : * exposed to internal JS code and accessed from native C++ code.
327 : : *
328 : : * @param global a global #JSObject
329 : : *
330 : : * @returns the registry map as a #JSObject
331 : : */
332 : 32481 : JSObject* gjs_get_native_registry(JSObject* global) {
333 : : JS::Value native_registry =
334 : 32481 : gjs_get_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY);
335 : :
336 : 32481 : g_assert(native_registry.isObject());
337 : 32481 : return &native_registry.toObject();
338 : : }
339 : :
340 : : /**
341 : : * gjs_get_module_registry:
342 : : *
343 : : * @brief Retrieves a global's module registry from the MODULE_REGISTRY slot.
344 : : * Registries are JS Maps. See gjs_get_native_registry for more detail.
345 : : *
346 : : * @param cx the current #JSContext
347 : : * @param global a global #JSObject
348 : : *
349 : : * @returns the registry map as a #JSObject
350 : : */
351 : 13517 : JSObject* gjs_get_module_registry(JSObject* global) {
352 : : JS::Value esm_registry =
353 : 13517 : gjs_get_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY);
354 : :
355 : 13517 : g_assert(esm_registry.isObject());
356 : 13517 : return &esm_registry.toObject();
357 : : }
358 : :
359 : : /**
360 : : * gjs_get_source_map_registry:
361 : : *
362 : : * @brief Retrieves a global's source map registry from the SOURCE_MAP_REGISTRY
363 : : * slot. Registries are JS Maps.
364 : : *
365 : : * @param cx the current #JSContext
366 : : * @param global a global #JSObject
367 : : *
368 : : * @returns the registry map as a #JSObject
369 : : */
370 : 240 : JSObject* gjs_get_source_map_registry(JSObject* global) {
371 : : JS::Value source_map_registry =
372 : 240 : gjs_get_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY);
373 : :
374 : 240 : g_assert(source_map_registry.isObject());
375 : 240 : return &source_map_registry.toObject();
376 : : }
377 : :
378 : : /**
379 : : * gjs_module_load:
380 : : *
381 : : * Loads and registers a module given a specifier and
382 : : * URI.
383 : : *
384 : : * @returns whether an error occurred while resolving the specifier.
385 : : */
386 : 639 : JSObject* gjs_module_load(JSContext* cx, const char* identifier,
387 : : const char* file_uri) {
388 : 639 : g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
389 : : gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
390 : : "gjs_module_load can only be called from module-enabled "
391 : : "globals.");
392 : :
393 : 639 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
394 : : JS::RootedValue v_loader(
395 : 639 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
396 : 639 : g_assert(v_loader.isObject());
397 : 639 : JS::RootedObject loader(cx, &v_loader.toObject());
398 : :
399 : 639 : JS::ConstUTF8CharsZ id_chars(identifier, strlen(identifier));
400 : 639 : JS::ConstUTF8CharsZ uri_chars(file_uri, strlen(file_uri));
401 : 639 : JS::RootedString id(cx, JS_NewStringCopyUTF8Z(cx, id_chars));
402 [ - + ]: 639 : if (!id)
403 : 0 : return nullptr;
404 : 639 : JS::RootedString uri(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
405 [ - + ]: 639 : if (!uri)
406 : 0 : return nullptr;
407 : :
408 : 639 : JS::RootedValueArray<2> args(cx);
409 : 639 : args[0].setString(id);
410 : 639 : args[1].setString(uri);
411 : :
412 : 639 : gjs_debug(GJS_DEBUG_IMPORTER,
413 : : "Module load hook for module '%s' (%s), global %p", identifier,
414 : 639 : file_uri, global.get());
415 : :
416 : 639 : JS::RootedValue result(cx);
417 [ + + ]: 639 : if (!JS::Call(cx, loader, "moduleLoadHook", args, &result))
418 : 1 : return nullptr;
419 : :
420 : 638 : g_assert(result.isObject() && "Module hook failed to return an object!");
421 : 638 : return &result.toObject();
422 : 639 : }
423 : :
424 : : /**
425 : : * import_native_module_sync:
426 : : *
427 : : * @brief Synchronously imports native "modules" from the import global's
428 : : * native registry. This function does not do blocking I/O so it is
429 : : * safe to call it synchronously for accessing native "modules" within
430 : : * modules. This function is always called within the import global's
431 : : * realm.
432 : : *
433 : : * Compare gjs_import_native_module() for the legacy importer.
434 : : *
435 : : * @param cx the current JSContext
436 : : * @param argc
437 : : * @param vp
438 : : *
439 : : * @returns whether an error occurred while importing the native module.
440 : : */
441 : 9685 : static bool import_native_module_sync(JSContext* cx, unsigned argc,
442 : : JS::Value* vp) {
443 : 9685 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
444 : 9685 : JS::UniqueChars id;
445 [ - + ]: 9685 : if (!gjs_parse_call_args(cx, "importSync", args, "s", "identifier", &id))
446 : 0 : return false;
447 : :
448 : 9685 : Gjs::AutoMainRealm ar{cx};
449 : 9685 : JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)};
450 : :
451 : 9685 : JS::AutoSaveExceptionState exc_state(cx);
452 : :
453 : 9685 : JS::RootedObject native_registry(cx, gjs_get_native_registry(global));
454 : 9685 : JS::RootedObject v_module(cx);
455 : :
456 : 9685 : JS::RootedId key(cx, gjs_intern_string_to_id(cx, id.get()));
457 [ - + ]: 9685 : if (!gjs_global_registry_get(cx, native_registry, key, &v_module))
458 : 0 : return false;
459 : :
460 [ + + ]: 9685 : if (v_module) {
461 : 9584 : args.rval().setObject(*v_module);
462 : 9584 : return true;
463 : : }
464 : :
465 : 101 : JS::RootedObject native_obj(cx);
466 [ - + ]: 101 : if (!Gjs::NativeModuleDefineFuncs::get().define(cx, id.get(),
467 : : &native_obj)) {
468 : 0 : gjs_throw(cx, "Failed to load native module: %s", id.get());
469 : 0 : return false;
470 : : }
471 : :
472 [ - + ]: 101 : if (!gjs_global_registry_set(cx, native_registry, key, native_obj))
473 : 0 : return false;
474 : :
475 : 101 : args.rval().setObject(*native_obj);
476 : 101 : return true;
477 : 9685 : }
478 : :
479 : : /**
480 : : * gjs_populate_module_meta:
481 : : *
482 : : * Hook SpiderMonkey calls to populate the import.meta object.
483 : : * Defines a property "import.meta.url", and additionally a method
484 : : * "import.meta.importSync" if this is an internal module.
485 : : *
486 : : * @param private_ref the private value for the #Module object
487 : : * @param meta the import.meta object
488 : : *
489 : : * @returns whether an error occurred while populating the module meta.
490 : : */
491 : 164 : bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref,
492 : : JS::HandleObject meta) {
493 : 164 : g_assert(private_ref.isObject());
494 : 164 : JS::RootedObject module(cx, &private_ref.toObject());
495 : :
496 : 164 : gjs_debug(GJS_DEBUG_IMPORTER, "Module metadata hook for module %p",
497 : 164 : &private_ref.toObject());
498 : :
499 : 164 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
500 : 164 : JS::RootedValue specifier{cx};
501 [ + - ]: 328 : if (!JS_GetProperty(cx, module, "id", &specifier) ||
502 [ - + - + ]: 328 : !JS_DefinePropertyById(cx, meta, atoms.url(), specifier,
503 : : GJS_MODULE_PROP_FLAGS))
504 : 0 : return false;
505 : :
506 : 164 : JS::RootedValue v_internal(cx);
507 [ - + ]: 164 : if (!JS_GetPropertyById(cx, module, atoms.internal(), &v_internal))
508 : 0 : return false;
509 [ + + ]: 164 : if (JS::ToBoolean(v_internal)) {
510 : 159 : gjs_debug(GJS_DEBUG_IMPORTER, "Defining meta.importSync for module %p",
511 : 159 : &private_ref.toObject());
512 [ - + ]: 159 : if (!JS_DefineFunctionById(cx, meta, atoms.importSync(),
513 : : import_native_module_sync, 1,
514 : : GJS_MODULE_PROP_FLAGS))
515 : 0 : return false;
516 : : }
517 : :
518 : 164 : return true;
519 : 164 : }
520 : :
521 : : // Canonicalize specifier so that differently-spelled specifiers referring to
522 : : // the same module don't result in duplicate entries in the registry
523 : 11627 : static bool canonicalize_specifier(JSContext* cx,
524 : : JS::MutableHandleString specifier) {
525 : 11627 : JS::UniqueChars specifier_utf8 = JS_EncodeStringToUTF8(cx, specifier);
526 [ - + ]: 11627 : if (!specifier_utf8)
527 : 0 : return false;
528 : :
529 : 11627 : Gjs::AutoChar scheme, host, path, query;
530 [ - + ]: 11627 : if (!g_uri_split(specifier_utf8.get(), G_URI_FLAGS_NONE, scheme.out(),
531 : : nullptr, host.out(), nullptr, path.out(), query.out(),
532 : : nullptr, nullptr))
533 : 0 : return false;
534 : :
535 [ + + ]: 11627 : if (g_strcmp0(scheme, "gi")) {
536 : : // canonicalize without the query portion to avoid it being encoded
537 : 11289 : Gjs::AutoChar for_file_uri{g_uri_join(G_URI_FLAGS_NONE, scheme.get(),
538 : 11289 : nullptr, host.get(), -1,
539 : 22578 : path.get(), nullptr, nullptr)};
540 : 11289 : Gjs::AutoUnref<GFile> file{g_file_new_for_uri(for_file_uri.get())};
541 : 11289 : for_file_uri = g_file_get_uri(file);
542 : 11289 : host.reset();
543 : 11289 : path.reset();
544 [ - + ]: 11289 : if (!g_uri_split(for_file_uri.get(), G_URI_FLAGS_NONE, nullptr, nullptr,
545 : : host.out(), nullptr, path.out(), nullptr, nullptr,
546 : : nullptr))
547 : 0 : return false;
548 [ + - + - ]: 11289 : }
549 : :
550 : : Gjs::AutoChar canonical_specifier{
551 : 11627 : g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1,
552 : 23254 : path.get(), query.get(), nullptr)};
553 : 11627 : JS::ConstUTF8CharsZ chars{canonical_specifier, strlen(canonical_specifier)};
554 : 11627 : JS::RootedString new_specifier{cx, JS_NewStringCopyUTF8Z(cx, chars)};
555 [ - + ]: 11627 : if (!new_specifier)
556 : 0 : return false;
557 : :
558 : 11627 : specifier.set(new_specifier);
559 : 11627 : return true;
560 : 11627 : }
561 : :
562 : : /**
563 : : * gjs_module_resolve:
564 : : *
565 : : * Hook SpiderMonkey calls to resolve import specifiers.
566 : : *
567 : : * @param importingModulePriv the private value of the #Module object initiating
568 : : * the import, or a JS null value
569 : : * @param specifier the import specifier to resolve
570 : : *
571 : : * @returns whether an error occurred while resolving the specifier.
572 : : */
573 : 11591 : JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv,
574 : : JS::HandleObject module_request) {
575 : 11591 : g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
576 : : gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
577 : : "gjs_module_resolve can only be called from module-enabled "
578 : : "globals.");
579 : : JS::RootedString specifier(
580 : 11591 : cx, JS::GetModuleRequestSpecifier(cx, module_request));
581 : :
582 : 11591 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
583 : : JS::RootedValue v_loader(
584 : 11591 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
585 : 11591 : g_assert(v_loader.isObject());
586 : 11591 : JS::RootedObject loader(cx, &v_loader.toObject());
587 : :
588 [ - + ]: 11591 : if (!canonicalize_specifier(cx, &specifier))
589 : 0 : return nullptr;
590 : :
591 : 11591 : JS::RootedValueArray<2> args(cx);
592 : 11591 : args[0].set(importingModulePriv);
593 : 11591 : args[1].setString(specifier);
594 : :
595 : 23182 : gjs_debug(GJS_DEBUG_IMPORTER,
596 : : "Module resolve hook for module %s (relative to %s), global %p",
597 : 23182 : gjs_debug_string(specifier).c_str(),
598 : 23182 : gjs_debug_value(importingModulePriv).c_str(), global.get());
599 : :
600 : 11591 : JS::RootedValue result(cx);
601 [ + + ]: 11591 : if (!JS::Call(cx, loader, "moduleResolveHook", args, &result))
602 : 2 : return nullptr;
603 : :
604 : 11589 : g_assert(result.isObject() && "resolve hook failed to return an object!");
605 : 11589 : return &result.toObject();
606 : 11591 : }
607 : :
608 : : // Call JS::FinishDynamicModuleImport() with the values stashed in the function.
609 : : // Can fail in JS::FinishDynamicModuleImport(), but will assert if anything
610 : : // fails in fetching the stashed values, since that would be a serious GJS bug.
611 : : GJS_JSAPI_RETURN_CONVENTION
612 : 36 : static bool finish_import(JSContext* cx, JS::HandleObject evaluation_promise,
613 : : const JS::CallArgs& args) {
614 : 36 : GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
615 : 36 : priv->main_loop_release();
616 : :
617 : 36 : JS::Value callback_priv = js::GetFunctionNativeReserved(&args.callee(), 0);
618 : 36 : g_assert(callback_priv.isObject() && "Wrong private value");
619 : 36 : JS::RootedObject callback_data(cx, &callback_priv.toObject());
620 : :
621 : 36 : JS::RootedValue importing_module_priv(cx);
622 : 36 : JS::RootedValue v_module_request(cx);
623 : 36 : JS::RootedValue v_internal_promise(cx);
624 : : bool ok GJS_USED_ASSERT =
625 [ + - ]: 72 : JS_GetProperty(cx, callback_data, "priv", &importing_module_priv) &&
626 [ + - + - ]: 72 : JS_GetProperty(cx, callback_data, "promise", &v_internal_promise) &&
627 : 36 : JS_GetProperty(cx, callback_data, "module_request", &v_module_request);
628 : 36 : g_assert(ok && "Wrong properties on private value");
629 : :
630 : 36 : g_assert(v_module_request.isObject() && "Wrong type for module request");
631 : 36 : g_assert(v_internal_promise.isObject() && "Wrong type for promise");
632 : :
633 : 36 : JS::RootedObject module_request(cx, &v_module_request.toObject());
634 : 36 : JS::RootedObject internal_promise(cx, &v_internal_promise.toObject());
635 : :
636 : 36 : args.rval().setUndefined();
637 : :
638 : 36 : return JS::FinishDynamicModuleImport(cx, evaluation_promise,
639 : : importing_module_priv, module_request,
640 : 36 : internal_promise);
641 : 36 : }
642 : :
643 : : // Failing a JSAPI function may result either in an exception pending on the
644 : : // context, in which case we must call JS::FinishDynamicModuleImport() to reject
645 : : // the internal promise; or in an uncatchable exception such as OOM, in which
646 : : // case we must not call JS::FinishDynamicModuleImport().
647 : : GJS_JSAPI_RETURN_CONVENTION
648 : 0 : static bool fail_import(JSContext* cx, const JS::CallArgs& args) {
649 [ # # ]: 0 : if (JS_IsExceptionPending(cx))
650 : 0 : return finish_import(cx, nullptr, args);
651 : 0 : return false;
652 : : }
653 : :
654 : : GJS_JSAPI_RETURN_CONVENTION
655 : 4 : static bool import_rejected(JSContext* cx, unsigned argc, JS::Value* vp) {
656 : 4 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
657 : :
658 : 4 : gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise rejected");
659 : :
660 : : // Throw the value that the promise is rejected with, so that
661 : : // FinishDynamicModuleImport will reject the internal_promise with it.
662 : 4 : JS_SetPendingException(cx, args.get(0),
663 : : JS::ExceptionStackBehavior::DoNotCapture);
664 : :
665 : 4 : return finish_import(cx, nullptr, args);
666 : : }
667 : :
668 : : GJS_JSAPI_RETURN_CONVENTION
669 : 32 : static bool import_resolved(JSContext* cx, unsigned argc, JS::Value* vp) {
670 : 32 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
671 : :
672 : 32 : gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise resolved");
673 : :
674 : 32 : Gjs::AutoMainRealm ar{cx};
675 : :
676 : 32 : g_assert(args[0].isObject());
677 : 32 : JS::RootedObject module(cx, &args[0].toObject());
678 : :
679 : 32 : JS::RootedValue evaluation_promise(cx);
680 [ + - ]: 64 : if (!JS::ModuleLink(cx, module) ||
681 [ - + - + ]: 64 : !JS::ModuleEvaluate(cx, module, &evaluation_promise))
682 : 0 : return fail_import(cx, args);
683 : :
684 : 32 : g_assert(evaluation_promise.isObject() &&
685 : : "got weird value from JS::ModuleEvaluate");
686 : : JS::RootedObject evaluation_promise_object(cx,
687 : 32 : &evaluation_promise.toObject());
688 : 32 : return finish_import(cx, evaluation_promise_object, args);
689 : 32 : }
690 : :
691 : 36 : bool gjs_dynamic_module_resolve(JSContext* cx,
692 : : JS::HandleValue importing_module_priv,
693 : : JS::HandleObject module_request,
694 : : JS::HandleObject internal_promise) {
695 : 36 : g_assert(gjs_global_is_type(cx, GjsGlobalType::DEFAULT) &&
696 : : "gjs_dynamic_module_resolve can only be called from the default "
697 : : "global.");
698 : :
699 : 36 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
700 : 36 : g_assert(global && "gjs_dynamic_module_resolve must be in a realm");
701 : :
702 : : JS::RootedValue v_loader(
703 : 36 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
704 : 36 : g_assert(v_loader.isObject());
705 : 36 : JS::RootedObject loader(cx, &v_loader.toObject());
706 : : JS::RootedString specifier(
707 : 36 : cx, JS::GetModuleRequestSpecifier(cx, module_request));
708 : :
709 [ - + ]: 36 : if (!canonicalize_specifier(cx, &specifier))
710 : 0 : return false;
711 : :
712 : 36 : JS::RootedObject callback_data(cx, JS_NewPlainObject(cx));
713 : 36 : if (!callback_data ||
714 [ + - ]: 36 : !JS_DefineProperty(cx, callback_data, "module_request", module_request,
715 : 36 : JSPROP_PERMANENT) ||
716 [ + - ]: 36 : !JS_DefineProperty(cx, callback_data, "promise", internal_promise,
717 [ + - ]: 72 : JSPROP_PERMANENT) ||
718 [ - + - + ]: 72 : !JS_DefineProperty(cx, callback_data, "priv", importing_module_priv,
719 : : JSPROP_PERMANENT))
720 : 0 : return false;
721 : :
722 [ + + ]: 36 : if (importing_module_priv.isObject()) {
723 : 33 : gjs_debug(GJS_DEBUG_IMPORTER,
724 : : "Async module resolve hook for module %s (relative to %p), "
725 : : "global %p",
726 : 66 : gjs_debug_string(specifier).c_str(),
727 : 33 : &importing_module_priv.toObject(), global.get());
728 : : } else {
729 : 3 : gjs_debug(GJS_DEBUG_IMPORTER,
730 : : "Async module resolve hook for module %s (unknown path), "
731 : : "global %p",
732 : 6 : gjs_debug_string(specifier).c_str(), global.get());
733 : : }
734 : :
735 : 36 : JS::RootedValueArray<2> args(cx);
736 : 36 : args[0].set(importing_module_priv);
737 : 36 : args[1].setString(specifier);
738 : :
739 : 36 : JS::RootedValue result(cx);
740 [ - + ]: 36 : if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result))
741 : 0 : return JS::FinishDynamicModuleImport(cx, nullptr, importing_module_priv,
742 : 0 : module_request, internal_promise);
743 : :
744 : : // Release in finish_import
745 : 36 : GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
746 : 36 : priv->main_loop_hold();
747 : :
748 : : JS::RootedObject resolved(
749 : 36 : cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
750 : 36 : cx, import_resolved, 1, 0, "async import resolved")));
751 [ - + ]: 36 : if (!resolved)
752 : 0 : return false;
753 : : JS::RootedObject rejected(
754 : 36 : cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
755 : 36 : cx, import_rejected, 1, 0, "async import rejected")));
756 [ - + ]: 36 : if (!rejected)
757 : 0 : return false;
758 : 36 : js::SetFunctionNativeReserved(resolved, 0, JS::ObjectValue(*callback_data));
759 : 36 : js::SetFunctionNativeReserved(rejected, 0, JS::ObjectValue(*callback_data));
760 : :
761 : 36 : JS::RootedObject promise(cx, &result.toObject());
762 : :
763 : : // Calling JS::FinishDynamicModuleImport() at the end of the resolve and
764 : : // reject handlers will also call the module resolve hook. The module will
765 : : // already have been resolved, but that is how SpiderMonkey obtains the
766 : : // module object.
767 : 36 : return JS::AddPromiseReactions(cx, promise, resolved, rejected);
768 : 36 : }
|